diff --git a/Cargo.lock b/Cargo.lock index 3a07185582..fa83aca684 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -2981,6 +2981,19 @@ dependencies = [ "payable-features", ] +[[package]] +name = "payable-interactor" +version = "0.0.0" +dependencies = [ + "clap", + "multiversx-sc-snippets", + "payable-features", + "serde", + "serial_test", + "tokio", + "toml", +] + [[package]] name = "payload-macro-generator" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3cc34e3bd4..10c6723126 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -181,6 +181,7 @@ members = [ "contracts/feature-tests/panic-message-features", "contracts/feature-tests/panic-message-features/meta", "contracts/feature-tests/payable-features", + "contracts/feature-tests/payable-features/interactor", "contracts/feature-tests/payable-features/meta", "contracts/feature-tests/rust-snippets-generator-test", "contracts/feature-tests/rust-snippets-generator-test/meta", diff --git a/chain/core/src/lib.rs b/chain/core/src/lib.rs index 503f690d82..dbd636cc8c 100644 --- a/chain/core/src/lib.rs +++ b/chain/core/src/lib.rs @@ -7,3 +7,6 @@ pub mod types; /// Re-exported for convenience. pub use multiversx_sc_codec as codec; + +/// The equivalent ESDT token identifier for transferring EGLD, the native MultiversX token. +pub const EGLD_000000_TOKEN_IDENTIFIER: &str = "EGLD-000000"; diff --git a/chain/vm/src/tx_mock/tx_cache_balance_util.rs b/chain/vm/src/tx_mock/tx_cache_balance_util.rs index 97eb5fb1a5..c1a713ed4e 100644 --- a/chain/vm/src/tx_mock/tx_cache_balance_util.rs +++ b/chain/vm/src/tx_mock/tx_cache_balance_util.rs @@ -1,3 +1,4 @@ +use multiversx_chain_core::EGLD_000000_TOKEN_IDENTIFIER; use num_bigint::BigUint; use crate::{ @@ -109,6 +110,10 @@ impl TxCache { nonce: u64, value: &BigUint, ) -> Result<(), TxPanic> { + if esdt_token_identifier == EGLD_000000_TOKEN_IDENTIFIER.as_bytes() { + return self.transfer_egld_balance(from, to, value); + } + if !is_system_sc_address(from) && !is_system_sc_address(to) { let metadata = self.subtract_esdt_balance(from, esdt_token_identifier, nonce, value)?; self.increase_esdt_balance(to, esdt_token_identifier, nonce, value, metadata); diff --git a/chain/vm/src/types.rs b/chain/vm/src/types.rs index 9ab80284a5..0a2eeac01d 100644 --- a/chain/vm/src/types.rs +++ b/chain/vm/src/types.rs @@ -10,11 +10,13 @@ pub type RawHandle = i32; use num_bigint::BigUint; use num_traits::Zero; -pub(crate) fn top_encode_u64(value: u64) -> Vec { +/// Helper function to quickly encode a u64 value, according to the MultiversX codec format. +pub fn top_encode_u64(value: u64) -> Vec { top_encode_big_uint(&BigUint::from(value)) } -pub(crate) fn top_encode_big_uint(value: &BigUint) -> Vec { +/// Helper function to quickly encode a BigUint value, according to the MultiversX codec format. +pub fn top_encode_big_uint(value: &BigUint) -> Vec { if value.is_zero() { Vec::new() } else { diff --git a/contracts/feature-tests/composability/forwarder/src/fwd_call_async.rs b/contracts/feature-tests/composability/forwarder/src/fwd_call_async.rs index 5492a45a10..047eb8f0ee 100644 --- a/contracts/feature-tests/composability/forwarder/src/fwd_call_async.rs +++ b/contracts/feature-tests/composability/forwarder/src/fwd_call_async.rs @@ -177,13 +177,13 @@ pub trait ForwarderAsyncCallModule { fn send_async_accept_multi_transfer( &self, to: ManagedAddress, - token_payments: MultiValueEncoded>, + token_payments: MultiValueEncoded>, ) { let mut all_token_payments = ManagedVec::new(); for multi_arg in token_payments.into_iter() { let (token_identifier, token_nonce, amount) = multi_arg.into_tuple(); - let payment = EsdtTokenPayment::new(token_identifier, token_nonce, amount); + let payment = EgldOrEsdtTokenPayment::new(token_identifier, token_nonce, amount); all_token_payments.push(payment); } diff --git a/contracts/feature-tests/composability/forwarder/src/fwd_call_transf_exec.rs b/contracts/feature-tests/composability/forwarder/src/fwd_call_transf_exec.rs index 952f50275c..f040dbce83 100644 --- a/contracts/feature-tests/composability/forwarder/src/fwd_call_transf_exec.rs +++ b/contracts/feature-tests/composability/forwarder/src/fwd_call_transf_exec.rs @@ -95,13 +95,13 @@ pub trait ForwarderTransferExecuteModule { fn transf_exec_multi_accept_funds( &self, to: ManagedAddress, - token_payments: MultiValueEncoded>, + token_payments: MultiValueEncoded>, ) { let mut all_token_payments = ManagedVec::new(); for multi_arg in token_payments.into_iter() { let (token_identifier, token_nonce, amount) = multi_arg.into_tuple(); - let payment = EsdtTokenPayment::new(token_identifier, token_nonce, amount); + let payment = EgldOrEsdtTokenPayment::new(token_identifier, token_nonce, amount); all_token_payments.push(payment); } diff --git a/contracts/feature-tests/composability/promises-features/src/fwd_call_promise_direct.rs b/contracts/feature-tests/composability/promises-features/src/fwd_call_promise_direct.rs index 78635d26b6..67f12798fb 100644 --- a/contracts/feature-tests/composability/promises-features/src/fwd_call_promise_direct.rs +++ b/contracts/feature-tests/composability/promises-features/src/fwd_call_promise_direct.rs @@ -35,7 +35,7 @@ pub trait CallPromisesDirectModule { ) { let mut token_payments_vec = ManagedVec::new(); for token_payment_arg in token_payment_args { - token_payments_vec.push(token_payment_arg.into_esdt_token_payment()); + token_payments_vec.push(token_payment_arg.into_inner()); } let gas_limit = (self.blockchain().get_gas_left() - extra_gas_for_callback) * 9 / 10; diff --git a/contracts/feature-tests/composability/scenarios/forwarder_call_async_multi_transfer_egld.scen.json b/contracts/feature-tests/composability/scenarios/forwarder_call_async_multi_transfer_egld.scen.json new file mode 100644 index 0000000000..50c842422e --- /dev/null +++ b/contracts/feature-tests/composability/scenarios/forwarder_call_async_multi_transfer_egld.scen.json @@ -0,0 +1,130 @@ +{ + "steps": [ + { + "step": "setState", + "accounts": { + "address:a_user": { + "nonce": "0", + "balance": "0" + }, + "sc:forwarder": { + "nonce": "0", + "balance": "4000", + "esdt": { + "str:FWD-TOKEN": "1000", + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1" + } + ] + }, + "str:SFT-456789": { + "instances": [ + { + "nonce": "3", + "balance": "10" + } + ] + } + }, + "code": "mxsc:../forwarder/output/forwarder.mxsc.json" + }, + "sc:vault": { + "nonce": "0", + "balance": "0", + "code": "mxsc:../vault/output/vault.mxsc.json" + } + } + }, + { + "step": "scCall", + "id": "2", + "comment": "send all types", + "tx": { + "from": "address:a_user", + "to": "sc:forwarder", + "function": "send_async_accept_multi_transfer", + "arguments": [ + "sc:vault", + "str:FWD-TOKEN", + "0", + "800", + "str:NFT-123456", + "1", + "1", + "str:EGLD-000000", + "0", + "100", + "str:SFT-456789", + "3", + "6" + ], + "gasLimit": "80,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:a_user": { + "nonce": "*", + "balance": "0", + "storage": {}, + "code": "" + }, + "sc:vault": { + "nonce": "0", + "balance": "100", + "esdt": { + "str:FWD-TOKEN": "800", + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1" + } + ] + }, + "str:SFT-456789": { + "instances": [ + { + "nonce": "3", + "balance": "6" + } + ] + } + }, + "storage": { + "str:call_counts|nested:str:accept_funds": "1" + }, + "code": "mxsc:../vault/output/vault.mxsc.json" + }, + "sc:forwarder": { + "nonce": "0", + "balance": "3900", + "esdt": { + "str:FWD-TOKEN": "200", + "str:SFT-456789": { + "instances": [ + { + "nonce": "3", + "balance": "4" + } + ] + } + }, + "code": "mxsc:../forwarder/output/forwarder.mxsc.json" + } + } + } + ] +} \ No newline at end of file diff --git a/contracts/feature-tests/composability/scenarios/forwarder_call_transf_exec_accept_multi_transfer_egld.scen.json b/contracts/feature-tests/composability/scenarios/forwarder_call_transf_exec_accept_multi_transfer_egld.scen.json new file mode 100644 index 0000000000..0f8a4c7c24 --- /dev/null +++ b/contracts/feature-tests/composability/scenarios/forwarder_call_transf_exec_accept_multi_transfer_egld.scen.json @@ -0,0 +1,300 @@ +{ + "gasSchedule": "v3", + "steps": [ + { + "step": "setState", + "accounts": { + "address:a_user": { + "nonce": "0", + "balance": "0" + }, + "sc:forwarder": { + "nonce": "0", + "balance": "100000", + "esdt": { + "str:FWD-TOKEN": "1000", + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1" + } + ] + }, + "str:SFT-456789": { + "instances": [ + { + "nonce": "3", + "balance": "10" + } + ] + } + }, + "code": "mxsc:../forwarder/output/forwarder.mxsc.json" + }, + "sc:vault": { + "nonce": "0", + "balance": "0", + "code": "mxsc:../vault/output/vault.mxsc.json" + } + } + }, + { + "step": "scCall", + "id": "forward egld x 1", + "tx": { + "from": "address:a_user", + "to": "sc:forwarder", + "function": "transf_exec_multi_accept_funds", + "arguments": [ + "sc:vault", + "str:EGLD", + "0", + "1000" + ], + "gasLimit": "1,400,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "logs": [ + { + "address": "sc:forwarder", + "endpoint": "str:transferValueOnly", + "topics": [ + "1000", + "sc:vault" + ], + "data": [ + "str:TransferAndExecute", + "str:accept_funds" + ] + }, + { + "address": "sc:vault", + "endpoint": "str:accept_funds", + "topics": [ + "str:accept_funds", + "1000" + ], + "data": [ + "" + ] + } + ] + } + }, + { + "step": "scCall", + "id": "forward egld x 2", + "comment": "send fungible twice", + "tx": { + "from": "address:a_user", + "to": "sc:forwarder", + "function": "transf_exec_multi_accept_funds", + "arguments": [ + "sc:vault", + "str:EGLD", + "0", + "1000", + "str:EGLD", + "0", + "2000" + ], + "gasLimit": "1,400,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "logs": [ + { + "address": "sc:forwarder", + "endpoint": "str:MultiESDTNFTTransfer", + "topics": [ + "str:EGLD-000000", + "0", + "1000", + "str:EGLD-000000", + "0", + "2000", + "sc:vault" + ], + "data": [ + "str:TransferAndExecute", + "str:MultiESDTNFTTransfer", + "sc:vault", + "2", + "str:EGLD-000000", + "0", + "1000", + "str:EGLD-000000", + "0", + "2000", + "str:accept_funds" + ] + }, + { + "address": "sc:vault", + "endpoint": "str:accept_funds", + "topics": [ + "str:accept_funds", + "0", + "str:EGLD", + "0", + "1000", + "str:EGLD", + "0", + "2000" + ], + "data": [ + "" + ] + } + ] + } + }, + { + "step": "checkState", + "accounts": { + "address:a_user": { + "nonce": "*", + "balance": "0", + "storage": {}, + "code": "" + }, + "sc:vault": { + "nonce": "0", + "balance": "4000", + "storage": { + "str:call_counts|nested:str:accept_funds": "2" + }, + "code": "mxsc:../vault/output/vault.mxsc.json" + }, + "sc:forwarder": { + "nonce": "0", + "balance": "96000", + "esdt": { + "str:FWD-TOKEN": "1000", + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1" + } + ] + }, + "str:SFT-456789": { + "instances": [ + { + "nonce": "3", + "balance": "10" + } + ] + } + }, + "storage": {}, + "code": "mxsc:../forwarder/output/forwarder.mxsc.json" + } + } + }, + { + "step": "scCall", + "id": "1", + "comment": "send EGLD+ESDT", + "tx": { + "from": "address:a_user", + "to": "sc:forwarder", + "function": "transf_exec_multi_accept_funds", + "arguments": [ + "sc:vault", + "str:FWD-TOKEN", + "0", + "500", + "str:FWD-TOKEN", + "0", + "300", + "str:EGLD", + "0", + "3200", + "str:NFT-123456", + "1", + "1", + "str:SFT-456789", + "3", + "6", + "str:EGLD", + "0", + "3300" + ], + "gasLimit": "1,400,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:a_user": { + "nonce": "*", + "balance": "0", + "storage": {}, + "code": "" + }, + "sc:vault": { + "nonce": "0", + "balance": "10500", + "esdt": { + "str:FWD-TOKEN": "800", + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1" + } + ] + }, + "str:SFT-456789": { + "instances": [ + { + "nonce": "3", + "balance": "6" + } + ] + } + }, + "storage": { + "str:call_counts|nested:str:accept_funds": "3" + }, + "code": "mxsc:../vault/output/vault.mxsc.json" + }, + "sc:forwarder": { + "nonce": "0", + "balance": "89500", + "esdt": { + "str:FWD-TOKEN": "200", + "str:SFT-456789": { + "instances": [ + { + "nonce": "3", + "balance": "4" + } + ] + } + }, + "storage": {}, + "code": "mxsc:../forwarder/output/forwarder.mxsc.json" + } + } + } + ] +} diff --git a/contracts/feature-tests/composability/scenarios/forwarder_send_esdt_multi_transfer.scen.json b/contracts/feature-tests/composability/scenarios/forwarder_send_esdt_multi_transfer.scen.json index 80d7b639e2..4affc8a547 100644 --- a/contracts/feature-tests/composability/scenarios/forwarder_send_esdt_multi_transfer.scen.json +++ b/contracts/feature-tests/composability/scenarios/forwarder_send_esdt_multi_transfer.scen.json @@ -178,31 +178,6 @@ "code": "mxsc:../forwarder/output/forwarder.mxsc.json" } } - }, - { - "step": "scCall", - "id": "EGLD-test", - "comment": "send all types", - "tx": { - "from": "address:a_user", - "to": "sc:forwarder", - "function": "send_esdt_direct_multi_transfer", - "arguments": [ - "address:a_user", - "str:EGLD", - "0", - "1000" - ], - "gasLimit": "80,000,000", - "gasPrice": "0" - }, - "expect": { - "out": [], - "status": "10", - "message": "str:insufficient funds", - "gas": "*", - "refund": "*" - } } ] } diff --git a/contracts/feature-tests/composability/tests/composability_scenario_go_test.rs b/contracts/feature-tests/composability/tests/composability_scenario_go_test.rs index 3dfb9fdc2d..eec670604c 100644 --- a/contracts/feature-tests/composability/tests/composability_scenario_go_test.rs +++ b/contracts/feature-tests/composability/tests/composability_scenario_go_test.rs @@ -184,6 +184,11 @@ fn forwarder_call_async_multi_transfer_go() { world().run("scenarios/forwarder_call_async_multi_transfer.scen.json"); } +#[test] +fn forwarder_call_async_multi_transfer_egld_go() { + world().run("scenarios/forwarder_call_async_multi_transfer_egld.scen.json"); +} + #[test] fn forwarder_call_async_retrieve_egld_go() { world().run("scenarios/forwarder_call_async_retrieve_egld.scen.json"); @@ -289,6 +294,11 @@ fn forwarder_call_transf_exec_accept_multi_transfer_go() { world().run("scenarios/forwarder_call_transf_exec_accept_multi_transfer.scen.json"); } +#[test] +fn forwarder_call_transf_exec_accept_multi_transfer_egld_go() { + world().run("scenarios/forwarder_call_transf_exec_accept_multi_transfer_egld.scen.json"); +} + #[test] fn forwarder_call_transf_exec_accept_nft_go() { world().run("scenarios/forwarder_call_transf_exec_accept_nft.scen.json"); diff --git a/contracts/feature-tests/composability/tests/composability_scenario_rs_test.rs b/contracts/feature-tests/composability/tests/composability_scenario_rs_test.rs index dce2f9031a..94fc855cf9 100644 --- a/contracts/feature-tests/composability/tests/composability_scenario_rs_test.rs +++ b/contracts/feature-tests/composability/tests/composability_scenario_rs_test.rs @@ -238,6 +238,11 @@ fn forwarder_call_async_multi_transfer_rs() { world().run("scenarios/forwarder_call_async_multi_transfer.scen.json"); } +#[test] +fn forwarder_call_async_multi_transfer_egld_rs() { + world().run("scenarios/forwarder_call_async_multi_transfer_egld.scen.json"); +} + #[test] fn forwarder_call_async_retrieve_egld_rs() { world().run("scenarios/forwarder_call_async_retrieve_egld.scen.json"); @@ -343,6 +348,11 @@ fn forwarder_call_transf_exec_accept_multi_transfer_rs() { world().run("scenarios/forwarder_call_transf_exec_accept_multi_transfer.scen.json"); } +#[test] +fn forwarder_call_transf_exec_accept_multi_transfer_egld_rs() { + world().run("scenarios/forwarder_call_transf_exec_accept_multi_transfer_egld.scen.json"); +} + #[test] fn forwarder_call_transf_exec_accept_nft_rs() { world().run("scenarios/forwarder_call_transf_exec_accept_nft.scen.json"); diff --git a/contracts/feature-tests/composability/vault/src/vault.rs b/contracts/feature-tests/composability/vault/src/vault.rs index d6f3feb81b..e700ebda36 100644 --- a/contracts/feature-tests/composability/vault/src/vault.rs +++ b/contracts/feature-tests/composability/vault/src/vault.rs @@ -50,9 +50,9 @@ pub trait Vault { self.blockchain().get_caller() } - fn esdt_transfers_multi(&self) -> MultiValueEncoded { + fn all_transfers_multi(&self) -> MultiValueEncoded { self.call_value() - .all_esdt_transfers() + .all_multi_transfers() .clone_value() .into_multi_value() } @@ -60,7 +60,7 @@ pub trait Vault { #[payable("*")] #[endpoint] fn accept_funds(&self) { - let esdt_transfers_multi = self.esdt_transfers_multi(); + let esdt_transfers_multi = self.all_transfers_multi(); self.accept_funds_event(&self.call_value().egld_value(), &esdt_transfers_multi); self.call_counts(ManagedBuffer::from(b"accept_funds")) @@ -71,9 +71,9 @@ pub trait Vault { #[endpoint] fn accept_funds_echo_payment( &self, - ) -> MultiValue2> { + ) -> MultiValue2> { let egld_value = self.call_value().egld_value(); - let esdt_transfers_multi = self.esdt_transfers_multi(); + let esdt_transfers_multi = self.all_transfers_multi(); self.accept_funds_event(&egld_value, &esdt_transfers_multi); self.call_counts(ManagedBuffer::from(b"accept_funds_echo_payment")) @@ -91,7 +91,7 @@ pub trait Vault { #[payable("*")] #[endpoint] fn reject_funds(&self) { - let esdt_transfers_multi = self.esdt_transfers_multi(); + let esdt_transfers_multi = self.all_transfers_multi(); self.reject_funds_event(&self.call_value().egld_value(), &esdt_transfers_multi); sc_panic!("reject_funds"); } @@ -259,14 +259,14 @@ pub trait Vault { fn accept_funds_event( &self, #[indexed] egld_value: &BigUint, - #[indexed] multi_esdt: &MultiValueEncoded, + #[indexed] multi_esdt: &MultiValueEncoded, ); #[event("reject_funds")] fn reject_funds_event( &self, #[indexed] egld_value: &BigUint, - #[indexed] multi_esdt: &MultiValueEncoded, + #[indexed] multi_esdt: &MultiValueEncoded, ); #[event("retrieve_funds")] diff --git a/contracts/feature-tests/payable-features/interactor/.gitignore b/contracts/feature-tests/payable-features/interactor/.gitignore new file mode 100644 index 0000000000..88af50ac47 --- /dev/null +++ b/contracts/feature-tests/payable-features/interactor/.gitignore @@ -0,0 +1,5 @@ +# Pem files are used for interactions, but shouldn't be committed +*.pem + +# Temporary storage of deployed contract address, so we can preserve the context between executions. +state.toml diff --git a/contracts/feature-tests/payable-features/interactor/Cargo.toml b/contracts/feature-tests/payable-features/interactor/Cargo.toml new file mode 100644 index 0000000000..bf611a1c81 --- /dev/null +++ b/contracts/feature-tests/payable-features/interactor/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "payable-interactor" +version = "0.0.0" +authors = ["MultiversX "] +edition = "2021" +publish = false + +[[bin]] +name = "payable-interactor" +path = "src/payable_interactor_main.rs" + +[lib] +path = "src/payable_interactor.rs" + +[dependencies.payable-features] +path = ".." + +[dependencies.multiversx-sc-snippets] +version = "0.54.6" +path = "../../../../framework/snippets" + +[dependencies] +clap = { version = "4.4.7", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +toml = "0.8.6" +tokio = { version = "1.24" } +serial_test = { version = "3.2.0" } + +[features] +chain-simulator-tests = [] diff --git a/contracts/feature-tests/payable-features/interactor/config.toml b/contracts/feature-tests/payable-features/interactor/config.toml new file mode 100644 index 0000000000..97acd5a5c6 --- /dev/null +++ b/contracts/feature-tests/payable-features/interactor/config.toml @@ -0,0 +1,7 @@ + +# chain_type = 'simulator' +# gateway_uri = 'http://localhost:8085' + +chain_type = 'real' +gateway_uri = 'https://devnet-gateway.multiversx.com' + diff --git a/contracts/feature-tests/payable-features/interactor/src/payable_interactor.rs b/contracts/feature-tests/payable-features/interactor/src/payable_interactor.rs new file mode 100644 index 0000000000..1096d14fbc --- /dev/null +++ b/contracts/feature-tests/payable-features/interactor/src/payable_interactor.rs @@ -0,0 +1,96 @@ +mod payable_interactor_cli; +mod payable_interactor_config; +mod payable_interactor_state; + +use clap::Parser; +use payable_features::payable_features_proxy; +pub use payable_interactor_config::Config; +use payable_interactor_state::State; + +use multiversx_sc_snippets::imports::*; + +const CODE_PATH: MxscPath = MxscPath::new("../output/payable-features.mxsc.json"); + +pub async fn adder_cli() { + env_logger::init(); + + let config = Config::load_config(); + + let mut basic_interact = PayableInteract::new(config).await; + + let cli = payable_interactor_cli::InteractCli::parse(); + match &cli.command { + Some(payable_interactor_cli::InteractCliCommand::Deploy) => { + basic_interact.deploy().await; + }, + Some(payable_interactor_cli::InteractCliCommand::AllTransfers) => { + basic_interact.check_all_transfers().await; + }, + None => {}, + } +} + +pub struct PayableInteract { + pub interactor: Interactor, + pub sc_owner_address: Bech32Address, + pub wallet_address: Bech32Address, + pub state: State, +} + +impl PayableInteract { + pub async fn new(config: Config) -> Self { + let mut interactor = Interactor::new(config.gateway_uri()) + .await + .use_chain_simulator(config.use_chain_simulator()); + + let sc_owner_address = interactor.register_wallet(test_wallets::heidi()).await; + let wallet_address = interactor.register_wallet(test_wallets::ivan()).await; + + interactor.generate_blocks(30u64).await.unwrap(); + + PayableInteract { + interactor, + sc_owner_address: sc_owner_address.into(), + wallet_address: wallet_address.into(), + state: State::load_state(), + } + } + + pub async fn deploy(&mut self) { + let new_address = self + .interactor + .tx() + .from(&self.sc_owner_address.clone()) + .gas(30_000_000) + .typed(payable_features_proxy::PayableFeaturesProxy) + .init() + .code(CODE_PATH) + .returns(ReturnsNewBech32Address) + .run() + .await; + + println!("new address: {new_address}"); + self.state.set_adder_address(new_address); + } + + pub async fn check_all_transfers(&mut self) { + let mut payment = MultiEgldOrEsdtPayment::new(); + payment.push(EgldOrEsdtTokenPayment::egld_payment(1_0000u64.into())); + payment.push(EgldOrEsdtTokenPayment::egld_payment(2_0000u64.into())); + + let result = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_adder_address()) + .gas(6_000_000u64) + .typed(payable_features_proxy::PayableFeaturesProxy) + .payable_all_transfers() + .payment(payment) + .returns(ReturnsResult) + .run() + .await; + + println!("Result: {result:?}"); + } +} diff --git a/contracts/feature-tests/payable-features/interactor/src/payable_interactor_cli.rs b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_cli.rs new file mode 100644 index 0000000000..b1d38ddba5 --- /dev/null +++ b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_cli.rs @@ -0,0 +1,19 @@ +use clap::{Parser, Subcommand}; + +/// Adder Interact CLI +#[derive(Default, PartialEq, Eq, Debug, Parser)] +#[command(version, about)] +#[command(propagate_version = true)] +pub struct InteractCli { + #[command(subcommand)] + pub command: Option, +} + +/// Adder Interact CLI Commands +#[derive(Clone, PartialEq, Eq, Debug, Subcommand)] +pub enum InteractCliCommand { + #[command(name = "deploy", about = "Deploy contract")] + Deploy, + #[command(name = "at", about = "Check all transfers")] + AllTransfers, +} diff --git a/contracts/feature-tests/payable-features/interactor/src/payable_interactor_config.rs b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_config.rs new file mode 100644 index 0000000000..bd19da629d --- /dev/null +++ b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_config.rs @@ -0,0 +1,49 @@ +use serde::Deserialize; +use std::io::Read; + +/// Config file +const CONFIG_FILE: &str = "config.toml"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ChainType { + Real, + Simulator, +} + +/// Adder Interact configuration +#[derive(Debug, Deserialize)] +pub struct Config { + pub gateway_uri: String, + pub chain_type: ChainType, +} + +impl Config { + // Deserializes config from file + pub fn load_config() -> Self { + let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + toml::from_str(&content).unwrap() + } + + pub fn chain_simulator_config() -> Self { + Config { + gateway_uri: "http://localhost:8085".to_owned(), + chain_type: ChainType::Simulator, + } + } + + // Returns the gateway URI + pub fn gateway_uri(&self) -> &str { + &self.gateway_uri + } + + // Returns if chain type is chain simulator + pub fn use_chain_simulator(&self) -> bool { + match self.chain_type { + ChainType::Real => false, + ChainType::Simulator => true, + } + } +} diff --git a/contracts/feature-tests/payable-features/interactor/src/payable_interactor_main.rs b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_main.rs new file mode 100644 index 0000000000..6d607fca05 --- /dev/null +++ b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_main.rs @@ -0,0 +1,6 @@ +extern crate payable_interactor; + +#[tokio::main] +pub async fn main() { + payable_interactor::adder_cli().await; +} diff --git a/contracts/feature-tests/payable-features/interactor/src/payable_interactor_state.rs b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_state.rs new file mode 100644 index 0000000000..bcab774134 --- /dev/null +++ b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_state.rs @@ -0,0 +1,50 @@ +use multiversx_sc_snippets::imports::*; +use serde::{Deserialize, Serialize}; +use std::{ + io::{Read, Write}, + path::Path, +}; + +/// State file +const STATE_FILE: &str = "state.toml"; + +/// Adder Interact state +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct State { + adder_address: Option, +} + +impl State { + // Deserializes state from file + pub fn load_state() -> Self { + if Path::new(STATE_FILE).exists() { + let mut file = std::fs::File::open(STATE_FILE).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + toml::from_str(&content).unwrap() + } else { + Self::default() + } + } + + /// Sets the adder address + pub fn set_adder_address(&mut self, address: Bech32Address) { + self.adder_address = Some(address); + } + + /// Returns the adder contract + pub fn current_adder_address(&self) -> &Bech32Address { + self.adder_address + .as_ref() + .expect("no known adder contract, deploy first") + } +} + +impl Drop for State { + // Serializes state to file + fn drop(&mut self) { + let mut file = std::fs::File::create(STATE_FILE).unwrap(); + file.write_all(toml::to_string(self).unwrap().as_bytes()) + .unwrap(); + } +} diff --git a/contracts/feature-tests/payable-features/sc-config.toml b/contracts/feature-tests/payable-features/sc-config.toml new file mode 100644 index 0000000000..86b3debac9 --- /dev/null +++ b/contracts/feature-tests/payable-features/sc-config.toml @@ -0,0 +1,4 @@ +[settings] + +[[proxy]] +path = "src/payable_features_proxy.rs" diff --git a/contracts/feature-tests/payable-features/scenarios/call-value-check-multi-egld.scen.json b/contracts/feature-tests/payable-features/scenarios/call-value-check-multi-egld.scen.json new file mode 100644 index 0000000000..8490c055fe --- /dev/null +++ b/contracts/feature-tests/payable-features/scenarios/call-value-check-multi-egld.scen.json @@ -0,0 +1,128 @@ +{ + "steps": [ + { + "step": "setState", + "accounts": { + "sc:payable-features": { + "nonce": "0", + "balance": "0", + "code": "mxsc:../output/payable-features.mxsc.json" + }, + "address:an-account": { + "nonce": "0", + "balance": "10000", + "esdt": { + "str:TOK-123456": "1000", + "str:OTHERTOK-123456": "500", + "str:SFT-123": { + "instances": [ + { + "nonce": "5", + "balance": "20" + } + ] + } + } + } + } + }, + { + "step": "scCall", + "id": "call-value-egld", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "egldValue": "100", + "function": "echo_call_value", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "100", + "" + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "call-value-single-esdt", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "esdtValue": [ + { + "tokenIdentifier": "str:TOK-123456", + "value": "100" + } + ], + "function": "echo_call_value", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "0", + [ + "nested:str:TOK-123456|u64:0|biguint:100" + ] + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "call-value-multi-esdt", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "esdtValue": [ + { + "tokenIdentifier": "str:EGLD-000000", + "value": "15" + }, + { + "tokenIdentifier": "str:TOK-123456", + "value": "100" + }, + { + "tokenIdentifier": "str:OTHERTOK-123456", + "value": "400" + }, + { + "tokenIdentifier": "str:SFT-123", + "nonce": "5", + "value": "10" + } + ], + "function": "echo_call_value", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "15", + [ + "nested:str:TOK-123456|u64:0|biguint:100", + "nested:str:OTHERTOK-123456|u64:0|biguint:400", + "nested:str:SFT-123|u64:5|biguint:10" + ] + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + } + ] +} \ No newline at end of file diff --git a/contracts/feature-tests/payable-features/scenarios/payable_all_transfers.scen.json b/contracts/feature-tests/payable-features/scenarios/payable_all_transfers.scen.json new file mode 100644 index 0000000000..8056c49898 --- /dev/null +++ b/contracts/feature-tests/payable-features/scenarios/payable_all_transfers.scen.json @@ -0,0 +1,162 @@ +{ + "name": "payable", + "steps": [ + { + "step": "setState", + "accounts": { + "sc:payable-features": { + "nonce": "0", + "balance": "0", + "code": "mxsc:../output/payable-features.mxsc.json" + }, + "address:an-account": { + "nonce": "0", + "balance": "10000", + "esdt": { + "str:TOK-123456": "1000", + "str:OTHERTOK-123456": "500", + "str:SFT-123": { + "instances": [ + { + "nonce": "5", + "balance": "20" + } + ] + } + } + } + } + }, + { + "step": "scCall", + "id": "payable_all_transfers-ZERO", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "function": "payable_all_transfers", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [""], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "payable_all_transfers-EGLD", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "egldValue": "1000", + "function": "payable_all_transfers", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + [ + "nested:str:EGLD|u64:0|biguint:1000" + ] + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "payable_all_transfers-multi-EGLD-2", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "esdtValue": [ + { + "tokenIdentifier": "str:EGLD-000000", + "value": "1001" + }, + { + "tokenIdentifier": "str:EGLD-000000", + "value": "1002" + } + ], + "function": "payable_all_transfers", + "arguments": [ + + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + [ + "nested:str:EGLD|u64:0|biguint:1001", + "nested:str:EGLD|u64:0|biguint:1002" + ] + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "payable_all_transfers-EGLD-with-ESDT", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "esdtValue": [ + { + "tokenIdentifier": "str:TOK-123456", + "value": "100" + }, + { + "tokenIdentifier": "str:EGLD-000000", + "value": "1005" + }, + { + "tokenIdentifier": "str:OTHERTOK-123456", + "value": "400" + }, + { + "tokenIdentifier": "str:SFT-123", + "nonce": "5", + "value": "10" + }, + { + "tokenIdentifier": "str:EGLD-000000", + "value": "1006" + } + ], + "function": "payable_all_transfers", + "arguments": [ + + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + [ + "nested:str:TOK-123456|u64:0|biguint:100|", + "nested:str:EGLD|u64:0|biguint:1005", + "nested:str:OTHERTOK-123456|u64:0|biguint:400", + "nested:str:SFT-123|u64:5|biguint:10", + "nested:str:EGLD|u64:0|biguint:1006" + ] + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + } + ] +} diff --git a/contracts/feature-tests/payable-features/scenarios/payable_multi_array_egld.scen.json b/contracts/feature-tests/payable-features/scenarios/payable_multi_array_egld.scen.json new file mode 100644 index 0000000000..16fa1cea20 --- /dev/null +++ b/contracts/feature-tests/payable-features/scenarios/payable_multi_array_egld.scen.json @@ -0,0 +1,146 @@ +{ + "name": "payable", + "gasSchedule": "v3", + "steps": [ + { + "step": "setState", + "accounts": { + "sc:payable-features": { + "nonce": "0", + "balance": "0", + "code": "mxsc:../output/payable-features.mxsc.json" + }, + "address:an-account": { + "nonce": "0", + "balance": "10000", + "esdt": { + "str:TOK-000001": "1000", + "str:TOK-000002": "500", + "str:TOK-000003": "500", + "str:SFT-123": { + "instances": [ + { + "nonce": "5", + "balance": "20" + } + ] + } + } + } + } + }, + { + "step": "scCall", + "id": "payment-array-too-many", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "esdtValue": [ + { + "tokenIdentifier": "str:TOK-000001", + "value": "100" + }, + { + "tokenIdentifier": "str:TOK-000002", + "value": "400" + }, + { + "tokenIdentifier": "str:TOK-000003", + "value": "400" + }, + { + "tokenIdentifier": "str:SFT-123", + "nonce": "5", + "value": "10" + }, + { + "tokenIdentifier": "str:EGLD-000000", + "nonce": "0", + "value": "103" + } + ], + "function": "payment_array_3", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "4", + "message": "str:incorrect number of ESDT transfers", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "payment-array-too-few", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "esdtValue": [ + { + "tokenIdentifier": "str:TOK-000001", + "value": "100" + }, + { + "tokenIdentifier": "str:SFT-123", + "nonce": "5", + "value": "10" + } + ], + "function": "payment_array_3", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "4", + "message": "str:incorrect number of ESDT transfers", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "payment-array-ok", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "esdtValue": [ + { + "tokenIdentifier": "str:TOK-000001", + "value": "100" + }, + { + "tokenIdentifier": "str:EGLD-000000", + "value": "400" + }, + { + "tokenIdentifier": "str:SFT-123", + "nonce": "5", + "value": "10" + } + ], + "function": "payment_array_3", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "nested:str:TOK-000001|u64:0|biguint:100", + "nested:str:EGLD-000000|u64:0|biguint:400", + "nested:str:SFT-123|u64:5|biguint:10" + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + } + ] +} \ No newline at end of file diff --git a/contracts/feature-tests/payable-features/scenarios/payable_multiple_egld.scen.json b/contracts/feature-tests/payable-features/scenarios/payable_multiple_egld.scen.json new file mode 100644 index 0000000000..28e667b322 --- /dev/null +++ b/contracts/feature-tests/payable-features/scenarios/payable_multiple_egld.scen.json @@ -0,0 +1,78 @@ +{ + "name": "payable", + "gasSchedule": "v3", + "steps": [ + { + "step": "setState", + "accounts": { + "sc:payable-features": { + "nonce": "0", + "balance": "0", + "code": "mxsc:../output/payable-features.mxsc.json" + }, + "address:an-account": { + "nonce": "0", + "balance": "10000", + "esdt": { + "str:TOK-123456": "1000", + "str:OTHERTOK-123456": "500", + "str:SFT-123": { + "instances": [ + { + "nonce": "5", + "balance": "20" + } + ] + } + } + } + } + }, + { + "step": "scCall", + "id": "payment-multiple", + "tx": { + "from": "address:an-account", + "to": "sc:payable-features", + "esdtValue": [ + { + "tokenIdentifier": "str:TOK-123456", + "value": "100" + }, + { + "tokenIdentifier": "str:OTHERTOK-123456", + "value": "400" + }, + { + "tokenIdentifier": "str:SFT-123", + "nonce": "5", + "value": "10" + }, + { + "tokenIdentifier": "str:EGLD-000000", + "nonce": "0", + "value": "120" + } + ], + "function": "payment_multiple", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + [ + "nested:str:TOK-123456|u64:0|biguint:100|", + "nested:str:OTHERTOK-123456|u64:0|biguint:400", + "nested:str:SFT-123|u64:5|biguint:10", + "nested:str:EGLD-000000|u64:0|biguint:120" + ] + ], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + } + ] +} \ No newline at end of file diff --git a/contracts/feature-tests/payable-features/src/payable_features.rs b/contracts/feature-tests/payable-features/src/payable_features.rs index cf418efc0c..0ffcaa94ec 100644 --- a/contracts/feature-tests/payable-features/src/payable_features.rs +++ b/contracts/feature-tests/payable-features/src/payable_features.rs @@ -152,4 +152,10 @@ pub trait PayableFeatures { let token = self.call_value().single_esdt().token_identifier.clone(); (payment, token).into() } + + #[endpoint] + #[payable("*")] + fn payable_all_transfers(&self) -> ManagedVec { + self.call_value().all_transfers().clone() + } } diff --git a/contracts/feature-tests/payable-features/src/payable_features_proxy.rs b/contracts/feature-tests/payable-features/src/payable_features_proxy.rs index 7d6e020c89..936752a561 100644 --- a/contracts/feature-tests/payable-features/src/payable_features_proxy.rs +++ b/contracts/feature-tests/payable-features/src/payable_features_proxy.rs @@ -181,4 +181,12 @@ where .raw_call("payable_token_4") .original_result() } + + pub fn payable_all_transfers( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .raw_call("payable_all_transfers") + .original_result() + } } diff --git a/contracts/feature-tests/payable-features/tests/payable_scenario_go_test.rs b/contracts/feature-tests/payable-features/tests/payable_scenario_go_test.rs index a3ababd9c7..95be700ffa 100644 --- a/contracts/feature-tests/payable-features/tests/payable_scenario_go_test.rs +++ b/contracts/feature-tests/payable-features/tests/payable_scenario_go_test.rs @@ -9,6 +9,11 @@ fn call_value_check_go() { world().run("scenarios/call-value-check.scen.json"); } +#[test] +fn payable_all_transfers_go() { + world().run("scenarios/payable_all_transfers.scen.json"); +} + #[test] fn payable_any_1_go() { world().run("scenarios/payable_any_1.scen.json"); diff --git a/contracts/feature-tests/payable-features/tests/payable_scenario_rs_test.rs b/contracts/feature-tests/payable-features/tests/payable_scenario_rs_test.rs index 01b0ea567b..f3c4f752d9 100644 --- a/contracts/feature-tests/payable-features/tests/payable_scenario_rs_test.rs +++ b/contracts/feature-tests/payable-features/tests/payable_scenario_rs_test.rs @@ -15,6 +15,11 @@ fn call_value_check_rs() { world().run("scenarios/call-value-check.scen.json"); } +#[test] +fn payable_all_transfers_rs() { + world().run("scenarios/payable_all_transfers.scen.json"); +} + #[test] fn payable_any_1_rs() { world().run("scenarios/payable_any_1.scen.json"); diff --git a/contracts/feature-tests/payable-features/wasm/src/lib.rs b/contracts/feature-tests/payable-features/wasm/src/lib.rs index 41f085e638..67b35b5de8 100644 --- a/contracts/feature-tests/payable-features/wasm/src/lib.rs +++ b/contracts/feature-tests/payable-features/wasm/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 15 +// Endpoints: 16 // Async Callback (empty): 1 -// Total number of exported functions: 17 +// Total number of exported functions: 18 #![no_std] @@ -33,6 +33,7 @@ multiversx_sc_wasm_adapter::endpoints! { payable_token_2 => payable_token_2 payable_token_3 => payable_token_3 payable_token_4 => payable_token_4 + payable_all_transfers => payable_all_transfers ) } diff --git a/framework/base/src/api/managed_types/const_handles.rs b/framework/base/src/api/managed_types/const_handles.rs index 283d9c55c6..ccf4e9a06c 100644 --- a/framework/base/src/api/managed_types/const_handles.rs +++ b/framework/base/src/api/managed_types/const_handles.rs @@ -7,25 +7,27 @@ pub const UNINITIALIZED_HANDLE: RawHandle = i32::MAX; /// WARNING! With the current VM this still needs to be initialized before use. pub const BIG_INT_CONST_ZERO: RawHandle = -10; - -pub const CALL_VALUE_EGLD: RawHandle = -11; -pub const CALL_VALUE_SINGLE_ESDT: RawHandle = -13; - -pub const BIG_INT_TEMPORARY_1: RawHandle = -14; -pub const BIG_INT_TEMPORARY_2: RawHandle = -15; -pub const BIG_FLOAT_TEMPORARY: RawHandle = -16; +pub const BIG_INT_TEMPORARY_1: RawHandle = -11; +pub const BIG_INT_TEMPORARY_2: RawHandle = -12; +pub const BIG_FLOAT_TEMPORARY: RawHandle = -15; /// WARNING! With the current VM this still needs to be initialized before use. pub const MBUF_CONST_EMPTY: RawHandle = -20; -pub const CALL_VALUE_MULTI_ESDT: RawHandle = -21; -pub const CALL_VALUE_SINGLE_ESDT_TOKEN_NAME: RawHandle = -22; -pub const CALLBACK_CLOSURE_ARGS_BUFFER: RawHandle = -23; pub const MBUF_TEMPORARY_1: RawHandle = -25; pub const MBUF_TEMPORARY_2: RawHandle = -26; pub const ADDRESS_CALLER: RawHandle = -30; pub const ADDRESS_SELF: RawHandle = -31; +pub const CALL_VALUE_EGLD: RawHandle = -35; +pub const CALL_VALUE_EGLD_MULTI: RawHandle = -36; +pub const CALL_VALUE_EGLD_FROM_ESDT: RawHandle = -37; +pub const CALL_VALUE_MULTI_ESDT: RawHandle = -38; +pub const CALL_VALUE_ALL: RawHandle = -39; +pub const MBUF_EGLD_000000: RawHandle = -40; + +pub const CALLBACK_CLOSURE_ARGS_BUFFER: RawHandle = -50; + pub const NEW_HANDLE_START_FROM: RawHandle = -200; // > -100 reserved for APIs // Vec of 64 entries of 1 bit @@ -39,3 +41,37 @@ pub const fn get_scaling_factor_handle(decimals: usize) -> i32 { let decimals_i32 = decimals as i32; SCALING_FACTOR_START - decimals_i32 } + +/// Payload of the singleton ManagedVec that contains the current single EGLD transfer, modelled as an ESDT payment. +pub const EGLD_PAYMENT_PAYLOAD: [u8; 16] = [ + 0xff, + 0xff, + 0xff, + (0x0100 + MBUF_EGLD_000000) as u8, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0xff, + 0xff, + 0xff, + (0x0100 + CALL_VALUE_EGLD) as u8, +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn egld_payment_payload_test() { + let mut bytes = [0u8; 4]; + bytes.copy_from_slice(&EGLD_PAYMENT_PAYLOAD[0..4]); + assert_eq!(i32::from_be_bytes(bytes), MBUF_EGLD_000000); + bytes.copy_from_slice(&EGLD_PAYMENT_PAYLOAD[12..16]); + assert_eq!(i32::from_be_bytes(bytes), CALL_VALUE_EGLD); + } +} diff --git a/framework/base/src/api/managed_types/static_var_api_flags.rs b/framework/base/src/api/managed_types/static_var_api_flags.rs index 1c9e153e69..2040d8acf3 100644 --- a/framework/base/src/api/managed_types/static_var_api_flags.rs +++ b/framework/base/src/api/managed_types/static_var_api_flags.rs @@ -3,11 +3,12 @@ use bitflags::bitflags; bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct StaticVarApiFlags: u8 { - const NONE = 0b00000000; - const CALL_VALUE_EGLD_INITIALIZED = 0b00000001; - const CALL_VALUE_EGLD_MULTI_INITIALIZED = 0b00000010; - const CALL_VALUE_MULTI_ESDT_INITIALIZED = 0b00000100; - const CALL_VALUE_ALL_INITIALIZED = 0b00001000; + const NONE = 0b00000000; + const CALL_VALUE_EGLD_SINGLE_INITIALIZED = 0b00000001; + const CALL_VALUE_ESDT_UNCHECKED_INITIALIZED = 0b00000010; + const CALL_VALUE_EGLD_MULTI_INITIALIZED = 0b00000100; + const CALL_VALUE_EGLD_FROM_ESDT_INITIALIZED = 0b00001000; + const CALL_VALUE_ALL_INITIALIZED = 0b00010000; } } @@ -32,21 +33,27 @@ pub mod tests { assert!(current.check_and_set(StaticVarApiFlags::NONE)); assert_eq!(current, StaticVarApiFlags::NONE); - assert!(!current.check_and_set(StaticVarApiFlags::CALL_VALUE_EGLD_INITIALIZED)); - assert_eq!(current, StaticVarApiFlags::CALL_VALUE_EGLD_INITIALIZED); - assert!(current.check_and_set(StaticVarApiFlags::CALL_VALUE_EGLD_INITIALIZED)); - assert_eq!(current, StaticVarApiFlags::CALL_VALUE_EGLD_INITIALIZED); + assert!(!current.check_and_set(StaticVarApiFlags::CALL_VALUE_EGLD_SINGLE_INITIALIZED)); + assert_eq!( + current, + StaticVarApiFlags::CALL_VALUE_EGLD_SINGLE_INITIALIZED + ); + assert!(current.check_and_set(StaticVarApiFlags::CALL_VALUE_EGLD_SINGLE_INITIALIZED)); + assert_eq!( + current, + StaticVarApiFlags::CALL_VALUE_EGLD_SINGLE_INITIALIZED + ); assert!(!current.check_and_set(StaticVarApiFlags::CALL_VALUE_ALL_INITIALIZED)); assert_eq!( current, - StaticVarApiFlags::CALL_VALUE_EGLD_INITIALIZED + StaticVarApiFlags::CALL_VALUE_EGLD_SINGLE_INITIALIZED | StaticVarApiFlags::CALL_VALUE_ALL_INITIALIZED ); assert!(current.check_and_set(StaticVarApiFlags::CALL_VALUE_ALL_INITIALIZED)); assert_eq!( current, - StaticVarApiFlags::CALL_VALUE_EGLD_INITIALIZED + StaticVarApiFlags::CALL_VALUE_EGLD_SINGLE_INITIALIZED | StaticVarApiFlags::CALL_VALUE_ALL_INITIALIZED ); } diff --git a/framework/base/src/contract_base/wrappers/call_value_wrapper.rs b/framework/base/src/contract_base/wrappers/call_value_wrapper.rs index 1edb635a9d..43f798acbc 100644 --- a/framework/base/src/contract_base/wrappers/call_value_wrapper.rs +++ b/framework/base/src/contract_base/wrappers/call_value_wrapper.rs @@ -1,15 +1,18 @@ use core::marker::PhantomData; +use multiversx_chain_core::EGLD_000000_TOKEN_IDENTIFIER; + use crate::{ api::{ const_handles, use_raw_handle, CallValueApi, CallValueApiImpl, ErrorApi, ErrorApiImpl, - ManagedTypeApi, StaticVarApiFlags, StaticVarApiImpl, + ManagedBufferApiImpl, ManagedTypeApi, RawHandle, StaticVarApiFlags, StaticVarApiImpl, }, err_msg, types::{ - BigUint, ConstDecimals, EgldOrEsdtTokenIdentifier, EgldOrEsdtTokenPayment, - EgldOrMultiEsdtPayment, EsdtTokenPayment, ManagedDecimal, ManagedRef, ManagedType, - ManagedVec, ManagedVecRef, TokenIdentifier, + big_num_cmp::bi_gt_zero, BigInt, BigUint, ConstDecimals, EgldOrEsdtTokenIdentifier, + EgldOrEsdtTokenPayment, EgldOrMultiEsdtPayment, EsdtTokenPayment, ManagedDecimal, + ManagedRef, ManagedType, ManagedVec, ManagedVecItem, ManagedVecItemPayload, + ManagedVecPayloadIterator, ManagedVecRef, TokenIdentifier, }, }; @@ -31,12 +34,47 @@ where } } + /// Cached transfers from the VM. + fn all_esdt_transfers_unchecked(&self) -> A::ManagedBufferHandle { + let all_transfers_unchecked_handle: A::ManagedBufferHandle = + use_raw_handle(const_handles::CALL_VALUE_MULTI_ESDT); + if !A::static_var_api_impl() + .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_ESDT_UNCHECKED_INITIALIZED) + { + A::call_value_api_impl() + .load_all_esdt_transfers(all_transfers_unchecked_handle.clone()); + } + all_transfers_unchecked_handle + } + + /// Cached egld transfer searched for in the ESDT transfers from the VM. + fn egld_from_multi_esdt(&self) -> A::BigIntHandle { + let egld_from_multi_esdt_handle: A::BigIntHandle = + use_raw_handle(const_handles::CALL_VALUE_EGLD_FROM_ESDT); + if !A::static_var_api_impl() + .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_EGLD_FROM_ESDT_INITIALIZED) + { + let all_transfers_unchecked_handle = self.all_esdt_transfers_unchecked(); + let egld_handle_result = find_egld_000000_transfer::(all_transfers_unchecked_handle); + if let Some(found_egld_handle) = egld_handle_result { + BigInt::::clone_to_handle( + use_raw_handle(found_egld_handle), + egld_from_multi_esdt_handle.clone(), + ); + } else { + BigInt::::set_value(egld_from_multi_esdt_handle.clone(), 0); + } + } + egld_from_multi_esdt_handle + } + /// Retrieves the EGLD call value from the VM. - /// Will return 0 in case of an ESDT transfer (cannot have both EGLD and ESDT transfer simultaneously). + /// + /// Will return 0 in case of an ESDT transfer, even though EGLD and ESDT transfers are now posible. pub fn egld_value(&self) -> ManagedRef<'static, A, BigUint> { let call_value_handle: A::BigIntHandle = use_raw_handle(const_handles::CALL_VALUE_EGLD); if !A::static_var_api_impl() - .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_EGLD_INITIALIZED) + .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_EGLD_SINGLE_INITIALIZED) { A::call_value_api_impl().load_egld_value(call_value_handle.clone()); } @@ -50,18 +88,91 @@ where ) } + /// The quantity of EGLD transfered, either via simple EGLD transfer, or via ESDT multi-transfer. + pub fn egld_value_multi(&self) -> ManagedRef<'static, A, BigUint> { + let egld_value_multi_handle: A::BigIntHandle = + use_raw_handle(const_handles::CALL_VALUE_EGLD_MULTI); + if !A::static_var_api_impl() + .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_EGLD_MULTI_INITIALIZED) + { + let egld_single = self.egld_value(); + if bi_gt_zero::(egld_single.get_handle()) { + BigInt::::clone_to_handle( + egld_single.get_handle(), + egld_value_multi_handle.clone(), + ); + } else { + let egld_from_multi_esdt_handle = self.egld_from_multi_esdt(); + BigInt::::clone_to_handle( + egld_from_multi_esdt_handle, + egld_value_multi_handle.clone(), + ); + } + } + unsafe { ManagedRef::wrap_handle(egld_value_multi_handle) } + } + /// Returns all ESDT transfers that accompany this SC call. /// Will return 0 results if nothing was transfered, or just EGLD. - /// Fully managed underlying types, very efficient. + /// + /// Will crash for EGLD + ESDT multi transfers. pub fn all_esdt_transfers(&self) -> ManagedRef<'static, A, ManagedVec>> { - let call_value_handle: A::ManagedBufferHandle = - use_raw_handle(const_handles::CALL_VALUE_MULTI_ESDT); + let multi_esdt_handle: A::ManagedBufferHandle = self.all_esdt_transfers_unchecked(); if !A::static_var_api_impl() - .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_MULTI_ESDT_INITIALIZED) + .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_ESDT_UNCHECKED_INITIALIZED) { - A::call_value_api_impl().load_all_esdt_transfers(call_value_handle.clone()); + let egld_value_multi_handle = self.egld_from_multi_esdt(); + if bi_gt_zero::(egld_value_multi_handle) { + A::error_api_impl().signal_error(err_msg::ESDT_UNEXPECTED_EGLD.as_bytes()) + } } - unsafe { ManagedRef::wrap_handle(call_value_handle) } + + unsafe { ManagedRef::wrap_handle(multi_esdt_handle) } + } + + /// Will return all transfers in the form of a list of EgldOrEsdtTokenPayment. + /// + /// Both EGLD and ESDT can be returned. + /// + /// In case of a single EGLD transfer, only one item will be returned, + /// the EGLD payment represented as an ESDT transfer (EGLD-000000). + pub fn all_transfers( + &self, + ) -> ManagedRef<'static, A, ManagedVec>> { + let all_transfers_handle: A::ManagedBufferHandle = + use_raw_handle(const_handles::CALL_VALUE_ALL); + if !A::static_var_api_impl() + .flag_is_set_or_update(StaticVarApiFlags::CALL_VALUE_ALL_INITIALIZED) + { + let egld_single = self.egld_value(); + if bi_gt_zero::(egld_single.get_handle()) { + A::managed_type_impl().mb_overwrite( + use_raw_handle(const_handles::MBUF_EGLD_000000), + EGLD_000000_TOKEN_IDENTIFIER.as_bytes(), + ); + A::managed_type_impl().mb_overwrite( + all_transfers_handle.clone(), + &const_handles::EGLD_PAYMENT_PAYLOAD[..], + ); + } else { + // clone all_esdt_transfers_unchecked -> all_transfers + let all_transfers_unchecked_handle = self.all_esdt_transfers_unchecked(); + A::managed_type_impl().mb_overwrite(all_transfers_handle.clone(), &[]); + A::managed_type_impl() + .mb_append(all_transfers_handle.clone(), all_transfers_unchecked_handle); + } + } + unsafe { ManagedRef::wrap_handle(all_transfers_handle) } + } + + /// Same as `all_transfers`, but without EGLD singleton. + /// + /// Temporary! + pub fn all_multi_transfers( + &self, + ) -> ManagedRef<'static, A, ManagedVec>> { + let all_transfers_unchecked_handle = self.all_esdt_transfers_unchecked(); + unsafe { ManagedRef::wrap_handle(all_transfers_unchecked_handle) } } /// Verify and casts the received multi ESDT transfer in to an array. @@ -121,14 +232,16 @@ where /// /// In case no transfer of value happen, it will return a payment of 0 EGLD. pub fn egld_or_single_esdt(&self) -> EgldOrEsdtTokenPayment { - let esdt_transfers = self.all_esdt_transfers(); + let esdt_transfers_handle = self.all_esdt_transfers_unchecked(); + let esdt_transfers: ManagedRef<'static, A, ManagedVec>> = + unsafe { ManagedRef::wrap_handle(esdt_transfers_handle) }; match esdt_transfers.len() { 0 => EgldOrEsdtTokenPayment { token_identifier: EgldOrEsdtTokenIdentifier::egld(), token_nonce: 0, amount: self.egld_value().clone_value(), }, - 1 => esdt_transfers.get(0).clone().into(), + 1 => esdt_transfers.get(0).clone(), _ => A::error_api_impl().signal_error(err_msg::INCORRECT_NUM_ESDT_TRANSFERS.as_bytes()), } } @@ -162,3 +275,34 @@ where } } } + +fn find_egld_000000_transfer(transfers_vec_handle: A::ManagedBufferHandle) -> Option +where + A: CallValueApi + ErrorApi + ManagedTypeApi, +{ + A::managed_type_impl().mb_overwrite( + use_raw_handle(const_handles::MBUF_EGLD_000000), + EGLD_000000_TOKEN_IDENTIFIER.as_bytes(), + ); + unsafe { + let mut iter: ManagedVecPayloadIterator< + A, + as ManagedVecItem>::PAYLOAD, + > = ManagedVecPayloadIterator::new(transfers_vec_handle); + + if iter.remaining_count() <= 1 { + // EGLD is not allowed in single transfers + return None; + } + + let egld_payload = iter.find(|payload| { + let token_identifier_handle = RawHandle::read_from_payload(payload.slice_unchecked(0)); + A::managed_type_impl().mb_eq( + use_raw_handle(const_handles::MBUF_EGLD_000000), + use_raw_handle(token_identifier_handle), + ) + }); + + egld_payload.map(|payload| RawHandle::read_from_payload(payload.slice_unchecked(12))) + } +} diff --git a/framework/base/src/contract_base/wrappers/send_raw_wrapper.rs b/framework/base/src/contract_base/wrappers/send_raw_wrapper.rs index 07466ac234..f3d22f8842 100644 --- a/framework/base/src/contract_base/wrappers/send_raw_wrapper.rs +++ b/framework/base/src/contract_base/wrappers/send_raw_wrapper.rs @@ -6,8 +6,8 @@ use crate::{ HandleConstraints, ManagedBufferApiImpl, RawHandle, SendApiImpl, StaticVarApiImpl, }, types::{ - BigUint, CodeMetadata, EsdtTokenPayment, ManagedAddress, ManagedArgBuffer, ManagedBuffer, - ManagedType, ManagedVec, TokenIdentifier, + BigUint, CodeMetadata, EgldOrEsdtTokenPayment, EsdtTokenPayment, ManagedAddress, + ManagedArgBuffer, ManagedBuffer, ManagedType, ManagedVec, TokenIdentifier, }, }; @@ -124,6 +124,34 @@ where ) } + pub fn multi_egld_or_esdt_transfer_execute( + &self, + to: &ManagedAddress, + payments: &ManagedVec>, + gas_limit: u64, + endpoint_name: &ManagedBuffer, + arg_buffer: &ManagedArgBuffer, + ) -> Result<(), &'static [u8]> { + if let Some(single_item) = payments.is_single_item() { + if single_item.token_identifier.is_egld() { + return self.direct_egld_execute( + to, + &single_item.amount, + gas_limit, + endpoint_name, + arg_buffer, + ); + } + } + A::send_api_impl().multi_transfer_esdt_nft_execute( + to.get_handle().get_raw_handle(), + payments.get_handle().get_raw_handle(), + gas_limit, + endpoint_name.get_handle().get_raw_handle(), + arg_buffer.get_handle().get_raw_handle(), + ) + } + pub fn async_call_raw( &self, to: &ManagedAddress, diff --git a/framework/base/src/err_msg.rs b/framework/base/src/err_msg.rs index 361775d5f9..5067013c77 100644 --- a/framework/base/src/err_msg.rs +++ b/framework/base/src/err_msg.rs @@ -7,6 +7,7 @@ pub const BAD_TOKEN_TICKER_FORMAT: &str = "bad token ticker format"; pub const SINGLE_ESDT_EXPECTED: &str = "function expects single ESDT payment"; pub const TOO_MANY_ESDT_TRANSFERS: &str = "too many ESDT transfers"; pub const ESDT_INVALID_TOKEN_INDEX: &str = "invalid token index"; +pub const ESDT_UNEXPECTED_EGLD: &str = "unexpected EGLD transfer"; pub const INCORRECT_NUM_ESDT_TRANSFERS: &str = "incorrect number of ESDT transfers"; pub static FUNGIBLE_TOKEN_EXPECTED_ERR_MSG: &str = "fungible ESDT token expected"; diff --git a/framework/base/src/types/interaction/contract_call_legacy/contract_call_convert.rs b/framework/base/src/types/interaction/contract_call_legacy/contract_call_convert.rs index 6a66c3e0f5..6b13a39bfc 100644 --- a/framework/base/src/types/interaction/contract_call_legacy/contract_call_convert.rs +++ b/framework/base/src/types/interaction/contract_call_legacy/contract_call_convert.rs @@ -78,7 +78,10 @@ where function_call: self .basic .function_call - .convert_to_multi_transfer_esdt_call(&self.basic.to, &payments), + .convert_to_multi_transfer_esdt_call( + &self.basic.to, + payments.as_multi_egld_or_esdt_payment(), + ), explicit_gas_limit: self.basic.explicit_gas_limit, _return_type: PhantomData, }, diff --git a/framework/base/src/types/interaction/tx_data/function_call.rs b/framework/base/src/types/interaction/tx_data/function_call.rs index 7ebf20dd1a..2d359a04e6 100644 --- a/framework/base/src/types/interaction/tx_data/function_call.rs +++ b/framework/base/src/types/interaction/tx_data/function_call.rs @@ -10,8 +10,8 @@ use crate::{ ESDT_TRANSFER_FUNC_NAME, }, types::{ - ContractCallNoPayment, EsdtTokenPayment, EsdtTokenPaymentRefs, ManagedAddress, - ManagedArgBuffer, ManagedBuffer, ManagedVec, MultiValueEncoded, TypedFunctionCall, + ContractCallNoPayment, EsdtTokenPaymentRefs, ManagedAddress, ManagedArgBuffer, + ManagedBuffer, MultiEgldOrEsdtPayment, MultiValueEncoded, TypedFunctionCall, }, }; @@ -166,8 +166,10 @@ where self, payment: EsdtTokenPaymentRefs<'_, Api>, ) -> FunctionCall { + // EGLD not supported + // but serializing token identifier buffer for efficiency, no need to convert to "EGLD" from "EGLD-000000" FunctionCall::new(ESDT_TRANSFER_FUNC_NAME) - .argument(&payment.token_identifier) + .argument(&payment.token_identifier.as_managed_buffer()) .argument(&payment.amount) .argument(&self) } @@ -184,8 +186,10 @@ where to: &ManagedAddress, payment: EsdtTokenPaymentRefs<'_, Api>, ) -> FunctionCall { + // EGLD not supported + // but serializing token identifier buffer for efficiency, no need to convert to "EGLD" from "EGLD-000000" FunctionCall::new(ESDT_NFT_TRANSFER_FUNC_NAME) - .argument(&payment.token_identifier) + .argument(&payment.token_identifier.as_managed_buffer()) .argument(&payment.token_nonce) .argument(&payment.amount) .argument(to) @@ -196,15 +200,16 @@ where pub(crate) fn convert_to_multi_transfer_esdt_call( self, to: &ManagedAddress, - payments: &ManagedVec>, + payments: &MultiEgldOrEsdtPayment, ) -> FunctionCall { let mut result = FunctionCall::new(ESDT_MULTI_TRANSFER_FUNC_NAME) .argument(&to) .argument(&payments.len()); for payment in payments { + // serializing token identifier buffer to get EGLD-00000 instead of EGLD result = result - .argument(&payment.token_identifier) + .argument(&payment.token_identifier.buffer) .argument(&payment.token_nonce) .argument(&payment.amount); } diff --git a/framework/base/src/types/interaction/tx_payment.rs b/framework/base/src/types/interaction/tx_payment.rs index bed41cc775..d2d4fe5362 100644 --- a/framework/base/src/types/interaction/tx_payment.rs +++ b/framework/base/src/types/interaction/tx_payment.rs @@ -5,6 +5,7 @@ mod tx_payment_egld_or_esdt_refs; mod tx_payment_egld_or_multi_esdt; mod tx_payment_egld_or_multi_esdt_ref; mod tx_payment_egld_value; +mod tx_payment_multi_egld_or_esdt; mod tx_payment_multi_esdt; mod tx_payment_none; mod tx_payment_not_payable; @@ -20,7 +21,7 @@ pub use tx_payment_not_payable::NotPayable; use crate::{ api::ManagedTypeApi, - types::{BigUint, ManagedAddress, ManagedBuffer, MultiEsdtPayment}, + types::{BigUint, ManagedAddress, ManagedBuffer, MultiEgldOrEsdtPayment}, }; use super::{AnnotatedValue, FunctionCall, TxEnv, TxFrom, TxToSpecified}; @@ -118,7 +119,7 @@ where Api: ManagedTypeApi, { pub egld: Option>, - pub multi_esdt: MultiEsdtPayment, + pub multi_esdt: MultiEgldOrEsdtPayment, } impl Default for FullPaymentData diff --git a/framework/base/src/types/interaction/tx_payment/tx_payment_multi_egld_or_esdt.rs b/framework/base/src/types/interaction/tx_payment/tx_payment_multi_egld_or_esdt.rs new file mode 100644 index 0000000000..4ee31b83e9 --- /dev/null +++ b/framework/base/src/types/interaction/tx_payment/tx_payment_multi_egld_or_esdt.rs @@ -0,0 +1,147 @@ +use core::ops::Deref; + +use crate::{ + contract_base::SendRawWrapper, + types::{BigUint, ManagedAddress, ManagedRef, MultiEgldOrEsdtPayment, TxFrom, TxToSpecified}, +}; + +use super::{FullPaymentData, FunctionCall, TxEnv, TxPayment}; + +impl TxPayment for &MultiEgldOrEsdtPayment +where + Env: TxEnv, +{ + fn is_no_payment(&self, _env: &Env) -> bool { + self.is_empty() + } + + fn perform_transfer_execute( + self, + _env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ) { + let _ = SendRawWrapper::::new().multi_egld_or_esdt_transfer_execute( + to, + self, + gas_limit, + &fc.function_name, + &fc.arg_buffer, + ); + } + + fn with_normalized( + self, + env: &Env, + from: &From, + to: To, + fc: FunctionCall, + f: F, + ) -> R + where + From: TxFrom, + To: TxToSpecified, + F: FnOnce(&ManagedAddress, &BigUint, FunctionCall) -> R, + { + to.with_address_ref(env, |to_addr| { + let fc_conv = fc.convert_to_multi_transfer_esdt_call(to_addr, self); + f(&from.resolve_address(env), &*BigUint::zero_ref(), fc_conv) + }) + } + + fn into_full_payment_data(self, _env: &Env) -> FullPaymentData { + FullPaymentData { + egld: None, + multi_esdt: self.clone(), + } + } +} + +impl TxPayment for ManagedRef<'_, Env::Api, MultiEgldOrEsdtPayment> +where + Env: TxEnv, +{ + #[inline] + fn is_no_payment(&self, _env: &Env) -> bool { + self.deref().is_empty() + } + + #[inline] + fn perform_transfer_execute( + self, + env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ) { + self.deref() + .perform_transfer_execute(env, to, gas_limit, fc) + } + + #[inline] + fn with_normalized( + self, + env: &Env, + from: &From, + to: To, + fc: FunctionCall, + f: F, + ) -> R + where + From: TxFrom, + To: TxToSpecified, + F: FnOnce(&ManagedAddress, &BigUint, FunctionCall) -> R, + { + self.deref().with_normalized(env, from, to, fc, f) + } + + fn into_full_payment_data(self, env: &Env) -> FullPaymentData { + self.deref().into_full_payment_data(env) + } +} + +impl TxPayment for MultiEgldOrEsdtPayment +where + Env: TxEnv, +{ + #[inline] + fn is_no_payment(&self, _env: &Env) -> bool { + self.is_empty() + } + + #[inline] + fn perform_transfer_execute( + self, + env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ) { + (&self).perform_transfer_execute(env, to, gas_limit, fc); + } + + #[inline] + fn with_normalized( + self, + env: &Env, + from: &From, + to: To, + fc: FunctionCall, + f: F, + ) -> R + where + From: TxFrom, + To: TxToSpecified, + F: FnOnce(&ManagedAddress, &BigUint, FunctionCall) -> R, + { + (&self).with_normalized(env, from, to, fc, f) + } + + fn into_full_payment_data(self, _env: &Env) -> FullPaymentData { + FullPaymentData { + egld: None, + multi_esdt: self, + } + } +} diff --git a/framework/base/src/types/interaction/tx_payment/tx_payment_multi_esdt.rs b/framework/base/src/types/interaction/tx_payment/tx_payment_multi_esdt.rs index ab030362d2..7916cef015 100644 --- a/framework/base/src/types/interaction/tx_payment/tx_payment_multi_esdt.rs +++ b/framework/base/src/types/interaction/tx_payment/tx_payment_multi_esdt.rs @@ -62,7 +62,10 @@ where 0 => ().with_normalized(env, from, to, fc, f), 1 => self.get(0).as_refs().with_normalized(env, from, to, fc, f), _ => to.with_address_ref(env, |to_addr| { - let fc_conv = fc.convert_to_multi_transfer_esdt_call(to_addr, self); + let fc_conv = fc.convert_to_multi_transfer_esdt_call( + to_addr, + self.as_multi_egld_or_esdt_payment(), + ); f(&from.resolve_address(env), &*BigUint::zero_ref(), fc_conv) }), } @@ -71,7 +74,7 @@ where fn into_full_payment_data(self, _env: &Env) -> FullPaymentData { FullPaymentData { egld: None, - multi_esdt: self.clone(), + multi_esdt: self.as_multi_egld_or_esdt_payment().clone(), } } } @@ -159,7 +162,7 @@ where fn into_full_payment_data(self, _env: &Env) -> FullPaymentData { FullPaymentData { egld: None, - multi_esdt: self, + multi_esdt: self.into_multi_egld_or_esdt_payment(), } } } diff --git a/framework/base/src/types/interaction/tx_payment/tx_payment_single_esdt.rs b/framework/base/src/types/interaction/tx_payment/tx_payment_single_esdt.rs index 9ace787df2..f52ed22345 100644 --- a/framework/base/src/types/interaction/tx_payment/tx_payment_single_esdt.rs +++ b/framework/base/src/types/interaction/tx_payment/tx_payment_single_esdt.rs @@ -1,6 +1,4 @@ -use crate::types::{ - BigUint, EsdtTokenPayment, ManagedAddress, MultiEsdtPayment, TxFrom, TxToSpecified, -}; +use crate::types::{BigUint, EsdtTokenPayment, ManagedAddress, ManagedVec, TxFrom, TxToSpecified}; use super::{FullPaymentData, FunctionCall, TxEnv, TxPayment}; @@ -45,7 +43,7 @@ where fn into_full_payment_data(self, _env: &Env) -> FullPaymentData { FullPaymentData { egld: None, - multi_esdt: MultiEsdtPayment::from_single_item(self), + multi_esdt: ManagedVec::from_single_item(self.into_multi_egld_or_esdt_payment()), } } } @@ -91,7 +89,7 @@ where fn into_full_payment_data(self, _env: &Env) -> FullPaymentData { FullPaymentData { egld: None, - multi_esdt: MultiEsdtPayment::from_single_item(self.clone()), + multi_esdt: ManagedVec::from_single_item(self.as_egld_or_esdt_payment().clone()), } } } diff --git a/framework/base/src/types/interaction/tx_payment/tx_payment_single_esdt_ref.rs b/framework/base/src/types/interaction/tx_payment/tx_payment_single_esdt_ref.rs index 6aecb277a6..33a160df63 100644 --- a/framework/base/src/types/interaction/tx_payment/tx_payment_single_esdt_ref.rs +++ b/framework/base/src/types/interaction/tx_payment/tx_payment_single_esdt_ref.rs @@ -1,8 +1,6 @@ use crate::{ contract_base::SendRawWrapper, - types::{ - BigUint, EsdtTokenPaymentRefs, ManagedAddress, MultiEsdtPayment, TxFrom, TxToSpecified, - }, + types::{BigUint, EsdtTokenPaymentRefs, ManagedAddress, ManagedVec, TxFrom, TxToSpecified}, }; use super::{FullPaymentData, FunctionCall, TxEnv, TxPayment}; @@ -74,7 +72,9 @@ where fn into_full_payment_data(self, _env: &Env) -> FullPaymentData { FullPaymentData { egld: None, - multi_esdt: MultiEsdtPayment::from_single_item(self.to_owned_payment()), + multi_esdt: ManagedVec::from_single_item( + self.to_owned_payment().into_multi_egld_or_esdt_payment(), + ), } } } diff --git a/framework/base/src/types/managed/basic/big_int.rs b/framework/base/src/types/managed/basic/big_int.rs index 464bc4150c..08adafea57 100644 --- a/framework/base/src/types/managed/basic/big_int.rs +++ b/framework/base/src/types/managed/basic/big_int.rs @@ -229,19 +229,19 @@ impl BigInt { result } } + + pub(crate) fn clone_to_handle(source_handle: M::BigIntHandle, dest_handle: M::BigIntHandle) { + let api = M::managed_type_impl(); + api.bi_set_int64(dest_handle.clone(), 0); + api.bi_add(dest_handle.clone(), dest_handle, source_handle); + } } impl Clone for BigInt { fn clone(&self) -> Self { - let api = M::managed_type_impl(); unsafe { let result = BigInt::new_uninit(); - api.bi_set_int64(result.get_handle(), 0); - api.bi_add( - result.get_handle(), - result.get_handle(), - self.handle.clone(), - ); + BigInt::::clone_to_handle(self.get_handle(), result.get_handle()); result } } diff --git a/framework/base/src/types/managed/basic/big_int_cmp.rs b/framework/base/src/types/managed/basic/big_int_cmp.rs index 3cad2af277..9097909591 100644 --- a/framework/base/src/types/managed/basic/big_int_cmp.rs +++ b/framework/base/src/types/managed/basic/big_int_cmp.rs @@ -1,8 +1,11 @@ use core::cmp::Ordering; -use crate::api::{BigIntApiImpl, ManagedTypeApi}; +use crate::{ + api::{BigIntApiImpl, ManagedTypeApi}, + types::ManagedType, +}; -use super::{big_num_cmp::cmp_i64, BigInt}; +use super::{big_num_cmp::bi_cmp_i64, BigInt}; impl PartialEq for BigInt { #[inline] @@ -34,14 +37,14 @@ macro_rules! partial_eq_and_ord { impl PartialEq<$small_int_type> for BigInt { #[inline] fn eq(&self, other: &$small_int_type) -> bool { - cmp_i64(self, *other as i64).is_eq() + bi_cmp_i64::(self.get_handle(), *other as i64).is_eq() } } impl PartialOrd<$small_int_type> for BigInt { #[inline] fn partial_cmp(&self, other: &$small_int_type) -> Option { - Some(cmp_i64(self, *other as i64)) + Some(bi_cmp_i64::(self.get_handle(), *other as i64)) } } }; diff --git a/framework/base/src/types/managed/basic/big_num_cmp.rs b/framework/base/src/types/managed/basic/big_num_cmp.rs index 7793ea3e00..dcb3bc6c49 100644 --- a/framework/base/src/types/managed/basic/big_num_cmp.rs +++ b/framework/base/src/types/managed/basic/big_num_cmp.rs @@ -1,26 +1,36 @@ use core::cmp::Ordering; -use crate::{ - api::{const_handles, BigIntApiImpl, ManagedTypeApi}, - types::ManagedType, -}; +use crate::api::{const_handles, BigIntApiImpl, ManagedTypeApi}; use super::BigInt; -pub(crate) fn cmp_i64(bi: &B, other: i64) -> Ordering +pub(crate) fn bi_cmp_zero(bi_handle: M::BigIntHandle) -> Ordering +where + M: ManagedTypeApi, +{ + match M::managed_type_impl().bi_sign(bi_handle) { + crate::api::Sign::Plus => Ordering::Greater, + crate::api::Sign::NoSign => Ordering::Equal, + crate::api::Sign::Minus => Ordering::Less, + } +} + +pub(crate) fn bi_gt_zero(bi_handle: M::BigIntHandle) -> bool +where + M: ManagedTypeApi, +{ + bi_cmp_zero::(bi_handle) == Ordering::Greater +} + +pub(crate) fn bi_cmp_i64(bi_handle: M::BigIntHandle, other: i64) -> Ordering where M: ManagedTypeApi, - B: ManagedType, { let api = M::managed_type_impl(); if other == 0 { - match api.bi_sign(bi.get_handle()) { - crate::api::Sign::Plus => Ordering::Greater, - crate::api::Sign::NoSign => Ordering::Equal, - crate::api::Sign::Minus => Ordering::Less, - } + bi_cmp_zero::(bi_handle) } else { let big_int_temp_1 = BigInt::::make_temp(const_handles::BIG_INT_TEMPORARY_1, other); - api.bi_cmp(bi.get_handle(), big_int_temp_1) + api.bi_cmp(bi_handle, big_int_temp_1) } } diff --git a/framework/base/src/types/managed/basic/managed_buffer.rs b/framework/base/src/types/managed/basic/managed_buffer.rs index d3c0c28e38..01f26277c8 100644 --- a/framework/base/src/types/managed/basic/managed_buffer.rs +++ b/framework/base/src/types/managed/basic/managed_buffer.rs @@ -406,14 +406,13 @@ impl PartialEq for ManagedBuffer { impl Eq for ManagedBuffer {} impl PartialEq<&[u8; N]> for ManagedBuffer { - #[allow(clippy::op_ref)] // clippy is wrong here, it is not needless fn eq(&self, other: &&[u8; N]) -> bool { if self.len() != N { return false; } let mut self_bytes = [0u8; N]; let _ = M::managed_type_impl().mb_load_slice(self.handle.clone(), 0, &mut self_bytes[..]); - &self_bytes[..] == &other[..] + self_bytes[..] == other[..] } } diff --git a/framework/base/src/types/managed/basic/mod.rs b/framework/base/src/types/managed/basic/mod.rs index ef10f15b5e..253adc0fb8 100644 --- a/framework/base/src/types/managed/basic/mod.rs +++ b/framework/base/src/types/managed/basic/mod.rs @@ -5,7 +5,7 @@ mod big_int; mod big_int_cmp; mod big_int_operators; mod big_int_sign; -mod big_num_cmp; +pub(crate) mod big_num_cmp; pub(crate) mod cast_to_i64; mod elliptic_curve; mod managed_buffer; diff --git a/framework/base/src/types/managed/multi_value/mod.rs b/framework/base/src/types/managed/multi_value.rs similarity index 87% rename from framework/base/src/types/managed/multi_value/mod.rs rename to framework/base/src/types/managed/multi_value.rs index bfd2981c16..29920ed566 100644 --- a/framework/base/src/types/managed/multi_value/mod.rs +++ b/framework/base/src/types/managed/multi_value.rs @@ -1,4 +1,5 @@ mod async_call_result_managed; +mod egld_or_esdt_token_payment_multi_value; mod esdt_token_payment_multi_value; mod multi_value_encoded; mod multi_value_encoded_counted; @@ -7,6 +8,7 @@ mod multi_value_managed_vec; mod multi_value_managed_vec_counted; pub use async_call_result_managed::{ManagedAsyncCallError, ManagedAsyncCallResult}; +pub use egld_or_esdt_token_payment_multi_value::EgldOrEsdtTokenPaymentMultiValue; pub use esdt_token_payment_multi_value::{EsdtTokenPaymentMultiArg, EsdtTokenPaymentMultiValue}; pub use multi_value_encoded::{ManagedMultiResultVec, ManagedVarArgs, MultiValueEncoded}; pub use multi_value_encoded_counted::MultiValueEncodedCounted; diff --git a/framework/base/src/types/managed/multi_value/egld_or_esdt_token_payment_multi_value.rs b/framework/base/src/types/managed/multi_value/egld_or_esdt_token_payment_multi_value.rs new file mode 100644 index 0000000000..e9f4079b96 --- /dev/null +++ b/framework/base/src/types/managed/multi_value/egld_or_esdt_token_payment_multi_value.rs @@ -0,0 +1,113 @@ +use crate::{ + abi::TypeAbiFrom, + codec::{ + multi_types::MultiValue3, DecodeErrorHandler, EncodeErrorHandler, TopDecodeMulti, + TopDecodeMultiInput, TopDecodeMultiLength, TopEncodeMulti, TopEncodeMultiOutput, + }, + types::{EgldOrEsdtTokenIdentifier, ManagedVecRef}, +}; + +use crate::{ + abi::{TypeAbi, TypeName}, + api::ManagedTypeApi, + types::{BigUint, EgldOrEsdtTokenPayment, ManagedVecItem, TokenIdentifier}, +}; + +/// Thin wrapper around EgldOrEsdtTokenPayment, which has different I/O behaviour: +/// - as input, is built from 3 arguments instead of 1: token identifier, nonce, value +/// - as output, it becomes 3 results instead of 1: token identifier, nonce, value +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct EgldOrEsdtTokenPaymentMultiValue { + obj: EgldOrEsdtTokenPayment, +} + +impl From> for EgldOrEsdtTokenPaymentMultiValue { + #[inline] + fn from(obj: EgldOrEsdtTokenPayment) -> Self { + EgldOrEsdtTokenPaymentMultiValue { obj } + } +} + +impl EgldOrEsdtTokenPaymentMultiValue { + pub fn into_inner(self) -> EgldOrEsdtTokenPayment { + self.obj + } +} + +impl ManagedVecItem for EgldOrEsdtTokenPaymentMultiValue { + type PAYLOAD = as ManagedVecItem>::PAYLOAD; + const SKIPS_RESERIALIZATION: bool = EgldOrEsdtTokenPayment::::SKIPS_RESERIALIZATION; + type Ref<'a> = ManagedVecRef<'a, Self>; + + fn read_from_payload(payload: &Self::PAYLOAD) -> Self { + EgldOrEsdtTokenPayment::read_from_payload(payload).into() + } + + unsafe fn borrow_from_payload<'a>(payload: &Self::PAYLOAD) -> Self::Ref<'a> { + ManagedVecRef::new(Self::read_from_payload(payload)) + } + + fn save_to_payload(self, payload: &mut Self::PAYLOAD) { + self.obj.save_to_payload(payload); + } +} + +impl TopEncodeMulti for EgldOrEsdtTokenPaymentMultiValue +where + M: ManagedTypeApi, +{ + fn multi_encode_or_handle_err(&self, output: &mut O, h: H) -> Result<(), H::HandledErr> + where + O: TopEncodeMultiOutput, + H: EncodeErrorHandler, + { + output.push_single_value(&self.obj.token_identifier, h)?; + output.push_single_value(&self.obj.token_nonce, h)?; + output.push_single_value(&self.obj.amount, h)?; + Ok(()) + } +} + +impl TopDecodeMulti for EgldOrEsdtTokenPaymentMultiValue +where + M: ManagedTypeApi, +{ + fn multi_decode_or_handle_err(input: &mut I, h: H) -> Result + where + I: TopDecodeMultiInput, + H: DecodeErrorHandler, + { + let token_identifier = EgldOrEsdtTokenIdentifier::multi_decode_or_handle_err(input, h)?; + let token_nonce = u64::multi_decode_or_handle_err(input, h)?; + let amount = BigUint::multi_decode_or_handle_err(input, h)?; + Ok(EgldOrEsdtTokenPayment::new(token_identifier, token_nonce, amount).into()) + } +} + +impl TopDecodeMultiLength for EgldOrEsdtTokenPaymentMultiValue +where + M: ManagedTypeApi, +{ + const LEN: usize = 3; +} + +impl TypeAbiFrom for EgldOrEsdtTokenPaymentMultiValue where M: ManagedTypeApi {} + +impl TypeAbi for EgldOrEsdtTokenPaymentMultiValue +where + M: ManagedTypeApi, +{ + type Unmanaged = Self; + + fn type_name() -> TypeName { + MultiValue3::, u64, BigUint>::type_name() + } + + fn type_name_rust() -> TypeName { + "EgldOrEsdtTokenPaymentMultiValue<$API>".into() + } + + fn is_variadic() -> bool { + true + } +} diff --git a/framework/base/src/types/managed/multi_value/esdt_token_payment_multi_value.rs b/framework/base/src/types/managed/multi_value/esdt_token_payment_multi_value.rs index e97cc2707c..34fb1ecacf 100644 --- a/framework/base/src/types/managed/multi_value/esdt_token_payment_multi_value.rs +++ b/framework/base/src/types/managed/multi_value/esdt_token_payment_multi_value.rs @@ -35,7 +35,7 @@ impl From> for EsdtTokenPaymentMultiValue } impl EsdtTokenPaymentMultiValue { - pub fn into_esdt_token_payment(self) -> EsdtTokenPayment { + pub fn into_inner(self) -> EsdtTokenPayment { self.obj } } diff --git a/framework/base/src/types/managed/wrapped.rs b/framework/base/src/types/managed/wrapped.rs index 52d827c79f..f32d17b47a 100644 --- a/framework/base/src/types/managed/wrapped.rs +++ b/framework/base/src/types/managed/wrapped.rs @@ -32,7 +32,9 @@ mod traits; pub use big_uint::BigUint; pub use builder::*; pub use egld_or_esdt_token_identifier::EgldOrEsdtTokenIdentifier; -pub use egld_or_esdt_token_payment::{EgldOrEsdtTokenPayment, EgldOrEsdtTokenPaymentRefs}; +pub use egld_or_esdt_token_payment::{ + EgldOrEsdtTokenPayment, EgldOrEsdtTokenPaymentRefs, MultiEgldOrEsdtPayment, +}; pub use egld_or_multi_esdt_payment::{EgldOrMultiEsdtPayment, EgldOrMultiEsdtPaymentRefs}; pub(crate) use encoded_managed_vec_item::EncodedManagedVecItem; pub use esdt_token_data::EsdtTokenData; diff --git a/framework/base/src/types/managed/wrapped/egld_or_esdt_token_identifier.rs b/framework/base/src/types/managed/wrapped/egld_or_esdt_token_identifier.rs index 7b46cb2e50..a2f14f6063 100644 --- a/framework/base/src/types/managed/wrapped/egld_or_esdt_token_identifier.rs +++ b/framework/base/src/types/managed/wrapped/egld_or_esdt_token_identifier.rs @@ -1,17 +1,18 @@ use alloc::string::ToString; +use multiversx_chain_core::EGLD_000000_TOKEN_IDENTIFIER; use crate::{ abi::{TypeAbi, TypeAbiFrom, TypeName}, - api::{HandleConstraints, ManagedTypeApi}, + api::{ + const_handles, use_raw_handle, ErrorApiImpl, HandleConstraints, ManagedBufferApiImpl, + ManagedTypeApi, + }, codec::*, - derive::ManagedVecItem, formatter::{FormatByteReceiver, SCDisplay, SCLowerHex}, proxy_imports::TestTokenIdentifier, - types::{ManagedBuffer, ManagedOption, ManagedRef, ManagedType, TokenIdentifier}, + types::{ManagedBuffer, ManagedRef, ManagedType, TokenIdentifier}, }; -use crate as multiversx_sc; // required by the ManagedVecItem derive - /// Specialized type for handling either EGLD or ESDT token identifiers. /// /// Equivalent to a structure of the form @@ -28,21 +29,47 @@ use crate as multiversx_sc; // required by the ManagedVecItem derive /// EGLD is indicated by a special, invalid token identifier handle. /// This way we can fit it inside a single i32 in memory. #[repr(transparent)] -#[derive(ManagedVecItem, Clone)] +#[derive(Clone)] pub struct EgldOrEsdtTokenIdentifier { - pub(crate) data: ManagedOption>, + pub(crate) buffer: ManagedBuffer, +} + +impl ManagedType for EgldOrEsdtTokenIdentifier { + type OwnHandle = M::ManagedBufferHandle; + + #[inline] + unsafe fn from_handle(handle: M::ManagedBufferHandle) -> Self { + EgldOrEsdtTokenIdentifier { + buffer: ManagedBuffer::from_handle(handle), + } + } + + fn get_handle(&self) -> M::ManagedBufferHandle { + self.buffer.get_handle() + } + + unsafe fn forget_into_handle(self) -> Self::OwnHandle { + self.buffer.forget_into_handle() + } + + fn transmute_from_handle_ref(handle_ref: &M::ManagedBufferHandle) -> &Self { + unsafe { core::mem::transmute(handle_ref) } + } + + fn transmute_from_handle_ref_mut(handle_ref: &mut M::ManagedBufferHandle) -> &mut Self { + unsafe { core::mem::transmute(handle_ref) } + } } impl EgldOrEsdtTokenIdentifier { /// This special representation is interpreted as the EGLD token. - #[allow(clippy::needless_borrow)] // clippy is wrog here, there is no other way - pub const EGLD_REPRESENTATION: &'static [u8; 4] = &b"EGLD"; + pub const EGLD_REPRESENTATION: &'static [u8; 4] = b"EGLD"; /// New instance of the special EGLD token representation. #[inline] pub fn egld() -> Self { - Self { - data: ManagedOption::none(), + EgldOrEsdtTokenIdentifier { + buffer: ManagedBuffer::from(EGLD_000000_TOKEN_IDENTIFIER), } } @@ -52,30 +79,36 @@ impl EgldOrEsdtTokenIdentifier { where TokenIdentifier: From, { - Self { - data: ManagedOption::some(TokenIdentifier::from(token_identifier)), - } + let ti_obj = TokenIdentifier::from(token_identifier); + ti_obj.data } pub fn parse(data: ManagedBuffer) -> Self { if data == Self::EGLD_REPRESENTATION { Self::egld() } else { - Self::esdt(TokenIdentifier::from(data)) + Self { buffer: data } } } #[inline] pub fn is_egld(&self) -> bool { - self.data.is_none() + M::managed_type_impl().mb_overwrite( + use_raw_handle(const_handles::MBUF_EGLD_000000), + EGLD_000000_TOKEN_IDENTIFIER.as_bytes(), + ); + M::managed_type_impl().mb_eq( + use_raw_handle(const_handles::MBUF_EGLD_000000), + self.buffer.handle.clone(), + ) } #[inline] pub fn is_esdt(&self) -> bool { - self.data.is_some() + !self.is_egld() } - #[inline] + /// Returns "EGLD" or the token identifier. pub fn into_name(self) -> ManagedBuffer { self.map_or_else( (), @@ -95,12 +128,31 @@ impl EgldOrEsdtTokenIdentifier { ) } + #[inline] + pub fn into_managed_buffer(self) -> ManagedBuffer { + self.buffer + } + + #[inline] + pub fn as_managed_buffer(&self) -> &ManagedBuffer { + &self.buffer + } + + #[inline] + pub fn to_boxed_bytes(&self) -> crate::types::heap::BoxedBytes { + self.buffer.to_boxed_bytes() + } + pub fn map_or_else(self, context: Context, for_egld: D, for_esdt: F) -> R where D: FnOnce(Context) -> R, F: FnOnce(Context, TokenIdentifier) -> R, { - self.data.map_or_else(context, for_egld, for_esdt) + if self.is_egld() { + for_egld(context) + } else { + unsafe { for_esdt(context, TokenIdentifier::esdt_unchecked(self)) } + } } pub fn map_ref_or_else(&self, context: Context, for_egld: D, for_esdt: F) -> R @@ -108,30 +160,65 @@ impl EgldOrEsdtTokenIdentifier { D: FnOnce(Context) -> R, F: FnOnce(Context, &TokenIdentifier) -> R, { - self.data.map_ref_or_else(context, for_egld, for_esdt) + if self.is_egld() { + for_egld(context) + } else { + unsafe { + let token_identifier = + ManagedRef::<'_, M, TokenIdentifier>::wrap_handle(self.get_handle()); + for_esdt(context, &token_identifier) + } + } } pub fn unwrap_esdt(self) -> TokenIdentifier { - self.data.unwrap_or_sc_panic("ESDT expected") + self.map_or_else( + (), + |()| M::error_api_impl().signal_error(b"ESDT expected"), + |(), token_identifier| token_identifier, + ) } /// Representation of the object as an `Option`. /// /// Because it does not consume `self` only a reference to the ESDT token identifier can be returned. pub fn as_esdt_option(&self) -> Option>> { - self.data.as_option() + if self.is_egld() { + None + } else { + unsafe { + Some(ManagedRef::<'_, M, TokenIdentifier>::wrap_handle( + self.get_handle(), + )) + } + } } /// Converts `self` into an `Option`. Consumes `self` in the process. pub fn into_esdt_option(self) -> Option> { - self.data.into_option() + self.map_or_else((), |()| None, |(), token_identifier| Some(token_identifier)) + } +} + +impl From> for EgldOrEsdtTokenIdentifier { + #[inline] + fn from(buffer: ManagedBuffer) -> Self { + EgldOrEsdtTokenIdentifier { buffer } + } +} + +impl From<&[u8]> for EgldOrEsdtTokenIdentifier { + fn from(bytes: &[u8]) -> Self { + EgldOrEsdtTokenIdentifier { + buffer: ManagedBuffer::new_from_bytes(bytes), + } } } impl PartialEq for EgldOrEsdtTokenIdentifier { #[inline] fn eq(&self, other: &Self) -> bool { - self.data == other.data + self.buffer == other.buffer } } @@ -155,10 +242,10 @@ impl NestedEncode for EgldOrEsdtTokenIdentifier { O: NestedEncodeOutput, H: EncodeErrorHandler, { - if let Some(token_identifier) = self.data.as_option() { - token_identifier.dep_encode_or_handle_err(dest, h) - } else { + if self.is_egld() { (&Self::EGLD_REPRESENTATION[..]).dep_encode_or_handle_err(dest, h) + } else { + self.buffer.dep_encode_or_handle_err(dest, h) } } } @@ -170,10 +257,10 @@ impl TopEncode for EgldOrEsdtTokenIdentifier { O: TopEncodeOutput, H: EncodeErrorHandler, { - if let Some(token_identifier) = self.data.as_option() { - token_identifier.top_encode_or_handle_err(output, h) - } else { + if self.is_egld() { (&Self::EGLD_REPRESENTATION[..]).top_encode_or_handle_err(output, h) + } else { + self.buffer.top_encode_or_handle_err(output, h) } } } @@ -231,12 +318,12 @@ impl TypeAbi for EgldOrEsdtTokenIdentifier { impl SCDisplay for EgldOrEsdtTokenIdentifier { fn fmt(&self, f: &mut F) { - if let Some(token_identifier) = self.data.as_option() { - let cast_handle = token_identifier.get_handle().cast_or_signal_error::(); + if self.is_egld() { + f.append_bytes(Self::EGLD_REPRESENTATION); + } else { + let cast_handle = self.buffer.get_handle().cast_or_signal_error::(); let wrap_cast = unsafe { ManagedRef::wrap_handle(cast_handle) }; f.append_managed_buffer(&wrap_cast); - } else { - f.append_bytes(Self::EGLD_REPRESENTATION); } } } @@ -245,12 +332,12 @@ const EGLD_REPRESENTATION_HEX: &[u8] = b"45474C44"; impl SCLowerHex for EgldOrEsdtTokenIdentifier { fn fmt(&self, f: &mut F) { - if let Some(token_identifier) = self.data.as_option() { - let cast_handle = token_identifier.get_handle().cast_or_signal_error::(); + if self.is_egld() { + f.append_bytes(EGLD_REPRESENTATION_HEX); + } else { + let cast_handle = self.buffer.get_handle().cast_or_signal_error::(); let wrap_cast = unsafe { ManagedRef::wrap_handle(cast_handle) }; f.append_managed_buffer_lower_hex(&wrap_cast); - } else { - f.append_bytes(EGLD_REPRESENTATION_HEX); } } } diff --git a/framework/base/src/types/managed/wrapped/egld_or_esdt_token_payment.rs b/framework/base/src/types/managed/wrapped/egld_or_esdt_token_payment.rs index e87d520981..6ef608c0a9 100644 --- a/framework/base/src/types/managed/wrapped/egld_or_esdt_token_payment.rs +++ b/framework/base/src/types/managed/wrapped/egld_or_esdt_token_payment.rs @@ -1,7 +1,9 @@ +use multiversx_sc_codec::IntoMultiValue; + use crate::{ abi::TypeAbiFrom, api::ManagedTypeApi, - types::{BigUint, EgldOrEsdtTokenIdentifier}, + types::{BigUint, EgldOrEsdtTokenIdentifier, EgldOrEsdtTokenPaymentMultiValue}, }; use crate::codec::{ @@ -12,7 +14,11 @@ use crate::codec::{ use crate as multiversx_sc; // needed by the TypeAbi generated code use crate::derive::type_abi; -use super::{EsdtTokenPayment, EsdtTokenPaymentRefs}; +use super::{ + managed_vec_item_read_from_payload_index, managed_vec_item_save_to_payload_index, + EsdtTokenPayment, EsdtTokenPaymentRefs, ManagedVec, ManagedVecItem, + ManagedVecItemPayloadBuffer, ManagedVecRef, +}; #[type_abi] #[derive(TopDecode, TopEncode, NestedDecode, NestedEncode, Clone, PartialEq, Eq, Debug)] @@ -22,13 +28,12 @@ pub struct EgldOrEsdtTokenPayment { pub amount: BigUint, } +/// Alias for a list of payments of EGLD or ESDT tokens. +pub type MultiEgldOrEsdtPayment = ManagedVec>; + impl EgldOrEsdtTokenPayment { pub fn no_payment() -> Self { - EgldOrEsdtTokenPayment { - token_identifier: EgldOrEsdtTokenIdentifier::egld(), - token_nonce: 0, - amount: BigUint::zero(), - } + Self::egld_payment(BigUint::zero()) } pub fn new( @@ -43,6 +48,11 @@ impl EgldOrEsdtTokenPayment { } } + /// A payment of token EGLD-000000. + pub fn egld_payment(amount: BigUint) -> Self { + Self::new(EgldOrEsdtTokenIdentifier::egld(), 0, amount) + } + /// Will convert to just ESDT or terminate execution if the token is EGLD. pub fn unwrap_esdt(self) -> EsdtTokenPayment { EsdtTokenPayment::new( @@ -125,6 +135,15 @@ impl From> for EgldOrEsdtTokenPayment } } +impl IntoMultiValue for EgldOrEsdtTokenPayment { + type MultiValue = EgldOrEsdtTokenPaymentMultiValue; + + #[inline] + fn into_multi_value(self) -> Self::MultiValue { + self.into() + } +} + impl TypeAbiFrom<&[u8]> for EgldOrEsdtTokenPayment where M: ManagedTypeApi {} impl EgldOrEsdtTokenPayment { @@ -182,3 +201,34 @@ impl<'a, M: ManagedTypeApi> EgldOrEsdtTokenPaymentRefs<'a, M> { ) } } + +impl ManagedVecItem for EgldOrEsdtTokenPayment { + type PAYLOAD = ManagedVecItemPayloadBuffer<16>; + const SKIPS_RESERIALIZATION: bool = false; + type Ref<'a> = ManagedVecRef<'a, Self>; + + fn read_from_payload(payload: &Self::PAYLOAD) -> Self { + let mut index = 0; + unsafe { + EgldOrEsdtTokenPayment { + token_identifier: managed_vec_item_read_from_payload_index(payload, &mut index), + token_nonce: managed_vec_item_read_from_payload_index(payload, &mut index), + amount: managed_vec_item_read_from_payload_index(payload, &mut index), + } + } + } + + unsafe fn borrow_from_payload<'a>(payload: &Self::PAYLOAD) -> Self::Ref<'a> { + ManagedVecRef::new(Self::read_from_payload(payload)) + } + + fn save_to_payload(self, payload: &mut Self::PAYLOAD) { + let mut index = 0; + + unsafe { + managed_vec_item_save_to_payload_index(self.token_identifier, payload, &mut index); + managed_vec_item_save_to_payload_index(self.token_nonce, payload, &mut index); + managed_vec_item_save_to_payload_index(self.amount, payload, &mut index); + } + } +} diff --git a/framework/base/src/types/managed/wrapped/esdt_token_payment.rs b/framework/base/src/types/managed/wrapped/esdt_token_payment.rs index 041ddd1f64..999b7b1cde 100644 --- a/framework/base/src/types/managed/wrapped/esdt_token_payment.rs +++ b/framework/base/src/types/managed/wrapped/esdt_token_payment.rs @@ -1,6 +1,9 @@ use crate::{ api::ManagedTypeApi, - types::{BigUint, EsdtTokenPaymentMultiValue, EsdtTokenType, ManagedVecItem, TokenIdentifier}, + types::{ + BigUint, EsdtTokenPaymentMultiValue, EsdtTokenType, ManagedType, ManagedVecItem, + TokenIdentifier, + }, }; use crate as multiversx_sc; // needed by the codec and TypeAbi generated code @@ -14,8 +17,9 @@ use crate::{ }; use super::{ - managed_vec_item_read_from_payload_index, managed_vec_item_save_to_payload_index, ManagedVec, - ManagedVecItemPayloadBuffer, ManagedVecRef, + managed_vec_item_read_from_payload_index, managed_vec_item_save_to_payload_index, + EgldOrEsdtTokenIdentifier, EgldOrEsdtTokenPayment, ManagedVec, ManagedVecItemPayloadBuffer, + ManagedVecRef, MultiEgldOrEsdtPayment, }; #[type_abi] @@ -57,6 +61,22 @@ impl EsdtTokenPayment { pub fn into_tuple(self) -> (TokenIdentifier, u64, BigUint) { (self.token_identifier, self.token_nonce, self.amount) } + + /// Zero-cost conversion that loosens the EGLD restriction. + /// + /// It is always safe to do, since the 2 types are guaranteed to have the same layout. + pub fn as_egld_or_esdt_payment(&self) -> &EgldOrEsdtTokenPayment { + unsafe { core::mem::transmute(self) } + } + + /// Conversion that loosens the EGLD restriction. + pub fn into_multi_egld_or_esdt_payment(self) -> EgldOrEsdtTokenPayment { + EgldOrEsdtTokenPayment { + token_identifier: EgldOrEsdtTokenIdentifier::esdt(self.token_identifier), + token_nonce: self.token_nonce, + amount: self.amount, + } + } } impl From<(TokenIdentifier, u64, BigUint)> for EsdtTokenPayment { @@ -233,6 +253,22 @@ impl<'a, M: ManagedTypeApi> EsdtTokenPaymentRefs<'a, M> { } } +impl MultiEsdtPayment { + /// Zero-cost conversion that loosens the EGLD restriction. + /// + /// It is always safe to do, since the 2 types are guaranteed to have the same layout. + pub fn as_multi_egld_or_esdt_payment(&self) -> &MultiEgldOrEsdtPayment { + unsafe { core::mem::transmute(self) } + } + + /// Zero-cost conversion that loosens the EGLD restriction. + /// + /// It is always safe to do, since the 2 types are guaranteed to have the same layout. + pub fn into_multi_egld_or_esdt_payment(self) -> MultiEgldOrEsdtPayment { + unsafe { MultiEgldOrEsdtPayment::from_handle(self.forget_into_handle()) } + } +} + impl From<()> for MultiEsdtPayment { #[inline] fn from(_value: ()) -> Self { diff --git a/framework/base/src/types/managed/wrapped/managed_vec_item.rs b/framework/base/src/types/managed/wrapped/managed_vec_item.rs index 62e62ad4dd..1148498634 100644 --- a/framework/base/src/types/managed/wrapped/managed_vec_item.rs +++ b/framework/base/src/types/managed/wrapped/managed_vec_item.rs @@ -12,8 +12,8 @@ use crate::{ }; use super::{ - ManagedVecItemNestedTuple, ManagedVecItemPayload, ManagedVecItemPayloadAdd, - ManagedVecItemPayloadBuffer, ManagedVecRef, + EgldOrEsdtTokenIdentifier, ManagedVecItemNestedTuple, ManagedVecItemPayload, + ManagedVecItemPayloadAdd, ManagedVecItemPayloadBuffer, ManagedVecRef, }; /// Types that implement this trait can be items inside a `ManagedVec`. @@ -230,6 +230,7 @@ impl_managed_type! {BigInt} impl_managed_type! {EllipticCurve} impl_managed_type! {ManagedAddress} impl_managed_type! {TokenIdentifier} +impl_managed_type! {EgldOrEsdtTokenIdentifier} impl ManagedVecItem for ManagedByteArray where diff --git a/framework/base/src/types/managed/wrapped/managed_vec_iter_payload.rs b/framework/base/src/types/managed/wrapped/managed_vec_iter_payload.rs index 28fd6e3943..aeebd36635 100644 --- a/framework/base/src/types/managed/wrapped/managed_vec_iter_payload.rs +++ b/framework/base/src/types/managed/wrapped/managed_vec_iter_payload.rs @@ -9,7 +9,7 @@ where M: ManagedTypeApi, P: ManagedVecItemPayload, { - pub(super) vec_handle: M::ManagedBufferHandle, + vec_handle: M::ManagedBufferHandle, byte_start: usize, byte_end: usize, _phantom: PhantomData

, @@ -41,7 +41,7 @@ where } } - pub(crate) fn remaining(&self) -> usize { + pub(crate) fn remaining_count(&self) -> usize { (self.byte_end - self.byte_start) / P::payload_size() } @@ -76,7 +76,7 @@ where } fn size_hint(&self) -> (usize, Option) { - let remaining = self.remaining(); + let remaining = self.remaining_count(); (remaining, Some(remaining)) } } @@ -87,7 +87,7 @@ where P: ManagedVecItemPayload, { fn len(&self) -> usize { - self.remaining() + self.remaining_count() } } diff --git a/framework/base/src/types/managed/wrapped/token_identifier.rs b/framework/base/src/types/managed/wrapped/token_identifier.rs index 39fd17f5e1..6228fba515 100644 --- a/framework/base/src/types/managed/wrapped/token_identifier.rs +++ b/framework/base/src/types/managed/wrapped/token_identifier.rs @@ -19,7 +19,7 @@ use super::{EgldOrEsdtTokenIdentifier, ManagedRef}; #[repr(transparent)] #[derive(Clone)] pub struct TokenIdentifier { - buffer: ManagedBuffer, + pub(crate) data: EgldOrEsdtTokenIdentifier, } impl ManagedType for TokenIdentifier { @@ -28,16 +28,16 @@ impl ManagedType for TokenIdentifier { #[inline] unsafe fn from_handle(handle: M::ManagedBufferHandle) -> Self { TokenIdentifier { - buffer: ManagedBuffer::from_handle(handle), + data: EgldOrEsdtTokenIdentifier::from_handle(handle), } } fn get_handle(&self) -> M::ManagedBufferHandle { - self.buffer.get_handle() + self.data.get_handle() } unsafe fn forget_into_handle(self) -> Self::OwnHandle { - self.buffer.forget_into_handle() + self.data.forget_into_handle() } fn transmute_from_handle_ref(handle_ref: &M::ManagedBufferHandle) -> &Self { @@ -50,36 +50,44 @@ impl ManagedType for TokenIdentifier { } impl TokenIdentifier { + /// Creates a new TokenIdentifier without verifying that it is not EGLD-000000. + /// + /// ## Safety + /// + /// Calling it for the EGLD token might lead to unexpected bugs. + pub unsafe fn esdt_unchecked(data: EgldOrEsdtTokenIdentifier) -> Self { + Self { data } + } + #[inline] pub fn from_esdt_bytes>>(bytes: B) -> Self { - TokenIdentifier { - buffer: bytes.into(), - } + TokenIdentifier::from(bytes.into()) } #[inline] pub fn into_managed_buffer(self) -> ManagedBuffer { - self.buffer + self.data.into_managed_buffer() } #[inline] pub fn as_managed_buffer(&self) -> &ManagedBuffer { - &self.buffer + self.data.as_managed_buffer() } #[inline] pub fn to_boxed_bytes(&self) -> crate::types::heap::BoxedBytes { - self.buffer.to_boxed_bytes() + self.data.to_boxed_bytes() } pub fn is_valid_esdt_identifier(&self) -> bool { - M::managed_type_impl().validate_token_identifier(self.buffer.handle.clone()) + M::managed_type_impl().validate_token_identifier(self.data.buffer.handle.clone()) } pub fn ticker(&self) -> ManagedBuffer { - let token_id_len = self.buffer.len(); + let buffer = self.as_managed_buffer(); + let token_id_len = buffer.len(); let ticker_len = M::managed_type_impl().get_token_ticker_len(token_id_len); - self.buffer.copy_slice(0, ticker_len).unwrap_or_else(|| { + buffer.copy_slice(0, ticker_len).unwrap_or_else(|| { M::error_api_impl().signal_error(err_msg::BAD_TOKEN_TICKER_FORMAT.as_bytes()) }) } @@ -88,15 +96,13 @@ impl TokenIdentifier { impl From> for TokenIdentifier { #[inline] fn from(buffer: ManagedBuffer) -> Self { - TokenIdentifier { buffer } + EgldOrEsdtTokenIdentifier::from(buffer).unwrap_esdt() } } impl From<&[u8]> for TokenIdentifier { fn from(bytes: &[u8]) -> Self { - TokenIdentifier { - buffer: ManagedBuffer::new_from_bytes(bytes), - } + EgldOrEsdtTokenIdentifier::from(bytes).unwrap_esdt() } } @@ -115,7 +121,7 @@ impl From<&crate::types::heap::String> for TokenIdentifier impl PartialEq for TokenIdentifier { #[inline] fn eq(&self, other: &Self) -> bool { - self.buffer == other.buffer + self.data == other.data } } @@ -139,7 +145,7 @@ impl NestedEncode for TokenIdentifier { O: NestedEncodeOutput, H: EncodeErrorHandler, { - self.buffer.dep_encode_or_handle_err(dest, h) + self.data.dep_encode_or_handle_err(dest, h) } } @@ -150,7 +156,7 @@ impl TopEncode for TokenIdentifier { O: TopEncodeOutput, H: EncodeErrorHandler, { - self.buffer.top_encode_or_handle_err(output, h) + self.data.top_encode_or_handle_err(output, h) } } @@ -160,9 +166,11 @@ impl NestedDecode for TokenIdentifier { I: NestedDecodeInput, H: DecodeErrorHandler, { - Ok(TokenIdentifier::from( - ManagedBuffer::dep_decode_or_handle_err(input, h)?, - )) + unsafe { + Ok(TokenIdentifier::esdt_unchecked( + EgldOrEsdtTokenIdentifier::dep_decode_or_handle_err(input, h)?, + )) + } } } @@ -172,9 +180,11 @@ impl TopDecode for TokenIdentifier { I: TopDecodeInput, H: DecodeErrorHandler, { - Ok(TokenIdentifier::from( - ManagedBuffer::top_decode_or_handle_err(input, h)?, - )) + unsafe { + Ok(TokenIdentifier::esdt_unchecked( + EgldOrEsdtTokenIdentifier::top_decode_or_handle_err(input, h)?, + )) + } } } @@ -198,7 +208,7 @@ impl TypeAbi for TokenIdentifier { impl SCDisplay for TokenIdentifier { fn fmt(&self, f: &mut F) { - let cast_handle = self.buffer.get_handle().cast_or_signal_error::(); + let cast_handle = self.get_handle().cast_or_signal_error::(); let wrap_cast = unsafe { ManagedRef::wrap_handle(cast_handle) }; f.append_managed_buffer(&wrap_cast); } @@ -206,7 +216,7 @@ impl SCDisplay for TokenIdentifier { impl SCLowerHex for TokenIdentifier { fn fmt(&self, f: &mut F) { - let cast_handle = self.buffer.get_handle().cast_or_signal_error::(); + let cast_handle = self.get_handle().cast_or_signal_error::(); let wrap_cast = unsafe { ManagedRef::wrap_handle(cast_handle) }; f.append_managed_buffer_lower_hex(&wrap_cast); } @@ -214,7 +224,7 @@ impl SCLowerHex for TokenIdentifier { impl core::fmt::Display for TokenIdentifier { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let bytes = self.buffer.to_boxed_bytes(); + let bytes = self.to_boxed_bytes(); let s = alloc::string::String::from_utf8_lossy(bytes.as_slice()); s.fmt(f) } diff --git a/framework/derive/src/preprocessing/substitution_list.rs b/framework/derive/src/preprocessing/substitution_list.rs index c03bd3b29e..6f04b19d5f 100644 --- a/framework/derive/src/preprocessing/substitution_list.rs +++ b/framework/derive/src/preprocessing/substitution_list.rs @@ -56,6 +56,7 @@ fn add_managed_types(substitutions: &mut SubstitutionsMap) { add_managed_type_with_generics(substitutions, "e!(ManagedAsyncCallResult)); add_managed_type(substitutions, "e!(EsdtTokenPaymentMultiArg)); add_managed_type(substitutions, "e!(EsdtTokenPaymentMultiValue)); + add_managed_type(substitutions, "e!(EgldOrEsdtTokenPaymentMultiValue)); add_managed_type_with_generics(substitutions, "e!(MultiValueEncodedIterator)); add_managed_type_with_generics(substitutions, "e!(MultiValueEncoded)); add_managed_type_with_generics(substitutions, "e!(ManagedVarArgs)); diff --git a/framework/scenario/src/scenario/model/transaction/tx_call.rs b/framework/scenario/src/scenario/model/transaction/tx_call.rs index 547466032e..550f713683 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_call.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_call.rs @@ -1,3 +1,8 @@ +use multiversx_chain_vm::types::{top_encode_big_uint, top_encode_u64}; +use multiversx_sc::api::{ + ESDT_MULTI_TRANSFER_FUNC_NAME, ESDT_NFT_TRANSFER_FUNC_NAME, ESDT_TRANSFER_FUNC_NAME, +}; + use crate::{ api::StaticApi, multiversx_sc::types::{ContractCall, EsdtTokenPayment}, @@ -127,4 +132,116 @@ impl TxCall { } contract_call } + + /// Converts call to builtin function ESDT transfer call, if necessary. + pub fn normalize(&self) -> TxCall { + let (function, arguments, to_self) = self.process_payments(); + TxCall { + from: self.from.clone(), + to: if to_self { + self.from.clone() + } else { + self.to.clone() + }, + egld_value: self.egld_value.clone(), + esdt_value: Vec::new(), + function, + arguments, + gas_limit: self.gas_limit.clone(), + gas_price: self.gas_price.clone(), + } + } + + fn process_payments(&self) -> (String, Vec, bool) { + assert!( + self.egld_value.is_zero() || self.esdt_value.is_empty(), + "Cannot have both EGLD and ESDT fields filled. To transfer EGLD and ESDT in the same transaction, represent EGLD as EGLD-000000 in the ESDTs."); + + match self.esdt_value.len() { + 0 => (self.function.clone(), self.arguments.clone(), false), + 1 => { + let payment = self.esdt_value.first().unwrap(); + if payment.is_egld() { + self.construct_multi_transfer_esdt_call() + } else if payment.nonce.value == 0 { + self.construct_single_transfer_fungible_call(payment) + } else { + self.construct_single_transfer_nft_call(payment) + } + }, + _ => self.construct_multi_transfer_esdt_call(), + } + } + + fn append_function_call_to_arguments(&self, arguments: &mut Vec) { + if !self.function.is_empty() { + arguments.push(BytesValue::from_str_expr(&self.function)); + } + for regular_arg in &self.arguments { + arguments.push(regular_arg.clone()) + } + } + + /// Constructs `ESDTTransfer` builtin function call. + pub(crate) fn construct_single_transfer_fungible_call( + &self, + payment: &TxESDT, + ) -> (String, Vec, bool) { + let mut arguments = Vec::new(); + arguments.push(payment.esdt_token_identifier.value.as_slice().into()); + arguments.push(top_encode_big_uint(&payment.esdt_value.value).into()); + self.append_function_call_to_arguments(&mut arguments); + + (ESDT_TRANSFER_FUNC_NAME.to_owned(), arguments, false) + } + + /// Constructs `ESDTNFTTransfer` builtin function call. + /// + /// `ESDTNFTTransfer` takes 4 arguments: + /// arg0 - token identifier + /// arg1 - nonce + /// arg2 - quantity to transfer + /// arg3 - destination address + pub(crate) fn construct_single_transfer_nft_call( + &self, + payment: &TxESDT, + ) -> (String, Vec, bool) { + let mut arguments = vec![ + payment.esdt_token_identifier.value.as_slice().into(), + top_encode_u64(payment.nonce.value).into(), + top_encode_big_uint(&payment.esdt_value.value).into(), + self.to.value.as_bytes().into(), // TODO: preserve representation + ]; + + self.append_function_call_to_arguments(&mut arguments); + + (ESDT_NFT_TRANSFER_FUNC_NAME.to_owned(), arguments, true) + } + + /// Constructs `MultiESDTNFTTransfer` builtin function call. + pub(crate) fn construct_multi_transfer_esdt_call(&self) -> (String, Vec, bool) { + let mut arguments = Vec::new(); + arguments.push(self.to.value.as_bytes().into()); // TODO: preserve representation + arguments.push(top_encode_u64(self.esdt_value.len() as u64).into()); + + for payment in &self.esdt_value { + arguments.push(payment.esdt_token_identifier.value.as_slice().into()); + arguments.push(top_encode_u64(payment.nonce.value).into()); + arguments.push(top_encode_big_uint(&payment.esdt_value.value).into()); + } + + self.append_function_call_to_arguments(&mut arguments); + + (ESDT_MULTI_TRANSFER_FUNC_NAME.to_owned(), arguments, true) + } + + /// Creates the data field of the transaction represented by object. + pub fn compute_data_field(&self) -> String { + let mut result = self.function.clone(); + for argument in &self.arguments { + result.push('@'); + result.push_str(hex::encode(argument.value.as_slice()).as_str()); + } + result + } } diff --git a/framework/scenario/src/scenario/model/transaction/tx_esdt.rs b/framework/scenario/src/scenario/model/transaction/tx_esdt.rs index 67ccf99956..e187f8f192 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_esdt.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_esdt.rs @@ -1,4 +1,8 @@ -use multiversx_sc::{api::ManagedTypeApi, types::EsdtTokenPayment}; +use multiversx_sc::{ + api::ManagedTypeApi, + chain_core::EGLD_000000_TOKEN_IDENTIFIER, + types::{EgldOrEsdtTokenPayment, EsdtTokenPayment}, +}; use crate::{ scenario::model::{BigUintValue, BytesValue, U64Value}, @@ -15,6 +19,12 @@ pub struct TxESDT { pub esdt_value: BigUintValue, } +impl TxESDT { + pub fn is_egld(&self) -> bool { + self.esdt_token_identifier.value == EGLD_000000_TOKEN_IDENTIFIER.as_bytes() + } +} + impl InterpretableFrom for TxESDT { fn interpret_from(from: TxESDTRaw, context: &InterpreterContext) -> Self { TxESDT { @@ -47,6 +57,18 @@ impl From> for TxESDT { } } +impl From> for TxESDT { + fn from(value: EgldOrEsdtTokenPayment) -> Self { + TxESDT { + esdt_token_identifier: BytesValue::from( + value.token_identifier.as_managed_buffer().to_vec(), + ), + nonce: U64Value::from(value.token_nonce), + esdt_value: BigUintValue::from(value.amount), + } + } +} + fn interpret_esdt_token_identifier( esdt_token_identifier: Option, context: &InterpreterContext, diff --git a/framework/scenario/src/scenario/model/value/value_set_big_uint.rs b/framework/scenario/src/scenario/model/value/value_set_big_uint.rs index 003370509d..0246b72147 100644 --- a/framework/scenario/src/scenario/model/value/value_set_big_uint.rs +++ b/framework/scenario/src/scenario/model/value/value_set_big_uint.rs @@ -6,6 +6,7 @@ use crate::scenario_format::{ use crate::multiversx_sc::api::ManagedTypeApi; use num_bigint::BigUint; +use num_traits::Zero; use std::fmt; #[derive(Debug, Clone)] @@ -14,6 +15,12 @@ pub struct BigUintValue { pub original: ValueSubTree, } +impl BigUintValue { + pub fn is_zero(&self) -> bool { + self.value.is_zero() + } +} + impl InterpretableFrom for BigUintValue { fn interpret_from(from: ValueSubTree, context: &InterpreterContext) -> Self { let bytes = interpret_subtree(&from, context); diff --git a/framework/scenario/src/scenario/model/value/value_set_bytes.rs b/framework/scenario/src/scenario/model/value/value_set_bytes.rs index 160f30f75a..2b545b3c3d 100644 --- a/framework/scenario/src/scenario/model/value/value_set_bytes.rs +++ b/framework/scenario/src/scenario/model/value/value_set_bytes.rs @@ -24,12 +24,21 @@ impl BytesValue { } } + /// Creates a `0x...` expression directly. pub fn from_hex(hex_value: &str) -> Self { Self { value: hex::decode(hex_value).expect("could not decode hex value"), original: ValueSubTree::Str(format!("0x{hex_value}")), } } + + /// Creates a `str:...` expression directly. + pub fn from_str_expr(value: &str) -> Self { + Self { + value: value.as_bytes().to_owned(), + original: ValueSubTree::Str(format!("str:{value}")), + } + } } impl InterpretableFrom for BytesValue { diff --git a/framework/scenario/tests/contract_call_test.rs b/framework/scenario/tests/contract_call_test.rs deleted file mode 100644 index b6ff5ea1fb..0000000000 --- a/framework/scenario/tests/contract_call_test.rs +++ /dev/null @@ -1,26 +0,0 @@ -use multiversx_sc::api::ESDT_MULTI_TRANSFER_FUNC_NAME; -use multiversx_sc_scenario::scenario_model::ScCallStep; -use num_traits::Zero; - -#[test] -#[allow(deprecated)] -fn test_contract_call_multi_esdt() { - let tx = ScCallStep::new() - .from("address:sender") - .to("address:recipient") - .esdt_transfer("str:WEGLD-abcdef", 0, 10u32) - .esdt_transfer("str:USDC-abcdef", 0, 11u32); - - let cc = tx.tx.to_contract_call(); - - assert_eq!( - cc.basic.function_call.function_name.to_vec(), - ESDT_MULTI_TRANSFER_FUNC_NAME.as_bytes().to_vec(), - ); - assert_eq!( - cc.to_call_data_string().to_string(), - "MultiESDTNFTTransfer@726563697069656e745f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f@02@5745474c442d616263646566@@0a@555344432d616263646566@@0b", - ); - assert!(tx.tx.egld_value.value.is_zero()); - assert_eq!(tx.tx.from.value, cc.basic.to.to_address()); -} diff --git a/framework/scenario/tests/multi_transfer_with_egld.rs b/framework/scenario/tests/multi_transfer_with_egld.rs new file mode 100644 index 0000000000..fdc5d0ffa4 --- /dev/null +++ b/framework/scenario/tests/multi_transfer_with_egld.rs @@ -0,0 +1,10 @@ +use multiversx_sc_scenario::*; + +fn world() -> ScenarioWorld { + ScenarioWorld::vm_go() +} + +#[test] +fn multi_transfer_with_egld_test() { + world().run("tests/scenarios-self/multi_transfer_with_egld.scen.json"); +} diff --git a/framework/scenario/tests/scenarios-self/multi-transfer-egld.scen.json b/framework/scenario/tests/scenarios-self/multi-transfer-egld.scen.json new file mode 100644 index 0000000000..32b5ee3aa1 --- /dev/null +++ b/framework/scenario/tests/scenarios-self/multi-transfer-egld.scen.json @@ -0,0 +1,100 @@ +{ + "comment": "ESDT multi-transfer with EGLD, no SC", + "steps": [ + { + "step": "setState", + "accounts": { + "address:A": { + "nonce": "0", + "balance": "1000000000", + "esdt": { + "str:TOK-123456": "150", + "str:OTHERTOK-123456": "500", + "str:NFT-123456": { + "instances": [ + { + "nonce": "5", + "balance": "20" + } + ] + } + } + }, + "address:B": { + "nonce": "0", + "balance": "0" + } + } + }, + { + "step": "transfer", + "id": "multi-transfer", + "tx": { + "from": "address:A", + "to": "address:B", + "esdtValue": [ + { + "tokenIdentifier": "str:TOK-123456", + "value": "100" + }, + { + "tokenIdentifier": "str:OTHERTOK-123456", + "value": "400" + }, + { + "tokenIdentifier": "str:EGLD-000000", + "value": "500" + }, + { + "tokenIdentifier": "str:NFT-123456", + "nonce": "5", + "value": "10" + } + ], + "gasLimit": "0x100000000" + } + }, + { + "step": "checkState", + "comment": "check after tx 1", + "accounts": { + "address:A": { + "nonce": "1", + "balance": "999999500", + "esdt": { + "str:TOK-123456": "50", + "str:OTHERTOK-123456": "100", + "str:NFT-123456": { + "instances": [ + { + "nonce": "5", + "balance": "10" + } + ] + } + }, + "storage": {}, + "code": "" + }, + "address:B": { + "nonce": "0", + "balance": "500", + "esdt": { + "str:TOK-123456": "100", + "str:OTHERTOK-123456": "400", + "str:NFT-123456": { + "instances": [ + { + "nonce": "5", + "balance": "10" + } + ] + } + }, + "storage": {}, + "code": "" + } + } + } + ] +} \ No newline at end of file diff --git a/framework/scenario/tests/scenarios-self/multi-transfer-esdt.scen.json b/framework/scenario/tests/scenarios-self/multi-transfer-esdt.scen.json index 3b1095be7a..09ebf399dd 100644 --- a/framework/scenario/tests/scenarios-self/multi-transfer-esdt.scen.json +++ b/framework/scenario/tests/scenarios-self/multi-transfer-esdt.scen.json @@ -6,7 +6,7 @@ "accounts": { "address:A": { "nonce": "0", - "balance": "0x1000000000", + "balance": "1000000000", "esdt": { "str:TOK-123456": "150", "str:OTHERTOK-123456": "500", @@ -47,8 +47,7 @@ "value": "10" } ], - "gasLimit": "0x100000000", - "gasPrice": "0x01" + "gasLimit": "0x100000000" } }, { @@ -57,7 +56,7 @@ "accounts": { "address:A": { "nonce": "1", - "balance": "0xf00000000", + "balance": "1000000000", "esdt": { "str:TOK-123456": "50", "str:OTHERTOK-123456": "100", diff --git a/framework/scenario/tests/scenarios-self/multi_transfer_with_egld.scen.json b/framework/scenario/tests/scenarios-self/multi_transfer_with_egld.scen.json new file mode 100644 index 0000000000..0dcd2bfc6d --- /dev/null +++ b/framework/scenario/tests/scenarios-self/multi_transfer_with_egld.scen.json @@ -0,0 +1,72 @@ +{ + "steps": [ + { + "step": "setState", + "accounts": { + "bech32:erd1y3z4vsn2m026glevhsqpwrc5w4v0xzr2qmukc5qde39jnzze324qf4e2r4": { + "nonce": "0", + "balance": "10", + "esdt": { + "str:TEST-6bb410": { + "instances": [ + { + "nonce": "0", + "balance": "100", + "royalties": "0", + "attributes": "0x" + } + ] + } + }, + "developerRewards": "0" + }, + "bech32:erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa": { + "nonce": "0", + "balance": "0", + "esdt": {}, + "developerRewards": "0" + } + } + }, + { + "step": "transfer", + "id": "", + "tx": { + "from": "bech32:erd1y3z4vsn2m026glevhsqpwrc5w4v0xzr2qmukc5qde39jnzze324qf4e2r4", + "to": "bech32:erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa", + "esdtValue": [ + { + "tokenIdentifier": "0x45474c442d303030303030", + "value": "4" + }, + { + "tokenIdentifier": "0x544553542d366262343130", + "value": "10" + } + ], + "gasLimit": "5,000,000" + } + }, + { + "step": "checkState", + "accounts": { + "bech32:erd1y3z4vsn2m026glevhsqpwrc5w4v0xzr2qmukc5qde39jnzze324qf4e2r4": { + "nonce": "*", + "balance": "6", + "esdt": { + "str:TEST-6bb410": "90" + }, + "storage": {} + }, + "bech32:erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa": { + "nonce": "*", + "balance": "4", + "esdt": { + "str:TEST-6bb410": "10" + }, + "storage": {} + } + } + } + ] +} \ No newline at end of file diff --git a/framework/scenario/tests/scenarios_self_test.rs b/framework/scenario/tests/scenarios_self_test.rs index b6bf9c1883..94f63a0539 100644 --- a/framework/scenario/tests/scenarios_self_test.rs +++ b/framework/scenario/tests/scenarios_self_test.rs @@ -160,6 +160,12 @@ fn esdt_zero_balance_check_err_rs() { world().run("tests/scenarios-self/esdt-zero-balance-check-err.scen.json"); } +#[test] +#[ignore = "TODO: not yet implemented in Rust VM"] +fn multi_transfer_egld_rs() { + world().run("tests/scenarios-self/multi-transfer-esdt.scen.json"); +} + #[test] fn multi_transfer_esdt_rs() { world().run("tests/scenarios-self/multi-transfer-esdt.scen.json"); diff --git a/framework/scenario/tests/tx_call_normalize.rs b/framework/scenario/tests/tx_call_normalize.rs new file mode 100644 index 0000000000..821d202c6a --- /dev/null +++ b/framework/scenario/tests/tx_call_normalize.rs @@ -0,0 +1,112 @@ +use multiversx_sc::api::ESDT_MULTI_TRANSFER_FUNC_NAME; +use multiversx_sc_scenario::scenario_model::ScCallStep; +use num_traits::Zero; + +#[test] +fn test_tx_call_normalize_single_esdt_token_fungible() { + let tx = ScCallStep::new() + .from("address:sender") + .to("address:recipient") + .esdt_transfer("str:WEGLD-abcdef", 0, 10u32) + .function("func"); + + assert_eq!( + tx.tx.normalize().compute_data_field(), + "ESDTTransfer@5745474c442d616263646566@0a@66756e63", + ); +} + +#[test] +fn test_tx_call_normalize_single_esdt_token_non_fungible() { + let tx = ScCallStep::new() + .from("address:sender") + .to("address:recipient") + .esdt_transfer("str:SFT-abcdef", 1, 10u32) + .function("func"); + + assert_eq!( + tx.tx.normalize().compute_data_field(), + "ESDTNFTTransfer@5346542d616263646566@01@0a@726563697069656e745f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f@66756e63", + ); +} + +/// Only MultiESDTNFTTransfer supports EGLD-000000, so it is used even though we have a single token transfer. +#[test] +fn test_tx_call_normalize_single_egld_000000() { + let tx = ScCallStep::new() + .from("address:sender") + .to("address:recipient") + .esdt_transfer("str:EGLD-000000", 0, 10u32) + .function("func"); + + assert_eq!( + tx.tx.normalize().compute_data_field(), + "MultiESDTNFTTransfer@726563697069656e745f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f@01@45474c442d303030303030@@0a@66756e63", + ); +} + +#[test] +fn test_tx_call_normalize_multi_esdt_1() { + let tx = ScCallStep::new() + .from("address:sender") + .to("address:recipient") + .esdt_transfer("str:WEGLD-abcdef", 0, 10u32) + .esdt_transfer("str:USDC-abcdef", 0, 11u32); + + assert_eq!( + tx.tx.normalize().compute_data_field(), + "MultiESDTNFTTransfer@726563697069656e745f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f@02@5745474c442d616263646566@@0a@555344432d616263646566@@0b", + ); +} + +#[test] +fn test_tx_call_normalize_multi_esdt_2() { + let tx = ScCallStep::new() + .from("address:sender") + .to("address:recipient") + .esdt_transfer("str:WEGLD-abcdef", 0, 10u32) + .esdt_transfer("str:USDC-abcdef", 0, 11u32) + .function("func"); + + assert_eq!( + tx.tx.normalize().compute_data_field(), + "MultiESDTNFTTransfer@726563697069656e745f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f@02@5745474c442d616263646566@@0a@555344432d616263646566@@0b@66756e63", + ); +} + +#[test] +fn test_tx_call_normalize_single_esdt_token() { + let tx = ScCallStep::new() + .from("address:sender") + .to("address:recipient") + .esdt_transfer("str:WEGLD-abcdef", 0, 10u32) + .function("func"); + + assert_eq!( + tx.tx.normalize().compute_data_field(), + "ESDTTransfer@5745474c442d616263646566@0a@66756e63", + ); +} + +#[test] +#[allow(deprecated)] +fn test_contract_call_multi_esdt_deprecated() { + let tx = ScCallStep::new() + .from("address:sender") + .to("address:recipient") + .esdt_transfer("str:WEGLD-abcdef", 0, 10u32) + .esdt_transfer("str:USDC-abcdef", 0, 11u32); + + let cc = tx.tx.to_contract_call(); + + assert_eq!( + cc.basic.function_call.function_name.to_vec(), + ESDT_MULTI_TRANSFER_FUNC_NAME.as_bytes().to_vec(), + ); + assert_eq!( + cc.to_call_data_string().to_string(), + "MultiESDTNFTTransfer@726563697069656e745f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f@02@5745474c442d616263646566@@0a@555344432d616263646566@@0b", + ); + assert!(tx.tx.egld_value.value.is_zero()); + assert_eq!(tx.tx.from.value, cc.basic.to.to_address()); +} diff --git a/framework/snippets/src/interactor/interactor_scenario/interactor_sc_call.rs b/framework/snippets/src/interactor/interactor_scenario/interactor_sc_call.rs index 86fd32a6f9..8f3657b829 100644 --- a/framework/snippets/src/interactor/interactor_scenario/interactor_sc_call.rs +++ b/framework/snippets/src/interactor/interactor_scenario/interactor_sc_call.rs @@ -1,7 +1,6 @@ use crate::{network_response, InteractorBase}; use log::info; use multiversx_sc_scenario::{ - api::StaticApi, scenario::ScenarioRunner, scenario_model::{ScCallStep, SetStateStep, TxCall}, }; @@ -58,10 +57,9 @@ where tx_hash } - #[allow(deprecated)] // TODO pub(crate) fn tx_call_to_blockchain_tx(&self, tx_call: &TxCall) -> Transaction { - let contract_call = tx_call.to_contract_call(); - let contract_call_tx_data = contract_call_to_tx_data(&contract_call); + let normalized = tx_call.normalize(); + let contract_call_tx_data = normalized.compute_data_field(); let data = if contract_call_tx_data.is_empty() { None } else { @@ -70,11 +68,11 @@ where Transaction { nonce: 0, - value: contract_call.egld_payment.to_alloc().to_string(), - sender: tx_call.from.to_address().into(), - receiver: contract_call.basic.to.to_address().into(), + value: normalized.egld_value.value.to_string(), + sender: normalized.from.to_address().into(), + receiver: normalized.to.to_address().into(), gas_price: self.network_config.min_gas_price, - gas_limit: tx_call.gas_limit.value, + gas_limit: normalized.gas_limit.value, data, signature: None, chain_id: self.network_config.chain_id.clone(), @@ -83,23 +81,3 @@ where } } } - -#[allow(deprecated)] // TODO -fn contract_call_to_tx_data( - contract_call: &multiversx_sc_scenario::imports::ContractCallWithEgld, -) -> String { - let mut result = String::from_utf8( - contract_call - .basic - .function_call - .function_name - .to_boxed_bytes() - .into_vec(), - ) - .unwrap(); - for argument in contract_call.basic.function_call.arg_buffer.raw_arg_iter() { - result.push('@'); - result.push_str(hex::encode(argument.to_boxed_bytes().as_slice()).as_str()); - } - result -} diff --git a/tools/rust-debugger/pretty-printers/multiversx_sc_lldb_pretty_printers.py b/tools/rust-debugger/pretty-printers/multiversx_sc_lldb_pretty_printers.py index 297c4c50be..6b397868cd 100644 --- a/tools/rust-debugger/pretty-printers/multiversx_sc_lldb_pretty_printers.py +++ b/tools/rust-debugger/pretty-printers/multiversx_sc_lldb_pretty_printers.py @@ -331,7 +331,7 @@ def value_summary(self, big_uint: lldb.value, context: lldb.value, type_info: ll class TokenIdentifier(PlainManagedVecItem, ManagedType): def lookup(self, token_identifier: lldb.value) -> lldb.value: - return token_identifier.buffer + return token_identifier.data.buffer def value_summary(self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: return buffer_as_string(buffer) @@ -417,14 +417,13 @@ def to_string(self, token_id: str, nonce: int, amount: str) -> str: class EgldOrEsdtTokenIdentifier(PlainManagedVecItem, ManagedType): def lookup(self, egld_or_esdt_token_identifier: lldb.value) -> lldb.value: - return egld_or_esdt_token_identifier.data + return egld_or_esdt_token_identifier.buffer - @check_invalid_handle - def summary_from_raw_handle(self, raw_handle: int, context: lldb.value, type_info: lldb.SBType) -> str: - if raw_handle == MANAGED_OPTION_NONE_HANDLE: + def value_summary(self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + token_id = buffer_as_string(buffer) + if token_id == '"EGLD-000000"': return "EgldOrEsdtTokenIdentifier::egld()" - token_summary = TokenIdentifier().summary_from_raw_handle(raw_handle, context, None) - return f"EgldOrEsdtTokenIdentifier::esdt({token_summary})" + return f"EgldOrEsdtTokenIdentifier::esdt({token_id})" class ManagedVec(PlainManagedVecItem, ManagedType):