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..aac950e844 --- /dev/null +++ b/contracts/feature-tests/payable-features/interactor/src/payable_interactor.rs @@ -0,0 +1,95 @@ +mod payable_interactor_cli; +mod payable_interactor_config; +mod payable_interactor_state; + +use payable_features::payable_features_proxy; +pub use payable_interactor_config::Config; +use payable_interactor_state::State; +use clap::Parser; + +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 = MultiEsdtPayment::new(); + payment.push(EsdtTokenPayment::new("EGLD-000000".into(), 0, 1_0000u64.into())); + payment.push(EsdtTokenPayment::new("EGLD-000000".into(), 0, 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() + .multi_esdt(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/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/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/contract_base/wrappers/call_value_wrapper.rs b/framework/base/src/contract_base/wrappers/call_value_wrapper.rs index 3304bfdcf2..211924accd 100644 --- a/framework/base/src/contract_base/wrappers/call_value_wrapper.rs +++ b/framework/base/src/contract_base/wrappers/call_value_wrapper.rs @@ -156,7 +156,7 @@ where } else { // clone all_esdt_transfers_unchecked -> all_transfers let all_transfers_unchecked_handle = self.all_esdt_transfers_unchecked(); - let _ = A::managed_type_impl().mb_set_slice(all_transfers_handle.clone(), 0, &[]); + let _ = A::managed_type_impl().mb_overwrite(all_transfers_handle.clone(), &[]); A::managed_type_impl() .mb_append(all_transfers_handle.clone(), all_transfers_unchecked_handle); }