diff --git a/_typos.toml b/_typos.toml index b092c77ae..eceda248e 100644 --- a/_typos.toml +++ b/_typos.toml @@ -12,6 +12,7 @@ inout = "inout" pool_tdx_2_1c4ml86h8lvfk7jma0jy0vksh8srcxhmtax8nd3aur29qtd2k2wmlzk = "pool_tdx_2_1c4ml86h8lvfk7jma0jy0vksh8srcxhmtax8nd3aur29qtd2k2wmlzk" account_sim1c8s2hass5g62ckwpv78y8ykdqljtetv4ve6etcz64gveykxznj36tr = "account_sim1c8s2hass5g62ckwpv78y8ykdqljtetv4ve6etcz64gveykxznj36tr" resource_tdx_2_1thw7yclz24h5xjp3086cj8z2ya0d7p9mydk0yh68c28ha02uhzrnyy = "resource_tdx_2_1thw7yclz24h5xjp3086cj8z2ya0d7p9mydk0yh68c28ha02uhzrnyy" +resource_tdx_2_1ntuaekqexa73m9en04jj3vdt3fk9u9kdk8q9su4efldun2y7nd3cga = "resource_tdx_2_1ntuaekqexa73m9en04jj3vdt3fk9u9kdk8q9su4efldun2y7nd3cga" ec4892a8ba3b86f1 = "ec4892a8ba3b86f1" f87611f279e0daa3 = "f87611f279e0daa3" diff --git a/src/core/types/decimal192.rs b/src/core/types/decimal192.rs index 89de29579..8c5960bf4 100644 --- a/src/core/types/decimal192.rs +++ b/src/core/types/decimal192.rs @@ -247,11 +247,28 @@ impl Decimal { } } + /// Rounds this number to the specified decimal places, or if + /// None, rounds to `Decimal192::SCALE` places, using the + /// rounding mode `ToNearestMidpointAwayFromZero`. + /// + /// # Panics + /// - Panic if the number of decimal places is not within [0..SCALE(=18)] + pub fn round(&self, decimal_places: impl Into>) -> Self { + let mode = RoundingMode::ToNearestMidpointAwayFromZero; + self.round_with_mode( + decimal_places.into().unwrap_or(Decimal192::SCALE as i32), + mode, + ) + .unwrap_or_else(|_| { + panic!("Should always be able to round using mode {}", mode) + }) + } + /// Rounds this number to the specified decimal places. /// /// # Panics /// - Panic if the number of decimal places is not within [0..SCALE(=18)] - pub fn round( + pub fn round_with_mode( &self, decimal_places: i32, rounding_mode: RoundingMode, @@ -510,7 +527,7 @@ pub fn decimal_round( decimal_places: i32, rounding_mode: RoundingMode, ) -> Result { - decimal.round(decimal_places, rounding_mode) + decimal.round_with_mode(decimal_places, rounding_mode) } #[cfg(test)] @@ -1104,22 +1121,22 @@ mod uniffi_tests { .unwrap(); assert!(max - .round(0, RoundingMode::ToNearestMidpointAwayFromZero) + .round_with_mode(0, RoundingMode::ToNearestMidpointAwayFromZero) .is_ok()); assert!(max - .round(0, RoundingMode::ToNearestMidpointTowardZero) + .round_with_mode(0, RoundingMode::ToNearestMidpointTowardZero) .is_ok()); - assert!(max.round(0, RoundingMode::ToZero).is_ok()); + assert!(max.round_with_mode(0, RoundingMode::ToZero).is_ok()); assert!(max - .round(18, RoundingMode::ToNearestMidpointAwayFromZero) + .round_with_mode(18, RoundingMode::ToNearestMidpointAwayFromZero) .is_ok()); assert!(max - .round(18, RoundingMode::ToNearestMidpointTowardZero) + .round_with_mode(18, RoundingMode::ToNearestMidpointTowardZero) .is_ok()); - assert!(max.round(18, RoundingMode::ToZero).is_ok()); + assert!(max.round_with_mode(18, RoundingMode::ToZero).is_ok()); - assert!(max.round(0, RoundingMode::AwayFromZero).is_err()); + assert!(max.round_with_mode(0, RoundingMode::AwayFromZero).is_err()); } #[test] diff --git a/src/core/types/rounding_mode.rs b/src/core/types/rounding_mode.rs index 734cf83ed..aec2b3d9d 100644 --- a/src/core/types/rounding_mode.rs +++ b/src/core/types/rounding_mode.rs @@ -4,7 +4,14 @@ use crate::prelude::*; /// /// Following the same naming convention as https://docs.rs/rust_decimal/latest/rust_decimal/enum.RoundingStrategy.html. #[derive( - Clone, Copy, Debug, PartialEq, Eq, enum_iterator::Sequence, uniffi::Enum, + Clone, + Copy, + Debug, + PartialEq, + Eq, + enum_iterator::Sequence, + strum::Display, + uniffi::Enum, )] pub enum RoundingMode { /// The number is always rounded toward positive infinity, e.g. `3.1 -> 4`, `-3.1 -> -3`. diff --git a/src/profile/v100/address/non_fungible_resource_address.rs b/src/profile/v100/address/non_fungible_resource_address.rs index c2add2164..4eba72ada 100644 --- a/src/profile/v100/address/non_fungible_resource_address.rs +++ b/src/profile/v100/address/non_fungible_resource_address.rs @@ -22,6 +22,8 @@ macro_rules! decl_specialized_address { PartialEq, Eq, Hash, + Ord, + PartialOrd, derive_more::Display, derive_more::Debug, derive_more::FromStr, @@ -203,6 +205,12 @@ mod tests { assert_ne!(SUT::sample_mainnet_other(), SUT::sample_stokenet_other()); } + #[test] + fn ord() { + assert!(SUT::sample_mainnet_other() < SUT::sample_mainnet()); + assert!(SUT::sample_stokenet_other() > SUT::sample_stokenet()); + } + #[test] fn display() { let s = "resource_rdx1nfyg2f68jw7hfdlg5hzvd8ylsa7e0kjl68t5t62v3ttamtejc9wlxa"; diff --git a/src/profile/v100/address/resource_address.rs b/src/profile/v100/address/resource_address.rs index 7e355d0de..6ac155337 100644 --- a/src/profile/v100/address/resource_address.rs +++ b/src/profile/v100/address/resource_address.rs @@ -13,6 +13,10 @@ impl ResourceAddress { Self::new(radix_engine::types::XRD, id) .expect("Should never fail to get XRD on network.") } + + pub fn is_xrd_on_network(&self, id: NetworkID) -> bool { + self == &Self::xrd_on_network(id) + } } #[uniffi::export] @@ -184,6 +188,35 @@ mod tests { assert!(SUT::sample_mainnet_xrd().is_fungible()); } + #[test] + fn is_xrd_on_network() { + assert!(SUT::sample_mainnet_xrd().is_xrd_on_network(NetworkID::Mainnet)); + assert!( + SUT::sample_stokenet_xrd().is_xrd_on_network(NetworkID::Stokenet) + ); + + // Not XRD + assert!( + !SUT::sample_mainnet_xrd().is_xrd_on_network(NetworkID::Stokenet) + ); + assert!( + !SUT::sample_stokenet_xrd().is_xrd_on_network(NetworkID::Mainnet) + ); + + assert!( + !SUT::sample_mainnet_candy().is_xrd_on_network(NetworkID::Mainnet) + ); + assert!( + !SUT::sample_mainnet_candy().is_xrd_on_network(NetworkID::Stokenet) + ); + + assert!(!SUT::sample_stokenet_candy() + .is_xrd_on_network(NetworkID::Stokenet)); + assert!( + !SUT::sample_stokenet_candy().is_xrd_on_network(NetworkID::Mainnet) + ); + } + #[test] fn hash() { assert_eq!( diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifest_assets_transfers.rs b/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifest_assets_transfers.rs new file mode 100644 index 000000000..ba86784af --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/manifest_building/manifest_assets_transfers.rs @@ -0,0 +1,573 @@ +use crate::prelude::*; + +impl TransactionManifest { + /// Uses `per_asset_transfers` after having transposed the `PerRecipientAssetTransfers` + /// into `PerAssetTransfers`. We always use `PerAssetTransfers` when building the manifest + /// since it is more efficient (allows a single withdraw per resource) => fewer instruction => + /// cheaper TX fee for user. + pub fn per_recipient_transfers( + transfers: PerRecipientAssetTransfers, + ) -> Self { + Self::per_asset_transfers(transfers.transpose()) + } + + pub fn per_asset_transfers(transfers: PerAssetTransfers) -> Self { + let mut builder = ScryptoManifestBuilder::new(); + let bucket_factory = BucketFactory::default(); + let from_account = &transfers.from_account; + + for fungible in transfers.fungible_resources { + let divisibility = fungible.resource.divisibility; + + let resource_address = &fungible.resource.resource_address; + + builder = builder.withdraw_from_account( + from_account, + resource_address, + fungible.total_transfer_amount(), + ); + + for transfer in fungible.transfers { + let bucket = &bucket_factory.next(); + + builder = builder.take_from_worktop( + resource_address, + transfer.amount(divisibility), + bucket, + ); + + builder = transfer.deposit_instruction(builder, bucket); + } + } + + for non_fungible in transfers.non_fungible_resources { + let resource_address = &non_fungible.resource; + + builder = builder.withdraw_non_fungibles_from_account( + from_account, + resource_address, + non_fungible.all_ids(), + ); + + for transfer in non_fungible.transfers { + let bucket = &bucket_factory.next(); + + builder = builder.take_non_fungibles_from_worktop( + resource_address, + transfer.local_ids(), + bucket, + ); + + builder = transfer.deposit_instruction(builder, bucket); + } + } + + let scrypto_manifest = builder.build(); + + TransactionManifest::from_scrypto( + scrypto_manifest, + from_account.network_id(), + ) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = TransactionManifest; + + #[test] + fn trivial() { + let sut = SUT::per_asset_transfers(PerAssetTransfers::new( + AccountAddress::sample(), + [], + [], + )); + manifest_eq(sut, ""); // empty! + } + + #[test] + fn use_try_deposit_or_abort_is_false() { + let transfers = PerRecipientAssetTransfers::new( + AccountAddress::sample_mainnet(), + [PerRecipientAssetTransfer::new( + AccountAddress::sample_mainnet_other(), + [PerRecipientFungibleTransfer::new( + ResourceAddress::sample_mainnet_candy(), + 1337, + false, + None, + )], + [PerRecipientNonFungiblesTransfer::new( + NonFungibleResourceAddress::sample_mainnet_other(), + false, + [NonFungibleLocalId::integer(237)], + )], + )], + ); + let manifest = SUT::per_recipient_transfers(transfers); + manifest_eq( + manifest, + r##" + CALL_METHOD + Address("account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease") + "withdraw" + Address("resource_rdx1t4dy69k6s0gv040xa64cyadyefwtett62ng6xfdnljyydnml7t6g3j") + Decimal("1337") + ; + TAKE_FROM_WORKTOP + Address("resource_rdx1t4dy69k6s0gv040xa64cyadyefwtett62ng6xfdnljyydnml7t6g3j") + Decimal("1337") + Bucket("bucket1") + ; + CALL_METHOD + Address("account_rdx16yf8jxxpdtcf4afpj5ddeuazp2evep7quuhgtq28vjznee08master") + "deposit" + Bucket("bucket1") + ; + CALL_METHOD + Address("account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease") + "withdraw_non_fungibles" + Address("resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd") + Array( + NonFungibleLocalId("#237#") + ) + ; + TAKE_NON_FUNGIBLES_FROM_WORKTOP + Address("resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd") + Array( + NonFungibleLocalId("#237#") + ) + Bucket("bucket2") + ; + CALL_METHOD + Address("account_rdx16yf8jxxpdtcf4afpj5ddeuazp2evep7quuhgtq28vjznee08master") + "deposit" + Bucket("bucket2") + ; + "##, + ); + } + + #[test] + fn multi_token_multi_recipient() { + let sender: AccountAddress = "account_tdx_2_128rkfzdztjpgajucstydar2gz2vp9jj779k33jy3gect2rh5r28rgn".parse().unwrap(); + let recip0: AccountAddress = "account_tdx_2_129e9h6zp5z08qkc0q5tdqz9zc67gg2k7tergrj9erznmke6qeevmsv".parse().unwrap(); + let recip1: AccountAddress = "account_tdx_2_128a45a7hetjfpfqdlsp07eyrmhq7edldefgd7263jd58puzuq09qks".parse().unwrap(); + + let nft_c0: NonFungibleResourceAddress = "resource_tdx_2_1n2sjxxtk6vm6pvk8dxr798e8zpxqz50q5wlmldlat0qhh04u2mwmy8".parse().unwrap(); + let nft_c1: NonFungibleResourceAddress = "resource_tdx_2_1ntuaekqexa73m9en04jj3vdt3fk9u9kdk8q9su4efldun2y7nd3cga".parse().unwrap(); + + let fung_0: ResourceAddress = ResourceAddress::sample_stokenet_xrd(); + let fung_1: ResourceAddress = + ResourceAddress::sample_stokenet_gc_tokens(); + + let per_recipient_transfers = PerRecipientAssetTransfers::new( + sender.clone(), + [ + PerRecipientAssetTransfer::new( + recip0.clone(), + [ + PerRecipientFungibleTransfer::new( + fung_0.clone(), + 30, + true, + 18, + ), + PerRecipientFungibleTransfer::new( + fung_1.clone(), + 3, + true, + 18, + ), + ], + [ + PerRecipientNonFungiblesTransfer::new( + nft_c0.clone(), + true, + [ + NonFungibleLocalId::integer(40), + NonFungibleLocalId::integer(48), + ], + ), + PerRecipientNonFungiblesTransfer::new( + nft_c1.clone(), + true, + [ + NonFungibleLocalId::integer(21), + NonFungibleLocalId::integer(3), + ], + ), + ], + ), + PerRecipientAssetTransfer::new( + recip1.clone(), + [ + PerRecipientFungibleTransfer::new( + fung_0.clone(), + 50, + true, + 18, + ), + PerRecipientFungibleTransfer::new( + fung_1.clone(), + 5, + true, + 18, + ), + ], + [ + PerRecipientNonFungiblesTransfer::new( + nft_c0.clone(), + true, + [ + NonFungibleLocalId::integer(34), + NonFungibleLocalId::integer(22), + ], + ), + PerRecipientNonFungiblesTransfer::new( + nft_c1.clone(), + true, + [ + NonFungibleLocalId::integer(15), + NonFungibleLocalId::integer(9), + NonFungibleLocalId::integer(13), + ], + ), + ], + ), + ], + ); + + let per_asset_transfers = PerAssetTransfers::new( + sender.clone(), + [ + PerAssetTransfersOfFungibleResource::new( + PerAssetFungibleResource::new(fung_0.clone(), 18), + [ + PerAssetFungibleTransfer::new(recip0.clone(), true, 30), + PerAssetFungibleTransfer::new(recip1.clone(), true, 50), + ], + ), + PerAssetTransfersOfFungibleResource::new( + PerAssetFungibleResource::new(fung_1.clone(), 18), + [ + PerAssetFungibleTransfer::new(recip0.clone(), true, 3), + PerAssetFungibleTransfer::new(recip1.clone(), true, 5), + ], + ), + ], + [ + PerAssetTransfersOfNonFungibleResource::new( + nft_c0.clone(), + [ + PerAssetNonFungibleTransfer::new( + recip0.clone(), + true, + [ + NonFungibleLocalId::integer(40), + NonFungibleLocalId::integer(48), + ], + ), + PerAssetNonFungibleTransfer::new( + recip1.clone(), + true, + [ + NonFungibleLocalId::integer(34), + NonFungibleLocalId::integer(22), + ], + ), + ], + ), + PerAssetTransfersOfNonFungibleResource::new( + nft_c1.clone(), + [ + PerAssetNonFungibleTransfer::new( + recip0.clone(), + true, + [ + NonFungibleLocalId::integer(21), + NonFungibleLocalId::integer(3), + ], + ), + PerAssetNonFungibleTransfer::new( + recip1.clone(), + true, + [ + NonFungibleLocalId::integer(15), + NonFungibleLocalId::integer(9), + NonFungibleLocalId::integer(13), + ], + ), + ], + ), + ], + ); + + let transposed = per_recipient_transfers.clone().transpose(); + pretty_assertions::assert_eq!(per_asset_transfers.clone(), transposed); + + let sut = SUT::per_asset_transfers(per_asset_transfers.clone()); + assert_eq!( + SUT::per_recipient_transfers(per_recipient_transfers), + sut.clone() + ); + + manifest_eq( + sut, + r##" + CALL_METHOD + Address("account_tdx_2_128rkfzdztjpgajucstydar2gz2vp9jj779k33jy3gect2rh5r28rgn") + "withdraw" + Address("resource_tdx_2_1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxtfd2jc") + Decimal("80") + ; + TAKE_FROM_WORKTOP + Address("resource_tdx_2_1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxtfd2jc") + Decimal("30") + Bucket("bucket1") + ; + CALL_METHOD + Address("account_tdx_2_129e9h6zp5z08qkc0q5tdqz9zc67gg2k7tergrj9erznmke6qeevmsv") + "try_deposit_or_abort" + Bucket("bucket1") + Enum<0u8>() + ; + TAKE_FROM_WORKTOP + Address("resource_tdx_2_1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxtfd2jc") + Decimal("50") + Bucket("bucket2") + ; + CALL_METHOD + Address("account_tdx_2_128a45a7hetjfpfqdlsp07eyrmhq7edldefgd7263jd58puzuq09qks") + "try_deposit_or_abort" + Bucket("bucket2") + Enum<0u8>() + ; + CALL_METHOD + Address("account_tdx_2_128rkfzdztjpgajucstydar2gz2vp9jj779k33jy3gect2rh5r28rgn") + "withdraw" + Address("resource_tdx_2_1thqcgjw37fjgycpvqr52nx4jcsdeuq75mf2nywme07kzsuds9a4psp") + Decimal("8") + ; + TAKE_FROM_WORKTOP + Address("resource_tdx_2_1thqcgjw37fjgycpvqr52nx4jcsdeuq75mf2nywme07kzsuds9a4psp") + Decimal("3") + Bucket("bucket3") + ; + CALL_METHOD + Address("account_tdx_2_129e9h6zp5z08qkc0q5tdqz9zc67gg2k7tergrj9erznmke6qeevmsv") + "try_deposit_or_abort" + Bucket("bucket3") + Enum<0u8>() + ; + TAKE_FROM_WORKTOP + Address("resource_tdx_2_1thqcgjw37fjgycpvqr52nx4jcsdeuq75mf2nywme07kzsuds9a4psp") + Decimal("5") + Bucket("bucket4") + ; + CALL_METHOD + Address("account_tdx_2_128a45a7hetjfpfqdlsp07eyrmhq7edldefgd7263jd58puzuq09qks") + "try_deposit_or_abort" + Bucket("bucket4") + Enum<0u8>() + ; + CALL_METHOD + Address("account_tdx_2_128rkfzdztjpgajucstydar2gz2vp9jj779k33jy3gect2rh5r28rgn") + "withdraw_non_fungibles" + Address("resource_tdx_2_1n2sjxxtk6vm6pvk8dxr798e8zpxqz50q5wlmldlat0qhh04u2mwmy8") + Array( + NonFungibleLocalId("#40#"), + NonFungibleLocalId("#48#"), + NonFungibleLocalId("#34#"), + NonFungibleLocalId("#22#") + ) + ; + TAKE_NON_FUNGIBLES_FROM_WORKTOP + Address("resource_tdx_2_1n2sjxxtk6vm6pvk8dxr798e8zpxqz50q5wlmldlat0qhh04u2mwmy8") + Array( + NonFungibleLocalId("#40#"), + NonFungibleLocalId("#48#") + ) + Bucket("bucket5") + ; + CALL_METHOD + Address("account_tdx_2_129e9h6zp5z08qkc0q5tdqz9zc67gg2k7tergrj9erznmke6qeevmsv") + "try_deposit_or_abort" + Bucket("bucket5") + Enum<0u8>() + ; + TAKE_NON_FUNGIBLES_FROM_WORKTOP + Address("resource_tdx_2_1n2sjxxtk6vm6pvk8dxr798e8zpxqz50q5wlmldlat0qhh04u2mwmy8") + Array( + NonFungibleLocalId("#34#"), + NonFungibleLocalId("#22#") + ) + Bucket("bucket6") + ; + CALL_METHOD + Address("account_tdx_2_128a45a7hetjfpfqdlsp07eyrmhq7edldefgd7263jd58puzuq09qks") + "try_deposit_or_abort" + Bucket("bucket6") + Enum<0u8>() + ; + CALL_METHOD + Address("account_tdx_2_128rkfzdztjpgajucstydar2gz2vp9jj779k33jy3gect2rh5r28rgn") + "withdraw_non_fungibles" + Address("resource_tdx_2_1ntuaekqexa73m9en04jj3vdt3fk9u9kdk8q9su4efldun2y7nd3cga") + Array( + NonFungibleLocalId("#21#"), + NonFungibleLocalId("#3#"), + NonFungibleLocalId("#15#"), + NonFungibleLocalId("#9#"), + NonFungibleLocalId("#13#") + ) + ; + TAKE_NON_FUNGIBLES_FROM_WORKTOP + Address("resource_tdx_2_1ntuaekqexa73m9en04jj3vdt3fk9u9kdk8q9su4efldun2y7nd3cga") + Array( + NonFungibleLocalId("#21#"), + NonFungibleLocalId("#3#") + ) + Bucket("bucket7") + ; + CALL_METHOD + Address("account_tdx_2_129e9h6zp5z08qkc0q5tdqz9zc67gg2k7tergrj9erznmke6qeevmsv") + "try_deposit_or_abort" + Bucket("bucket7") + Enum<0u8>() + ; + TAKE_NON_FUNGIBLES_FROM_WORKTOP + Address("resource_tdx_2_1ntuaekqexa73m9en04jj3vdt3fk9u9kdk8q9su4efldun2y7nd3cga") + Array( + NonFungibleLocalId("#15#"), + NonFungibleLocalId("#9#"), + NonFungibleLocalId("#13#") + ) + Bucket("bucket8") + ; + CALL_METHOD + Address("account_tdx_2_128a45a7hetjfpfqdlsp07eyrmhq7edldefgd7263jd58puzuq09qks") + "try_deposit_or_abort" + Bucket("bucket8") + Enum<0u8>() + ; + "##, + ); + } + + #[test] + fn simple() { + let sut = SUT::per_asset_transfers(PerAssetTransfers::sample()); + manifest_eq( + sut, + r##" + CALL_METHOD + Address("account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease") + "withdraw" + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + Decimal("987891.25712718281828") + ; + TAKE_FROM_WORKTOP + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + Decimal("237.13372718281828") + Bucket("bucket1") + ; + CALL_METHOD + Address("account_rdx12xvg2sssh0rpca6e8xyqv5vf4nqu928083yzf0fdrnvjdz2pvc000x") + "try_deposit_or_abort" + Bucket("bucket1") + Enum<0u8>() + ; + TAKE_FROM_WORKTOP + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + Decimal("987654.1234") + Bucket("bucket2") + ; + CALL_METHOD + Address("account_rdx129a9wuey40lducsf6yu232zmzk5kscpvnl6fv472r0ja39f3hced69") + "try_deposit_or_abort" + Bucket("bucket2") + Enum<0u8>() + ; + CALL_METHOD + Address("account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease") + "withdraw" + Address("resource_rdx1t4dy69k6s0gv040xa64cyadyefwtett62ng6xfdnljyydnml7t6g3j") + Decimal("987654.1234") + ; + TAKE_FROM_WORKTOP + Address("resource_rdx1t4dy69k6s0gv040xa64cyadyefwtett62ng6xfdnljyydnml7t6g3j") + Decimal("987654.1234") + Bucket("bucket3") + ; + CALL_METHOD + Address("account_rdx129a9wuey40lducsf6yu232zmzk5kscpvnl6fv472r0ja39f3hced69") + "try_deposit_or_abort" + Bucket("bucket3") + Enum<0u8>() + ; + CALL_METHOD + Address("account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease") + "withdraw_non_fungibles" + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + Array( + NonFungibleLocalId("{deaddeaddeaddead-deaddeaddeaddead-deaddeaddeaddead-deaddeaddeaddead}"), + NonFungibleLocalId("") + ) + ; + TAKE_NON_FUNGIBLES_FROM_WORKTOP + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + Array( + NonFungibleLocalId("{deaddeaddeaddead-deaddeaddeaddead-deaddeaddeaddead-deaddeaddeaddead}"), + NonFungibleLocalId("") + ) + Bucket("bucket4") + ; + CALL_METHOD + Address("account_rdx12xvg2sssh0rpca6e8xyqv5vf4nqu928083yzf0fdrnvjdz2pvc000x") + "try_deposit_or_abort" + Bucket("bucket4") + Enum<0u8>() + ; + TAKE_NON_FUNGIBLES_FROM_WORKTOP + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + Array( + NonFungibleLocalId("") + ) + Bucket("bucket5") + ; + CALL_METHOD + Address("account_rdx129a9wuey40lducsf6yu232zmzk5kscpvnl6fv472r0ja39f3hced69") + "try_deposit_or_abort" + Bucket("bucket5") + Enum<0u8>() + ; + CALL_METHOD + Address("account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease") + "withdraw_non_fungibles" + Address("resource_rdx1t4dy69k6s0gv040xa64cyadyefwtett62ng6xfdnljyydnml7t6g3j") + Array( + NonFungibleLocalId("") + ) + ; + TAKE_NON_FUNGIBLES_FROM_WORKTOP + Address("resource_rdx1t4dy69k6s0gv040xa64cyadyefwtett62ng6xfdnljyydnml7t6g3j") + Array( + NonFungibleLocalId("") + ) + Bucket("bucket6") + ; + CALL_METHOD + Address("account_rdx129a9wuey40lducsf6yu232zmzk5kscpvnl6fv472r0ja39f3hced69") + "try_deposit_or_abort" + Bucket("bucket6") + Enum<0u8>() + ; + "##, + ); + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/metadata.rs b/src/wrapped_radix_engine_toolkit/high_level/manifest_building/metadata.rs index 0df310498..cae36692a 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/metadata.rs +++ b/src/wrapped_radix_engine_toolkit/high_level/manifest_building/metadata.rs @@ -5,17 +5,11 @@ use strum::*; #[strum(serialize_all = "snake_case")] pub enum MetadataKey { AccountType, - OwnerKeys, - Name, - Symbol, - IconUrl, - Description, - Tags, } diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs b/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs index 4679a304c..1bfa46d35 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs +++ b/src/wrapped_radix_engine_toolkit/high_level/manifest_building/mod.rs @@ -4,6 +4,7 @@ mod assert_manifest; mod addresses_manifest_builder_support; mod bucket; mod bucket_factory; +mod manifest_assets_transfers; mod manifests; mod manifests_create_tokens; mod metadata; @@ -14,6 +15,7 @@ pub use addresses_manifest_builder_support::*; pub use assert_manifest::*; pub use bucket::*; pub use bucket_factory::*; +pub use manifest_assets_transfers::*; pub use manifests::*; pub use manifests_create_tokens::*; pub use metadata::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/modify_manifest.rs b/src/wrapped_radix_engine_toolkit/high_level/manifest_building/modify_manifest.rs index e6b526dbb..fcbbca0f7 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/manifest_building/modify_manifest.rs +++ b/src/wrapped_radix_engine_toolkit/high_level/manifest_building/modify_manifest.rs @@ -214,13 +214,7 @@ CALL_METHOD let divisibility = 4; let rounded_guaranteed_amount: Decimal = "0.1234".parse().unwrap(); assert_eq!( - added_guaranteed_amount - .clone() - .round( - divisibility, - RoundingMode::ToNearestMidpointAwayFromZero // ofc must match mode in impl of `modify_add_guarantees` - ) - .unwrap(), + added_guaranteed_amount.clone().round(divisibility), rounded_guaranteed_amount.clone() ); let mut manifest = TransactionManifest::new( diff --git a/src/wrapped_radix_engine_toolkit/high_level/ret_api.rs b/src/wrapped_radix_engine_toolkit/high_level/ret_api.rs index 511f9e011..ff1cb91e7 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/ret_api.rs +++ b/src/wrapped_radix_engine_toolkit/high_level/ret_api.rs @@ -145,29 +145,27 @@ pub fn debug_print_compiled_notarized_intent( format!("{:?}", notarized) } -/// REQUIRES NETWORK CALL (and probable cache) -/// Requires kotlinx to be setup -// #[uniffi::export] -// pub async fn manifest_assets_transfers( -// _transfers: AssetsTransfersTransactionPrototype, -// _message: Message, -// ) -> Result { -// unreachable!() -// } - -/// REQUIRES NETWORK CALL (and probable cache) -/// Requires kotlinx to be setup -// #[uniffi::export] -// pub async fn needs_signature_for_depositing( -// _into_account: Account, -// _resource: ResourceAddress, -// ) -> Result { -// unreachable!() -// } +/// Uses `per_asset_transfers` after having transposed the `PerRecipientAssetTransfers` +/// into `PerAssetTransfers`. We always use `PerAssetTransfers` when building the manifest +/// since it is more efficient (allows a single withdraw per resource) => fewer instruction => +/// cheaper TX fee for user. +#[uniffi::export] +pub fn manifest_per_recipient_transfers( + transfers: PerRecipientAssetTransfers, +) -> TransactionManifest { + TransactionManifest::per_recipient_transfers(transfers) +} + +#[uniffi::export] +pub fn manifest_per_asset_transfers( + transfers: PerAssetTransfers, +) -> TransactionManifest { + TransactionManifest::per_asset_transfers(transfers) +} #[cfg(test)] mod tests { - use crate::prelude::*; + use super::*; #[test] fn test_manifest_for_faucet() { @@ -442,4 +440,13 @@ mod tests { "NotarizedTransaction { signed_intent: SignedIntent { intent: header:\nTransactionHeader { network_id: Mainnet, start_epoch_inclusive: Epoch(76935), end_epoch_exclusive: Epoch(76945), nonce: Nonce(2371337), notary_public_key: Ed25519 { value: ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf }, notary_is_signatory: true, tip_percentage: 0 }\n\nmessage:\nPlainText { plaintext: PlaintextMessage { mime_type: \"text/plain\", message: StringMessage { string: \"Hello Radix!\" } } }\n\nmanifest:\nCALL_METHOD\n Address(\"account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease\")\n \"lock_fee\"\n Decimal(\"0.61\")\n;\nCALL_METHOD\n Address(\"account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease\")\n \"withdraw\"\n Address(\"resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd\")\n Decimal(\"1337\")\n;\nTAKE_FROM_WORKTOP\n Address(\"resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd\")\n Decimal(\"1337\")\n Bucket(\"bucket1\")\n;\nCALL_METHOD\n Address(\"account_rdx16yf8jxxpdtcf4afpj5ddeuazp2evep7quuhgtq28vjznee08master\")\n \"try_deposit_or_abort\"\n Bucket(\"bucket1\")\n Enum<0u8>()\n;\n\n\n, intent_signatures: IntentSignatures { signatures: [] } }, notary_signature: NotarySignature { secret_magic: Ed25519 { value: 839ac9c47db45950fc0cd453c5ebbbfa7ae5f7c20753abe2370b5b40fdee89e522c4d810d060e0c56211d036043fd32b9908e97bf114c1835ca02d74018fdd09 } } }" ); } + + #[test] + fn per_recipient_uses_per_asset_transfer() { + let transfers = PerRecipientAssetTransfers::sample(); + assert_eq!( + manifest_per_asset_transfers(transfers.clone().transpose()), + manifest_per_recipient_transfers(transfers) + ); + } } diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/assets_transfers_recipient.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/assets_transfers_recipient.rs new file mode 100644 index 000000000..48536f554 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/assets_transfers_recipient.rs @@ -0,0 +1,116 @@ +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Enum)] +#[allow(clippy::large_enum_variant)] // we cannot Box, since Box is not UniFFI compatible. +pub enum AssetsTransfersRecipient { + MyOwnAccount { value: Account }, + ForeignAccount { value: AccountAddress }, +} + +impl From for AssetsTransfersRecipient { + fn from(value: Account) -> Self { + Self::MyOwnAccount { value } + } +} + +impl From for AssetsTransfersRecipient { + fn from(value: AccountAddress) -> Self { + Self::ForeignAccount { value } + } +} + +impl AssetsTransfersRecipient { + pub fn account_address(&self) -> &AccountAddress { + match self { + AssetsTransfersRecipient::MyOwnAccount { value } => &value.address, + AssetsTransfersRecipient::ForeignAccount { value } => value, + } + } +} + +impl AssetsTransfersRecipient { + pub(crate) fn sample_mainnet() -> Self { + Self::MyOwnAccount { + value: Account::sample_mainnet_bob(), + } + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::ForeignAccount { + value: AccountAddress::sample_mainnet_other(), + } + } + + pub(crate) fn sample_stokenet() -> Self { + Self::MyOwnAccount { + value: Account::sample_stokenet_carol(), + } + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::ForeignAccount { + value: AccountAddress::sample_stokenet_other(), + } + } +} + +impl HasSampleValues for AssetsTransfersRecipient { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = AssetsTransfersRecipient; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } + + #[test] + fn from_account() { + let acc = Account::sample(); + let exp = &acc.clone().address; + assert_eq!(SUT::from(acc).account_address(), exp) + } + + #[test] + fn from_address() { + let exp = &AccountAddress::sample(); + assert_eq!(SUT::from(exp.clone()).account_address(), exp) + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/mod.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/mod.rs new file mode 100644 index 000000000..9d7a3ff87 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/mod.rs @@ -0,0 +1,9 @@ +mod assets_transfers_recipient; +mod per_asset; +mod per_recipient; +mod transfer_types; + +pub use assets_transfers_recipient::*; +pub use per_asset::*; +pub use per_recipient::*; +pub use transfer_types::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/mod.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/mod.rs new file mode 100644 index 000000000..56764b3ba --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/mod.rs @@ -0,0 +1,13 @@ +mod per_asset_fungible_resource; +mod per_asset_fungible_transfer; +mod per_asset_non_fungible_transfer; +mod per_asset_transfers; +mod per_asset_transfers_of_fungible_resource; +mod per_asset_transfers_of_non_fungible_resource; + +pub use per_asset_fungible_resource::*; +pub use per_asset_fungible_transfer::*; +pub use per_asset_non_fungible_transfer::*; +pub use per_asset_transfers::*; +pub use per_asset_transfers_of_fungible_resource::*; +pub use per_asset_transfers_of_non_fungible_resource::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_resource.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_resource.rs new file mode 100644 index 000000000..9209828fa --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_resource.rs @@ -0,0 +1,85 @@ +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] +pub struct PerAssetFungibleResource { + pub resource_address: ResourceAddress, + pub divisibility: Option, +} + +impl PerAssetFungibleResource { + pub fn new( + resource_address: ResourceAddress, + divisibility: impl Into>, + ) -> Self { + Self { + resource_address, + divisibility: divisibility.into(), + } + } +} + +impl PerAssetFungibleResource { + pub(crate) fn sample_mainnet() -> Self { + Self::new(ResourceAddress::sample_mainnet_xrd(), None) + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::new(ResourceAddress::sample_mainnet_candy(), 4) + } + + pub(crate) fn sample_stokenet() -> Self { + Self::new(ResourceAddress::sample_stokenet_xrd(), None) + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::new(ResourceAddress::sample_stokenet_gum(), 6) + } +} + +impl HasSampleValues for PerAssetFungibleResource { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerAssetFungibleResource; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_transfer.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_transfer.rs new file mode 100644 index 000000000..e64ca1d46 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_fungible_transfer.rs @@ -0,0 +1,123 @@ +use crate::prelude::*; + +impl PerAssetFungibleTransfer { + pub fn new( + recipient: impl Into, + use_try_deposit_or_abort: bool, + amount: impl Into, + ) -> Self { + Self { + recipient: recipient.into(), + use_try_deposit_or_abort, + amount: amount.into(), + } + } + + pub fn amount( + &self, + divisibility: impl Into>, + ) -> ScryptoDecimal192 { + self.amount.round(divisibility).into() + } +} + +impl From<(&AssetsTransfersRecipient, PerRecipientFungibleTransfer)> + for PerAssetFungibleTransfer +{ + fn from( + value: (&AssetsTransfersRecipient, PerRecipientFungibleTransfer), + ) -> Self { + let (recipient, transfer) = value; + Self::new( + recipient.clone(), + transfer.use_try_deposit_or_abort, + transfer.amount, + ) + } +} + +impl HasSampleValues for PerAssetFungibleTransfer { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +impl PerAssetFungibleTransfer { + pub(crate) fn sample_mainnet() -> Self { + Self::new( + AssetsTransfersRecipient::MyOwnAccount { + value: Account::sample_mainnet_carol(), + }, + true, + Decimal192::from_str("237.13372718281828").unwrap(), + ) + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::new(AssetsTransfersRecipient::ForeignAccount { + value: AccountAddress::from_str("account_rdx129a9wuey40lducsf6yu232zmzk5kscpvnl6fv472r0ja39f3hced69").unwrap() + }, + true, + Decimal192::from_str("987654.1234").unwrap()) + } + + pub(crate) fn sample_stokenet() -> Self { + Self::new( + AssetsTransfersRecipient::MyOwnAccount { + value: Account::sample_stokenet_diana(), + }, + true, + Decimal192::from_str("42.311415").unwrap(), + ) + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::new(AssetsTransfersRecipient::ForeignAccount { + value: AccountAddress::from_str("account_tdx_2_1288efhmjt8kzce77par4ex997x2zgnlv5qqv9ltpxqg7ur0xpqm6gk").unwrap() + }, + true, + Decimal192::from_str("1337.2371828128").unwrap()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerAssetFungibleTransfer; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_non_fungible_transfer.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_non_fungible_transfer.rs new file mode 100644 index 000000000..a4a5c8dc0 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_non_fungible_transfer.rs @@ -0,0 +1,136 @@ +use crate::prelude::*; + +impl PerAssetNonFungibleTransfer { + pub fn new( + recipient: impl Into, + use_try_deposit_or_abort: bool, + non_fungible_local_ids: impl IntoIterator, + ) -> Self { + Self { + recipient: recipient.into(), + use_try_deposit_or_abort, + non_fungible_local_ids: non_fungible_local_ids + .into_iter() + .collect_vec(), + } + } + + pub fn local_ids(&self) -> Vec { + self.non_fungible_local_ids + .clone() + .into_iter() + .map(ScryptoNonFungibleLocalId::from) + .collect_vec() + } +} + +impl From<(&AssetsTransfersRecipient, PerRecipientNonFungiblesTransfer)> + for PerAssetNonFungibleTransfer +{ + fn from( + value: (&AssetsTransfersRecipient, PerRecipientNonFungiblesTransfer), + ) -> Self { + let (recipient, non_fungibles) = value; + Self::new( + recipient.clone(), + non_fungibles.use_try_deposit_or_abort, + non_fungibles.local_ids, + ) + } +} + +impl PerAssetNonFungibleTransfer { + pub(crate) fn sample_mainnet() -> Self { + Self::new( + AssetsTransfersRecipient::MyOwnAccount { + value: Account::sample_mainnet_carol(), + }, + true, + [ + NonFungibleLocalId::sample(), + NonFungibleLocalId::sample_other(), + ], + ) + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::new( + AssetsTransfersRecipient::ForeignAccount { + value: AccountAddress::from_str("account_rdx129a9wuey40lducsf6yu232zmzk5kscpvnl6fv472r0ja39f3hced69").unwrap() + }, + true, + [NonFungibleLocalId::sample_other()] + ) + } + + pub(crate) fn sample_stokenet() -> Self { + Self::new( + AssetsTransfersRecipient::MyOwnAccount { + value: Account::sample_stokenet_carol(), + }, + true, + [ + NonFungibleLocalId::sample(), + NonFungibleLocalId::sample_other(), + ], + ) + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::new( + AssetsTransfersRecipient::ForeignAccount { + value: AccountAddress::from_str("account_tdx_2_1288efhmjt8kzce77par4ex997x2zgnlv5qqv9ltpxqg7ur0xpqm6gk").unwrap() + }, + true, + [NonFungibleLocalId::sample_other()] + ) + } +} + +impl HasSampleValues for PerAssetNonFungibleTransfer { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerAssetNonFungibleTransfer; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers.rs new file mode 100644 index 000000000..5fd4dadbe --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers.rs @@ -0,0 +1,73 @@ +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] +pub struct PerAssetTransfers { + pub from_account: AccountAddress, + pub fungible_resources: Vec, + pub non_fungible_resources: Vec, +} + +impl PerAssetTransfers { + pub fn new( + from: AccountAddress, + fungibles: impl IntoIterator, + non_fungibles: impl IntoIterator< + Item = PerAssetTransfersOfNonFungibleResource, + >, + ) -> Self { + Self { + from_account: from, + fungible_resources: fungibles.into_iter().collect_vec(), + non_fungible_resources: non_fungibles.into_iter().collect_vec(), + } + } +} + +impl HasSampleValues for PerAssetTransfers { + fn sample() -> Self { + Self::new( + AccountAddress::sample_mainnet(), + [ + PerAssetTransfersOfFungibleResource::sample_mainnet(), + PerAssetTransfersOfFungibleResource::sample_mainnet_other(), + ], + [ + PerAssetTransfersOfNonFungibleResource::sample_mainnet(), + PerAssetTransfersOfNonFungibleResource::sample_mainnet_other(), + ], + ) + } + + fn sample_other() -> Self { + Self::new( + AccountAddress::sample_stokenet(), + [ + PerAssetTransfersOfFungibleResource::sample_stokenet(), + PerAssetTransfersOfFungibleResource::sample_stokenet_other(), + ], + [ + PerAssetTransfersOfNonFungibleResource::sample_stokenet(), + PerAssetTransfersOfNonFungibleResource::sample_stokenet_other(), + ], + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerAssetTransfers; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_fungible_resource.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_fungible_resource.rs new file mode 100644 index 000000000..67238840a --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_fungible_resource.rs @@ -0,0 +1,126 @@ +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] +pub struct PerAssetTransfersOfFungibleResource { + pub resource: PerAssetFungibleResource, + pub transfers: Vec, +} + +impl PerAssetTransfersOfFungibleResource { + pub(crate) fn expanded( + &mut self, + transfer: impl Into, + ) { + self.transfers.push(transfer.into()); + } +} + +impl PerAssetTransfersOfFungibleResource { + pub fn new( + resource: PerAssetFungibleResource, + transfers: impl IntoIterator, + ) -> Self { + Self { + resource, + transfers: transfers.into_iter().collect_vec(), + } + } + + /// sum of all `amount`s in `self.transfers.map(|x| x.amount)` + pub fn total_transfer_amount(&self) -> ScryptoDecimal192 { + let amount = self + .transfers + .clone() + .into_iter() + .map(|x| x.amount) + .fold(Decimal::zero(), |acc, x| acc + x); + + let rounded = amount.round(self.resource.divisibility); + + rounded.into() + } +} + +impl PerAssetTransfersOfFungibleResource { + pub(crate) fn sample_mainnet() -> Self { + Self::new( + PerAssetFungibleResource::sample_mainnet(), + [ + PerAssetFungibleTransfer::sample_mainnet(), + PerAssetFungibleTransfer::sample_mainnet_other(), + ], + ) + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::new( + PerAssetFungibleResource::sample_mainnet_other(), + [PerAssetFungibleTransfer::sample_mainnet_other()], + ) + } + + pub(crate) fn sample_stokenet() -> Self { + Self::new( + PerAssetFungibleResource::sample_stokenet(), + [ + PerAssetFungibleTransfer::sample_stokenet(), + PerAssetFungibleTransfer::sample_stokenet_other(), + ], + ) + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::new( + PerAssetFungibleResource::sample_stokenet_other(), + [PerAssetFungibleTransfer::sample_stokenet_other()], + ) + } +} + +impl HasSampleValues for PerAssetTransfersOfFungibleResource { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerAssetTransfersOfFungibleResource; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_non_fungible_resource.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_non_fungible_resource.rs new file mode 100644 index 000000000..4cecb14e0 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_asset/per_asset_transfers_of_non_fungible_resource.rs @@ -0,0 +1,123 @@ +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] +pub struct PerAssetTransfersOfNonFungibleResource { + pub resource: ResourceAddress, + pub transfers: Vec, +} + +impl PerAssetTransfersOfNonFungibleResource { + pub fn new( + resource: impl Into, + transfers: impl IntoIterator, + ) -> Self { + Self { + resource: resource.into(), + transfers: transfers.into_iter().collect_vec(), + } + } +} + +impl PerAssetTransfersOfNonFungibleResource { + pub(crate) fn expanded( + &mut self, + transfer: impl Into, + ) { + self.transfers.push(transfer.into()); + } +} + +impl PerAssetTransfersOfNonFungibleResource { + pub fn all_ids(&self) -> Vec { + self.transfers + .clone() + .into_iter() + .flat_map(|x| x.non_fungible_local_ids) + .map(ScryptoNonFungibleLocalId::from) + .collect_vec() + } +} + +impl PerAssetTransfersOfNonFungibleResource { + pub(crate) fn sample_mainnet() -> Self { + Self::new( + ResourceAddress::sample_mainnet_xrd(), + [ + PerAssetNonFungibleTransfer::sample_mainnet(), + PerAssetNonFungibleTransfer::sample_mainnet_other(), + ], + ) + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::new( + ResourceAddress::sample_mainnet_candy(), + [PerAssetNonFungibleTransfer::sample_mainnet_other()], + ) + } + + pub(crate) fn sample_stokenet() -> Self { + Self::new( + ResourceAddress::sample_stokenet_candy(), + [ + PerAssetNonFungibleTransfer::sample_stokenet(), + PerAssetNonFungibleTransfer::sample_stokenet_other(), + ], + ) + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::new( + ResourceAddress::sample_stokenet_gum(), + [PerAssetNonFungibleTransfer::sample_stokenet_other()], + ) + } +} + +impl HasSampleValues for PerAssetTransfersOfNonFungibleResource { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerAssetTransfersOfNonFungibleResource; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/mod.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/mod.rs new file mode 100644 index 000000000..9927c2f43 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/mod.rs @@ -0,0 +1,9 @@ +mod per_recipient_asset_transfer; +mod per_recipient_asset_transfers; +mod per_recipient_fungible_transfer; +mod per_recipient_non_fungibles_transfer; + +pub use per_recipient_asset_transfer::*; +pub use per_recipient_asset_transfers::*; +pub use per_recipient_fungible_transfer::*; +pub use per_recipient_non_fungibles_transfer::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfer.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfer.rs new file mode 100644 index 000000000..b0d671f5b --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfer.rs @@ -0,0 +1,117 @@ +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] +pub struct PerRecipientAssetTransfer { + pub recipient: AssetsTransfersRecipient, + pub fungibles: Vec, + pub non_fungibles: Vec, +} + +impl PerRecipientAssetTransfer { + pub fn new( + recipient: impl Into, + fungibles: impl IntoIterator, + non_fungibles: impl IntoIterator, + ) -> Self { + Self { + recipient: recipient.into(), + fungibles: fungibles.into_iter().collect_vec(), + non_fungibles: non_fungibles.into_iter().collect_vec(), + } + } +} + +#[allow(unused)] +impl PerRecipientAssetTransfer { + pub(crate) fn sample_mainnet() -> Self { + Self::new( + AssetsTransfersRecipient::sample_mainnet(), + [ + PerRecipientFungibleTransfer::sample_mainnet(), + PerRecipientFungibleTransfer::sample_mainnet_other(), + ], + [ + PerRecipientNonFungiblesTransfer::sample_mainnet(), + PerRecipientNonFungiblesTransfer::sample_mainnet_other(), + ], + ) + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::new( + AssetsTransfersRecipient::sample_mainnet_other(), + [PerRecipientFungibleTransfer::sample_mainnet_other()], + [PerRecipientNonFungiblesTransfer::sample_mainnet_other()], + ) + } + + pub(crate) fn sample_stokenet() -> Self { + Self::new( + AssetsTransfersRecipient::sample_stokenet(), + [ + PerRecipientFungibleTransfer::sample_stokenet(), + PerRecipientFungibleTransfer::sample_stokenet_other(), + ], + [ + PerRecipientNonFungiblesTransfer::sample_stokenet(), + PerRecipientNonFungiblesTransfer::sample_stokenet_other(), + ], + ) + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::new( + AssetsTransfersRecipient::sample_stokenet_other(), + [PerRecipientFungibleTransfer::sample_stokenet_other()], + [PerRecipientNonFungiblesTransfer::sample_stokenet_other()], + ) + } +} + +impl HasSampleValues for PerRecipientAssetTransfer { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerRecipientAssetTransfer; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfers.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfers.rs new file mode 100644 index 000000000..85f72f498 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_asset_transfers.rs @@ -0,0 +1,176 @@ +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] +pub struct PerRecipientAssetTransfers { + pub address_of_sender: AccountAddress, + pub transfers: Vec, +} + +impl PerRecipientAssetTransfers { + pub fn new( + address_of_sender: AccountAddress, + transfers: impl IntoIterator, + ) -> Self { + Self { + address_of_sender, + transfers: transfers.into_iter().collect_vec(), + } + } +} + +#[allow(unused)] +impl PerRecipientAssetTransfers { + pub(crate) fn sample_mainnet() -> Self { + Self::new( + AccountAddress::sample_mainnet(), + [ + PerRecipientAssetTransfer::sample_mainnet(), + PerRecipientAssetTransfer::sample_mainnet_other(), + ], + ) + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::new( + AccountAddress::sample_mainnet_other(), + [PerRecipientAssetTransfer::sample_mainnet_other()], + ) + } + + pub(crate) fn sample_stokenet() -> Self { + Self::new( + AccountAddress::sample_stokenet(), + [ + PerRecipientAssetTransfer::sample_stokenet(), + PerRecipientAssetTransfer::sample_stokenet_other(), + ], + ) + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::new( + AccountAddress::sample_stokenet_other(), + [PerRecipientAssetTransfer::sample_stokenet_other()], + ) + } +} + +impl HasSampleValues for PerRecipientAssetTransfers { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +impl PerRecipientAssetTransfers { + /// Transpose: `PerRecipient` -> `PerAsset` + pub fn transpose(&self) -> PerAssetTransfers { + let mut per_asset_fungibles = IndexMap::< + ResourceAddress, + PerAssetTransfersOfFungibleResource, + >::new(); + + let mut per_asset_non_fungibles = IndexMap::< + ResourceAddress, + PerAssetTransfersOfNonFungibleResource, + >::new(); + + self.transfers.clone().into_iter().for_each(|t| { + let x = t.clone(); + let recipient = &x.recipient; + + x.fungibles.clone().into_iter().for_each(|y| { + if let Some(existing_transfers) = + per_asset_fungibles.get_mut(&y.resource_address) + { + existing_transfers.expanded((recipient, y.clone())); + } else { + per_asset_fungibles.insert( + y.resource_address.clone(), + PerAssetTransfersOfFungibleResource::new( + PerAssetFungibleResource::new( + y.clone().resource_address, + y.clone().divisibility, + ), + [PerAssetFungibleTransfer::from(( + recipient, + y.clone(), + ))], + ), + ); + } + }); + + x.non_fungibles.clone().into_iter().for_each(|y| { + if let Some(existing_transfers) = + per_asset_non_fungibles.get_mut(&y.resource_address) + { + existing_transfers.expanded((recipient, y.clone())); + } else { + per_asset_non_fungibles.insert( + y.resource_address.clone(), + PerAssetTransfersOfNonFungibleResource::new( + y.clone().resource_address, + [PerAssetNonFungibleTransfer::from(( + recipient, + y.clone(), + ))], + ), + ); + } + }); + }); + + PerAssetTransfers::new( + self.address_of_sender.clone(), + per_asset_fungibles.values().cloned(), + per_asset_non_fungibles.values().cloned(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerRecipientAssetTransfers; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } + + // #[test] + // fn transpose() { + // pretty_assertions::assert_eq!(SUT::sample().transpose(), PerAssetTransfers::sample()) + // } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_fungible_transfer.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_fungible_transfer.rs new file mode 100644 index 000000000..558d4a145 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_fungible_transfer.rs @@ -0,0 +1,83 @@ +use crate::prelude::*; + +impl PerRecipientFungibleTransfer { + pub fn new( + resource_address: ResourceAddress, + amount: impl Into, + use_try_deposit_or_abort: bool, + divisibility: impl Into>, + ) -> Self { + Self { + resource_address, + amount: amount.into(), + use_try_deposit_or_abort, + divisibility: divisibility.into(), + } + } +} + +impl PerRecipientFungibleTransfer { + pub(crate) fn sample_mainnet() -> Self { + Self::new(ResourceAddress::sample_mainnet_xrd(), 237, true, None) + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::new(ResourceAddress::sample_mainnet_candy(), 1337, true, 4) + } + + pub(crate) fn sample_stokenet() -> Self { + Self::new(ResourceAddress::sample_stokenet_xrd(), 42, false, None) + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::new(ResourceAddress::sample_stokenet_candy(), 3, true, 6) + } +} + +impl HasSampleValues for PerRecipientFungibleTransfer { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerRecipientFungibleTransfer; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_non_fungibles_transfer.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_non_fungibles_transfer.rs new file mode 100644 index 000000000..3f6617008 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/per_recipient/per_recipient_non_fungibles_transfer.rs @@ -0,0 +1,109 @@ +use crate::prelude::*; + +impl PerRecipientNonFungiblesTransfer { + pub fn new( + resource_address: impl Into, + use_try_deposit_or_abort: bool, + local_ids: impl IntoIterator, + ) -> Self { + Self { + resource_address: resource_address.into(), + use_try_deposit_or_abort, + local_ids: local_ids.into_iter().collect_vec(), + } + } +} + +impl PerRecipientNonFungiblesTransfer { + pub(crate) fn sample_mainnet() -> Self { + Self::new( + ResourceAddress::sample_mainnet_xrd(), + true, + [ + NonFungibleLocalId::integer(1), + NonFungibleLocalId::integer(2), + ], + ) + } + + pub(crate) fn sample_mainnet_other() -> Self { + Self::new( + ResourceAddress::sample_mainnet_candy(), + true, + [ + NonFungibleLocalId::integer(1), + NonFungibleLocalId::integer(2), + ], + ) + } + + pub(crate) fn sample_stokenet() -> Self { + Self::new( + ResourceAddress::sample_stokenet_xrd(), + false, + [ + NonFungibleLocalId::integer(1), + NonFungibleLocalId::integer(2), + ], + ) + } + + pub(crate) fn sample_stokenet_other() -> Self { + Self::new( + ResourceAddress::sample_stokenet_candy(), + true, + [ + NonFungibleLocalId::integer(1), + NonFungibleLocalId::integer(2), + ], + ) + } +} + +impl HasSampleValues for PerRecipientNonFungiblesTransfer { + fn sample() -> Self { + Self::sample_mainnet() + } + + fn sample_other() -> Self { + Self::sample_stokenet_other() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PerRecipientNonFungiblesTransfer; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + // duplicates should be removed + SUT::sample_mainnet(), + SUT::sample_mainnet_other(), + SUT::sample_stokenet(), + SUT::sample_stokenet_other(), + ]) + .len(), + 4 + ) + } +} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/transfer_types.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/transfer_types.rs new file mode 100644 index 000000000..3bb1da980 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers/transfer_types.rs @@ -0,0 +1,124 @@ +use crate::prelude::*; + +macro_rules! decl_transfer_of { + ( + $( + #[doc = $expr: expr] + )* + $struct_prefix: ident, + $struct_name: ident, + $($fields:tt)* + ) => { + paste! { + $( + #[doc = $expr] + )* + #[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] + pub struct [< $struct_prefix $struct_name Transfer >] { + + /// If `true` the `try_deposit_batch_or_abort` method will be used instead of `deposit`, + /// typically wallets sets this to try if and only if the recipient is a self-owned account + /// (`AssetsTransfersRecipient::MyOwnAccount`) controlled by a DeviceFactorSource thy have + /// access to and which third party deposit setting's `DepositRule` is `AcceptKnown` and + /// which resource is known (`resource_address` is owned or has been owned before). + pub(crate) use_try_deposit_or_abort: bool, + + $($fields)* + + } + } + }; +} + +macro_rules! decl_per_asset_transfer_of { + ( + $( + #[doc = $expr: expr] + )* + $struct_name: ident, + $($fields:tt)* + ) => { + + decl_transfer_of!( + $( + #[doc = $expr] + )* + PerAsset, + $struct_name, + $($fields)* + /// The account or account address to send the tokens to. + pub recipient: AssetsTransfersRecipient, + ); + + paste! { + impl [< PerAsset $struct_name Transfer>] { + + pub(crate) fn deposit_instruction(&self, builder: ScryptoManifestBuilder, bucket: &Bucket) -> ScryptoManifestBuilder { + + if self.use_try_deposit_or_abort { + return builder.try_deposit_or_abort( + self.recipient.account_address(), + None, + bucket, + ) + } else { + return builder + .deposit(self.recipient.account_address(), bucket); + } + } + } + } + }; +} + +macro_rules! decl_per_recipient_transfer_of { + ( + $( + #[doc = $expr: expr] + )* + $struct_name: ident, + $($fields:tt)* + ) => { + + decl_transfer_of!( + $( + #[doc = $expr] + )* + PerRecipient, + $struct_name, + $($fields)* + /// The address of the resource being sent + pub resource_address: ResourceAddress, + ); + }; +} + +decl_per_asset_transfer_of!( + /// A fungible transfer to `recipient`, with a specified amount of tokens to send. + Fungible, + /// Amount + pub(crate) amount: Decimal192, +); + +decl_per_asset_transfer_of!( + /// A non fungible transfer to `recipient`, with specified Local IDs to send. + NonFungible, + /// Amount + pub(crate) non_fungible_local_ids: Vec, +); + +decl_per_recipient_transfer_of!( + /// A fungible transfer of `resource_address` token, with a specified amount + /// of tokens and divisibility. + Fungible, + /// Amount + pub(crate) amount: Decimal192, + pub divisibility: Option, +); + +decl_per_recipient_transfer_of!( + /// A non fungible transfer of `resource_address` token, with specified Local IDs to send. + NonFungibles, + /// The local IDS of the NonFungible tokens being sent + pub(crate) local_ids: Vec, +); diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers_transaction_prototype.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers_transaction_prototype.rs deleted file mode 100644 index a57d5f300..000000000 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/assets_transfers_transaction_prototype.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::prelude::*; - -#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] -pub struct AssetsTransfersTransactionPrototype { - pub from_account: AccountAddress, - pub transfers: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] -pub struct AssetsTransfersToRecipient { - pub recipient: AssetsTransfersRecipient, - pub fungibles: Vec, - pub non_fungibles: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Record)] - -pub struct FungiblePositiveAmount { - pub resource_address: ResourceAddress, - pub amount: Decimal192, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, uniffi::Enum)] -#[allow(clippy::large_enum_variant)] // we cannot Box, since Box is not UniFFI compatible. -pub enum AssetsTransfersRecipient { - MyOwnAccount { value: Account }, - ForeignAccount { value: AccountAddress }, -} diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/mod.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/mod.rs index 37e8be910..771d94a9f 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/mod.rs +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/mod.rs @@ -1,11 +1,11 @@ mod address_of_account_or_persona; -mod assets_transfers_transaction_prototype; +mod assets_transfers; mod build_information; mod stake_claim; mod transaction_guarantee; pub use address_of_account_or_persona::*; -pub use assets_transfers_transaction_prototype::*; +pub use assets_transfers::*; pub use build_information::*; pub use stake_claim::*; pub use transaction_guarantee::*; diff --git a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/transaction_guarantee.rs b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/transaction_guarantee.rs index 2407da143..e8e984195 100644 --- a/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/transaction_guarantee.rs +++ b/src/wrapped_radix_engine_toolkit/high_level/sargon_specific_types/transaction_guarantee.rs @@ -26,14 +26,7 @@ impl TransactionGuarantee { impl TransactionGuarantee { pub(crate) fn rounded_amount(&self) -> Decimal192 { - let decimal_places = self - .resource_divisibility - .unwrap_or(Decimal192::SCALE as i32); - - self.amount - .clone() - .round(decimal_places, RoundingMode::ToNearestMidpointAwayFromZero) - .expect("Rounding to never fail.") + self.amount.clone().round(self.resource_divisibility) } }