#[cfg(feature = "transactions")]
use crate::move_bindings::interface::meta_schema::{
MetaSchema,
OutputVariantSchema,
PortSchema,
ValueKind,
};
#[cfg(feature = "transactions")]
use crate::move_bindings::primitives::data::NexusValue;
#[cfg(any(feature = "transactions", feature = "move_publish"))]
use crate::sui;
#[cfg(feature = "transactions")]
use crate::{
move_bindings::{interface, move_std, primitives, sui_framework},
types::{NexusContext, NexusData, NexusObjects, SharedRoot},
};
#[cfg(feature = "transactions")]
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
#[cfg(feature = "transactions")]
use sui_sdk_types::Argument;
#[cfg(feature = "transactions")]
use talus_sui_move_call::{CallTarget, ReceivingMoveObject};
#[cfg(feature = "transactions")]
pub use talus_sui_move_ptb::CLOCK_OBJECT_ID;
#[cfg(feature = "transactions")]
use talus_sui_move_ptb::{BuildError, PtbBuilder};
#[cfg(feature = "transactions")]
pub const RANDOM_OBJECT_ID: sui::types::Address = sui::types::Address::from_static("0x8");
#[cfg(feature = "transactions")]
const MAX_PURE_INPUT_BYTES: usize = 16_384;
#[cfg(feature = "transactions")]
const BYTE_VECTOR_CHUNK_BYTES: usize = MAX_PURE_INPUT_BYTES - (256 * 3);
#[cfg(feature = "move_publish")]
pub(crate) fn publish_dependency_ids_or_framework_defaults(
dependency_ids: impl IntoIterator<Item = sui::types::Address>,
) -> Vec<sui::types::Address> {
let dependency_ids = dependency_ids.into_iter().collect::<Vec<_>>();
if dependency_ids.is_empty() {
vec![
sui::types::Address::from_static("0x1"),
sui::types::Address::from_static("0x2"),
]
} else {
dependency_ids
}
}
#[cfg(feature = "transactions")]
pub struct NexusPtbBuilder {
context: Arc<NexusContext>,
tx: PtbBuilder,
}
#[cfg(feature = "transactions")]
impl NexusPtbBuilder {
pub(crate) fn new(context: Arc<NexusContext>) -> Self {
Self {
context,
tx: PtbBuilder::new(),
}
}
pub fn objects(&self) -> &NexusObjects {
self.context.objects()
}
pub fn context(&self) -> &NexusContext {
self.context.as_ref()
}
pub fn call_target(
&mut self,
target: impl FnOnce() -> Result<CallTarget, talus_sui_move_call::CallSpecError>,
arguments: Vec<Argument>,
) -> anyhow::Result<Argument> {
let target = crate::move_bindings::with_nexus_scope(self.context.as_ref(), target)?;
let allowed = target.package == sui::types::Address::from_static("0x1")
|| target.package == sui::types::Address::from_static("0x2")
|| target.package == self.context.us_token.package_id
|| self
.context
.packages()
.all()
.any(|package| package.storage_id == target.package);
if !allowed {
anyhow::bail!(
"Generated call '{}::{}::{}' uses a package role absent from the operation graph",
target.package,
target.module,
target.function
);
}
Ok(self.tx.call_target(target, arguments)?)
}
pub fn shared_root<M: Into<sui::types::Mutability>>(
&mut self,
root: &SharedRoot,
mutability: M,
) -> Result<Argument, BuildError> {
self.tx
.shared_object_by_id(root.object_id, root.initial_shared_version, mutability)
}
pub fn runtime_authority<M: Into<sui::types::Mutability>>(
&mut self,
mutability: M,
) -> Result<Argument, BuildError> {
let authority = self.objects().runtime_authority;
self.shared_root(&authority, mutability)
}
pub fn immutable_object(
&mut self,
object: &sui::types::ObjectReference,
) -> Result<Argument, BuildError> {
self.tx.owned_object(object)
}
fn call_raw_target(
&mut self,
target: CallTarget,
arguments: Vec<Argument>,
) -> Result<Argument, BuildError> {
self.tx.call_target(target, arguments)
}
pub fn call_function(
&mut self,
package: sui::types::Address,
module: impl AsRef<str>,
function: impl AsRef<str>,
arguments: Vec<Argument>,
) -> anyhow::Result<Argument> {
Ok(self.tx.call_target(
CallTarget::new(package, module.as_ref(), function.as_ref())?,
arguments,
)?)
}
pub fn call_function_with_type_args(
&mut self,
package: sui::types::Address,
module: impl AsRef<str>,
function: impl AsRef<str>,
type_arguments: Vec<sui::types::TypeTag>,
arguments: Vec<Argument>,
) -> anyhow::Result<Argument> {
let mut target = CallTarget::new(package, module.as_ref(), function.as_ref())?;
target.type_arguments = type_arguments;
Ok(self.tx.call_target(target, arguments)?)
}
pub fn ascii_string(&mut self, value: impl AsRef<str>) -> Result<Argument, BuildError> {
ascii_string(&mut self.tx, value)
}
pub fn object_id(&mut self, object_id: sui::types::Address) -> Result<Argument, BuildError> {
let address = self.tx.arg(&object_id)?;
self.tx.call_target(
sui_framework::object::id_from_address_target()?,
vec![address],
)
}
pub fn receiving_object<T>(
&mut self,
object: &sui::types::ObjectReference,
) -> Result<Argument, BuildError>
where
T: talus_sui_move::MoveStruct + talus_sui_move::HasKey,
{
self.tx.arg(&ReceivingMoveObject::<T>::new(object.clone()))
}
pub fn withdraw_coin_from_address_balance(
&mut self,
coin_type: sui::types::TypeTag,
amount: u64,
) -> Result<Argument, BuildError> {
self.tx.funds_withdrawal_coin(coin_type, amount)
}
pub fn withdraw_sui_coin(&mut self, amount: u64) -> Result<Argument, BuildError> {
let sui_type =
crate::move_bindings::type_tag::<sui_framework::sui::SUI>(self.context.as_ref());
self.withdraw_coin_from_address_balance(sui_type, amount)
}
pub fn send_coin_to_address_balance(
&mut self,
coin_type: sui::types::TypeTag,
coin: Argument,
recipient: sui::types::Address,
) -> anyhow::Result<()> {
let recipient = self.tx.arg(&recipient)?;
let mut target = CallTarget::new(sui::types::Address::TWO, "coin", "send_funds")?;
target.type_arguments = vec![coin_type];
self.call_raw_target(target, vec![coin, recipient])?;
Ok(())
}
pub fn send_sui_to_address_balance(
&mut self,
coin: Argument,
recipient: sui::types::Address,
) -> anyhow::Result<()> {
let sui_type =
crate::move_bindings::type_tag::<sui_framework::sui::SUI>(self.context.as_ref());
self.send_coin_to_address_balance(sui_type, coin, recipient)
}
fn call_target_with_ascii(
&mut self,
value: impl AsRef<str>,
target: impl FnOnce() -> Result<CallTarget, talus_sui_move_call::CallSpecError>,
) -> anyhow::Result<Argument> {
let value = self.ascii_string(value)?;
self.call_target(target, vec![value])
}
pub(crate) fn graph_vertex(&mut self, value: impl AsRef<str>) -> anyhow::Result<Argument> {
self.call_target_with_ascii(value, interface::graph::vertex_from_string_target)
}
pub(crate) fn graph_input_port(&mut self, value: impl AsRef<str>) -> anyhow::Result<Argument> {
self.call_target_with_ascii(value, interface::graph::input_port_from_string_target)
}
pub(crate) fn graph_output_port(&mut self, value: impl AsRef<str>) -> anyhow::Result<Argument> {
self.call_target_with_ascii(value, interface::graph::output_port_from_string_target)
}
pub(crate) fn graph_output_variant(
&mut self,
value: impl AsRef<str>,
) -> anyhow::Result<Argument> {
self.call_target_with_ascii(value, interface::graph::output_variant_from_string_target)
}
pub(crate) fn graph_entry_group(&mut self, value: impl AsRef<str>) -> anyhow::Result<Argument> {
self.call_target_with_ascii(value, interface::graph::entry_group_from_string_target)
}
pub(crate) fn graph_vertex_kind_on_chain(
&mut self,
tool_fqn: impl AsRef<str>,
) -> anyhow::Result<Argument> {
self.call_target_with_ascii(tool_fqn, interface::graph::vertex_on_chain_target)
}
pub(crate) fn graph_vertex_kind_off_chain(
&mut self,
tool_fqn: impl AsRef<str>,
) -> anyhow::Result<Argument> {
self.call_target_with_ascii(tool_fqn, interface::graph::vertex_off_chain_target)
}
pub(crate) fn graph_post_failure_action(
&mut self,
action: &interface::graph::PostFailureAction,
) -> anyhow::Result<Argument> {
let target = match action {
interface::graph::PostFailureAction::Terminate => {
interface::graph::post_failure_action_terminate_target
}
interface::graph::PostFailureAction::TransientContinue => {
interface::graph::post_failure_action_transient_continue_target
}
};
self.call_target(target, vec![])
}
pub(crate) fn graph_edge_kind(
&mut self,
edge_kind: &interface::graph::EdgeKind,
) -> anyhow::Result<Argument> {
let target = match edge_kind {
interface::graph::EdgeKind::Normal => interface::graph::edge_kind_normal_target,
interface::graph::EdgeKind::ForEach => interface::graph::edge_kind_for_each_target,
interface::graph::EdgeKind::Collect => interface::graph::edge_kind_collect_target,
interface::graph::EdgeKind::DoWhile => interface::graph::edge_kind_do_while_target,
interface::graph::EdgeKind::Break => interface::graph::edge_kind_break_target,
interface::graph::EdgeKind::Static => interface::graph::edge_kind_static_target,
};
self.call_target(target, vec![])
}
pub(crate) fn tool_verifier_mode(
&mut self,
mode: &interface::verifier::ToolVerifierMode,
) -> anyhow::Result<Argument> {
let target = match mode {
interface::verifier::ToolVerifierMode::None => {
interface::verifier::verifier_mode_none_target
}
interface::verifier::ToolVerifierMode::RegisteredKey => {
interface::verifier::verifier_mode_registered_key_target
}
interface::verifier::ToolVerifierMode::External => {
interface::verifier::verifier_mode_external_target
}
};
self.call_target(target, vec![])
}
pub(crate) fn nexus_value(&mut self, value: &NexusValue) -> anyhow::Result<Argument> {
if !value.is_well_formed() {
anyhow::bail!("cannot build malformed NexusValue");
}
match value {
NexusValue::Object { id } => {
let id = self.object_id(id.address())?;
self.call_target(primitives::data::object_value_target, vec![id])
}
NexusValue::InlineData { bytes } => {
let bytes = self.byte_vector(bytes)?;
self.call_target(primitives::data::inline_data_value_target, vec![bytes])
}
NexusValue::WalrusData {
blob_id,
content_digest,
} => {
let blob_id = self.byte_vector(blob_id)?;
let content_digest = self.byte_vector(content_digest)?;
self.call_target(
primitives::data::walrus_data_value_target,
vec![blob_id, content_digest],
)
}
}
}
pub(crate) fn nexus_data(&mut self, value: &NexusData) -> anyhow::Result<Argument> {
if !value.is_well_formed() {
anyhow::bail!("cannot build malformed NexusData");
}
match value {
NexusData::One { value } => {
let value = self.nexus_value(value)?;
self.call_target(primitives::data::one_target, vec![value])
}
NexusData::Many { values } => {
let values = values
.iter()
.map(|value| self.nexus_value(value))
.collect::<anyhow::Result<Vec<_>>>()?;
let values = self.move_vector::<NexusValue>(values)?;
self.call_target(primitives::data::many_target, vec![values])
}
}
}
pub(crate) fn meta_schema(&mut self, schema: &MetaSchema) -> anyhow::Result<Argument> {
let input_ports = schema
.input_ports
.iter()
.map(|port| self.port_schema(port))
.collect::<anyhow::Result<Vec<_>>>()?;
let input_ports = self.move_vector::<PortSchema>(input_ports)?;
let output_variants = schema
.output_variants
.iter()
.map(|variant| {
let name = self.tx.arg(&variant.variant_name)?;
let ports = variant
.ports
.iter()
.map(|port| self.port_schema(port))
.collect::<anyhow::Result<Vec<_>>>()?;
let ports = self.move_vector::<PortSchema>(ports)?;
self.call_target(
interface::meta_schema::output_variant_schema_target,
vec![name, ports],
)
})
.collect::<anyhow::Result<Vec<_>>>()?;
let output_variants = self.move_vector::<OutputVariantSchema>(output_variants)?;
self.call_target(
interface::meta_schema::new_target,
vec![input_ports, output_variants],
)
}
fn port_schema(&mut self, schema: &PortSchema) -> anyhow::Result<Argument> {
let port_name = self.tx.arg(&schema.port_name)?;
let is_many = self.tx.arg(&schema.is_many)?;
let value_kind = self.value_kind(schema.value_kind)?;
self.call_target(
interface::meta_schema::port_schema_target,
vec![port_name, is_many, value_kind],
)
}
fn byte_vector(&mut self, bytes: &[u8]) -> anyhow::Result<Argument> {
let mut chunks = bytes.chunks(BYTE_VECTOR_CHUNK_BYTES);
let first = chunks.next().unwrap_or_default().to_vec();
let vector = self.tx.arg(&first)?;
for chunk in chunks {
let suffix = self.tx.arg(&chunk.to_vec())?;
let mut target =
CallTarget::new(sui::types::Address::from_static("0x1"), "vector", "append")?;
target.type_arguments.push(sui::types::TypeTag::U8);
self.call_raw_target(target, vec![vector, suffix])?;
}
Ok(vector)
}
fn value_kind(&mut self, kind: ValueKind) -> anyhow::Result<Argument> {
let target = match kind {
ValueKind::Object => interface::meta_schema::value_kind_object_target,
ValueKind::Data => interface::meta_schema::value_kind_data_target,
};
self.call_target(target, vec![])
}
pub fn move_vector<T>(&mut self, elements: Vec<Argument>) -> Result<Argument, BuildError>
where
T: talus_sui_move::MoveType,
{
let element_type = crate::move_bindings::type_tag::<T>(self.context.as_ref());
self.tx.make_move_vector(Some(element_type), elements)
}
pub fn option<T>(&mut self, value: Option<Argument>) -> Result<Argument, BuildError>
where
T: talus_sui_move::MoveType,
{
crate::move_bindings::with_nexus_scope(self.context.as_ref(), || {
option::<T>(&mut self.tx, value)
})
}
pub fn finish(self) -> sui::types::ProgrammableTransaction {
self.tx.finish()
}
}
#[cfg(feature = "transactions")]
pub fn ptb(
context: &NexusContext,
build: impl FnOnce(&mut NexusPtbBuilder) -> anyhow::Result<()>,
) -> anyhow::Result<sui::types::ProgrammableTransaction> {
let mut tx = NexusPtbBuilder::new(Arc::new(context.clone()));
crate::move_bindings::with_nexus_scope(context, || build(&mut tx))?;
Ok(tx.finish())
}
#[cfg(feature = "transactions")]
impl Deref for NexusPtbBuilder {
type Target = PtbBuilder;
fn deref(&self) -> &Self::Target {
&self.tx
}
}
#[cfg(feature = "transactions")]
impl DerefMut for NexusPtbBuilder {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.tx
}
}
#[cfg(feature = "transactions")]
fn ascii_string(tx: &mut PtbBuilder, value: impl AsRef<str>) -> Result<Argument, BuildError> {
let bytes = tx.arg(&value.as_ref().as_bytes().to_vec())?;
tx.call_target(move_std::ascii::string_target()?, vec![bytes])
}
#[cfg(feature = "transactions")]
fn option<T>(tx: &mut PtbBuilder, value: Option<Argument>) -> Result<Argument, BuildError>
where
T: talus_sui_move::MoveType,
{
match value {
Some(value) => tx.call_target(move_std::option::some_target::<T>()?, vec![value]),
None => tx.call_target(move_std::option::none_target::<T>()?, vec![]),
}
}
#[cfg(all(test, feature = "transactions"))]
mod tests {
use {
super::*,
crate::{
move_bindings::{
interface::agent::FixedTool,
tool::tool_cashier::{send_deposit_target, ToolCashier},
},
sui,
test_utils::sui_mocks,
types::{NexusContext, NexusPackages, PackageRole, PackageVersion},
},
talus_sui_move::MoveStruct,
talus_sui_move_call::CallArg,
};
fn addr(byte: u8) -> sui::types::Address {
sui::types::Address::new([byte; 32])
}
#[test]
fn derives_tool_and_payment_ids_from_their_respective_parents() {
let registry_id = sui::types::Address::from_static(
"0x940f0dd81d4e4ae2cd476ff61ca5699e0d9356e1874d6c4ba3a5bdf28e67b9e9",
);
let fqn = crate::fqn!("xyz.taluslabs.math.i64.add@1");
assert_eq!(
crate::move_bindings::derive_tool_id(registry_id, &fqn).unwrap(),
sui::types::Address::from_static(
"0x63152163bf12d54f38742656cba5d37a05e89d3ef5df7e9d22062e7bff0aed35"
)
);
let add_tool = crate::move_bindings::derive_tool_id(registry_id, &fqn).unwrap();
let add_payment =
crate::move_bindings::derive_tool_cashier_id(addr(0xa7), add_tool).unwrap();
assert_ne!(add_payment, add_tool);
let fqn = crate::fqn!("xyz.taluslabs.math.i64.mul@1");
assert_eq!(
crate::move_bindings::derive_tool_id(registry_id, &fqn).unwrap(),
sui::types::Address::from_static(
"0xc841b225a7e79c76942f3df05f1fcf17c2b259626ed51cb84e562cb3403604da"
)
);
let mul_tool = crate::move_bindings::derive_tool_id(registry_id, &fqn).unwrap();
let mul_payment =
crate::move_bindings::derive_tool_cashier_id(addr(0xa7), mul_tool).unwrap();
assert_ne!(mul_payment, mul_tool);
assert_ne!(mul_payment, add_payment);
}
#[test]
fn derives_network_auth_binding_id_matches_snapshot() {
let registry_pkg_id = "0x1b7beaf7c749f48e8746b2ee2803eaad6303bd353ad967c3e23db50317919beb"
.parse()
.unwrap();
let network_auth_object_id =
"0x47fc1741e0f9d0c3a8f573f82fc5c632bc3f3068c325bff24ecb76e4d685b696"
.parse()
.unwrap();
let leader_cap_id = "0x1b7b4eeb8a11033f52b9394b6e284abd6dc33a2a22ff18f678b65d7a909b6eb7"
.parse()
.unwrap();
assert_eq!(
crate::move_bindings::derive_network_auth_binding_id(
registry_pkg_id,
network_auth_object_id,
&crate::move_bindings::registry::network_auth::IdentityKey::leader(leader_cap_id),
)
.unwrap(),
"0xcd2e634ec159ea299824d23a437992dba70c2a2239cfb7cd16a8ee767b17c040"
.parse()
.unwrap()
);
}
fn objects() -> NexusContext {
fn package(initial: u8, storage: u8) -> PackageVersion {
PackageVersion::new(
addr(initial),
addr(storage),
2,
Default::default(),
Default::default(),
)
}
NexusContext::new(
Arc::new(sui_mocks::mock_nexus_objects()),
NexusPackages {
primitives: Some(package(0x10, 0x11)),
interface: Some(package(0x20, 0x22)),
tool: Some(package(0x80, 0x88)),
registry: Some(package(0x30, 0x33)),
workflow: Some(package(0x40, 0x44)),
scheduler: Some(package(0x50, 0x55)),
},
)
}
fn nexus_value_constructors(value: &NexusData) -> Vec<String> {
let mut transaction = NexusPtbBuilder::new(Arc::new(objects()));
transaction.nexus_data(value).unwrap();
transaction
.finish()
.commands
.into_iter()
.filter_map(|command| match command {
sui::types::Command::MoveCall(call) => Some(call.function.to_string()),
_ => None,
})
.collect()
}
fn assert_sui_protocol_limits(transaction: &sui::types::ProgrammableTransaction) {
const MAX_PROGRAMMABLE_TX_COMMANDS: usize = 1_024;
const MAX_PROGRAMMABLE_TX_BYTES_WITH_HEADROOM: usize = 120 * 1_024;
assert!(transaction.commands.len() <= MAX_PROGRAMMABLE_TX_COMMANDS);
for input in &transaction.inputs {
if let sui::types::Input::Pure(bytes) = input {
assert!(bytes.len() < MAX_PURE_INPUT_BYTES);
}
}
assert!(
bcs::to_bytes(transaction).unwrap().len() <= MAX_PROGRAMMABLE_TX_BYTES_WITH_HEADROOM
);
}
#[test]
fn nexus_data_one_builds_direct_inline_value() {
let value = NexusData::inline_data(b"one").expect("fixture is bounded");
assert_eq!(
nexus_value_constructors(&value),
["inline_data_value", "one"]
);
}
#[test]
fn nexus_data_many_builds_direct_inline_values() {
let value = NexusData::inline_data_many([b"one".to_vec(), b"two".to_vec()])
.expect("fixture shape matches");
assert_eq!(
nexus_value_constructors(&value),
["inline_data_value", "inline_data_value", "many"]
);
}
#[test]
fn nexus_data_empty_many_builds_no_ptb_inputs_or_commands() {
let value = NexusData::Many { values: Vec::new() };
let mut transaction = NexusPtbBuilder::new(Arc::new(objects()));
assert!(transaction
.nexus_data(&value)
.unwrap_err()
.to_string()
.contains("cannot build malformed NexusData"));
let transaction = transaction.finish();
assert!(transaction.inputs.is_empty());
assert!(transaction.commands.is_empty());
}
#[test]
fn nexus_data_builds_object_and_walrus_values() {
let mut transaction = NexusPtbBuilder::new(Arc::new(objects()));
let object = NexusData::object(addr(0x99));
let walrus =
NexusData::walrus_data(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", vec![7; 32])
.expect("fixture is valid");
transaction
.nexus_data(&object)
.expect("typed Object should build");
transaction
.nexus_data(&walrus)
.expect("typed Walrus Data should build");
let functions = transaction
.finish()
.commands
.into_iter()
.filter_map(|command| match command {
sui::types::Command::MoveCall(call) => Some(call.function.to_string()),
_ => None,
})
.collect::<Vec<_>>();
assert!(functions.iter().any(|name| name == "object_value"));
assert!(functions.iter().any(|name| name == "walrus_data_value"));
}
#[test]
fn maximal_valid_inline_one_is_sui_ptb_buildable() {
let value = NexusData::inline_data(vec![0; 61_440]).unwrap();
let mut builder = NexusPtbBuilder::new(Arc::new(objects()));
builder.nexus_data(&value).unwrap();
assert_sui_protocol_limits(&builder.finish());
}
#[test]
fn aggregate_limit_inline_many_is_sui_ptb_buildable() {
let value = NexusData::inline_data_many((0..256).map(|_| vec![0; 240])).unwrap();
let mut builder = NexusPtbBuilder::new(Arc::new(objects()));
builder.nexus_data(&value).unwrap();
assert_sui_protocol_limits(&builder.finish());
}
#[test]
fn maximal_count_walrus_many_is_sui_ptb_buildable() {
let blob_id = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let value =
NexusData::walrus_data_many((0..256).map(|_| (blob_id.to_vec(), vec![0; 32]))).unwrap();
let mut builder = NexusPtbBuilder::new(Arc::new(objects()));
builder.nexus_data(&value).unwrap();
assert_sui_protocol_limits(&builder.finish());
}
#[test]
fn maximal_meta_schema_is_sui_ptb_buildable() {
let input_ports = (0..32)
.map(|index| {
PortSchema::new(
format!("input-{index}").into_bytes(),
false,
ValueKind::Data,
)
})
.collect();
let output_variants = (0..8)
.map(|variant| {
OutputVariantSchema::new(
format!("variant-{variant}").into_bytes(),
(0..16)
.map(|port| {
PortSchema::new(
format!("port-{port}").into_bytes(),
true,
ValueKind::Data,
)
})
.collect(),
)
})
.collect();
let schema = MetaSchema::new(input_ports, output_variants);
schema.validate_for_tool(false).unwrap();
let mut builder = NexusPtbBuilder::new(Arc::new(objects()));
builder.meta_schema(&schema).unwrap();
let transaction = builder.finish();
assert_eq!(transaction.commands.len(), 339);
assert_sui_protocol_limits(&transaction);
}
#[test]
fn scopes_call_package_and_type_package_separately() {
let objects = objects();
let (target, tag) = crate::move_bindings::with_nexus_scope(&objects, || {
(
send_deposit_target().unwrap(),
ToolCashier::struct_tag_static(),
)
});
let tool = objects.require_package(PackageRole::Tool).unwrap();
assert_eq!(target.package, tool.storage_id);
assert_eq!(*tag.address(), tool.initial_id);
}
#[test]
fn scopes_generated_generic_types() {
let objects = objects();
let mut transaction = NexusPtbBuilder::new(Arc::new(objects.clone()));
transaction.move_vector::<FixedTool>(vec![]).unwrap();
transaction.option::<FixedTool>(None).unwrap();
let transaction = transaction.finish();
let sui::types::Command::MakeMoveVector(vector) = &transaction.commands[0] else {
panic!("expected a Move vector command");
};
let Some(sui::types::TypeTag::Struct(element_type)) = &vector.type_ else {
panic!("expected a generated struct element type");
};
assert_eq!(
*element_type.address(),
objects
.require_package(PackageRole::Interface)
.unwrap()
.initial_id
);
let sui::types::Command::MoveCall(option) = &transaction.commands[1] else {
panic!("expected an Option constructor call");
};
let sui::types::TypeTag::Struct(element_type) = &option.type_arguments[0] else {
panic!("expected a generated Option element type");
};
assert_eq!(
*element_type.address(),
objects
.require_package(PackageRole::Interface)
.unwrap()
.initial_id
);
}
#[test]
fn verifier_modes_call_the_matching_move_constructors() {
use crate::move_bindings::interface::verifier::ToolVerifierMode;
for (mode, expected_function) in [
(ToolVerifierMode::None, "verifier_mode_none"),
(
ToolVerifierMode::RegisteredKey,
"verifier_mode_registered_key",
),
(ToolVerifierMode::External, "verifier_mode_external"),
] {
let transaction = ptb(&objects(), |tx| {
tx.tool_verifier_mode(&mode)?;
Ok(())
})
.unwrap();
let call = transaction
.commands
.iter()
.find_map(|command| match command {
sui::types::Command::MoveCall(call) => Some(call),
_ => None,
})
.expect("mode constructor emits one Move call");
assert_eq!(call.function.as_str(), expected_function);
}
}
#[test]
fn withdraw_sui_coin_uses_sender_address_balance() {
let objects = objects();
let ptb = ptb(&objects, |tx| {
tx.withdraw_sui_coin(42)?;
Ok(())
})
.unwrap();
let CallArg::FundsWithdrawal(withdrawal) = &ptb.inputs[0] else {
panic!("expected funds withdrawal input");
};
assert_eq!(withdrawal.amount(), Some(42));
assert_eq!(withdrawal.source(), sui::types::WithdrawFrom::Sender);
assert_eq!(
withdrawal.coin_type(),
&crate::move_bindings::type_tag::<crate::move_bindings::sui_framework::sui::SUI>(
&objects
)
);
let sui::types::Command::MoveCall(redeem) = &ptb.commands[0] else {
panic!("expected redeem funds call");
};
assert_eq!(redeem.package, sui::types::Address::from_static("0x2"));
assert_eq!(redeem.module.as_str(), "coin");
assert_eq!(redeem.function.as_str(), "redeem_funds");
}
#[test]
fn send_sui_to_address_balance_consumes_the_coin() {
let objects = objects();
let recipient = addr(9);
let ptb = ptb(&objects, |tx| {
let coin = tx.withdraw_sui_coin(42)?;
tx.send_sui_to_address_balance(coin, recipient)?;
Ok(())
})
.unwrap();
let sui::types::Command::MoveCall(send) = &ptb.commands[1] else {
panic!("expected send_funds call");
};
assert_eq!(send.package, sui::types::Address::TWO);
assert_eq!(send.module.as_str(), "coin");
assert_eq!(send.function.as_str(), "send_funds");
}
}