From 4df7b0e4fe974f56f1f41a13342e02a7ed1ee264 Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Thu, 12 Oct 2023 09:13:23 +0300 Subject: [PATCH] execute custom user call --- common/transaction/src/lib.rs | 5 +- common/tx-batch-module/src/lib.rs | 53 +++++++++++---- esdt-safe/src/lib.rs | 52 +++++++++------ multi-transfer-esdt/Cargo.toml | 1 + multi-transfer-esdt/src/lib.rs | 106 ++++++++++++++++++++++-------- 5 files changed, 156 insertions(+), 61 deletions(-) diff --git a/common/transaction/src/lib.rs b/common/transaction/src/lib.rs index 61e0d20b..3712f184 100644 --- a/common/transaction/src/lib.rs +++ b/common/transaction/src/lib.rs @@ -9,8 +9,11 @@ pub mod transaction_status; pub const MIN_BLOCKS_FOR_FINALITY: u64 = 10; pub const TX_MULTIRESULT_NR_FIELDS: usize = 6; +pub type BatchId = u64; +pub type TxId = u64; pub type GasLimit = u64; pub type TxNonce = u64; + pub type BlockNonce = u64; pub type SenderAddress = ManagedAddress; pub type ReceiverAddress = ManagedAddress; @@ -24,7 +27,7 @@ pub type TxAsMultiValue = MultiValue7< Option>, >; pub type PaymentsVec = ManagedVec>; -pub type TxBatchSplitInFields = MultiValue2>>; +pub type TxBatchSplitInFields = MultiValue2>>; #[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, ManagedVecItem, Clone)] pub struct TransferData { diff --git a/common/tx-batch-module/src/lib.rs b/common/tx-batch-module/src/lib.rs index 545a4cca..da96a10d 100644 --- a/common/tx-batch-module/src/lib.rs +++ b/common/tx-batch-module/src/lib.rs @@ -4,9 +4,14 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); pub use batch_status::BatchStatus; -use transaction::{Transaction, TxBatchSplitInFields, MIN_BLOCKS_FOR_FINALITY}; +use transaction::{ + BatchId, GasLimit, Transaction, TxBatchSplitInFields, TxNonce, MIN_BLOCKS_FOR_FINALITY, +}; use tx_batch_mapper::TxBatchMapper; +pub const FIRST_BATCH_ID: BatchId = 1; +pub const MAX_GAS_LIMIT_PER_BATCH: GasLimit = 500_000_000; + pub mod batch_status; pub mod tx_batch_mapper; @@ -65,7 +70,7 @@ pub trait TxBatchModule { } #[view(getBatch)] - fn get_batch(&self, batch_id: u64) -> OptionalValue> { + fn get_batch(&self, batch_id: BatchId) -> OptionalValue> { let tx_batch = self.pending_batches(batch_id); if tx_batch.is_empty() { return OptionalValue::None; @@ -80,7 +85,7 @@ pub trait TxBatchModule { } #[view(getBatchStatus)] - fn get_batch_status(&self, batch_id: u64) -> BatchStatus { + fn get_batch_status(&self, batch_id: BatchId) -> BatchStatus { let first_batch_id = self.first_batch_id().get(); if batch_id < first_batch_id { return BatchStatus::AlreadyProcessed; @@ -115,17 +120,34 @@ pub trait TxBatchModule { // private - fn add_to_batch(&self, transaction: Transaction) -> u64 { + fn add_to_batch( + &self, + transaction: Transaction, + default_gas_limit: GasLimit, + ) -> BatchId { let first_batch_id = self.first_batch_id().get(); let last_batch_id = self.last_batch_id().get(); let mut last_batch = self.pending_batches(last_batch_id); - if self.is_batch_full(&last_batch, last_batch_id, first_batch_id) { + let gas_cost = match &transaction.opt_transfer_data { + Some(transfer_data) => transfer_data.gas_limit, + None => transaction.tokens.len() as u64 * default_gas_limit, + }; + + let gas_cost_mapper = self.total_gas_cost(last_batch_id); + let last_batch_total_gas_cost = gas_cost_mapper.get(); + let new_total_gas_cost = last_batch_total_gas_cost + gas_cost; + + if self.is_batch_full(&last_batch, last_batch_id, first_batch_id) + || new_total_gas_cost > MAX_GAS_LIMIT_PER_BATCH + { let (new_batch_id, _) = self.create_new_batch(transaction); + self.total_gas_cost(new_batch_id).set(gas_cost); new_batch_id } else { last_batch.push(transaction); + gas_cost_mapper.set(new_total_gas_cost); last_batch_id } @@ -135,7 +157,7 @@ pub trait TxBatchModule { fn add_multiple_tx_to_batch( &self, transactions: &ManagedVec>, - ) -> ManagedVec { + ) -> ManagedVec { if transactions.is_empty() { return ManagedVec::new(); } @@ -161,7 +183,7 @@ pub trait TxBatchModule { fn create_new_batch( &self, transaction: Transaction, - ) -> (u64, TxBatchMapper) { + ) -> (BatchId, TxBatchMapper) { let last_batch_id = self.last_batch_id().get(); let new_batch_id = last_batch_id + 1; @@ -176,8 +198,8 @@ pub trait TxBatchModule { fn is_batch_full( &self, tx_batch: &TxBatchMapper, - batch_id: u64, - first_batch_id: u64, + batch_id: BatchId, + first_batch_id: BatchId, ) -> bool { if tx_batch.is_empty() { return false; @@ -242,7 +264,7 @@ pub trait TxBatchModule { mapper.clear(); } - fn get_and_save_next_tx_id(&self) -> u64 { + fn get_and_save_next_tx_id(&self) -> TxNonce { self.last_tx_nonce().update(|last_tx_nonce| { *last_tx_nonce += 1; *last_tx_nonce @@ -253,17 +275,20 @@ pub trait TxBatchModule { #[view(getFirstBatchId)] #[storage_mapper("firstBatchId")] - fn first_batch_id(&self) -> SingleValueMapper; + fn first_batch_id(&self) -> SingleValueMapper; #[view(getLastBatchId)] #[storage_mapper("lastBatchId")] - fn last_batch_id(&self) -> SingleValueMapper; + fn last_batch_id(&self) -> SingleValueMapper; #[storage_mapper("pendingBatches")] - fn pending_batches(&self, batch_id: u64) -> TxBatchMapper; + fn pending_batches(&self, batch_id: BatchId) -> TxBatchMapper; + + #[storage_mapper("totalGasCost")] + fn total_gas_cost(&self, batch_id: BatchId) -> SingleValueMapper; #[storage_mapper("lastTxNonce")] - fn last_tx_nonce(&self) -> SingleValueMapper; + fn last_tx_nonce(&self) -> SingleValueMapper; // configurable diff --git a/esdt-safe/src/lib.rs b/esdt-safe/src/lib.rs index 7d2e1355..d7617ddf 100644 --- a/esdt-safe/src/lib.rs +++ b/esdt-safe/src/lib.rs @@ -4,12 +4,16 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); -use transaction::{transaction_status::TransactionStatus, Transaction, TransferData}; +use transaction::{ + transaction_status::TransactionStatus, BatchId, GasLimit, Transaction, TransferData, TxId, +}; +use tx_batch_module::FIRST_BATCH_ID; const DEFAULT_MAX_TX_BATCH_SIZE: usize = 10; const DEFAULT_MAX_TX_BATCH_BLOCK_DURATION: u64 = 100; // ~10 minutes const MAX_TRANSFERS_PER_TX: usize = 10; -const MAX_GAS_LIMIT: u64 = 300_000_000; + +const MAX_USER_TX_GAS_LIMIT: GasLimit = 300_000_000; #[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem)] pub struct NonceAmountPair { @@ -28,17 +32,16 @@ pub trait EsdtSafe: /// In case of SC gas limits, this value is provided by the user /// Will be used to compute the fees for the transfer #[init] - fn init(&self, sovereign_tx_gas_limit: BigUint) { - self.sovereign_tx_gas_limit().set(&sovereign_tx_gas_limit); + fn init(&self, sovereign_tx_gas_limit: GasLimit) { + self.sovereign_tx_gas_limit().set(sovereign_tx_gas_limit); - self.max_tx_batch_size() - .set_if_empty(DEFAULT_MAX_TX_BATCH_SIZE); + self.max_tx_batch_size().set(DEFAULT_MAX_TX_BATCH_SIZE); self.max_tx_batch_block_duration() - .set_if_empty(DEFAULT_MAX_TX_BATCH_BLOCK_DURATION); + .set(DEFAULT_MAX_TX_BATCH_BLOCK_DURATION); // batch ID 0 is considered invalid - self.first_batch_id().set_if_empty(1); - self.last_batch_id().set_if_empty(1); + self.first_batch_id().set(FIRST_BATCH_ID); + self.last_batch_id().set(FIRST_BATCH_ID); self.set_paused(true); } @@ -54,7 +57,7 @@ pub trait EsdtSafe: #[endpoint(setTransactionBatchStatus)] fn set_transaction_batch_status( &self, - batch_id: u64, + batch_id: BatchId, tx_statuses: MultiValueEncoded, ) { let first_batch_id = self.first_batch_id().get(); @@ -82,7 +85,7 @@ pub trait EsdtSafe: // tokens will remain locked forever in that case // otherwise, the whole batch would fail for token in &tx.tokens { - if self.is_local_role_set(&token.token_identifier, &EsdtLocalRole::Burn) { + if self.is_burn_role_set(&token) { self.send().esdt_local_burn( &token.token_identifier, token.token_nonce, @@ -164,7 +167,7 @@ pub trait EsdtSafe: if let OptionalValue::Some(transfer_data) = &opt_transfer_data { require!( - transfer_data.gas_limit <= MAX_GAS_LIMIT, + transfer_data.gas_limit <= MAX_USER_TX_GAS_LIMIT, "Gas limit too high" ); } @@ -201,7 +204,8 @@ pub trait EsdtSafe: is_refund_tx: false, }; - let batch_id = self.add_to_batch(tx); + let default_gas_cost = self.sovereign_tx_gas_limit().get(); + let batch_id = self.add_to_batch(tx, default_gas_cost); self.create_transaction_event(batch_id, tx_nonce); } @@ -265,24 +269,32 @@ pub trait EsdtSafe: }); } + fn is_burn_role_set(&self, payment: &EsdtTokenPayment) -> bool { + if payment.token_nonce == 0 { + self.is_local_role_set(&payment.token_identifier, &EsdtLocalRole::Burn) + } else { + self.is_local_role_set(&payment.token_identifier, &EsdtLocalRole::NftBurn) + } + } + // events #[event("createTransactionEvent")] - fn create_transaction_event(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + fn create_transaction_event(&self, #[indexed] batch_id: BatchId, #[indexed] tx_id: TxId); #[event("addRefundTransactionEvent")] fn add_refund_transaction_event( &self, - #[indexed] batch_id: u64, - #[indexed] tx_id: u64, - #[indexed] original_tx_id: u64, + #[indexed] batch_id: BatchId, + #[indexed] tx_id: TxId, + #[indexed] original_tx_id: TxId, ); #[event("setStatusEvent")] fn set_status_event( &self, - #[indexed] batch_id: u64, - #[indexed] tx_id: u64, + #[indexed] batch_id: BatchId, + #[indexed] tx_id: TxId, #[indexed] tx_status: TransactionStatus, ); @@ -297,5 +309,5 @@ pub trait EsdtSafe: #[view(getSovereignTxGasLimit)] #[storage_mapper("sovereignTxGasLimit")] - fn sovereign_tx_gas_limit(&self) -> SingleValueMapper; + fn sovereign_tx_gas_limit(&self) -> SingleValueMapper; } diff --git a/multi-transfer-esdt/Cargo.toml b/multi-transfer-esdt/Cargo.toml index 27407ba3..03b6638e 100644 --- a/multi-transfer-esdt/Cargo.toml +++ b/multi-transfer-esdt/Cargo.toml @@ -19,6 +19,7 @@ path = "../common/max-bridged-amount-module" [dependencies.multiversx-sc] version = "0.43.4" +features = ["promises"] [dev-dependencies.multiversx-sc-scenario] version = "0.43.4" diff --git a/multi-transfer-esdt/src/lib.rs b/multi-transfer-esdt/src/lib.rs index e3d0d482..07f180dc 100644 --- a/multi-transfer-esdt/src/lib.rs +++ b/multi-transfer-esdt/src/lib.rs @@ -2,28 +2,33 @@ multiversx_sc::imports!(); +use core::ops::Deref; + use transaction::{ - PaymentsVec, StolenFromFrameworkEsdtTokenData, Transaction, TxBatchSplitInFields, + BatchId, GasLimit, PaymentsVec, StolenFromFrameworkEsdtTokenData, Transaction, + TxBatchSplitInFields, TxId, TxNonce, }; +use tx_batch_module::FIRST_BATCH_ID; const DEFAULT_MAX_TX_BATCH_SIZE: usize = 10; const DEFAULT_MAX_TX_BATCH_BLOCK_DURATION: u64 = u64::MAX; const NFT_AMOUNT: u32 = 1; +const CALLBACK_GAS: GasLimit = 1_000_000; // Increase if not enough + #[multiversx_sc::contract] pub trait MultiTransferEsdt: tx_batch_module::TxBatchModule + max_bridged_amount_module::MaxBridgedAmountModule { #[init] fn init(&self) { - self.max_tx_batch_size() - .set_if_empty(DEFAULT_MAX_TX_BATCH_SIZE); + self.max_tx_batch_size().set(DEFAULT_MAX_TX_BATCH_SIZE); self.max_tx_batch_block_duration() - .set_if_empty(DEFAULT_MAX_TX_BATCH_BLOCK_DURATION); + .set(DEFAULT_MAX_TX_BATCH_BLOCK_DURATION); // batch ID 0 is considered invalid - self.first_batch_id().set_if_empty(1); - self.last_batch_id().set_if_empty(1); + self.first_batch_id().set(FIRST_BATCH_ID); + self.last_batch_id().set(FIRST_BATCH_ID); } #[endpoint] @@ -33,11 +38,11 @@ pub trait MultiTransferEsdt: #[endpoint(batchTransferEsdtToken)] fn batch_transfer_esdt_token( &self, - batch_id: u64, + batch_id: BatchId, transfers: MultiValueEncoded>, ) { - let mut valid_payments_list = ManagedVec::new(); - let mut valid_dest_addresses_list = ManagedVec::new(); + let mut successful_tx_list = ManagedVec::new(); + let mut all_tokens_to_send = ManagedVec::new(); let mut refund_tx_list = ManagedVec::new(); let own_sc_address = self.blockchain().get_sc_address(); @@ -70,15 +75,12 @@ pub trait MultiTransferEsdt: } let user_tokens_to_send = self.mint_tokens(tokens_to_send, sent_token_data); + all_tokens_to_send.push(user_tokens_to_send); - // emit event before the actual transfer so we don't have to save the tx_nonces as well - self.transfer_performed_event(batch_id, sov_tx.nonce); - - valid_dest_addresses_list.push(sov_tx.to); - valid_payments_list.push(user_tokens_to_send); + successful_tx_list.push(sov_tx); } - self.distribute_payments(valid_dest_addresses_list, valid_payments_list); + self.distribute_payments(batch_id, successful_tx_list, all_tokens_to_send); self.add_multiple_tx_to_batch(&refund_tx_list); } @@ -103,8 +105,8 @@ pub trait MultiTransferEsdt: &self, token: &EsdtTokenPayment, dest: &ManagedAddress, - batch_id: u64, - tx_nonce: u64, + batch_id: BatchId, + tx_nonce: TxNonce, sc_shard: u32, ) -> bool { if token.token_nonce == 0 { @@ -232,29 +234,81 @@ pub trait MultiTransferEsdt: fn distribute_payments( &self, - dest_addresses: ManagedVec, - payments: ManagedVec>, + batch_id: BatchId, + tx_list: ManagedVec>, + tokens_list: ManagedVec>, + ) { + for (tx, payments) in tx_list.iter().zip(tokens_list.iter()) { + match &tx.opt_transfer_data { + Some(tx_data) => { + let mut args = ManagedArgBuffer::new(); + for arg in &tx_data.args { + args.push_arg(arg); + } + + self.send() + .contract_call::<()>(tx.to.clone(), tx_data.function.clone()) + .with_raw_arguments(args) + .with_multi_token_transfer(payments.deref().clone()) + .with_gas_limit(tx_data.gas_limit) + .async_call_promise() + .with_extra_gas_for_callback(CALLBACK_GAS) + .with_callback(self.callbacks().transfer_callback(batch_id, tx.nonce, tx)) + .register_promise(); + } + None => { + self.send().direct_multi(&tx.to, payments.deref()); + + self.transfer_performed_event(batch_id, tx.nonce); + } + } + } + } + + #[promises_callback] + fn transfer_callback( + &self, + batch_id: BatchId, + tx_nonce: TxNonce, + original_tx: Transaction, + #[call_result] result: ManagedAsyncCallResult, ) { - for (dest, user_tokens) in dest_addresses.iter().zip(payments.iter()) { - self.send().direct_multi(&dest, &user_tokens); + match result { + ManagedAsyncCallResult::Ok(_) => { + self.transfer_performed_event(batch_id, tx_nonce); + } + ManagedAsyncCallResult::Err(_) => { + let tokens = original_tx.tokens.clone(); + let refund_tx = self.convert_to_refund_tx(original_tx, tokens); + self.add_multiple_tx_to_batch(&ManagedVec::from_single_item(refund_tx)); + + self.transfer_failed_execution_failed(batch_id, tx_nonce); + } } } // events #[event("transferPerformedEvent")] - fn transfer_performed_event(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + fn transfer_performed_event(&self, #[indexed] batch_id: BatchId, #[indexed] tx_id: TxId); #[event("transferFailedInvalidToken")] - fn transfer_failed_invalid_token(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + fn transfer_failed_invalid_token(&self, #[indexed] batch_id: BatchId, #[indexed] tx_id: TxId); #[event("transferFailedFrozenDestinationAccount")] fn transfer_failed_frozen_destination_account( &self, - #[indexed] batch_id: u64, - #[indexed] tx_id: u64, + #[indexed] batch_id: BatchId, + #[indexed] tx_id: TxId, ); #[event("transferOverMaxAmount")] - fn transfer_over_max_amount(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + fn transfer_over_max_amount(&self, #[indexed] batch_id: BatchId, #[indexed] tx_id: TxId); + + #[event("transferFailedExecutionFailed")] + fn transfer_failed_execution_failed( + &self, + #[indexed] batch_id: BatchId, + #[indexed] tx_id: TxId, + ); }