diff --git a/contracts/examples/adder/tests/adder_blackbox_with_values_test.rs b/contracts/examples/adder/tests/adder_blackbox_with_values_test.rs index 159b71cf14..cc602aa1f0 100644 --- a/contracts/examples/adder/tests/adder_blackbox_with_values_test.rs +++ b/contracts/examples/adder/tests/adder_blackbox_with_values_test.rs @@ -1,5 +1,8 @@ use adder::*; -use multiversx_sc::storage::mappers::SingleValue; +use multiversx_sc::{ + storage::mappers::SingleValue, + types::{AddressExpr, ScExpr}, +}; use multiversx_sc_scenario::{api::StaticApi, num_bigint::BigUint, scenario_model::*, *}; const ADDER_PATH_EXPR: &str = "mxsc:output/adder.mxsc.json"; @@ -41,12 +44,14 @@ fn adder_blackbox_with_values() { .call(adder_contract.sum()) .expect_value(SingleValue::from(BigUint::from(5u32))), ) - .sc_call( - ScCallStep::new() - .from(owner_address) - .to(&adder_contract) - .call(adder_contract.add(3u32)), - ) + .tx(|tx| { + tx.from(AddressExpr("owner")) + .to(ScExpr("adder")) + .call(adder_contract.add(3u32)) + .callback(WithTxResult(|response| { + assert!(response.tx_error.is_success()); + })) + }) .check_state_step( CheckStateStep::new() .put_account(owner_address, CheckAccount::new()) diff --git a/contracts/examples/rewards-distribution/src/rewards_distribution.rs b/contracts/examples/rewards-distribution/src/rewards_distribution.rs index 59672da272..da8492e19e 100644 --- a/contracts/examples/rewards-distribution/src/rewards_distribution.rs +++ b/contracts/examples/rewards-distribution/src/rewards_distribution.rs @@ -267,10 +267,12 @@ pub trait RewardsDistribution: } } - self.send() - .direct_non_zero_egld(&caller, &total_egld_reward); - self.send().direct_multi(&caller, &rewards); - self.send().direct_multi(&caller, &nfts); + self.tx().to(&caller).egld(total_egld_reward).transfer(); + self.tx().to(&caller).multi_esdt(rewards).transfer(); + self.tx() + .to(&caller) + .multi_esdt(nfts.clone_value()) + .transfer(); } fn claim_reward_token( diff --git a/contracts/feature-tests/composability/vault/src/vault.rs b/contracts/feature-tests/composability/vault/src/vault.rs index 9dfaa11a07..e7f11e25d9 100644 --- a/contracts/feature-tests/composability/vault/src/vault.rs +++ b/contracts/feature-tests/composability/vault/src/vault.rs @@ -164,10 +164,12 @@ pub trait Vault { let caller = self.blockchain().get_caller(); if let Some(esdt_token_id) = token.into_esdt_option() { - self.send() - .direct_esdt(&caller, &esdt_token_id, nonce, &amount); + self.tx() + .to(caller) + .esdt((esdt_token_id, nonce, amount)) + .transfer(); } else { - self.send().direct_egld(&caller, &amount); + self.tx().to(caller).egld(amount).transfer(); } } @@ -185,7 +187,7 @@ pub trait Vault { all_payments.push(EsdtTokenPayment::new(token_id, nonce, amount)); } - self.send().direct_multi(&caller, &all_payments); + self.tx().to(caller).multi_esdt(all_payments).transfer(); } #[payable("*")] @@ -223,8 +225,7 @@ pub trait Vault { )); } - self.send() - .direct_multi(&self.blockchain().get_caller(), &new_tokens); + self.tx().to_caller().multi_esdt(new_tokens).transfer(); } #[event("accept_funds")] diff --git a/framework/base/src/contract_base/contract_base_trait.rs b/framework/base/src/contract_base/contract_base_trait.rs index cec45dfa0f..342b9ea61d 100644 --- a/framework/base/src/contract_base/contract_base_trait.rs +++ b/framework/base/src/contract_base/contract_base_trait.rs @@ -2,7 +2,10 @@ use super::{ BlockchainWrapper, CallValueWrapper, CryptoWrapper, ErrorHelper, ManagedSerializer, SendRawWrapper, SendWrapper, StorageRawWrapper, }; -use crate::api::VMApi; +use crate::{ + api::VMApi, + types::{Tx, TxBaseWithEnv, TxScEnv}, +}; /// Interface to be used by the actual smart contract code. /// @@ -26,6 +29,12 @@ pub trait ContractBase: Sized { SendWrapper::new() } + /// Starts the declaration of a new transaction. + #[inline] + fn tx(&self) -> TxBaseWithEnv> { + Tx::new_tx_from_sc() + } + /// Low-level functionality related to sending transactions from the current contract. /// /// For almost all cases contracts should instead use `self.send()` and `ContractCall`. 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 5635c0b44a..5d290369df 100644 --- a/framework/base/src/contract_base/wrappers/send_raw_wrapper.rs +++ b/framework/base/src/contract_base/wrappers/send_raw_wrapper.rs @@ -79,20 +79,12 @@ where &self, to: &ManagedAddress, token: &TokenIdentifier, - egld_value: &BigUint, + value: &BigUint, gas_limit: u64, endpoint_name: &ManagedBuffer, arg_buffer: &ManagedArgBuffer, ) -> Result<(), &'static [u8]> { - self.transfer_esdt_nft_execute( - to, - token, - 0, - egld_value, - gas_limit, - endpoint_name, - arg_buffer, - ) + self.transfer_esdt_nft_execute(to, token, 0, value, gas_limit, endpoint_name, arg_buffer) } #[allow(clippy::too_many_arguments)] diff --git a/framework/base/src/contract_base/wrappers/send_wrapper.rs b/framework/base/src/contract_base/wrappers/send_wrapper.rs index 78f4f045d6..62f99b767c 100644 --- a/framework/base/src/contract_base/wrappers/send_wrapper.rs +++ b/framework/base/src/contract_base/wrappers/send_wrapper.rs @@ -15,6 +15,7 @@ use crate::{ types::{ BigUint, ContractCall, ContractCallNoPayment, EgldOrEsdtTokenIdentifier, EsdtTokenPayment, ManagedAddress, ManagedArgBuffer, ManagedBuffer, ManagedType, ManagedVec, TokenIdentifier, + Tx, }, }; @@ -314,10 +315,10 @@ where nonce: u64, amount: BigUint, ) -> ! { - ContractCallNoPayment::::new(to, ManagedBuffer::new()) - .with_esdt_transfer((token, nonce, amount)) - .async_call() - .call_and_exit_ignore_callback() + Tx::new_tx_from_sc() + .to(to) + .esdt((token, nonce, amount)) + .async_call_and_exit() } /// Performs a simple ESDT/NFT transfer, but via async call. @@ -339,10 +340,7 @@ where if amount == 0 { return; } - ContractCallNoPayment::::new(to, ManagedBuffer::new()) - .with_esdt_transfer((token, nonce, amount)) - .async_call() - .call_and_exit_ignore_callback() + self.transfer_esdt_via_async_call(to, token, nonce, amount) } /// Sends multiple ESDT tokens to a target address, via an async call. @@ -351,10 +349,10 @@ where to: ManagedAddress, payments: ManagedVec>, ) -> ! { - ContractCallNoPayment::::new(to, ManagedBuffer::new()) - .with_multi_token_transfer(payments) - .async_call() - .call_and_exit_ignore_callback() + Tx::new_tx_from_sc() + .to(to) + .multi_esdt(payments) + .async_call_and_exit() } /// Creates a call to the `ClaimDeveloperRewards` builtin function. diff --git a/framework/base/src/types/interaction/annotated.rs b/framework/base/src/types/interaction/annotated.rs new file mode 100644 index 0000000000..9463ed7c12 --- /dev/null +++ b/framework/base/src/types/interaction/annotated.rs @@ -0,0 +1,51 @@ +use crate::{ + api::ManagedTypeApi, + types::{ManagedAddress, ManagedBuffer}, +}; + +use super::TxEnv; + +pub trait AnnotatedValue +where + Env: TxEnv, +{ + fn annotation(&self, _env: &Env) -> ManagedBuffer; + + fn into_value(self) -> T; + + fn with_value_ref(&self, f: F); +} + +impl AnnotatedValue> for ManagedAddress +where + Env: TxEnv, +{ + fn annotation(&self, _env: &Env) -> ManagedBuffer { + self.hex_expr() + } + + fn into_value(self) -> ManagedAddress { + self + } + + fn with_value_ref)>(&self, f: F) { + f(self) + } +} + +impl AnnotatedValue> for &ManagedAddress +where + Env: TxEnv, +{ + fn annotation(&self, _env: &Env) -> crate::types::ManagedBuffer { + self.hex_expr() + } + + fn into_value(self) -> ManagedAddress { + self.clone() + } + + fn with_value_ref)>(&self, f: F) { + f(self) + } +} diff --git a/framework/base/src/types/interaction/async_call.rs b/framework/base/src/types/interaction/async_call.rs index 9c5554baba..bcc07bcced 100644 --- a/framework/base/src/types/interaction/async_call.rs +++ b/framework/base/src/types/interaction/async_call.rs @@ -1,60 +1,46 @@ use crate::{ api::{CallTypeApi, StorageWriteApi}, contract_base::SendRawWrapper, - types::{BigUint, CallbackClosure, ManagedAddress}, + types::{BigUint, CallbackClosure, EgldPayment, ManagedAddress}, }; -use super::FunctionCall; +use super::{FunctionCall, Tx, TxAsyncCallCallback, TxScEnv}; -#[must_use] -pub struct AsyncCall -where - SA: CallTypeApi + 'static, -{ - pub(crate) to: ManagedAddress, - pub(crate) egld_payment: BigUint, - pub(crate) function_call: FunctionCall, - pub(crate) callback_call: Option>, -} +pub type AsyncCall = Tx< + TxScEnv, + (), + ManagedAddress, + EgldPayment, + (), + FunctionCall, + Option>, +>; #[allow(clippy::return_self_not_must_use)] -impl AsyncCall +impl AsyncCall where - SA: CallTypeApi, + Api: CallTypeApi, { - pub fn with_callback(self, callback_call: CallbackClosure) -> Self { - AsyncCall { - callback_call: Some(callback_call), - ..self - } + pub fn with_callback(mut self, callback_call: CallbackClosure) -> Self { + self.callback = Some(callback_call); + self } } -impl AsyncCall +impl AsyncCall where - SA: CallTypeApi, + Api: CallTypeApi + StorageWriteApi, { - pub fn call_and_exit_ignore_callback(&self) -> ! { - SendRawWrapper::::new().async_call_raw( - &self.to, - &self.egld_payment, - &self.function_call.function_name, - &self.function_call.arg_buffer, - ) + pub fn call_and_exit_ignore_callback(self) -> ! { + self.async_call_and_exit() } } -impl AsyncCall +impl AsyncCall where - SA: CallTypeApi + StorageWriteApi, + Api: CallTypeApi + StorageWriteApi, { - pub fn call_and_exit(&self) -> ! { - // first, save the callback closure - if let Some(callback_call) = &self.callback_call { - callback_call.save_to_storage::(); - } - - // last, send the async call, which will kill the execution - self.call_and_exit_ignore_callback() + pub fn call_and_exit(self) -> ! { + self.async_call_and_exit() } } diff --git a/framework/base/src/types/interaction/callback_closure.rs b/framework/base/src/types/interaction/callback_closure.rs index 98b6fece64..a8884695f7 100644 --- a/framework/base/src/types/interaction/callback_closure.rs +++ b/framework/base/src/types/interaction/callback_closure.rs @@ -28,11 +28,22 @@ pub const CALLBACK_CLOSURE_STORAGE_BASE_KEY: &[u8] = b"CB_CLOSURE"; /// /// In both cases the framework hides all the magic, the developer shouldn't worry about it. #[derive(TopEncode)] -pub struct CallbackClosure { +pub struct CallbackClosure +where + M: ManagedTypeApi + ErrorApi, +{ pub(super) callback_name: &'static str, pub(super) closure_args: ManagedArgBuffer, } +pub struct CallbackClosureWithGas +where + M: ManagedTypeApi + ErrorApi, +{ + pub(super) closure: CallbackClosure, + pub(super) gas_for_callback: u64, +} + /// Syntactical sugar to help macros to generate code easier. /// Unlike calling `CallbackClosure::::new`, here types can be inferred from the context. pub fn new_callback_call(callback_name: &'static str) -> CallbackClosure diff --git a/framework/base/src/types/interaction/contract_call_exec.rs b/framework/base/src/types/interaction/contract_call_exec.rs index 260897e1a6..b32f143fdc 100644 --- a/framework/base/src/types/interaction/contract_call_exec.rs +++ b/framework/base/src/types/interaction/contract_call_exec.rs @@ -14,7 +14,7 @@ use crate::{ }, }; -use super::{AsyncCall, ContractCallNoPayment, ContractCallWithEgld}; +use super::{AsyncCall, ContractCallNoPayment, ContractCallWithEgld, Tx}; use crate::api::managed_types::handles::HandleConstraints; /// Using max u64 to represent maximum possible gas, @@ -66,12 +66,11 @@ where } pub(super) fn async_call(self) -> AsyncCall { - AsyncCall { - to: self.basic.to, - egld_payment: self.egld_payment, - function_call: self.basic.function_call, - callback_call: None, - } + Tx::new_tx_from_sc() + .to(self.basic.to) + .egld(self.egld_payment) + .call(self.basic.function_call) + .callback(None) } pub(super) fn async_call_promise(self) -> super::AsyncCallPromises { diff --git a/framework/base/src/types/interaction/contract_call_no_payment.rs b/framework/base/src/types/interaction/contract_call_no_payment.rs index 7198f71cd1..4fc75125a5 100644 --- a/framework/base/src/types/interaction/contract_call_no_payment.rs +++ b/framework/base/src/types/interaction/contract_call_no_payment.rs @@ -14,6 +14,7 @@ use super::{ contract_call_exec::UNSPECIFIED_GAS_LIMIT, contract_call_with_egld::ContractCallWithEgld, contract_call_with_multi_esdt::ContractCallWithMultiEsdt, ContractCall, ContractCallWithAnyPayment, ContractCallWithEgldOrSingleEsdt, FunctionCall, ManagedArgBuffer, + Tx, TxScEnv, }; /// Holds metadata for calling another contract, without payments. @@ -166,4 +167,8 @@ where pub fn into_function_call(self) -> FunctionCall { self.function_call } + + pub fn tx(self) -> Tx, (), (), (), (), FunctionCall, ()> { + Tx::new_tx_from_sc().call(self.function_call) + } } diff --git a/framework/base/src/types/interaction/expr_address.rs b/framework/base/src/types/interaction/expr_address.rs new file mode 100644 index 0000000000..69a13cd6e3 --- /dev/null +++ b/framework/base/src/types/interaction/expr_address.rs @@ -0,0 +1,85 @@ +use core::ptr; + +use crate::{ + api::CallTypeApi, + types::{ManagedAddress, ManagedBuffer}, +}; + +use super::{AnnotatedValue, TxEnv, TxFrom, TxFromSpecified}; + +const ADDRESS_PREFIX: &str = "address:"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct AddressExpr(pub &'static str); + +impl AnnotatedValue> for AddressExpr +where + Env: TxEnv, +{ + fn annotation(&self, _env: &Env) -> ManagedBuffer { + let mut result = ManagedBuffer::new_from_bytes(ADDRESS_PREFIX.as_bytes()); + result.append_bytes(self.0.as_bytes()); + result + } + + fn into_value(self) -> ManagedAddress { + let expr: [u8; 32] = self.eval_to_array(); + expr.into() + } + + fn with_value_ref)>(&self, f: F) { + let expr: [u8; 32] = self.eval_to_array(); + let ma = expr.into(); + f(&ma); + } +} +impl TxFrom for AddressExpr +where + Env: TxEnv, +{ + fn resolve_address(&self, _env: &Env) -> ManagedAddress { + let expr: [u8; 32] = self.eval_to_array(); + expr.into() + } +} +impl TxFromSpecified for AddressExpr where Env: TxEnv {} + +impl AddressExpr { + pub const fn eval_to_array(&self) -> [u8; 32] { + let result = [b'_'; 32]; + let expr_bytes = self.0.as_bytes(); + let mut len = expr_bytes.len(); + if len > 32 { + len = 32; + } + unsafe { + ptr::copy_nonoverlapping(expr_bytes.as_ptr(), result.as_ptr() as *mut u8, len); + } + result + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + fn assert_eq_eval(expr: &'static str, expected: &[u8; 32]) { + assert_eq!(&AddressExpr(expr).eval_to_array(), expected); + } + + #[test] + fn test_address_value() { + assert_eq_eval("", b"________________________________"); + assert_eq_eval("a", b"a_______________________________"); + assert_eq_eval("a\x05", b"a\x05______________________________"); + assert_eq_eval("an_address", b"an_address______________________"); + assert_eq_eval( + "12345678901234567890123456789012", + b"12345678901234567890123456789012", + ); + assert_eq_eval( + "123456789012345678901234567890123", + b"12345678901234567890123456789012", + ); + } +} diff --git a/framework/base/src/types/interaction/expr_sc.rs b/framework/base/src/types/interaction/expr_sc.rs new file mode 100644 index 0000000000..56fed5a5e3 --- /dev/null +++ b/framework/base/src/types/interaction/expr_sc.rs @@ -0,0 +1,91 @@ +use core::ptr; + +use crate::{ + api::CallTypeApi, + types::{ManagedAddress, ManagedBuffer}, +}; + +use super::{AnnotatedValue, TxEnv, TxFrom, TxFromSpecified, TxTo, TxToSpecified}; + +const SC_PREFIX: &str = "sc:"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ScExpr<'a>(pub &'a str); + +impl<'a, Env> AnnotatedValue> for ScExpr<'a> +where + Env: TxEnv, +{ + fn annotation(&self, _env: &Env) -> ManagedBuffer { + let mut result = ManagedBuffer::new_from_bytes(SC_PREFIX.as_bytes()); + result.append_bytes(self.0.as_bytes()); + result + } + + fn into_value(self) -> ManagedAddress { + let expr: [u8; 32] = self.eval_to_array(); + expr.into() + } + + fn with_value_ref)>(&self, f: F) { + let expr: [u8; 32] = self.eval_to_array(); + let ma = expr.into(); + f(&ma); + } +} +impl<'a, Env> TxFrom for ScExpr<'a> +where + Env: TxEnv, +{ + fn resolve_address(&self, _env: &Env) -> ManagedAddress { + let expr: [u8; 32] = self.eval_to_array(); + expr.into() + } +} +impl<'a, Env> TxFromSpecified for ScExpr<'a> where Env: TxEnv {} +impl<'a, Env> TxTo for ScExpr<'a> where Env: TxEnv {} +impl<'a, Env> TxToSpecified for ScExpr<'a> where Env: TxEnv {} + +impl<'a> ScExpr<'a> { + pub const fn eval_to_array(&self) -> [u8; 32] { + let result = *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00______________________"; + let expr_bytes = self.0.as_bytes(); + let mut len = expr_bytes.len(); + if len > 22 { + len = 22; + } + unsafe { + ptr::copy_nonoverlapping( + expr_bytes.as_ptr(), + result.as_ptr().offset(10) as *mut u8, + len, + ); + } + result + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + fn assert_eq_eval(expr: &'static str, expected: &[u8; 32]) { + assert_eq!(&ScExpr(expr).eval_to_array(), expected); + } + + #[test] + fn test_address_value() { + assert_eq_eval( + "", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00______________________", + ); + assert_eq_eval( + "a", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00a_____________________", + ); + assert_eq_eval( + "12345678901234567890120s", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001234567890123456789012", + ); + } +} diff --git a/framework/base/src/types/interaction/function_call.rs b/framework/base/src/types/interaction/function_call.rs index a2140f14fa..bfe74958dc 100644 --- a/framework/base/src/types/interaction/function_call.rs +++ b/framework/base/src/types/interaction/function_call.rs @@ -6,17 +6,13 @@ use multiversx_sc_codec::{ use crate::{ abi::{TypeAbi, TypeName}, api::{ - ManagedTypeApi, ESDT_MULTI_TRANSFER_FUNC_NAME, ESDT_NFT_TRANSFER_FUNC_NAME, + CallTypeApi, ManagedTypeApi, ESDT_MULTI_TRANSFER_FUNC_NAME, ESDT_NFT_TRANSFER_FUNC_NAME, ESDT_TRANSFER_FUNC_NAME, }, - formatter::SCLowerHex, - types::{ - EsdtTokenPayment, ManagedAddress, ManagedBuffer, ManagedBufferCachedBuilder, ManagedVec, - MultiValueEncoded, - }, + types::{EsdtTokenPayment, ManagedAddress, ManagedBuffer, ManagedVec, MultiValueEncoded}, }; -use super::ManagedArgBuffer; +use super::{ContractCallNoPayment, ManagedArgBuffer}; /// Encodes a function call on the blockchain, composed of a function name and its encoded arguments. /// @@ -62,15 +58,23 @@ where self.arg_buffer.push_multi_arg(arg); self } +} - pub fn to_call_data_string(&self) -> ManagedBuffer { - let mut result = ManagedBufferCachedBuilder::default(); - result.append_managed_buffer(&self.function_name); - for arg in self.arg_buffer.raw_arg_iter() { - result.append_bytes(b"@"); - SCLowerHex::fmt(&*arg, &mut result); - } - result.into_managed_buffer() +impl From<()> for FunctionCall +where + Api: ManagedTypeApi, +{ + fn from(_: ()) -> Self { + FunctionCall::empty() + } +} + +impl From> for FunctionCall +where + Api: CallTypeApi, +{ + fn from(ccnp: ContractCallNoPayment) -> Self { + ccnp.function_call } } diff --git a/framework/base/src/types/interaction/managed_arg_buffer.rs b/framework/base/src/types/interaction/managed_arg_buffer.rs index b0cee5db30..7af6346c7b 100644 --- a/framework/base/src/types/interaction/managed_arg_buffer.rs +++ b/framework/base/src/types/interaction/managed_arg_buffer.rs @@ -172,6 +172,10 @@ where pub fn into_vec_of_buffers(self) -> ManagedVec> { self.data } + + pub fn iter_buffers(&self) -> ManagedVecRefIterator> { + ManagedVecRefIterator::new(&self.data) + } } impl ManagedArgBuffer diff --git a/framework/base/src/types/interaction/mod.rs b/framework/base/src/types/interaction/mod.rs index 3de007ee79..ebb0d4d67f 100644 --- a/framework/base/src/types/interaction/mod.rs +++ b/framework/base/src/types/interaction/mod.rs @@ -1,3 +1,6 @@ +#![allow(unused)] // TEMP + +mod annotated; mod async_call; mod async_call_promises; mod back_transfers; @@ -12,9 +15,24 @@ mod contract_call_with_egld; mod contract_call_with_egld_or_single_esdt; mod contract_call_with_multi_esdt; mod contract_deploy; +mod expr_address; +mod expr_sc; mod function_call; mod managed_arg_buffer; +mod tx; +mod tx_async_call; +mod tx_async_call_promises; +mod tx_async_te; +mod tx_callback; +mod tx_data; +mod tx_env; +mod tx_env_sc; +mod tx_from; +mod tx_gas; +mod tx_payment; +mod tx_to; +pub use annotated::*; pub use async_call::AsyncCall; pub use async_call_promises::AsyncCallPromises; pub use back_transfers::BackTransfers; @@ -29,5 +47,20 @@ pub use contract_call_with_egld::ContractCallWithEgld; pub use contract_call_with_egld_or_single_esdt::ContractCallWithEgldOrSingleEsdt; pub use contract_call_with_multi_esdt::ContractCallWithMultiEsdt; pub use contract_deploy::{new_contract_deploy, ContractDeploy}; +pub use expr_address::AddressExpr; +pub use expr_sc::ScExpr; pub use function_call::FunctionCall; pub use managed_arg_buffer::ManagedArgBuffer; +pub use tx::*; +pub use tx_async_call::*; +pub use tx_async_call_promises::*; +pub use tx_callback::*; +pub use tx_data::*; +pub use tx_env::*; +pub use tx_env_sc::*; +pub use tx_from::*; +pub use tx_gas::*; +pub use tx_payment::*; +pub use tx_to::*; + +pub type TxScBase = TxBaseWithEnv>; diff --git a/framework/base/src/types/interaction/tx.rs b/framework/base/src/types/interaction/tx.rs new file mode 100644 index 0000000000..b115b5e297 --- /dev/null +++ b/framework/base/src/types/interaction/tx.rs @@ -0,0 +1,420 @@ +use core::marker::PhantomData; + +use multiversx_sc_codec::TopEncodeMulti; + +use crate::{ + api::CallTypeApi, + contract_base::BlockchainWrapper, + types::{ + BigUint, EgldPayment, EsdtTokenPayment, ManagedAddress, ManagedBuffer, ManagedVec, + MultiEsdtPayment, + }, +}; + +use super::{ + AsyncCall, ExplicitGas, FunctionCall, TxCallback, TxData, TxDataFunctionCall, TxEnv, TxFrom, + TxFromSpecified, TxGas, TxPayment, TxScEnv, TxTo, TxToSpecified, +}; + +#[must_use] +pub struct Tx +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Payment: TxPayment, + Gas: TxGas, + Data: TxData, + Callback: TxCallback, +{ + pub env: Env, + pub from: From, + pub to: To, + pub payment: Payment, + pub gas: Gas, + pub data: Data, + pub callback: Callback, +} + +impl Tx +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Payment: TxPayment, + Gas: TxGas, + Data: TxData, + Callback: TxCallback, +{ + /// TODO: does nothing, delete, added for easier copy-paste. + #[inline] + pub fn nothing(self) -> Tx { + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: self.payment, + gas: self.gas, + data: self.data, + callback: self.callback, + } + } +} + +pub type TxBaseWithEnv = Tx; + +impl TxBaseWithEnv +where + Env: TxEnv, +{ + #[inline] + pub fn new_with_env(env: Env) -> Self { + Tx { + env, + from: (), + to: (), + payment: (), + gas: (), + data: (), + callback: (), + } + } +} + +impl Tx +where + Env: TxEnv, + To: TxTo, + Payment: TxPayment, + Gas: TxGas, + Data: TxData, + Callback: TxCallback, +{ + pub fn from(self, from: From) -> Tx + where + From: TxFromSpecified, + { + let mut env = self.env; + env.annotate_from(&from); + Tx { + env, + from, + to: self.to, + payment: self.payment, + gas: self.gas, + data: self.data, + callback: self.callback, + } + } +} + +impl Tx +where + Env: TxEnv, + From: TxFrom, + Payment: TxPayment, + Gas: TxGas, + Data: TxData, + Callback: TxCallback, +{ + pub fn to(self, to: To) -> Tx + where + To: TxToSpecified, + { + let mut env = self.env; + env.annotate_to(&to); + Tx { + env, + from: self.from, + to, + payment: self.payment, + gas: self.gas, + data: self.data, + callback: self.callback, + } + } + + pub fn to_caller( + self, + ) -> Tx, Payment, Gas, Data, Callback> { + let caller = BlockchainWrapper::::new().get_caller(); + self.to(caller) + } +} + +impl Tx +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, + Data: TxData, + Callback: TxCallback, +{ + pub fn egld( + self, + egld_amount: BigUint, + ) -> Tx, Gas, Data, Callback> { + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: EgldPayment { value: egld_amount }, + gas: self.gas, + data: self.data, + callback: self.callback, + } + } +} + +impl Tx +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, + Data: TxData, + Callback: TxCallback, +{ + /// Adds a single ESDT token transfer to a transaction. + /// + /// Since this is the first ESDT payment, a single payment tx is produced. Can be called again for multiple payments. + pub fn esdt>>( + self, + payment: P, + ) -> Tx, Gas, Data, Callback> { + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: payment.into(), + gas: self.gas, + data: self.data, + callback: self.callback, + } + } + + /// Adds a collection of ESDT payments to a transaction. + pub fn multi_esdt( + self, + payments: MultiEsdtPayment, // TODO: references + ) -> Tx, Gas, Data, Callback> { + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: payments, + gas: self.gas, + data: self.data, + callback: self.callback, + } + } +} + +impl + Tx, Gas, Data, Callback> +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, + Data: TxData, + Callback: TxCallback, +{ + /// Adds a single ESDT token transfer to a contract call. + /// + /// Can be called multiple times on the same call. + pub fn with_esdt_transfer>>( + self, + payment: P, + ) -> Tx, Gas, Data, Callback> { + let mut payments = ManagedVec::new(); + payments.push(self.payment); + payments.push(payment.into()); + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: payments, + gas: self.gas, + data: self.data, + callback: self.callback, + } + } +} + +impl + Tx, Gas, Data, Callback> +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, + Data: TxData, + Callback: TxCallback, +{ + /// Adds a single ESDT token transfer to a contract call. + /// + /// Can be called multiple times on the same call. + pub fn with_esdt_transfer>>( + mut self, + payment: P, + ) -> Tx, Gas, Data, Callback> { + self.payment.push(payment.into()); + self + } +} + +impl Tx +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Payment: TxPayment, + Data: TxData, + Callback: TxCallback, +{ + /// Sets an explicit gas limit to the call. + #[inline] + pub fn with_gas_limit( + self, + gas_limit: u64, + ) -> Tx { + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: self.payment, + gas: ExplicitGas(gas_limit), + data: self.data, + callback: self.callback, + } + } +} + +impl Tx +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Payment: TxPayment, + Gas: TxGas, + Callback: TxCallback, +{ + #[inline] + pub fn call>>( + self, + call: FC, + ) -> Tx, Callback> { + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: self.payment, + gas: self.gas, + data: call.into(), + callback: self.callback, + } + } + + #[inline] + pub fn function_name>>( + self, + function_name: N, + ) -> Tx, Callback> { + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: self.payment, + gas: self.gas, + data: FunctionCall::new(function_name), + callback: self.callback, + } + } +} + +impl + Tx, Callback> +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Payment: TxPayment, + Gas: TxGas, + Callback: TxCallback, +{ + #[inline] + pub fn argument(mut self, arg: &T) -> Self { + self.data = self.data.argument(arg); + self + } +} + +impl Tx +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Payment: TxPayment, + Gas: TxGas, + Data: TxData, +{ + #[inline] + pub fn callback( + self, + callback: Callback, + ) -> Tx + where + Callback: TxCallback, + { + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: self.payment, + gas: self.gas, + data: self.data, + callback, + } + } +} + +impl Tx +where + Env: TxEnv, + From: TxFrom, + To: TxToSpecified, + Payment: TxPayment, + Gas: TxGas, + FC: TxDataFunctionCall, + Callback: TxCallback, +{ + pub fn normalize_tx( + self, + ) -> Tx< + Env, + From, + ManagedAddress, + EgldPayment, + Gas, + FunctionCall, + Callback, + > { + let result = self.payment.convert_tx_data( + &self.env, + &self.from, + self.to.into_value(), + self.data.into(), + ); + Tx { + env: self.env, + from: self.from, + to: result.to, + payment: result.egld_payment, + gas: self.gas, + data: result.fc, + callback: self.callback, + } + } +} diff --git a/framework/base/src/types/interaction/tx_async_call.rs b/framework/base/src/types/interaction/tx_async_call.rs new file mode 100644 index 0000000000..27d6e2a194 --- /dev/null +++ b/framework/base/src/types/interaction/tx_async_call.rs @@ -0,0 +1,65 @@ +use crate::{ + api::{CallTypeApi, StorageWriteApi}, + contract_base::SendRawWrapper, + types::{BigUint, CallbackClosure, ManagedAddress}, +}; + +use super::{ + FunctionCall, Tx, TxCallback, TxDataFunctionCall, TxEnv, TxPayment, TxScEnv, TxToSpecified, +}; + +pub trait TxAsyncCallCallback: TxCallback> +where + Api: CallTypeApi, +{ + fn save_callback_closure_to_storage(&self); +} + +impl TxAsyncCallCallback for () +where + Api: CallTypeApi, +{ + fn save_callback_closure_to_storage(&self) {} +} + +impl TxCallback> for CallbackClosure where Api: CallTypeApi {} +impl TxAsyncCallCallback for CallbackClosure +where + Api: CallTypeApi + StorageWriteApi, +{ + fn save_callback_closure_to_storage(&self) { + self.save_to_storage::(); + } +} + +impl TxCallback> for Option> where Api: CallTypeApi {} +impl TxAsyncCallCallback for Option> +where + Api: CallTypeApi + StorageWriteApi, +{ + fn save_callback_closure_to_storage(&self) { + if let Some(closure) = self { + closure.save_callback_closure_to_storage(); + } + } +} + +impl Tx, (), To, Payment, (), FC, Callback> +where + Api: CallTypeApi, + To: TxToSpecified>, + Payment: TxPayment>, + FC: TxDataFunctionCall>, + Callback: TxAsyncCallCallback, +{ + pub fn async_call_and_exit(self) -> ! { + let normalized = self.normalize_tx(); + normalized.callback.save_callback_closure_to_storage(); + SendRawWrapper::::new().async_call_raw( + &normalized.to, + &normalized.payment.value, + &normalized.data.function_name, + &normalized.data.arg_buffer, + ) + } +} diff --git a/framework/base/src/types/interaction/tx_async_call_promises.rs b/framework/base/src/types/interaction/tx_async_call_promises.rs new file mode 100644 index 0000000000..55282d708a --- /dev/null +++ b/framework/base/src/types/interaction/tx_async_call_promises.rs @@ -0,0 +1,119 @@ +use crate::{ + api::{const_handles, CallTypeApi}, + contract_base::SendRawWrapper, + types::{BigUint, CallbackClosure, ManagedAddress, ManagedBuffer, ManagedType}, +}; + +use super::{ + callback_closure::CallbackClosureWithGas, ExplicitGas, FunctionCall, Tx, TxCallback, TxGas, + TxPayment, TxScEnv, TxToSpecified, +}; + +pub trait TxPromisesCallback: TxCallback> +where + Api: CallTypeApi, +{ + fn callback_name(&self) -> &'static str; + + fn overwrite_with_serialized_args(&self, cb_closure_args_serialized: &mut ManagedBuffer); + + fn gas_for_callback(&self) -> u64; +} + +impl TxPromisesCallback for () +where + Api: CallTypeApi, +{ + fn callback_name(&self) -> &'static str { + "" + } + + fn overwrite_with_serialized_args(&self, cb_closure_args_serialized: &mut ManagedBuffer) { + cb_closure_args_serialized.overwrite(&[]); + } + + fn gas_for_callback(&self) -> u64 { + 0 + } +} + +impl TxCallback> for CallbackClosureWithGas where Api: CallTypeApi {} +impl TxPromisesCallback for CallbackClosureWithGas +where + Api: CallTypeApi, +{ + fn callback_name(&self) -> &'static str { + self.closure.callback_name + } + + fn overwrite_with_serialized_args(&self, cb_closure_args_serialized: &mut ManagedBuffer) { + self.closure + .closure_args + .serialize_overwrite(cb_closure_args_serialized); + } + + fn gas_for_callback(&self) -> u64 { + self.gas_for_callback + } +} + +impl + Tx, (), To, Payment, Gas, FunctionCall, CallbackClosure> +where + Api: CallTypeApi, + To: TxToSpecified>, + Payment: TxPayment>, + Gas: TxGas>, +{ + pub fn gas_for_callback( + self, + gas: u64, + ) -> Tx, (), To, Payment, Gas, FunctionCall, CallbackClosureWithGas> + { + Tx { + env: self.env, + from: self.from, + to: self.to, + payment: self.payment, + gas: self.gas, + data: self.data, + callback: CallbackClosureWithGas { + closure: self.callback, + gas_for_callback: gas, + }, + } + } +} + +impl + Tx, (), To, Payment, ExplicitGas, FunctionCall, Callback> +where + Api: CallTypeApi, + To: TxToSpecified>, + Payment: TxPayment>, + Callback: TxPromisesCallback, +{ + // #[cfg(feature = "promises")] + pub fn async_call_promise(self) { + let callback_name = self.callback.callback_name(); + let mut cb_closure_args_serialized = + ManagedBuffer::::from_raw_handle(const_handles::MBUF_TEMPORARY_1); + self.callback + .overwrite_with_serialized_args(&mut cb_closure_args_serialized); + let extra_gas_for_callback = self.callback.gas_for_callback(); + + let normalized = self.normalize_tx(); + + SendRawWrapper::::new().create_async_call_raw( + &normalized.to, + &normalized.payment.value, + &normalized.data.function_name, + &normalized.data.arg_buffer, + callback_name, + callback_name, + normalized.gas.0, + extra_gas_for_callback, + &cb_closure_args_serialized, + ) + } +} diff --git a/framework/base/src/types/interaction/tx_async_te.rs b/framework/base/src/types/interaction/tx_async_te.rs new file mode 100644 index 0000000000..4f7dfc292b --- /dev/null +++ b/framework/base/src/types/interaction/tx_async_te.rs @@ -0,0 +1,58 @@ +use core::marker::PhantomData; + +use crate::{ + api::{BlockchainApiImpl, CallTypeApi}, + contract_base::BlockchainWrapper, + types::{ManagedAddress, ManagedBuffer}, +}; + +use super::{ + contract_call_exec::TRANSFER_EXECUTE_DEFAULT_LEFTOVER, AnnotatedValue, AsyncCall, ExplicitGas, + FunctionCall, Tx, TxBaseWithEnv, TxData, TxEnv, TxFrom, TxGas, TxPayment, TxScEnv, + TxToSpecified, +}; + +impl Tx, From, To, Payment, Gas, FC, ()> +where + Api: CallTypeApi, + From: TxFrom>, + To: TxToSpecified>, + Payment: TxPayment>, + Gas: TxGas>, + FC: TxData> + Into>, +{ + fn transfer_execute_with_gas(self, gas_limit: u64) { + self.to.with_value_ref(|to| { + self.payment + .perform_transfer_execute(&self.env, to, gas_limit, self.data.into()); + }); + } + + pub fn transfer_execute(self) { + let gas_limit: u64; + if self.data.is_no_call() { + if self.payment.is_no_payment() { + return; + } else { + gas_limit = 0; + } + } else { + gas_limit = self.gas.resolve_gas(&self.env); + } + + self.transfer_execute_with_gas(gas_limit); + } +} + +impl Tx, From, To, Payment, (), (), ()> +where + Api: CallTypeApi, + From: TxFrom>, + To: TxToSpecified>, + Payment: TxPayment>, +{ + /// Only allowed for simple transfers. + pub fn transfer(self) { + self.transfer_execute_with_gas(0) + } +} diff --git a/framework/base/src/types/interaction/tx_callback.rs b/framework/base/src/types/interaction/tx_callback.rs new file mode 100644 index 0000000000..a7e5710e21 --- /dev/null +++ b/framework/base/src/types/interaction/tx_callback.rs @@ -0,0 +1,28 @@ +use crate::{ + api::ManagedTypeApi, + formatter::SCLowerHex, + types::{ManagedBuffer, ManagedBufferCachedBuilder}, +}; + +use super::{FunctionCall, TxEnv}; + +pub trait TxCallback +where + Env: TxEnv, +{ +} + +pub trait TxRunnableCallback: TxCallback +where + Env: TxEnv, +{ + fn run_callback(self, env: &Env); +} + +impl TxCallback for () where Env: TxEnv {} +impl TxRunnableCallback for () +where + Env: TxEnv, +{ + fn run_callback(self, _env: &Env) {} +} diff --git a/framework/base/src/types/interaction/tx_data.rs b/framework/base/src/types/interaction/tx_data.rs new file mode 100644 index 0000000000..f76f9edb4a --- /dev/null +++ b/framework/base/src/types/interaction/tx_data.rs @@ -0,0 +1,56 @@ +use crate::{ + api::ManagedTypeApi, + formatter::SCLowerHex, + types::{ManagedBuffer, ManagedBufferCachedBuilder}, +}; + +use super::{FunctionCall, TxEnv}; + +pub trait TxData +where + Env: TxEnv, +{ + fn is_no_call(&self) -> bool; + + fn to_call_data_string(&self) -> ManagedBuffer; +} + +pub trait TxDataFunctionCall: TxData + Into> +where + Env: TxEnv, +{ +} + +impl TxData for () +where + Env: TxEnv, +{ + fn is_no_call(&self) -> bool { + true + } + + fn to_call_data_string(&self) -> ManagedBuffer { + ManagedBuffer::new() + } +} +impl TxDataFunctionCall for () where Env: TxEnv {} + +impl TxData for FunctionCall +where + Env: TxEnv, +{ + fn is_no_call(&self) -> bool { + self.is_empty() + } + + fn to_call_data_string(&self) -> ManagedBuffer { + let mut result = ManagedBufferCachedBuilder::default(); + result.append_managed_buffer(&self.function_name); + for arg in self.arg_buffer.raw_arg_iter() { + result.append_bytes(b"@"); + SCLowerHex::fmt(&*arg, &mut result); + } + result.into_managed_buffer() + } +} +impl TxDataFunctionCall for FunctionCall where Env: TxEnv {} diff --git a/framework/base/src/types/interaction/tx_env.rs b/framework/base/src/types/interaction/tx_env.rs new file mode 100644 index 0000000000..59e5406970 --- /dev/null +++ b/framework/base/src/types/interaction/tx_env.rs @@ -0,0 +1,24 @@ +use core::marker::PhantomData; + +use crate::{ + api::CallTypeApi, + types::{ManagedAddress, ManagedBuffer}, +}; + +use super::AnnotatedValue; + +pub trait TxEnv: Sized { + type Api: CallTypeApi; + + fn annotate_from(&mut self, from: &From) + where + From: AnnotatedValue>; + + fn annotate_to(&mut self, to: &To) + where + To: AnnotatedValue>; + + fn resolve_sender_address(&self) -> ManagedAddress; + + fn default_gas(&self) -> u64; +} diff --git a/framework/base/src/types/interaction/tx_env_sc.rs b/framework/base/src/types/interaction/tx_env_sc.rs new file mode 100644 index 0000000000..05fa0265be --- /dev/null +++ b/framework/base/src/types/interaction/tx_env_sc.rs @@ -0,0 +1,70 @@ +use core::marker::PhantomData; + +use crate::{ + api::{BlockchainApiImpl, CallTypeApi}, + contract_base::BlockchainWrapper, + types::{ManagedAddress, ManagedBuffer}, +}; + +use super::{ + contract_call_exec::TRANSFER_EXECUTE_DEFAULT_LEFTOVER, AnnotatedValue, AsyncCall, ExplicitGas, + FunctionCall, Tx, TxBaseWithEnv, TxData, TxEnv, TxFrom, TxGas, TxPayment, TxToSpecified, +}; + +pub struct TxScEnv +where + Api: CallTypeApi, +{ + _phantom: PhantomData, +} + +impl Default for TxScEnv +where + Api: CallTypeApi, +{ + fn default() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +impl TxBaseWithEnv> +where + Api: CallTypeApi, +{ + pub fn new_tx_from_sc() -> Self { + Tx::new_with_env(TxScEnv::default()) + } +} + +impl TxEnv for TxScEnv +where + Api: CallTypeApi, +{ + type Api = Api; + + fn annotate_from(&mut self, _from: &From) + where + From: AnnotatedValue>, + { + } + + fn annotate_to(&mut self, _to: &To) + where + To: AnnotatedValue>, + { + } + + fn resolve_sender_address(&self) -> ManagedAddress { + BlockchainWrapper::::new().get_sc_address() + } + + fn default_gas(&self) -> u64 { + let mut gas_left = Api::blockchain_api_impl().get_gas_left(); + if gas_left > TRANSFER_EXECUTE_DEFAULT_LEFTOVER { + gas_left -= TRANSFER_EXECUTE_DEFAULT_LEFTOVER; + } + gas_left + } +} diff --git a/framework/base/src/types/interaction/tx_from.rs b/framework/base/src/types/interaction/tx_from.rs new file mode 100644 index 0000000000..83db2cb85a --- /dev/null +++ b/framework/base/src/types/interaction/tx_from.rs @@ -0,0 +1,46 @@ +use crate::{api::CallTypeApi, contract_base::BlockchainWrapper, types::ManagedAddress}; + +use super::{AnnotatedValue, TxEnv}; + +pub trait TxFrom +where + Env: TxEnv, +{ + fn resolve_address(&self, env: &Env) -> ManagedAddress; +} + +pub trait TxFromSpecified: + TxFrom + AnnotatedValue> +where + Env: TxEnv, +{ +} + +impl TxFrom for () +where + Env: TxEnv, +{ + fn resolve_address(&self, env: &Env) -> ManagedAddress { + env.resolve_sender_address() + } +} + +impl TxFrom for ManagedAddress +where + Env: TxEnv, +{ + fn resolve_address(&self, _env: &Env) -> ManagedAddress { + self.clone() + } +} +impl TxFromSpecified for ManagedAddress where Env: TxEnv {} + +impl TxFrom for &ManagedAddress +where + Env: TxEnv, +{ + fn resolve_address(&self, _env: &Env) -> ManagedAddress { + (*self).clone() + } +} +impl TxFromSpecified for &ManagedAddress where Env: TxEnv {} diff --git a/framework/base/src/types/interaction/tx_gas.rs b/framework/base/src/types/interaction/tx_gas.rs new file mode 100644 index 0000000000..eec6693837 --- /dev/null +++ b/framework/base/src/types/interaction/tx_gas.rs @@ -0,0 +1,31 @@ +use crate::api::{BlockchainApiImpl, CallTypeApi}; + +use super::{contract_call_exec::TRANSFER_EXECUTE_DEFAULT_LEFTOVER, TxEnv}; + +pub trait TxGas +where + Env: TxEnv, +{ + fn resolve_gas(&self, env: &Env) -> u64; +} + +impl TxGas for () +where + Env: TxEnv, +{ + fn resolve_gas(&self, env: &Env) -> u64 { + env.default_gas() + } +} + +pub struct ExplicitGas(pub u64); + +impl TxGas for ExplicitGas +where + Env: TxEnv, +{ + #[inline] + fn resolve_gas(&self, _env: &Env) -> u64 { + self.0 + } +} diff --git a/framework/base/src/types/interaction/tx_payment.rs b/framework/base/src/types/interaction/tx_payment.rs new file mode 100644 index 0000000000..66fa7b6785 --- /dev/null +++ b/framework/base/src/types/interaction/tx_payment.rs @@ -0,0 +1,338 @@ +use num_traits::Zero; + +use crate::{ + api::{CallTypeApi, ManagedTypeApi}, + contract_base::SendRawWrapper, + types::{ + BigUint, EgldOrEsdtTokenPayment, EgldOrMultiEsdtPayment, EgldPayment, EsdtTokenPayment, + ManagedAddress, MultiEsdtPayment, + }, +}; + +use super::{FunctionCall, TxEnv, TxFrom}; + +/// Temporary structure for returning a normalized transfer. +pub struct PaymentConversionResult +where + Api: ManagedTypeApi, +{ + pub to: ManagedAddress, + pub egld_payment: EgldPayment, + pub fc: FunctionCall, +} + +pub trait TxPayment +where + Env: TxEnv, +{ + fn is_no_payment(&self) -> bool; + + fn convert_tx_data( + self, + env: &Env, + from: &From, + to: ManagedAddress, + fc: FunctionCall, + ) -> PaymentConversionResult + where + From: TxFrom; + + fn perform_transfer_execute( + self, + env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ); +} + +impl TxPayment for () +where + Env: TxEnv, +{ + fn is_no_payment(&self) -> bool { + true + } + + fn convert_tx_data( + self, + _env: &Env, + _from: &From, + to: ManagedAddress, + fc: FunctionCall, + ) -> PaymentConversionResult + where + From: TxFrom, + { + PaymentConversionResult { + to, + egld_payment: EgldPayment::no_payment(), + fc, + } + } + + fn perform_transfer_execute( + self, + env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ) { + EgldPayment::no_payment().perform_transfer_execute(env, to, gas_limit, fc); + } +} + +impl TxPayment for EgldPayment +where + Env: TxEnv, +{ + fn is_no_payment(&self) -> bool { + self.value == 0u32 + } + + fn convert_tx_data( + self, + _env: &Env, + _from: &From, + to: ManagedAddress, + fc: FunctionCall, + ) -> PaymentConversionResult + where + From: TxFrom, + { + PaymentConversionResult { + to, + egld_payment: self, + fc, + } + } + + fn perform_transfer_execute( + self, + _env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ) { + let _ = SendRawWrapper::::new().direct_egld_execute( + to, + &self.value, + gas_limit, + &fc.function_name, + &fc.arg_buffer, + ); + } +} + +impl TxPayment for EsdtTokenPayment +where + Env: TxEnv, +{ + fn is_no_payment(&self) -> bool { + self.amount == 0u32 + } + + fn convert_tx_data( + self, + env: &Env, + from: &From, + to: ManagedAddress, + fc: FunctionCall, + ) -> PaymentConversionResult + where + From: TxFrom, + { + if self.token_nonce == 0 { + convert_tx_data_fungible(self, to, fc) + } else { + convert_tx_data_nft(self, from.resolve_address(env), to, fc) + } + } + + fn perform_transfer_execute( + self, + env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ) { + MultiEsdtPayment::from_single_item(self).perform_transfer_execute(env, to, gas_limit, fc); + } +} + +impl TxPayment for MultiEsdtPayment +where + Env: TxEnv, +{ + fn is_no_payment(&self) -> bool { + self.is_empty() + } + + fn convert_tx_data( + self, + env: &Env, + from: &From, + to: ManagedAddress, + fc: FunctionCall, + ) -> PaymentConversionResult + where + From: TxFrom, + { + match self.len() { + 0 => ().convert_tx_data(env, from, to, fc), + 1 => self.get(0).convert_tx_data(env, from, to, fc), + _ => convert_tx_data_multi(self, from.resolve_address(env), to, fc), + } + } + + fn perform_transfer_execute( + self, + _env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ) { + let _ = SendRawWrapper::::new().multi_esdt_transfer_execute( + to, + &self, + gas_limit, + &fc.function_name, + &fc.arg_buffer, + ); + } +} + +impl TxPayment for EgldOrEsdtTokenPayment +where + Env: TxEnv, +{ + fn is_no_payment(&self) -> bool { + self.amount == 0u32 + } + + fn convert_tx_data( + self, + env: &Env, + from: &From, + to: ManagedAddress, + fc: FunctionCall, + ) -> PaymentConversionResult + where + From: TxFrom, + { + self.map_egld_or_esdt( + (to, fc), + |(to, fc), amount| EgldPayment::from(amount).convert_tx_data(env, from, to, fc), + |(to, fc), esdt_payment| esdt_payment.convert_tx_data(env, from, to, fc), + ) + } + + fn perform_transfer_execute( + self, + env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ) { + self.map_egld_or_esdt( + (to, fc), + |(to, fc), amount| { + EgldPayment::from(amount).perform_transfer_execute(env, to, gas_limit, fc) + }, + |(to, fc), esdt_payment| esdt_payment.perform_transfer_execute(env, to, gas_limit, fc), + ) + } +} + +impl TxPayment for EgldOrMultiEsdtPayment +where + Env: TxEnv, +{ + fn is_no_payment(&self) -> bool { + self.is_empty() + } + + fn convert_tx_data( + self, + env: &Env, + from: &From, + to: ManagedAddress, + fc: FunctionCall, + ) -> PaymentConversionResult + where + From: TxFrom, + { + match self { + EgldOrMultiEsdtPayment::Egld(egld_amount) => { + EgldPayment::from(egld_amount).convert_tx_data(env, from, to, fc) + }, + EgldOrMultiEsdtPayment::MultiEsdt(multi_esdt_payment) => { + multi_esdt_payment.convert_tx_data(env, from, to, fc) + }, + } + } + + fn perform_transfer_execute( + self, + env: &Env, + to: &ManagedAddress, + gas_limit: u64, + fc: FunctionCall, + ) { + match self { + EgldOrMultiEsdtPayment::Egld(egld_amount) => { + EgldPayment::from(egld_amount).perform_transfer_execute(env, to, gas_limit, fc) + }, + EgldOrMultiEsdtPayment::MultiEsdt(multi_esdt_payment) => { + multi_esdt_payment.perform_transfer_execute(env, to, gas_limit, fc) + }, + } + } +} + +fn convert_tx_data_fungible( + payment: EsdtTokenPayment, + to: ManagedAddress, + fc: FunctionCall, +) -> PaymentConversionResult +where + Api: ManagedTypeApi, +{ + PaymentConversionResult { + to, + egld_payment: EgldPayment::no_payment(), + fc: fc.convert_to_single_transfer_fungible_call(payment), + } +} + +fn convert_tx_data_nft( + payment: EsdtTokenPayment, + from: ManagedAddress, + to: ManagedAddress, + fc: FunctionCall, +) -> PaymentConversionResult +where + Api: ManagedTypeApi, +{ + PaymentConversionResult { + to: from, + egld_payment: EgldPayment::no_payment(), + fc: fc.convert_to_single_transfer_nft_call(&to, payment), + } +} + +fn convert_tx_data_multi( + payment: MultiEsdtPayment, + from: ManagedAddress, + to: ManagedAddress, + fc: FunctionCall, +) -> PaymentConversionResult +where + Api: ManagedTypeApi, +{ + PaymentConversionResult { + to: from, + egld_payment: EgldPayment::no_payment(), + fc: fc.convert_to_multi_transfer_esdt_call(&to, payment), + } +} diff --git a/framework/base/src/types/interaction/tx_to.rs b/framework/base/src/types/interaction/tx_to.rs new file mode 100644 index 0000000000..af733870dd --- /dev/null +++ b/framework/base/src/types/interaction/tx_to.rs @@ -0,0 +1,23 @@ +use crate::{api::ManagedTypeApi, types::ManagedAddress}; + +use super::{AnnotatedValue, TxEnv}; + +pub trait TxTo +where + Env: TxEnv, +{ +} + +impl TxTo for () where Env: TxEnv {} + +pub trait TxToSpecified: TxTo + AnnotatedValue> +where + Env: TxEnv, +{ +} + +impl TxTo for ManagedAddress where Env: TxEnv {} +impl TxToSpecified for ManagedAddress where Env: TxEnv {} + +impl TxTo for &ManagedAddress where Env: TxEnv {} +impl TxToSpecified for &ManagedAddress where Env: TxEnv {} diff --git a/framework/base/src/types/managed/basic/managed_buffer.rs b/framework/base/src/types/managed/basic/managed_buffer.rs index 7c08f32699..3e4347aa1d 100644 --- a/framework/base/src/types/managed/basic/managed_buffer.rs +++ b/framework/base/src/types/managed/basic/managed_buffer.rs @@ -159,6 +159,26 @@ where } } +impl From for ManagedBuffer +where + M: ManagedTypeApi, +{ + #[inline] + fn from(s: crate::types::heap::String) -> Self { + Self::new_from_bytes(s.as_bytes()) + } +} + +impl From<&crate::types::heap::String> for ManagedBuffer +where + M: ManagedTypeApi, +{ + #[inline] + fn from(s: &crate::types::heap::String) -> Self { + Self::new_from_bytes(s.as_bytes()) + } +} + impl Default for ManagedBuffer { #[inline] fn default() -> Self { 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 3e7ae8afb5..ed2c7083a7 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 @@ -26,7 +26,7 @@ use crate as multiversx_sc; // required by the ManagedVecItem derive #[repr(transparent)] #[derive(ManagedVecItem, Clone)] pub struct EgldOrEsdtTokenIdentifier { - data: ManagedOption>, + pub(crate) data: ManagedOption>, } impl EgldOrEsdtTokenIdentifier { 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 ed76946617..8d5e692610 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 @@ -53,6 +53,29 @@ impl EgldOrEsdtTokenPayment { ) } + /// Equivalent to a `match { Egld | Esdt }`. + /// + /// Context passed on from function to closures, to avoid ownership issues. + /// More precisely, since only one of the two closures `for_egld` and `for_esdt` is called, + /// it is ok for them to have fully owned access to anything from the environment. + /// The compiler doesn't know that only one of them can ever be called, + /// so if we pass context to both closures, it will complain that they are moved twice.. + pub fn map_egld_or_esdt(self, context: Context, for_egld: D, for_esdt: F) -> U + where + D: FnOnce(Context, BigUint) -> U, + F: FnOnce(Context, EsdtTokenPayment) -> U, + { + if self.token_identifier.data.is_some() { + let token_identifier = unsafe { self.token_identifier.data.unwrap_no_check() }; + for_esdt( + context, + EsdtTokenPayment::new(token_identifier, self.token_nonce, self.amount), + ) + } else { + for_egld(context, self.amount) + } + } + pub fn into_tuple(self) -> (EgldOrEsdtTokenIdentifier, u64, BigUint) { (self.token_identifier, self.token_nonce, self.amount) } diff --git a/framework/base/src/types/managed/wrapped/egld_or_multi_esdt_payment.rs b/framework/base/src/types/managed/wrapped/egld_or_multi_esdt_payment.rs index 653b76d5a0..bcd4d2cd03 100644 --- a/framework/base/src/types/managed/wrapped/egld_or_multi_esdt_payment.rs +++ b/framework/base/src/types/managed/wrapped/egld_or_multi_esdt_payment.rs @@ -24,3 +24,12 @@ pub enum EgldOrMultiEsdtPayment { } impl CodecFromSelf for EgldOrMultiEsdtPayment where M: ManagedTypeApi {} + +impl EgldOrMultiEsdtPayment { + pub fn is_empty(&self) -> bool { + match self { + EgldOrMultiEsdtPayment::Egld(egld_value) => egld_value == &0u32, + EgldOrMultiEsdtPayment::MultiEsdt(esdt_payments) => esdt_payments.is_empty(), + } + } +} diff --git a/framework/base/src/types/managed/wrapped/egld_payment.rs b/framework/base/src/types/managed/wrapped/egld_payment.rs new file mode 100644 index 0000000000..f4c190e899 --- /dev/null +++ b/framework/base/src/types/managed/wrapped/egld_payment.rs @@ -0,0 +1,30 @@ +use crate::{api::ManagedTypeApi, types::BigUint}; + +/// Simple newtype wrapper around a BigUint value. +/// +/// Its purpose is to indicate +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EgldPayment +where + Api: ManagedTypeApi + 'static, +{ + pub value: BigUint, +} + +impl From> for EgldPayment +where + Api: ManagedTypeApi + 'static, +{ + fn from(value: BigUint) -> Self { + EgldPayment { value } + } +} + +impl EgldPayment +where + Api: ManagedTypeApi + 'static, +{ + pub fn no_payment() -> Self { + EgldPayment::from(BigUint::zero()) + } +} diff --git a/framework/base/src/types/managed/wrapped/managed_address.rs b/framework/base/src/types/managed/wrapped/managed_address.rs index 1ce1524bb6..be24c8e99b 100644 --- a/framework/base/src/types/managed/wrapped/managed_address.rs +++ b/framework/base/src/types/managed/wrapped/managed_address.rs @@ -8,10 +8,12 @@ use crate::{ NestedDecode, NestedDecodeInput, NestedEncode, NestedEncodeOutput, TopDecode, TopDecodeInput, TopEncode, TopEncodeOutput, TryStaticCast, }, - formatter::{hex_util::encode_bytes_as_hex, FormatByteReceiver, SCLowerHex}, + formatter::{hex_util::encode_bytes_as_hex, FormatBuffer, FormatByteReceiver, SCLowerHex}, types::{heap::Address, ManagedBuffer, ManagedByteArray, ManagedType}, }; +use super::ManagedBufferCachedBuilder; + #[repr(transparent)] #[derive(Clone)] pub struct ManagedAddress { @@ -250,6 +252,14 @@ impl SCLowerHex for ManagedAddress { } } +impl ManagedAddress { + pub fn hex_expr(&self) -> ManagedBuffer { + let mut result = ManagedBufferCachedBuilder::new_from_slice(b"0x"); + result.append_lower_hex(self); + result.into_managed_buffer() + } +} + impl core::fmt::Debug for ManagedAddress { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("ManagedAddress") diff --git a/framework/base/src/types/managed/wrapped/managed_option.rs b/framework/base/src/types/managed/wrapped/managed_option.rs index 64766f43db..1fe4366eb0 100644 --- a/framework/base/src/types/managed/wrapped/managed_option.rs +++ b/framework/base/src/types/managed/wrapped/managed_option.rs @@ -73,9 +73,18 @@ where !self.is_none() } + /// Assumes that value is Some and unwraps without checking. + /// + /// # Safety + /// + /// Must always be called under an `if` checking `.is_some()`, otherwise will lead to undefined behaviour. + pub unsafe fn unwrap_no_check(self) -> T { + T::from_handle(self.handle) + } + pub fn into_option(self) -> Option { if self.is_some() { - Some(T::from_handle(self.handle)) + Some(unsafe { self.unwrap_no_check() }) } else { None } @@ -91,7 +100,7 @@ where pub fn unwrap_or_else T>(self, f: F) -> T { if self.is_some() { - T::from_handle(self.handle) + unsafe { self.unwrap_no_check() } } else { f() } @@ -107,7 +116,7 @@ where F: FnOnce(T) -> U, { if self.is_some() { - ManagedOption::::some(f(T::from_handle(self.handle))) + ManagedOption::::some(f(unsafe { self.unwrap_no_check() })) } else { ManagedOption::::none() } @@ -119,7 +128,7 @@ where F: FnOnce(T) -> U, { if self.is_some() { - f(T::from_handle(self.handle)) + f(unsafe { self.unwrap_no_check() }) } else { default() } diff --git a/framework/base/src/types/managed/wrapped/mod.rs b/framework/base/src/types/managed/wrapped/mod.rs index 8ac485fa38..dee1161f44 100644 --- a/framework/base/src/types/managed/wrapped/mod.rs +++ b/framework/base/src/types/managed/wrapped/mod.rs @@ -1,6 +1,7 @@ mod egld_or_esdt_token_identifier; mod egld_or_esdt_token_payment; mod egld_or_multi_esdt_payment; +mod egld_payment; mod encoded_managed_vec_item; mod esdt_token_data; mod esdt_token_payment; @@ -21,6 +22,7 @@ mod token_identifier; pub use egld_or_esdt_token_identifier::EgldOrEsdtTokenIdentifier; pub use egld_or_esdt_token_payment::EgldOrEsdtTokenPayment; pub use egld_or_multi_esdt_payment::EgldOrMultiEsdtPayment; +pub use egld_payment::EgldPayment; pub(crate) use encoded_managed_vec_item::EncodedManagedVecItem; pub use esdt_token_data::EsdtTokenData; pub use esdt_token_payment::{EsdtTokenPayment, MultiEsdtPayment}; diff --git a/framework/scenario/src/facade.rs b/framework/scenario/src/facade.rs index f53625895e..b6e2eef3a8 100644 --- a/framework/scenario/src/facade.rs +++ b/framework/scenario/src/facade.rs @@ -1,12 +1,16 @@ mod contract_info; mod debugger_backend; +mod scenario_callbacks; mod scenario_world; mod scenario_world_runner; mod scenario_world_steps; mod scenario_world_steps_deprecated; +mod scenario_world_steps_tx; mod scenario_world_whitebox; mod whitebox_contract; pub use contract_info::ContractInfo; +pub use scenario_callbacks::*; pub use scenario_world::ScenarioWorld; +pub use scenario_world_steps_tx::*; pub use whitebox_contract::WhiteboxContract; diff --git a/framework/scenario/src/facade/contract_info.rs b/framework/scenario/src/facade/contract_info.rs index 35b3ee1afb..8376e859f8 100644 --- a/framework/scenario/src/facade/contract_info.rs +++ b/framework/scenario/src/facade/contract_info.rs @@ -1,5 +1,9 @@ use std::ops::{Deref, DerefMut}; +use multiversx_sc::types::{ + AnnotatedValue, ManagedBuffer, TxEnv, TxFrom, TxFromSpecified, TxTo, TxToSpecified, +}; + use crate::multiversx_sc::{ api::ManagedTypeApi, codec::{CodecFrom, EncodeErrorHandler, TopEncode, TopEncodeOutput}, @@ -90,3 +94,49 @@ impl CodecFrom> for Address {} impl CodecFrom<&ContractInfo

> for Address {} impl CodecFrom> for ManagedAddress {} impl CodecFrom<&ContractInfo

> for ManagedAddress {} + +impl AnnotatedValue> for &ContractInfo

+where + Env: TxEnv, + P: ProxyObjBase, +{ + fn annotation(&self, _env: &Env) -> ManagedBuffer { + self.scenario_address_expr.original.as_str().into() + } + + fn into_value(self) -> ManagedAddress { + (&self.scenario_address_expr.value).into() + } + + fn with_value_ref)>(&self, f: F) { + let ma: ManagedAddress = (&self.scenario_address_expr.value).into(); + f(&ma); + } +} +impl TxFrom for &ContractInfo

+where + Env: TxEnv, + P: ProxyObjBase, +{ + fn resolve_address(&self, _env: &Env) -> ManagedAddress { + (&self.scenario_address_expr.value).into() + } +} +impl TxFromSpecified for &ContractInfo

+where + Env: TxEnv, + P: ProxyObjBase, +{ +} +impl TxTo for &ContractInfo

+where + Env: TxEnv, + P: ProxyObjBase, +{ +} +impl TxToSpecified for &ContractInfo

+where + Env: TxEnv, + P: ProxyObjBase, +{ +} diff --git a/framework/scenario/src/facade/scenario_callbacks.rs b/framework/scenario/src/facade/scenario_callbacks.rs new file mode 100644 index 0000000000..1429f7ed16 --- /dev/null +++ b/framework/scenario/src/facade/scenario_callbacks.rs @@ -0,0 +1,19 @@ +use multiversx_sc::types::{TxCallback, TxRunnableCallback}; + +use crate::scenario_model::TxResponse; + +use super::ScenarioTxEnvironment; + +pub struct WithTxResult(pub F) +where + F: FnOnce(&TxResponse); + +impl TxCallback for WithTxResult where F: FnOnce(&TxResponse) {} +impl TxRunnableCallback for WithTxResult +where + F: FnOnce(&TxResponse), +{ + fn run_callback(self, env: &ScenarioTxEnvironment) { + (self.0)(env.response.as_ref().unwrap()) + } +} diff --git a/framework/scenario/src/facade/scenario_world_steps_tx.rs b/framework/scenario/src/facade/scenario_world_steps_tx.rs new file mode 100644 index 0000000000..ad8134edca --- /dev/null +++ b/framework/scenario/src/facade/scenario_world_steps_tx.rs @@ -0,0 +1,99 @@ +use std::path::PathBuf; + +use multiversx_sc::types::{ + AnnotatedValue, FunctionCall, ManagedAddress, Tx, TxBaseWithEnv, TxEnv, TxFromSpecified, TxGas, + TxPayment, TxRunnableCallback, TxToSpecified, +}; + +use crate::{ + api::StaticApi, + facade::ScenarioWorld, + scenario_model::{ScCallStep, TxResponse}, +}; + +#[derive(Default, Debug, Clone)] +pub struct ScenarioTxEnvironment { + pub context_path: PathBuf, + pub from_annotation: Option, + pub to_annotation: Option, + pub response: Option, +} + +impl TxEnv for ScenarioTxEnvironment { + type Api = StaticApi; + + fn annotate_from(&mut self, to: &From) + where + From: AnnotatedValue>, + { + self.from_annotation = Some(to.annotation(self).to_string()) + } + + fn annotate_to(&mut self, to: &To) + where + To: AnnotatedValue>, + { + self.to_annotation = Some(to.annotation(self).to_string()) + } + + fn resolve_sender_address(&self) -> ManagedAddress { + panic!("Explicit sender address expected") + } + + fn default_gas(&self) -> u64 { + // TODO: annotate + 5_000_000 + } +} + +pub type TxScenarioBase = TxBaseWithEnv; + +pub trait ScenarioTx { + fn run_as_scenario_step(self, world: &mut ScenarioWorld); +} + +impl ScenarioWorld { + fn tx_env(&self) -> ScenarioTxEnvironment { + ScenarioTxEnvironment { + context_path: self.current_dir.clone(), + ..Default::default() + } + } + + pub fn tx(&mut self, f: F) -> &mut Self + where + STx: ScenarioTx, + F: FnOnce(TxScenarioBase) -> STx, + { + let env = self.tx_env(); + let tx_base = TxScenarioBase::new_with_env(env); + let tx = f(tx_base); + tx.run_as_scenario_step(self); + self + } +} + +impl ScenarioTx + for Tx, Callback> +where + From: TxFromSpecified, + To: TxToSpecified, + Payment: TxPayment, + Gas: TxGas, + Callback: TxRunnableCallback, +{ + fn run_as_scenario_step(self, world: &mut ScenarioWorld) { + let mut env = self.env; + let mut step = ScCallStep::new() + .from(env.from_annotation.as_ref().unwrap().as_str()) + .to(env.to_annotation.as_ref().unwrap().as_str()) + .function(self.data.function_name.to_string().as_str()); + for arg in self.data.arg_buffer.iter_buffers() { + step = step.argument(arg.to_vec()); + } + + world.sc_call(&mut step); + env.response = step.response; + self.callback.run_callback(&env); + } +} diff --git a/framework/scenario/src/lib.rs b/framework/scenario/src/lib.rs index 0110147ed3..8f240fe3ee 100644 --- a/framework/scenario/src/lib.rs +++ b/framework/scenario/src/lib.rs @@ -44,7 +44,7 @@ pub use crate::scenario as mandos_system; // Re-exporting the whole mandos crate for easier use in tests. pub use multiversx_chain_scenario_format as scenario_format; -pub use facade::{ContractInfo, ScenarioWorld, WhiteboxContract}; +pub use facade::{ContractInfo, ScenarioWorld, WhiteboxContract, WithTxResult}; use std::path::Path;