diff --git a/bridge-proxy/src/bridge-proxy.rs b/bridge-proxy/src/bridge-proxy.rs index 3ddd7c21..f587010c 100644 --- a/bridge-proxy/src/bridge-proxy.rs +++ b/bridge-proxy/src/bridge-proxy.rs @@ -2,18 +2,19 @@ use multiversx_sc::imports::*; pub mod config; +mod events; use sc_proxies::bridged_tokens_wrapper_proxy; use sc_proxies::esdt_safe_proxy; use transaction::{CallData, EthTransaction}; const MIN_GAS_LIMIT_FOR_SC_CALL: u64 = 10_000_000; const MAX_GAS_LIMIT_FOR_SC_CALL: u64 = 249999999; -const DEFAULT_GAS_LIMIT_FOR_REFUND_CALLBACK: u64 = 20_000_000; // 20 million -const DELAY_BEFORE_OWNER_CAN_CANCEL_TRANSACTION: u64 = 300; +const DEFAULT_GAS_LIMIT_FOR_REFUND_CALLBACK: u64 = 1_000_000; // 1 million #[multiversx_sc::contract] pub trait BridgeProxyContract: config::ConfigModule + + events::EventsModule + multiversx_sc_modules::pause::PauseModule + storage_module::CommonStorageModule { @@ -103,39 +104,28 @@ pub trait BridgeProxyContract: tx_call.register_promise(); } - // TODO: will activate endpoint in a future release - // #[endpoint(cancel)] - fn cancel(&self, tx_id: usize) { - let tx_start_round = self.ongoing_execution(tx_id).get(); - let current_block_round = self.blockchain().get_block_round(); - require!( - current_block_round - tx_start_round > DELAY_BEFORE_OWNER_CAN_CANCEL_TRANSACTION, - "Transaction can't be cancelled yet" - ); - - let tx = self.get_pending_transaction_by_id(tx_id); - let payment = self.payments(tx_id).get(); - self.tx().to(tx.to).payment(payment).transfer(); - self.cleanup_transaction(tx_id); - } #[promises_callback] fn execution_callback(&self, #[call_result] result: ManagedAsyncCallResult<()>, tx_id: usize) { if result.is_err() { - self.refund_transaction(tx_id); + self.add_pending_tx_to_refund(tx_id); + self.execute_generated_refund(tx_id); + } else { + self.execute_succesfully_finished(tx_id); } - self.cleanup_transaction(tx_id); + self.pending_transactions().remove(&tx_id); } - fn refund_transaction(&self, tx_id: usize) { - let tx = self.get_pending_transaction_by_id(tx_id); + #[endpoint(executeRefundTransaction)] + fn execute_refund_transaction(&self, tx_id: usize) { + let tx = self.get_refund_transaction_by_id(tx_id); let esdt_safe_contract_address = self.get_esdt_safe_address(); - let unwrapped_token = self.unwrap_token(&tx.token_id, tx_id); let batch_id = self.batch_id(tx_id).get(); + self.tx() .to(esdt_safe_contract_address) .typed(esdt_safe_proxy::EsdtSafeProxy) - .create_transaction( + .create_refund_transaction( tx.from, OptionalValue::Some(esdt_safe_proxy::RefundInfo { address: tx.to, @@ -149,6 +139,8 @@ pub trait BridgeProxyContract: &unwrapped_token.amount, ) .sync_call(); + + self.finish_refund(tx_id); } fn unwrap_token(&self, requested_token: &TokenIdentifier, tx_id: usize) -> EsdtTokenPayment { @@ -184,13 +176,20 @@ pub trait BridgeProxyContract: } fn finish_execute_gracefully(&self, tx_id: usize) { - self.refund_transaction(tx_id); - self.cleanup_transaction(tx_id); + self.add_pending_tx_to_refund(tx_id); + self.pending_transactions().remove(&tx_id); } - fn cleanup_transaction(&self, tx_id: usize) { - self.pending_transactions().remove(&tx_id); + fn finish_refund(&self, tx_id: usize) { self.ongoing_execution(tx_id).clear(); + self.payments(tx_id).clear(); + self.batch_id(tx_id).clear(); + self.refund_transactions().remove(&tx_id); + } + + fn add_pending_tx_to_refund(&self, tx_id: usize) { + let tx = self.get_pending_transaction_by_id(tx_id); + self.refund_transactions().insert(tx_id, tx); } fn get_next_tx_id(&self) -> usize { @@ -217,4 +216,22 @@ pub trait BridgeProxyContract: } transactions } + + #[view(getRefundTransactionById)] + fn get_refund_transaction_by_id(&self, tx_id: usize) -> EthTransaction { + let tx = self.refund_transactions().get(&tx_id); + require!(tx.is_some(), "Invalid tx id"); + tx.unwrap() + } + + #[view(getRefundTransactions)] + fn get_refund_transactions( + &self, + ) -> MultiValueEncoded>> { + let mut transactions = MultiValueEncoded::new(); + for (tx_id, tx) in self.refund_transactions().iter() { + transactions.push(MultiValue2((tx_id, tx))); + } + transactions + } } diff --git a/bridge-proxy/src/config.rs b/bridge-proxy/src/config.rs index 79f94ee1..0da6985c 100644 --- a/bridge-proxy/src/config.rs +++ b/bridge-proxy/src/config.rs @@ -4,17 +4,21 @@ use transaction::EthTransaction; #[multiversx_sc::module] pub trait ConfigModule { - #[storage_mapper("pending_transactions")] + #[storage_mapper("pendingTransactions")] fn pending_transactions(&self) -> MapMapper>; + #[view(refundTransactions)] + #[storage_mapper("refundTransactions")] + fn refund_transactions(&self) -> MapMapper>; + #[storage_mapper("payments")] fn payments(&self, tx_id: usize) -> SingleValueMapper>; - #[storage_mapper("batch_id")] + #[storage_mapper("batchId")] fn batch_id(&self, tx_id: usize) -> SingleValueMapper; #[view(highestTxId)] - #[storage_mapper("highest_tx_id")] + #[storage_mapper("highestTxId")] fn highest_tx_id(&self) -> SingleValueMapper; #[storage_mapper("ongoingExecution")] diff --git a/bridge-proxy/src/events.rs b/bridge-proxy/src/events.rs new file mode 100644 index 00000000..beea9b17 --- /dev/null +++ b/bridge-proxy/src/events.rs @@ -0,0 +1,8 @@ +#[multiversx_sc::module] +pub trait EventsModule { + #[event("executeSuccesfullyFinished")] + fn execute_succesfully_finished(&self, #[indexed] tx_id: usize); + + #[event("executeGeneratedRefund")] + fn execute_generated_refund(&self, #[indexed] tx_id: usize); +} diff --git a/bridge-proxy/tests/bridge_proxy_blackbox_test.rs b/bridge-proxy/tests/bridge_proxy_blackbox_test.rs index 20e287fc..b0087c2b 100644 --- a/bridge-proxy/tests/bridge_proxy_blackbox_test.rs +++ b/bridge-proxy/tests/bridge_proxy_blackbox_test.rs @@ -33,7 +33,7 @@ use multiversx_sc_scenario::{ scenario_model::*, ContractInfo, ScenarioWorld, }; -use multiversx_sc_scenario::{ExpectValue, ScenarioTxRun}; +use multiversx_sc_scenario::{ExpectError, ExpectValue, ScenarioTxRun}; use eth_address::*; use mock_proxies::mock_multisig_proxy; @@ -43,12 +43,15 @@ use transaction::{CallData, EthTransaction}; const BRIDGE_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("BRIDGE-123456"); const WBRIDGE_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("WBRIDGE-123456"); +const DELAY_BEFORE_OWNER_CAN_REFUND_TRANSACTION: u64 = 300; const GAS_LIMIT: u64 = 10_000_000; const TOO_SMALL_GAS_LIMIT: u64 = 1_000_000; const CF_DEADLINE: u64 = 7 * 24 * 60 * 60; // 1 week in seconds +const INITIAL_BALANCE: u64 = 10_000u64; const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const USER_ADDRESS: TestAddress = TestAddress::new("user"); const BRIDGE_PROXY_ADDRESS: TestSCAddress = TestSCAddress::new("bridge-proxy"); const CROWDFUNDING_ADDRESS: TestSCAddress = TestSCAddress::new("crowfunding"); const MULTI_TRANSFER_ADDRESS: TestSCAddress = TestSCAddress::new("multi-transfer"); @@ -119,10 +122,13 @@ impl BridgeProxyTestState { world .account(OWNER_ADDRESS) .nonce(1) - .esdt_balance(TokenIdentifier::from(BRIDGE_TOKEN_ID), 10_000u64) + .esdt_balance(TokenIdentifier::from(BRIDGE_TOKEN_ID), INITIAL_BALANCE) + .account(USER_ADDRESS) + .nonce(1) + .esdt_balance(TokenIdentifier::from(BRIDGE_TOKEN_ID), INITIAL_BALANCE) .account(MULTI_TRANSFER_ADDRESS) - .esdt_balance(TokenIdentifier::from(WBRIDGE_TOKEN_ID), 10_000u64) - .esdt_balance(TokenIdentifier::from(BRIDGE_TOKEN_ID), 10_000u64) + .esdt_balance(TokenIdentifier::from(WBRIDGE_TOKEN_ID), INITIAL_BALANCE) + .esdt_balance(TokenIdentifier::from(BRIDGE_TOKEN_ID), INITIAL_BALANCE) .code(multi_transfer_code) .account(ESDT_SAFE_ADDRESS) .code(esdt_safe_code); @@ -233,6 +239,9 @@ impl BridgeProxyTestState { self } + fn set_block_round(&mut self, block_round_expr: u64) { + self.world.current_block().block_round(block_round_expr); + } } #[test] @@ -536,8 +545,7 @@ fn test_highest_tx_id() { } } -// Will be moved to integration test -// #[test] +#[test] fn bridge_proxy_wrong_formatting_sc_call_test() { let mut test = BridgeProxyTestState::new(); @@ -558,7 +566,7 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { }; let amount = BigUint::from(500u64); - // Destination is not an initialized contract + test.world .tx() .from(MULTI_TRANSFER_ADDRESS) @@ -590,8 +598,31 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { // Refund: Funds are transfered to BridgedTokensWrapper test.world - .check_account(BRIDGED_TOKENS_WRAPPER_ADDRESS) - .esdt_balance(BRIDGE_TOKEN_ID, amount.clone()); + .check_account(BRIDGE_PROXY_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, amount); + + test.world + .check_account(BRIDGE_PROXY_ADDRESS) + .check_storage("str:refundTransactions.mapped|u32:1", "0x3031303230333034303530363037303830393130000000000000000005006e6f2d696e69742d73635f5f5f5f5f5f5f5f5f5f5f5f0000000d4252494447452d3132333435360000000201f4000000000000000100") + .check_storage("str:refundTransactions.value|u32:1", "0x01") + .check_storage("str:refundTransactions.node_id|u32:1", "0x01") + .check_storage("str:refundTransactions.info", "0x00000001000000010000000100000001") + .check_storage("str:refundTransactions.node_links|u32:1", "0x0000000000000000") + .check_storage("str:batchId|u32:1", "1") + .check_storage("str:highestTxId", "1") + .check_storage("str:payments|u32:1", "nested:str:BRIDGE-123456|u64:0|biguint:500"); +} + +#[test] +fn bridge_proxy_wrong_endpoint_sc_call_test() { + let mut test = BridgeProxyTestState::new(); + + test.multisig_deploy(); + test.deploy_bridge_proxy(); + test.deploy_crowdfunding(); + test.config_bridge(); + + let amount = BigUint::from(500u64); // Wrong endpoint for callData let mut args = ManagedVec::new(); @@ -632,7 +663,7 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { .query() .to(BRIDGE_PROXY_ADDRESS) .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) - .get_pending_transaction_by_id(2u32) + .get_pending_transaction_by_id(1u32) .returns(ExpectValue(eth_tx)) .run(); @@ -641,13 +672,36 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { .from(OWNER_ADDRESS) .to(BRIDGE_PROXY_ADDRESS) .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) - .execute(2u32) + .execute(1u32) + .with_gas_limit(GAS_LIMIT * 2) .run(); - // Refund: Funds are transfered to BridgedTokensWrapper test.world - .check_account(BRIDGED_TOKENS_WRAPPER_ADDRESS) - .esdt_balance(BRIDGE_TOKEN_ID, amount.clone() * 2u64); + .check_account(BRIDGE_PROXY_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, amount); + + test.world + .check_account(BRIDGE_PROXY_ADDRESS) + .check_storage("str:refundTransactions.mapped|u32:1", "0x30313032303330343035303630373038303931300000000000000000050063726f7766756e64696e675f5f5f5f5f5f5f5f5f5f5f0000000d4252494447452d3132333435360000000201f400000000000000020100000017000000066e6f66756e6300000000009896800100000000") + .check_storage("str:refundTransactions.value|u32:1", "0x01") + .check_storage("str:refundTransactions.node_id|u32:1", "0x01") + .check_storage("str:refundTransactions.info", "0x00000001000000010000000100000001") + .check_storage("str:refundTransactions.node_links|u32:1", "0x0000000000000000") + .check_storage("str:batchId|u32:1", "1") + .check_storage("str:highestTxId", "1") + .check_storage("str:payments|u32:1", "nested:str:BRIDGE-123456|u64:0|biguint:500"); +} + +#[test] +fn bridge_proxy_wrong_args_sc_call_test() { + let mut test = BridgeProxyTestState::new(); + + test.multisig_deploy(); + test.deploy_bridge_proxy(); + test.deploy_crowdfunding(); + test.config_bridge(); + + let amount = BigUint::from(500u64); // Wrong args let mut args = ManagedVec::new(); @@ -690,7 +744,7 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { .query() .to(BRIDGE_PROXY_ADDRESS) .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) - .get_pending_transaction_by_id(3u32) + .get_pending_transaction_by_id(1u32) .returns(ExpectValue(eth_tx)) .run(); @@ -699,13 +753,25 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { .from(OWNER_ADDRESS) .to(BRIDGE_PROXY_ADDRESS) .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) - .execute(3u32) + .execute(1u32) + .with_gas_limit(GAS_LIMIT * 2) .run(); // Refund: Funds are transfered to BridgedTokensWrapper test.world - .check_account(BRIDGED_TOKENS_WRAPPER_ADDRESS) - .esdt_balance(BRIDGE_TOKEN_ID, amount * 3u64); + .check_account(BRIDGE_PROXY_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, amount); + + test.world + .check_account(BRIDGE_PROXY_ADDRESS) + .check_storage("str:refundTransactions.mapped|u32:1", "0x30313032303330343035303630373038303931300000000000000000050063726f7766756e64696e675f5f5f5f5f5f5f5f5f5f5f0000000d4252494447452d3132333435360000000201f4000000000000000301000000220000000466756e64000000000098968001000000010000000977726f6e6761726773") + .check_storage("str:refundTransactions.value|u32:1", "0x01") + .check_storage("str:refundTransactions.node_id|u32:1", "0x01") + .check_storage("str:refundTransactions.info", "0x00000001000000010000000100000001") + .check_storage("str:refundTransactions.node_links|u32:1", "0x0000000000000000") + .check_storage("str:batchId|u32:1", "1") + .check_storage("str:highestTxId", "1") + .check_storage("str:payments|u32:1", "nested:str:BRIDGE-123456|u64:0|biguint:500"); } #[test] @@ -741,7 +807,159 @@ fn bridge_proxy_too_small_gas_sc_call_test() { }; let amount = BigUint::from(500u64); - // Destination is not an initialized contract + + test.world + .tx() + .from(MULTI_TRANSFER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .deposit(ð_tx, 1u64) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(BRIDGE_TOKEN_ID), + 0, + &amount, + ) + .run(); + + test.world + .query() + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .get_pending_transaction_by_id(1u32) + .returns(ExpectValue(eth_tx.clone())) + .run(); + + test.world + .tx() + .from(OWNER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .execute(1u32) + .run(); + + test.world + .check_account(BRIDGE_PROXY_ADDRESS) + .check_storage("str:refundTransactions.mapped|u32:1", "0x30313032303330343035303630373038303931300000000000000000050063726f7766756e64696e675f5f5f5f5f5f5f5f5f5f5f0000000d4252494447452d3132333435360000000201f4000000000000000101000000150000000466756e6400000000000f42400100000000") + .check_storage("str:refundTransactions.value|u32:1", "0x01") + .check_storage("str:refundTransactions.node_id|u32:1", "0x01") + .check_storage("str:refundTransactions.info", "0x00000001000000010000000100000001") + .check_storage("str:refundTransactions.node_links|u32:1", "0x0000000000000000") + .check_storage("str:batchId|u32:1", "1") + .check_storage("str:highestTxId", "1") + .check_storage("str:payments|u32:1", "nested:str:BRIDGE-123456|u64:0|biguint:500"); +} + +#[test] +fn bridge_proxy_empty_endpoint_with_args_test() { + let mut test = BridgeProxyTestState::new(); + + test.world.start_trace(); + + test.multisig_deploy(); + test.deploy_bridge_proxy(); + test.deploy_crowdfunding(); + test.config_bridge(); + + let mut args = ManagedVec::new(); + let call_data: CallData = CallData { + endpoint: ManagedBuffer::new(), + gas_limit: GAS_LIMIT, + args: ManagedOption::some(args), + }; + + let call_data: ManagedBuffer = + ManagedSerializer::new().top_encode_to_managed_buffer(&call_data); + + let eth_tx = EthTransaction { + from: EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + to: ManagedAddress::from(CROWDFUNDING_ADDRESS.eval_to_array()), + token_id: BRIDGE_TOKEN_ID.into(), + amount: BigUint::from(500u64), + tx_nonce: 1u64, + call_data: ManagedOption::some(call_data), + }; + + let amount = BigUint::from(500u64); + + test.world + .tx() + .from(MULTI_TRANSFER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .deposit(ð_tx, 1u64) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(BRIDGE_TOKEN_ID), + 0, + &amount, + ) + .run(); + + test.world + .query() + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .get_pending_transaction_by_id(1u32) + .returns(ExpectValue(eth_tx)) + .run(); + + test.world + .tx() + .from(OWNER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .execute(1u32) + .run(); + + test.world + .check_account(BRIDGE_PROXY_ADDRESS) + .check_storage("str:refundTransactions.mapped|u32:1", "0x30313032303330343035303630373038303931300000000000000000050063726f7766756e64696e675f5f5f5f5f5f5f5f5f5f5f0000000d4252494447452d3132333435360000000201f4000000000000000101000000110000000000000000009896800100000000") + .check_storage("str:refundTransactions.value|u32:1", "0x01") + .check_storage("str:refundTransactions.node_id|u32:1", "0x01") + .check_storage("str:refundTransactions.info", "0x00000001000000010000000100000001") + .check_storage("str:refundTransactions.node_links|u32:1", "0x0000000000000000") + .check_storage("str:batchId|u32:1", "1") + .check_storage("str:highestTxId", "1") + .check_storage( + "str:payments|u32:1", + "nested:str:BRIDGE-123456|u64:0|biguint:500", + ); +} + +#[test] +fn bridge_proxy_empty_endpoint_with_gas_test() { + let mut test = BridgeProxyTestState::new(); + + test.world.start_trace(); + + test.multisig_deploy(); + test.deploy_bridge_proxy(); + test.deploy_crowdfunding(); + test.config_bridge(); + + let call_data: CallData = CallData { + endpoint: ManagedBuffer::new(), + gas_limit: GAS_LIMIT, + args: ManagedOption::none(), + }; + + let call_data: ManagedBuffer = + ManagedSerializer::new().top_encode_to_managed_buffer(&call_data); + + let eth_tx = EthTransaction { + from: EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + to: ManagedAddress::from(CROWDFUNDING_ADDRESS.eval_to_array()), + token_id: BRIDGE_TOKEN_ID.into(), + amount: BigUint::from(500u64), + tx_nonce: 1u64, + call_data: ManagedOption::some(call_data), + }; + + let amount = BigUint::from(500u64); + test.world .tx() .from(MULTI_TRANSFER_ADDRESS) @@ -771,8 +989,119 @@ fn bridge_proxy_too_small_gas_sc_call_test() { .execute(1u32) .run(); - // Refund: Funds are transfered to EsdtSafe test.world - .check_account(ESDT_SAFE_ADDRESS) + .check_account(BRIDGE_PROXY_ADDRESS) + .check_storage("str:refundTransactions.mapped|u32:1", "0x30313032303330343035303630373038303931300000000000000000050063726f7766756e64696e675f5f5f5f5f5f5f5f5f5f5f0000000d4252494447452d3132333435360000000201f40000000000000001010000000d00000000000000000098968000") + .check_storage("str:refundTransactions.value|u32:1", "0x01") + .check_storage("str:refundTransactions.node_id|u32:1", "0x01") + .check_storage("str:refundTransactions.info", "0x00000001000000010000000100000001") + .check_storage("str:refundTransactions.node_links|u32:1", "0x0000000000000000") + .check_storage("str:batchId|u32:1", "1") + .check_storage("str:highestTxId", "1") + .check_storage( + "str:payments|u32:1", + "nested:str:BRIDGE-123456|u64:0|biguint:500", + ); +} + +#[test] +fn bridge_proxy_refund_tx_test() { + let mut test = BridgeProxyTestState::new(); + + test.world.start_trace(); + + test.multisig_deploy(); + test.deploy_bridge_proxy(); + test.deploy_crowdfunding(); + test.config_bridge(); + + let mut args = ManagedVec::new(); + let call_data: CallData = CallData { + endpoint: ManagedBuffer::new(), + gas_limit: GAS_LIMIT, + args: ManagedOption::some(args), + }; + + let call_data: ManagedBuffer = + ManagedSerializer::new().top_encode_to_managed_buffer(&call_data); + + let eth_tx = EthTransaction { + from: EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + to: ManagedAddress::from(CROWDFUNDING_ADDRESS.eval_to_array()), + token_id: BRIDGE_TOKEN_ID.into(), + amount: BigUint::from(500u64), + tx_nonce: 1u64, + call_data: ManagedOption::some(call_data), + }; + + let amount = BigUint::from(500u64); + + test.world + .tx() + .from(MULTI_TRANSFER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .deposit(ð_tx, 1u64) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(BRIDGE_TOKEN_ID), + 0, + &amount, + ) + .run(); + + test.world + .query() + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .get_pending_transaction_by_id(1u32) + .returns(ExpectValue(eth_tx)) + .run(); + + test.world + .tx() + .from(USER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .execute(1u32) + .run(); + + test.world + .check_account(BRIDGE_PROXY_ADDRESS) + .check_storage("str:refundTransactions.mapped|u32:1", "0x30313032303330343035303630373038303931300000000000000000050063726f7766756e64696e675f5f5f5f5f5f5f5f5f5f5f0000000d4252494447452d3132333435360000000201f4000000000000000101000000110000000000000000009896800100000000") + .check_storage("str:refundTransactions.value|u32:1", "0x01") + .check_storage("str:refundTransactions.node_id|u32:1", "0x01") + .check_storage("str:refundTransactions.info", "0x00000001000000010000000100000001") + .check_storage("str:refundTransactions.node_links|u32:1", "0x0000000000000000") + .check_storage("str:batchId|u32:1", "1") + .check_storage("str:highestTxId", "1") + .check_storage( + "str:payments|u32:1", + "nested:str:BRIDGE-123456|u64:0|biguint:500", + ); + + test.world + .check_account(BRIDGE_PROXY_ADDRESS) .esdt_balance(BRIDGE_TOKEN_ID, amount.clone()); + + test.world + .check_account(USER_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, BigUint::from(INITIAL_BALANCE)); + + test.world + .tx() + .from(USER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .execute_refund_transaction(1u32) + .run(); + + test.world + .check_account(BRIDGE_PROXY_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, BigUint::zero()); + + test.world + .check_account(BRIDGE_PROXY_ADDRESS) + .check_storage("str:highestTxId", "1"); } diff --git a/bridge-proxy/wasm/src/lib.rs b/bridge-proxy/wasm/src/lib.rs index 97c28d7c..cb212759 100644 --- a/bridge-proxy/wasm/src/lib.rs +++ b/bridge-proxy/wasm/src/lib.rs @@ -6,10 +6,10 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 8 +// Endpoints: 12 // Async Callback (empty): 1 // Promise callbacks: 1 -// Total number of exported functions: 12 +// Total number of exported functions: 16 #![no_std] @@ -23,8 +23,12 @@ multiversx_sc_wasm_adapter::endpoints! { upgrade => upgrade deposit => deposit execute => execute + executeRefundTransaction => execute_refund_transaction getPendingTransactionById => get_pending_transaction_by_id getPendingTransactions => get_pending_transactions + getRefundTransactionById => get_refund_transaction_by_id + getRefundTransactions => get_refund_transactions + refundTransactions => refund_transactions highestTxId => highest_tx_id pause => pause_endpoint unpause => unpause_endpoint diff --git a/bridged-tokens-wrapper/scenarios/add_wrapped_token.scen.json b/bridged-tokens-wrapper/scenarios/add_wrapped_token.scen.json index 0a0d8c44..851a9ca4 100644 --- a/bridged-tokens-wrapper/scenarios/add_wrapped_token.scen.json +++ b/bridged-tokens-wrapper/scenarios/add_wrapped_token.scen.json @@ -120,7 +120,7 @@ "str:universalBridgedTokenIds.len": "1", "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", - "str:token_decimals_num|nested:str:WUSDC-abcdef": "6" + "str:tokenDecimalsNum|nested:str:WUSDC-abcdef": "6" }, "code": "file:../output/bridged-tokens-wrapper.wasm", "owner": "address:owner" @@ -128,4 +128,4 @@ } } ] -} +} \ No newline at end of file diff --git a/bridged-tokens-wrapper/scenarios/blacklist_token.scen.json b/bridged-tokens-wrapper/scenarios/blacklist_token.scen.json index 7f36f67d..5604d551 100644 --- a/bridged-tokens-wrapper/scenarios/blacklist_token.scen.json +++ b/bridged-tokens-wrapper/scenarios/blacklist_token.scen.json @@ -19,34 +19,9 @@ "gasLimit": "5,000,000", "gasPrice": "0" }, - "expect": { - "status": "0", - "message": "", - "gas": "*", - "refund": "*" - } - }, - { - "step": "scCall", - "txId": "unwrap-token", - "tx": { - "from": "address:user", - "to": "sc:bridged_tokens_wrapper", - "value": "0", - "esdt": { - "tokenIdentifier": "str:WUSDC-abcdef", - "value": "100" - }, - "function": "unwrapToken", - "arguments": [ - "str:USDC-aaaaaa" - ], - "gasLimit": "5,000,000", - "gasPrice": "0" - }, "expect": { "status": "4", - "message": "str:Esdt token unavailable", + "message": "str:Cannot blacklist token due to remaining liquidity", "gas": "*", "refund": "*" } @@ -60,7 +35,7 @@ "storage": {} }, "address:user": { - "nonce": "5", + "nonce": "4", "esdt": { "str:USDC-aaaaaa": { "balance": "200000000000000" @@ -102,17 +77,21 @@ } }, "storage": { - "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.len": "1", - "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-cccccc": "1", - "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:1": "str:USDC-cccccc", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.len": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-aaaaaa": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-cccccc": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:1": "str:USDC-aaaaaa", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:2": "str:USDC-cccccc", "str:chainSpecificToUniversalMapping|nested:str:USDC-cccccc": "str:WUSDC-abcdef", + "str:chainSpecificToUniversalMapping|nested:str:USDC-aaaaaa": "str:WUSDC-abcdef", "str:universalBridgedTokenIds.len": "1", "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", "str:tokenLiquidity|nested:str:USDC-aaaaaa": "300000000000000", "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", - "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", - "str:token_decimals_num|nested:str:USDC-cccccc": "18" + "str:tokenDecimalsNum|nested:str:WUSDC-abcdef": "6", + "str:tokenDecimalsNum|nested:str:USDC-cccccc": "18", + "str:tokenDecimalsNum|nested:str:USDC-aaaaaa": "18" }, "code": "file:../output/bridged-tokens-wrapper.wasm", "owner": "address:owner" @@ -120,4 +99,4 @@ } } ] -} +} \ No newline at end of file diff --git a/bridged-tokens-wrapper/scenarios/remove_wrapped_token.scen.json b/bridged-tokens-wrapper/scenarios/remove_wrapped_token.scen.json index 79970914..b0366a74 100644 --- a/bridged-tokens-wrapper/scenarios/remove_wrapped_token.scen.json +++ b/bridged-tokens-wrapper/scenarios/remove_wrapped_token.scen.json @@ -41,8 +41,8 @@ "gasPrice": "0" }, "expect": { - "status": "0", - "message": "", + "status": "4", + "message": "str:Cannot remove wrapped token due to remaining liquidity", "gas": "*", "refund": "*" } @@ -56,7 +56,7 @@ "storage": {} }, "address:user": { - "nonce": "5", + "nonce": "4", "esdt": { "str:USDC-aaaaaa": { "balance": "200000000000000" @@ -98,12 +98,24 @@ } }, "storage": { - "str:universalBridgedTokenIds.len": "1", - "str:universalBridgedTokenIds.index|nested:str:WUSDC-uvwxyz": "1", - "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-uvwxyz", + "str:universalBridgedTokenIds.len": "2", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-uvwxyz": "2", + "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", + "str:universalBridgedTokenIds.item|u32:2": "str:WUSDC-uvwxyz", "str:tokenLiquidity|nested:str:USDC-aaaaaa": "300000000000000", "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", - "str:token_decimals_num|nested:str:WUSDC-uvwxyz": "18" + "str:tokenDecimalsNum|nested:str:WUSDC-uvwxyz": "18", + "str:tokenDecimalsNum|nested:str:USDC-aaaaaa": "18", + "str:tokenDecimalsNum|nested:str:USDC-cccccc": "18", + "str:tokenDecimalsNum|nested:str:WUSDC-abcdef": "6", + "str:chainSpecificToUniversalMapping|nested:str:USDC-cccccc": "str:WUSDC-abcdef", + "str:chainSpecificToUniversalMapping|nested:str:USDC-aaaaaa": "str:WUSDC-abcdef", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.len": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-aaaaaa": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-cccccc": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:1": "str:USDC-aaaaaa", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:2": "str:USDC-cccccc" }, "code": "file:../output/bridged-tokens-wrapper.wasm", "owner": "address:owner" @@ -111,4 +123,4 @@ } } ] -} +} \ No newline at end of file diff --git a/bridged-tokens-wrapper/scenarios/setup.scen.json b/bridged-tokens-wrapper/scenarios/setup.scen.json index 6a11bc37..ad7c5348 100644 --- a/bridged-tokens-wrapper/scenarios/setup.scen.json +++ b/bridged-tokens-wrapper/scenarios/setup.scen.json @@ -105,10 +105,10 @@ "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", "str:tokenLiquidity|nested:str:USDC-eeeeee": "400000000000000", "str:tokenLiquidity|nested:str:USDC-ffffff": "400000000000000", - "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", - "str:token_decimals_num|nested:str:WUSDC-ghijkl": "6", - "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", - "str:token_decimals_num|nested:str:USDC-cccccc": "18" + "str:tokenDecimalsNum|nested:str:WUSDC-abcdef": "6", + "str:tokenDecimalsNum|nested:str:WUSDC-ghijkl": "6", + "str:tokenDecimalsNum|nested:str:USDC-aaaaaa": "18", + "str:tokenDecimalsNum|nested:str:USDC-cccccc": "18" }, "code": "file:../output/bridged-tokens-wrapper.wasm", "owner": "address:owner" @@ -218,10 +218,10 @@ "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", "str:tokenLiquidity|nested:str:USDC-eeeeee": "400000000000000", "str:tokenLiquidity|nested:str:USDC-ffffff": "400000000000000", - "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", - "str:token_decimals_num|nested:str:WUSDC-ghijkl": "6", - "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", - "str:token_decimals_num|nested:str:USDC-cccccc": "18" + "str:tokenDecimalsNum|nested:str:WUSDC-abcdef": "6", + "str:tokenDecimalsNum|nested:str:WUSDC-ghijkl": "6", + "str:tokenDecimalsNum|nested:str:USDC-aaaaaa": "18", + "str:tokenDecimalsNum|nested:str:USDC-cccccc": "18" }, "code": "file:../output/bridged-tokens-wrapper.wasm", "owner": "address:owner" @@ -229,4 +229,4 @@ } } ] -} +} \ No newline at end of file diff --git a/bridged-tokens-wrapper/scenarios/unwrap_token.scen.json b/bridged-tokens-wrapper/scenarios/unwrap_token.scen.json index 819c3ecd..f3b9e7f6 100644 --- a/bridged-tokens-wrapper/scenarios/unwrap_token.scen.json +++ b/bridged-tokens-wrapper/scenarios/unwrap_token.scen.json @@ -81,31 +81,6 @@ "refund": "*" } }, - { - "step": "scCall", - "txId": "chain-token-requires_updating", - "tx": { - "from": "address:user", - "to": "sc:bridged_tokens_wrapper", - "value": "0", - "esdt": { - "tokenIdentifier": "str:WUSDC-ghijkl", - "value": "500" - }, - "function": "unwrapToken", - "arguments": [ - "str:USDC-ffffff" - ], - "gasLimit": "5,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "4", - "message": "str:Chain-specific token requires updating", - "gas": "*", - "refund": "*" - } - }, { "step": "scCall", "txId": "unwrap-token", @@ -140,7 +115,7 @@ "storage": {} }, "address:user": { - "nonce": "12", + "nonce": "11", "esdt": { "str:USDC-aaaaaa": { "balance": "400000000000000" @@ -234,10 +209,10 @@ "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", "str:tokenLiquidity|nested:str:USDC-eeeeee": "400000000000000", "str:tokenLiquidity|nested:str:USDC-ffffff": "400000000000000", - "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", - "str:token_decimals_num|nested:str:WUSDC-ghijkl": "6", - "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", - "str:token_decimals_num|nested:str:USDC-cccccc": "18" + "str:tokenDecimalsNum|nested:str:WUSDC-abcdef": "6", + "str:tokenDecimalsNum|nested:str:WUSDC-ghijkl": "6", + "str:tokenDecimalsNum|nested:str:USDC-aaaaaa": "18", + "str:tokenDecimalsNum|nested:str:USDC-cccccc": "18" }, "code": "file:../output/bridged-tokens-wrapper.wasm", "owner": "address:owner" @@ -245,4 +220,4 @@ } } ] -} +} \ No newline at end of file diff --git a/bridged-tokens-wrapper/scenarios/whitelist_token.scen.json b/bridged-tokens-wrapper/scenarios/whitelist_token.scen.json index 5a05ecaa..2d85e971 100644 --- a/bridged-tokens-wrapper/scenarios/whitelist_token.scen.json +++ b/bridged-tokens-wrapper/scenarios/whitelist_token.scen.json @@ -127,9 +127,9 @@ "str:universalBridgedTokenIds.len": "1", "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", - "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", - "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", - "str:token_decimals_num|nested:str:USDC-cccccc": "18" + "str:tokenDecimalsNum|nested:str:WUSDC-abcdef": "6", + "str:tokenDecimalsNum|nested:str:USDC-aaaaaa": "18", + "str:tokenDecimalsNum|nested:str:USDC-cccccc": "18" }, "code": "file:../output/bridged-tokens-wrapper.wasm", "owner": "address:owner" @@ -137,4 +137,4 @@ } } ] -} +} \ No newline at end of file diff --git a/bridged-tokens-wrapper/scenarios/wrap_token.scen.json b/bridged-tokens-wrapper/scenarios/wrap_token.scen.json index 65a5dfba..8ab688c4 100644 --- a/bridged-tokens-wrapper/scenarios/wrap_token.scen.json +++ b/bridged-tokens-wrapper/scenarios/wrap_token.scen.json @@ -65,7 +65,7 @@ "function": "wrapTokens", "esdt": { "tokenIdentifier": "str:USDC-cccccc", - "value": "100000000000000" + "value": "100,000,000,000,000" }, "arguments": [], "gasLimit": "5,000,000", @@ -144,9 +144,9 @@ "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", "str:tokenLiquidity|nested:str:USDC-aaaaaa": "300000000000000", "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", - "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", - "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", - "str:token_decimals_num|nested:str:USDC-cccccc": "18" + "str:tokenDecimalsNum|nested:str:WUSDC-abcdef": "6", + "str:tokenDecimalsNum|nested:str:USDC-aaaaaa": "18", + "str:tokenDecimalsNum|nested:str:USDC-cccccc": "18" }, "code": "file:../output/bridged-tokens-wrapper.wasm", "owner": "address:owner" @@ -154,4 +154,4 @@ } } ] -} +} \ No newline at end of file diff --git a/bridged-tokens-wrapper/src/dfp_big_uint.rs b/bridged-tokens-wrapper/src/dfp_big_uint.rs index 0a7c0090..546d784e 100644 --- a/bridged-tokens-wrapper/src/dfp_big_uint.rs +++ b/bridged-tokens-wrapper/src/dfp_big_uint.rs @@ -30,13 +30,6 @@ impl DFPBigUint { } } - pub fn trunc(&self) -> Self { - DFPBigUint { - bu: self.bu.clone() / 10u64.pow(self.num_decimals), - num_decimals: 1, - } - } - pub fn to_raw(&self) -> BigUint { self.bu.clone() } diff --git a/bridged-tokens-wrapper/src/lib.rs b/bridged-tokens-wrapper/src/lib.rs index deb5541f..4bca2d4b 100644 --- a/bridged-tokens-wrapper/src/lib.rs +++ b/bridged-tokens-wrapper/src/lib.rs @@ -30,6 +30,12 @@ pub trait BridgedTokensWrapper: #[only_owner] #[endpoint(addWrappedToken)] fn add_wrapped_token(&self, universal_bridged_token_ids: TokenIdentifier, num_decimals: u32) { + require!( + !self + .universal_bridged_token_ids() + .contains(&universal_bridged_token_ids), + "Token already added" + ); self.require_mint_and_burn_roles(&universal_bridged_token_ids); self.token_decimals_num(&universal_bridged_token_ids) .set(num_decimals); @@ -62,6 +68,11 @@ pub trait BridgedTokensWrapper: let mut chain_specific_tokens = self.chain_specific_token_ids(&universal_bridged_token_ids); for token in chain_specific_tokens.iter() { + let token_liquidity = self.token_liquidity(&token).get(); + require!( + token_liquidity == 0, + "Cannot remove wrapped token due to remaining liquidity" + ); self.chain_specific_to_universal_mapping(&token).clear(); self.token_decimals_num(&token).clear(); } @@ -102,26 +113,14 @@ pub trait BridgedTokensWrapper: } #[only_owner] - #[endpoint(updateWhitelistedToken)] - fn update_whitelisted_token( - &self, - chain_specific_token_id: TokenIdentifier, - chain_specific_token_decimals: u32, - ) { - let chain_to_universal_mapper = - self.chain_specific_to_universal_mapping(&chain_specific_token_id); + #[endpoint(blacklistToken)] + fn blacklist_token(&self, chain_specific_token_id: TokenIdentifier) { + let token_liquidity = self.token_liquidity(&chain_specific_token_id).get(); require!( - !chain_to_universal_mapper.is_empty(), - "Chain-specific token was not whitelisted yet" + token_liquidity == 0, + "Cannot blacklist token due to remaining liquidity" ); - self.token_decimals_num(&chain_specific_token_id) - .set(chain_specific_token_decimals); - } - - #[only_owner] - #[endpoint(blacklistToken)] - fn blacklist_token(&self, chain_specific_token_id: TokenIdentifier) { let chain_to_universal_mapper = self.chain_specific_to_universal_mapping(&chain_specific_token_id); @@ -135,10 +134,18 @@ pub trait BridgedTokensWrapper: self.token_decimals_num(&chain_specific_token_id).clear(); } + #[only_owner] #[payable("*")] #[endpoint(depositLiquidity)] fn deposit_liquidity(&self) { let (payment_token, payment_amount) = self.call_value().single_fungible_esdt(); + require!( + !self + .chain_specific_to_universal_mapping(&payment_token) + .is_empty(), + "Provided token ID is not registered as a chain specific token" + ); + self.token_liquidity(&payment_token) .update(|liq| *liq += payment_amount); } @@ -156,6 +163,10 @@ pub trait BridgedTokensWrapper: let mut new_payments = ManagedVec::new(); for payment in &original_payments { + require!( + payment.token_nonce == 0, + "Only fungible tokens accepted for wrapping" + ); let universal_token_id_mapper = self.chain_specific_to_universal_mapping(&payment.token_identifier); @@ -250,21 +261,14 @@ pub trait BridgedTokensWrapper: requested_token: TokenIdentifier, safe_address: ManagedAddress, to: EthAddress, + opt_min_bridge_amount: OptionalValue>, ) { let converted_amount = self.unwrap_token_common(&requested_token); - let caller = self.blockchain().get_caller(); self.tx() .to(safe_address) .typed(esdt_safe_proxy::EsdtSafeProxy) - .create_transaction( - to, - OptionalValue::Some(esdt_safe_proxy::RefundInfo { - address: caller, - initial_batch_id: 0, - initial_nonce: 0, - }), - ) + .create_transaction(to, opt_min_bridge_amount) .single_esdt(&requested_token, 0, &converted_amount) .sync_call(); } @@ -327,6 +331,6 @@ pub trait BridgedTokensWrapper: universal_token_id: &TokenIdentifier, ) -> UnorderedSetMapper; - #[storage_mapper("token_decimals_num")] + #[storage_mapper("tokenDecimalsNum")] fn token_decimals_num(&self, token: &TokenIdentifier) -> SingleValueMapper; } diff --git a/bridged-tokens-wrapper/tests/bridged_tokens_wrapper_whitebox_test.rs b/bridged-tokens-wrapper/tests/bridged_tokens_wrapper_whitebox_test.rs index a36bd34d..214d0588 100644 --- a/bridged-tokens-wrapper/tests/bridged_tokens_wrapper_whitebox_test.rs +++ b/bridged-tokens-wrapper/tests/bridged_tokens_wrapper_whitebox_test.rs @@ -203,6 +203,9 @@ fn test_deposit_liquidity_should_work() { ), |sc| { sc.set_paused(false); + sc.chain_specific_to_universal_mapping(&managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)) + .set(managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)); + sc.deposit_liquidity(); let result = sc .token_liquidity(&managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)) @@ -391,10 +394,9 @@ fn test_unwrap_token_create_transaction_should_fail_case_1() { let address = convert_to_eth_address(ETH_ADDRESS); sc.unwrap_token_create_transaction( managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER), - ManagedAddress::new_from_bytes( - b"0102030405060708090a0b0c0d0e0f10", - ), + ManagedAddress::new_from_bytes(b"0102030405060708090a0b0c0d0e0f10"), address, + OptionalValue::None, ); }, |r| r.assert_user_error("Contract is paused"), @@ -431,10 +433,9 @@ fn test_unwrap_token_create_transaction_should_fail_case_2() { let address = convert_to_eth_address(ETH_ADDRESS); sc.unwrap_token_create_transaction( managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER), - ManagedAddress::new_from_bytes( - b"0102030405060708090a0b0c0d0e0f10", - ), + ManagedAddress::new_from_bytes(b"0102030405060708090a0b0c0d0e0f10"), address, + OptionalValue::None, ); }, |r| r.assert_user_error("Must pay more than 0 tokens!"), @@ -473,6 +474,7 @@ fn test_unwrap_token_create_transaction_should_fail_case_3() { managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER), ManagedAddress::zero(), address, + OptionalValue::None, ); }, |r| r.assert_user_error("Esdt token unavailable"), @@ -514,12 +516,13 @@ fn test_unwrap_token_create_transaction_should_fail_case_4() { |sc| { sc.set_paused(false); sc.add_wrapped_token(managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER), NUM_DECIMALS); + sc.chain_specific_to_universal_mapping(&managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)) + .set(managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)); + sc.deposit_liquidity(); let result = sc .token_liquidity(&managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)) .get(); - sc.chain_specific_to_universal_mapping(&managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)) - .set(managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)); assert_ne!(result, 0); }, ); @@ -546,6 +549,7 @@ fn test_unwrap_token_create_transaction_should_fail_case_4() { managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER), ManagedAddress::zero(), address, + OptionalValue::None, ); }, |r| r.assert_user_error("Contract does not have enough funds"), @@ -586,9 +590,10 @@ fn test_unwrap_token_create_transaction_should_work() { |sc| { sc.set_paused(false); sc.add_wrapped_token(managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER), NUM_DECIMALS); - sc.deposit_liquidity(); sc.chain_specific_to_universal_mapping(&managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)) .set(managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER)); + + sc.deposit_liquidity(); }, ); } @@ -658,57 +663,6 @@ fn test_whitelist_token_should_work() { ); } -#[test] -fn test_update_whitelisted_token_should_fail_case_1() { - let mut world = setup(); - let bridged_tokens_wrapper = WhiteboxContract::new( - BRIDGE_TOKENS_WRAPPER_ADDRESS_EXPR, - bridged_tokens_wrapper::contract_obj, - ); - - world.whitebox_call_check( - &bridged_tokens_wrapper, - ScCallStep::new() - .from(OWNER_ADDRESS_EXPR) - .expect(TxExpect::user_error( - "str:Chain-specific token was not whitelisted yet", - )), - |sc| { - sc.update_whitelisted_token(managed_token_id!(CHAIN_TOKEN_IDENTIFIER), 18u32); - }, - |r| r.assert_user_error("Chain-specific token was not whitelisted yet"), - ); -} - -#[test] -fn test_update_whitelisted_token_should_work() { - let mut world = setup(); - let bridged_tokens_wrapper = WhiteboxContract::new( - BRIDGE_TOKENS_WRAPPER_ADDRESS_EXPR, - bridged_tokens_wrapper::contract_obj, - ); - let contract_address = AddressValue::from(BRIDGE_TOKENS_WRAPPER_ADDRESS_EXPR); - - world.set_esdt_local_roles( - managed_address!(&contract_address.to_address()), - UNIVERSAL_TOKEN_IDENTIFIER, - &[EsdtLocalRole::Mint, EsdtLocalRole::Burn], - ); - - world.whitebox_call( - &bridged_tokens_wrapper, - ScCallStep::new().from(OWNER_ADDRESS_EXPR), - |sc| { - sc.whitelist_token( - managed_token_id!(CHAIN_TOKEN_IDENTIFIER), - NUM_DECIMALS, - managed_token_id!(UNIVERSAL_TOKEN_IDENTIFIER), - ); - sc.update_whitelisted_token(managed_token_id!(CHAIN_TOKEN_IDENTIFIER), 12); - }, - ); -} - #[test] fn test_blacklist_token_should_work() { let mut world = setup(); @@ -1008,9 +962,10 @@ fn test_unwrap_token_should_fail_case_4() { |sc| { sc.set_paused(false); sc.add_wrapped_token(managed_token_id!(CHAIN_TOKEN_IDENTIFIER), NUM_DECIMALS); - sc.deposit_liquidity(); sc.chain_specific_to_universal_mapping(&managed_token_id!(CHAIN_TOKEN_IDENTIFIER)) .set(managed_token_id!(CHAIN_TOKEN_IDENTIFIER)); + + sc.deposit_liquidity(); }, ); @@ -1067,9 +1022,10 @@ fn test_unwrap_token_should_work() { |sc| { sc.set_paused(false); sc.add_wrapped_token(managed_token_id!(CHAIN_TOKEN_IDENTIFIER), NUM_DECIMALS); - sc.deposit_liquidity(); sc.chain_specific_to_universal_mapping(&managed_token_id!(CHAIN_TOKEN_IDENTIFIER)) .set(managed_token_id!(CHAIN_TOKEN_IDENTIFIER)); + + sc.deposit_liquidity(); }, ); diff --git a/bridged-tokens-wrapper/tests/dfp_big_uint_test.rs b/bridged-tokens-wrapper/tests/dfp_big_uint_test.rs deleted file mode 100644 index 1c1c794b..00000000 --- a/bridged-tokens-wrapper/tests/dfp_big_uint_test.rs +++ /dev/null @@ -1,38 +0,0 @@ -use bridged_tokens_wrapper::DFPBigUint; -use multiversx_sc_scenario::DebugApi; - -#[test] -fn test_biguint() { - DebugApi::dummy(); - let raw = 123456789u64; - let dfp = DFPBigUint::::from_raw(raw.into(), 6); - let converted = dfp.clone().convert(9); - assert!(dfp.trunc() == converted.trunc()); - assert!(converted.clone().convert(9).to_raw() == 123456789000u64); - assert!(converted.clone().convert(1).to_raw() == 1234u64); - assert!(converted.clone().convert(3).to_raw() == 123456u64); - assert!(converted.clone().convert(0).to_raw() == 123u64); - assert!(converted.convert(5).to_raw() == 12345678u64); -} - -#[test] -fn test_biguint_zero_dec() { - DebugApi::dummy(); - let raw = 123u64; - let dfp = DFPBigUint::::from_raw(raw.into(), 0); - let converted = dfp.clone().convert(9); - assert!(dfp.trunc() == converted.trunc()); - assert!(converted.clone().convert(9).to_raw() == 123000000000u64); - assert!(converted.clone().convert(1).to_raw() == 1230u64); - assert!(converted.clone().convert(3).to_raw() == 123000u64); - assert!(converted.clone().convert(0).to_raw() == 123u64); - assert!(converted.convert(5).to_raw() == 12300000u64); -} - -#[test] -fn test_mandos_scenario_values() { - DebugApi::dummy(); - let raw = 300000000000000u64; - let dfp = DFPBigUint::::from_raw(raw.into(), 18); - assert!(dfp.convert(6).to_raw() == 300u64); -} diff --git a/bridged-tokens-wrapper/wasm/src/lib.rs b/bridged-tokens-wrapper/wasm/src/lib.rs index cdad1731..62c77484 100644 --- a/bridged-tokens-wrapper/wasm/src/lib.rs +++ b/bridged-tokens-wrapper/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 17 +// Endpoints: 16 // Async Callback (empty): 1 -// Total number of exported functions: 20 +// Total number of exported functions: 19 #![no_std] @@ -24,7 +24,6 @@ multiversx_sc_wasm_adapter::endpoints! { updateWrappedToken => update_wrapped_token removeWrappedToken => remove_wrapped_token whitelistToken => whitelist_token - updateWhitelistedToken => update_whitelisted_token blacklistToken => blacklist_token depositLiquidity => deposit_liquidity wrapTokens => wrap_tokens diff --git a/common/mock-contracts/mock-esdt-safe/src/mock_esdt_safe.rs b/common/mock-contracts/mock-esdt-safe/src/mock_esdt_safe.rs index 1c5a8e3a..1397c8c4 100644 --- a/common/mock-contracts/mock-esdt-safe/src/mock_esdt_safe.rs +++ b/common/mock-contracts/mock-esdt-safe/src/mock_esdt_safe.rs @@ -22,8 +22,8 @@ pub trait MockEsdtSafe { fn upgrade(&self) {} #[payable("*")] - #[endpoint(createTransaction)] - fn create_transaction( + #[endpoint(createRefundTransaction)] + fn create_refund_transaction( &self, _to: EthAddress, _opt_refund_info: OptionalValue>, diff --git a/common/mock-contracts/mock-esdt-safe/wasm/src/lib.rs b/common/mock-contracts/mock-esdt-safe/wasm/src/lib.rs index 7c9a8414..30309bd8 100644 --- a/common/mock-contracts/mock-esdt-safe/wasm/src/lib.rs +++ b/common/mock-contracts/mock-esdt-safe/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 1 +// Endpoints: 3 // Async Callback (empty): 1 -// Total number of exported functions: 4 +// Total number of exported functions: 6 #![no_std] @@ -20,7 +20,9 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade - createTransaction => create_transaction + createRefundTransaction => create_refund_transaction + withdrawTransactionFees => withdraw_transaction_fees + withdrawRefundFeesForEthereum => withdraw_refund_fees_for_ethereum ) } diff --git a/common/sc-proxies/src/bridge_proxy_contract_proxy.rs b/common/sc-proxies/src/bridge_proxy_contract_proxy.rs index e6010500..3c771c45 100644 --- a/common/sc-proxies/src/bridge_proxy_contract_proxy.rs +++ b/common/sc-proxies/src/bridge_proxy_contract_proxy.rs @@ -109,6 +109,19 @@ where .original_result() } + pub fn execute_refund_transaction< + Arg0: ProxyArg, + >( + self, + tx_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("executeRefundTransaction") + .argument(&tx_id) + .original_result() + } + pub fn get_pending_transaction_by_id< Arg0: ProxyArg, >( @@ -131,6 +144,37 @@ where .original_result() } + pub fn get_refund_transaction_by_id< + Arg0: ProxyArg, + >( + self, + tx_id: Arg0, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getRefundTransactionById") + .argument(&tx_id) + .original_result() + } + + pub fn get_refund_transactions( + self, + ) -> TxTypedCall>>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getRefundTransactions") + .original_result() + } + + pub fn refund_transactions( + self, + ) -> TxTypedCall>>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("refundTransactions") + .original_result() + } + pub fn highest_tx_id( self, ) -> TxTypedCall { diff --git a/common/sc-proxies/src/bridged_tokens_wrapper_proxy.rs b/common/sc-proxies/src/bridged_tokens_wrapper_proxy.rs index ac306e11..0610fc9b 100644 --- a/common/sc-proxies/src/bridged_tokens_wrapper_proxy.rs +++ b/common/sc-proxies/src/bridged_tokens_wrapper_proxy.rs @@ -145,22 +145,6 @@ where .original_result() } - pub fn update_whitelisted_token< - Arg0: ProxyArg>, - Arg1: ProxyArg, - >( - self, - chain_specific_token_id: Arg0, - chain_specific_token_decimals: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("updateWhitelistedToken") - .argument(&chain_specific_token_id) - .argument(&chain_specific_token_decimals) - .original_result() - } - pub fn blacklist_token< Arg0: ProxyArg>, >( @@ -207,17 +191,20 @@ where Arg0: ProxyArg>, Arg1: ProxyArg>, Arg2: ProxyArg>, + Arg3: ProxyArg>>, >( self, requested_token: Arg0, safe_address: Arg1, to: Arg2, + opt_min_bridge_amount: Arg3, ) -> TxTypedCall { self.wrapped_tx .raw_call("unwrapTokenCreateTransaction") .argument(&requested_token) .argument(&safe_address) .argument(&to) + .argument(&opt_min_bridge_amount) .original_result() } diff --git a/common/sc-proxies/src/esdt_safe_proxy.rs b/common/sc-proxies/src/esdt_safe_proxy.rs index 154e5081..39207314 100644 --- a/common/sc-proxies/src/esdt_safe_proxy.rs +++ b/common/sc-proxies/src/esdt_safe_proxy.rs @@ -131,6 +131,15 @@ where .original_result() } + pub fn add_refund_batch_for_failed_tx( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addRefundBatchForFailedTx") + .original_result() + } + /// Create an MultiversX -> Ethereum transaction. Only fungible tokens are accepted. /// /// Every transfer will have a part of the tokens subtracted as fees. @@ -139,6 +148,28 @@ where /// /// fee_amount = price_per_gas_unit * eth_tx_gas_limit pub fn create_transaction< + Arg0: ProxyArg>, + Arg1: ProxyArg>>, + >( + self, + to: Arg0, + opt_min_bridge_amount: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("createTransaction") + .argument(&to) + .argument(&opt_min_bridge_amount) + .original_result() + } + + /// Create an Ethereum -> MultiversX refund transaction. Only fungible tokens are accepted. + /// + /// Every transfer will have a part of the tokens subtracted as fees. + /// The fee amount depends on the global eth_tx_gas_limit + /// and the current GWEI price, respective to the bridged token + /// + /// fee_amount = price_per_gas_unit * eth_tx_gas_limit + pub fn create_refund_transaction< Arg0: ProxyArg>, Arg1: ProxyArg>>, >( @@ -147,7 +178,7 @@ where opt_refund_info: Arg1, ) -> TxTypedCall { self.wrapped_tx - .raw_call("createTransaction") + .raw_call("createRefundTransaction") .argument(&to) .argument(&opt_refund_info) .original_result() @@ -221,14 +252,17 @@ where /// Useful for knowing which token IDs to pass to the claimRefund endpoint. pub fn get_refund_amounts< Arg0: ProxyArg>, + Arg1: ProxyArg>>>, >( self, address: Arg0, + opt_tokens: Arg1, ) -> TxTypedCall, BigUint>>> { self.wrapped_tx .payment(NotPayable) .raw_call("getRefundAmounts") .argument(&address) + .argument(&opt_tokens) .original_result() } @@ -356,14 +390,17 @@ where /// where percentages must add up to the PERCENTAGE_TOTAL constant pub fn distribute_fees< Arg0: ProxyArg>>, + Arg1: ProxyArg>>>, >( self, address_percentage_pairs: Arg0, + opt_tokens_to_distribute: Arg1, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) .raw_call("distributeFees") .argument(&address_percentage_pairs) + .argument(&opt_tokens_to_distribute) .original_result() } diff --git a/common/sc-proxies/src/multisig_proxy.rs b/common/sc-proxies/src/multisig_proxy.rs index 80334b47..f3e21fdf 100644 --- a/common/sc-proxies/src/multisig_proxy.rs +++ b/common/sc-proxies/src/multisig_proxy.rs @@ -298,6 +298,19 @@ where .original_result() } + pub fn clear_actions_for_batch_id< + Arg0: ProxyArg, + >( + self, + eth_batch_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("clearActionsForBatchId") + .argument(ð_batch_id) + .original_result() + } + /// Used by board members to sign actions. pub fn sign< Arg0: ProxyArg, @@ -315,21 +328,18 @@ where pub fn upgrade_child_contract_from_source< Arg0: ProxyArg>, Arg1: ProxyArg>, - Arg2: ProxyArg, - Arg3: ProxyArg>>, + Arg2: ProxyArg>>, >( self, child_sc_address: Arg0, source_address: Arg1, - is_payable: Arg2, - init_args: Arg3, + init_args: Arg2, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) .raw_call("upgradeChildContractFromSource") .argument(&child_sc_address) .argument(&source_address) - .argument(&is_payable) .argument(&init_args) .original_result() } @@ -887,8 +897,6 @@ where .original_result() } - /// Actions are cleared after execution, so an empty entry means the action was executed already - /// Returns "false" if the action ID is invalid pub fn was_action_executed< Arg0: ProxyArg, >( diff --git a/common/token-module/src/lib.rs b/common/token-module/src/lib.rs index 4366d5ae..713b54e0 100644 --- a/common/token-module/src/lib.rs +++ b/common/token-module/src/lib.rs @@ -27,9 +27,8 @@ pub trait TokenModule: fn distribute_fees( &self, address_percentage_pairs: ManagedVec>, + opt_tokens_to_distribute: OptionalValue>>, ) { - let percentage_total = BigUint::from(PERCENTAGE_TOTAL); - let mut percentage_sum = 0u64; for pair in &address_percentage_pairs { percentage_sum += pair.percentage as u64; @@ -39,28 +38,51 @@ pub trait TokenModule: INVALID_PERCENTAGE_SUM_OVER_ERR_MSG ); - for token_id in self.token_whitelist().iter() { - let accumulated_fees = self.accumulated_transaction_fees(&token_id).get(); - if accumulated_fees == 0u32 { - continue; + match opt_tokens_to_distribute { + OptionalValue::Some(tokens_to_distrbute) => { + for token_id in tokens_to_distrbute { + self.distribute_fees_token(&address_percentage_pairs, token_id); + } } + OptionalValue::None => { + for token_id in self.token_whitelist().iter() { + self.distribute_fees_token(&address_percentage_pairs, token_id); + } + } + }; + } - let mut remaining_fees = accumulated_fees.clone(); + fn distribute_fees_token( + &self, + address_percentage_pairs: &ManagedVec>, + token_id: TokenIdentifier, + ) { + let percentage_total = BigUint::from(PERCENTAGE_TOTAL); - for pair in &address_percentage_pairs { - let amount_to_send = - &(&accumulated_fees * &BigUint::from(pair.percentage)) / &percentage_total; + let accumulated_fees = self.accumulated_transaction_fees(&token_id).get(); + if accumulated_fees == 0u32 { + return; + } - if amount_to_send > 0 { - remaining_fees -= &amount_to_send; + let mut remaining_fees = accumulated_fees.clone(); - self.tx() - .to(&pair.address) - .single_esdt(&token_id, 0, &amount_to_send) - .transfer(); - } + for pair in address_percentage_pairs { + let amount_to_send = + &(&accumulated_fees * &BigUint::from(pair.percentage)) / &percentage_total; + + if amount_to_send > 0 { + remaining_fees -= &amount_to_send; + + self.tx() + .to(&pair.address) + .single_esdt(&token_id, 0, &amount_to_send) + .transfer(); } + } + if remaining_fees == 0 { + self.accumulated_transaction_fees(&token_id).clear(); + } else { self.accumulated_transaction_fees(&token_id) .set(&remaining_fees); } @@ -80,6 +102,11 @@ pub trait TokenModule: burn_balance: &BigUint, opt_default_price_per_gas_unit: OptionalValue, ) { + require!( + !self.token_whitelist().contains(token_id), + "Token already whitelisted" + ); + self.token_ticker(token_id).set(&ticker); if let OptionalValue::Some(default_price_per_gas_unit) = opt_default_price_per_gas_unit { @@ -111,6 +138,13 @@ pub trait TokenModule: burn_balance == &BigUint::zero(), "Stored tokens must have 0 burn balance!" ); + let payments = self.call_value().all_esdt_transfers(); + if !payments.is_empty() { + let (payment_token_id, _, payment_token_amount) = payments.get(0).into_tuple(); + require!(payment_token_id == *token_id, "Wrong token id"); + require!(payment_token_amount == *total_balance, "Wrong payment"); + } + if total_balance > &BigUint::zero() { self.init_supply(token_id, total_balance); } @@ -124,8 +158,6 @@ pub trait TokenModule: self.token_ticker(&token_id).clear(); self.default_price_per_gas_unit(&token_id).clear(); - self.mint_burn_token(&token_id).clear(); - self.native_token(&token_id).clear(); self.token_whitelist().swap_remove(&token_id); } @@ -194,7 +226,7 @@ pub trait TokenModule: ); require!( self.native_token(token_id).get(), - "Only native tokens can be stored!" + "Only native tokens can be stored" ); self.total_balances(token_id).update(|total| { @@ -213,7 +245,7 @@ pub trait TokenModule: self.require_token_in_whitelist(token_id); require!( self.mint_burn_token(token_id).get(), - "Cannot init for non mintable/burnable tokens" + "Can init only for mintable/burnable tokens" ); self.mint_balances(token_id).set(mint_amount); diff --git a/common/tx-batch-module/src/lib.rs b/common/tx-batch-module/src/lib.rs index c033a911..046211e5 100644 --- a/common/tx-batch-module/src/lib.rs +++ b/common/tx-batch-module/src/lib.rs @@ -9,6 +9,9 @@ use tx_batch_mapper::TxBatchMapper; pub mod batch_status; pub mod tx_batch_mapper; +const MAX_TX_BATCH_BLOCK_DURATION: u64 = 1000; +const MAX_TX_BATCH_SIZE: usize = 100; + #[multiversx_sc::module] pub trait TxBatchModule { // endpoints - owner-only @@ -17,8 +20,8 @@ pub trait TxBatchModule { #[endpoint(setMaxTxBatchSize)] fn set_max_tx_batch_size(&self, new_max_tx_batch_size: usize) { require!( - new_max_tx_batch_size > 0, - "Max tx batch size must be more than 0" + new_max_tx_batch_size > 0 && new_max_tx_batch_size < MAX_TX_BATCH_SIZE, + "Max tx batch size out of limits" ); self.max_tx_batch_size().set(new_max_tx_batch_size); @@ -28,8 +31,9 @@ pub trait TxBatchModule { #[endpoint(setMaxTxBatchBlockDuration)] fn set_max_tx_batch_block_duration(&self, new_max_tx_batch_block_duration: u64) { require!( - new_max_tx_batch_block_duration > 0, - "Max tx batch block duration must be more than 0" + new_max_tx_batch_block_duration > 0 + && new_max_tx_batch_block_duration < MAX_TX_BATCH_BLOCK_DURATION, + "Max tx batch block duration out of limits" ); self.max_tx_batch_block_duration() diff --git a/common/tx-batch-module/src/tx_batch_mapper.rs b/common/tx-batch-module/src/tx_batch_mapper.rs index 589d7c08..1b72bb2d 100644 --- a/common/tx-batch-module/src/tx_batch_mapper.rs +++ b/common/tx-batch-module/src/tx_batch_mapper.rs @@ -48,6 +48,9 @@ where { fn clear(&mut self) { self.vec_mapper.clear(); + self.vec_len = 0; + self.first_tx = None; + self.last_tx = None; } } diff --git a/esdt-safe/scenarios/setup_accounts.scen.json b/esdt-safe/scenarios/setup_accounts.scen.json index cb7d0d3c..8eda41e2 100644 --- a/esdt-safe/scenarios/setup_accounts.scen.json +++ b/esdt-safe/scenarios/setup_accounts.scen.json @@ -150,8 +150,11 @@ "nonce": "0", "balance": "0", "storage": { - "str:feeEstimatorAddress": "sc:price_aggregator", - "str:multiTransferEsdtAddress": "sc:multi_transfer" + "str:esdtSafeAddress": "sc:esdt_safe", + "str:multiTransferEsdtAddress": "sc:multi_transfer", + "str:proxyAddress": "sc:bridge_proxy", + "str:bridgedTokensWrapperAddress": "sc:bridged_tokens_wrapper", + "str:feeEstimatorAddress": "sc:price_aggregator" }, "code": "file:../../multisig/output/multisig.wasm", "owner": "address:owner" diff --git a/esdt-safe/scenarios/zero_fees.scen.json b/esdt-safe/scenarios/zero_fees.scen.json index de13dd78..6e7bf148 100644 --- a/esdt-safe/scenarios/zero_fees.scen.json +++ b/esdt-safe/scenarios/zero_fees.scen.json @@ -13,7 +13,11 @@ "nonce": "0", "balance": "0", "storage": { - "str:feeEstimatorAddress": "0x0000000000000000000000000000000000000000000000000000000000000000" + "str:feeEstimatorAddress": "0x0000000000000000000000000000000000000000000000000000000000000000", + "str:esdtSafeAddress": "sc:esdt_safe", + "str:multiTransferEsdtAddress": "sc:multi_transfer", + "str:proxyAddress": "sc:bridge_proxy", + "str:bridgedTokensWrapperAddress": "sc:bridged_tokens_wrapper" }, "code": "file:../../multisig/output/multisig.wasm", "owner": "address:owner" diff --git a/esdt-safe/src/lib.rs b/esdt-safe/src/lib.rs index 47c913b4..d9e6c4fb 100644 --- a/esdt-safe/src/lib.rs +++ b/esdt-safe/src/lib.rs @@ -24,7 +24,6 @@ pub struct TransactionDetails { pub required_fee: BigUint, pub to_address: ManagedBuffer, pub is_refund_tx: bool, - pub refund_info: RefundInfo, } #[type_abi] @@ -86,7 +85,7 @@ pub trait EsdtSafe: // set ticker for "GWEI" let gwei_token_id = TokenIdentifier::from(GWEI_STRING); self.token_ticker(&gwei_token_id) - .set(gwei_token_id.as_managed_buffer()); + .set_if_empty(gwei_token_id.as_managed_buffer()); self.set_paused(true); } @@ -195,6 +194,7 @@ pub trait EsdtSafe: }; if refund_tx.amount <= required_fee { + self.failed_refunds().push(&refund_tx); continue; } @@ -249,12 +249,23 @@ pub trait EsdtSafe: } } + #[endpoint(addRefundBatchForFailedTx)] + fn add_refund_batch_for_failed_tx(&self) { + let mut refund_transactions: ManagedVec> = ManagedVec::new(); + for failed_refund in self.failed_refunds().iter() { + refund_transactions.push(failed_refund); + } + + self.add_refund_batch(refund_transactions); + } + fn create_transaction_common( &self, to: EthAddress, - opt_refund_info: OptionalValue>, + opt_min_bridge_amount: OptionalValue>, ) -> TransactionDetails { require!(self.not_paused(), "Cannot create transaction while paused"); + require!(to != EthAddress::zero(), "Cannot send to an empty address"); let (payment_token, payment_amount) = self.call_value().single_fungible_esdt(); self.require_token_in_whitelist(&payment_token); @@ -265,29 +276,23 @@ pub trait EsdtSafe: "Transaction fees cost more than the entire bridged amount" ); + match opt_min_bridge_amount { + OptionalValue::Some(min_bridge_amount) => { + require!( + required_fee < min_bridge_amount, + "Minimum bridged amount after fee is not achieved" + ); + } + OptionalValue::None => {} + }; + self.require_below_max_amount(&payment_token, &payment_amount); + let caller = self.blockchain().get_caller(); - // This addr is used for the refund, if the transaction fails - // This is passed by the BridgeTokenWrapper contract let mut is_refund_tx = false; - let caller = self.blockchain().get_caller(); - let refund_info = match opt_refund_info { - OptionalValue::Some(refund_info) => { - if caller == self.get_bridge_proxy_address() { - is_refund_tx = true; - refund_info - } else if caller == self.get_bridged_tokens_wrapper_address() { - refund_info - } else { - sc_panic!("Cannot specify a refund address from this caller"); - } - } - OptionalValue::None => RefundInfo { - address: caller, - initial_batch_id: 0, - initial_nonce: 0, - }, - }; + if caller == self.get_bridge_proxy_address() { + is_refund_tx = true; + } self.accumulated_transaction_fees(&payment_token) .update(|fees| *fees += &required_fee); @@ -297,7 +302,7 @@ pub trait EsdtSafe: let tx = Transaction { block_nonce: self.blockchain().get_block_nonce(), nonce: tx_nonce, - from: refund_info.address.as_managed_buffer().clone(), + from: caller.as_managed_buffer().clone(), to: to.as_managed_buffer().clone(), token_identifier: payment_token.clone(), amount: actual_bridged_amount.clone(), @@ -333,7 +338,6 @@ pub trait EsdtSafe: required_fee, to_address: tx.to, is_refund_tx, - refund_info, } } @@ -349,37 +353,67 @@ pub trait EsdtSafe: #[payable("*")] #[endpoint(createTransaction)] fn create_transaction( + &self, + to: EthAddress, + opt_min_bridge_amount: OptionalValue>, + ) { + let transaction_details = self.create_transaction_common(to, opt_min_bridge_amount); + let caller = self.blockchain().get_caller(); + + self.create_transaction_event( + transaction_details.batch_id, + transaction_details.tx_nonce, + transaction_details.payment_token, + transaction_details.actual_bridged_amount, + transaction_details.required_fee, + caller.as_managed_buffer().clone(), + transaction_details.to_address, + ); + } + + /// Create an Ethereum -> MultiversX refund transaction. Only fungible tokens are accepted. + /// + /// Every transfer will have a part of the tokens subtracted as fees. + /// The fee amount depends on the global eth_tx_gas_limit + /// and the current GWEI price, respective to the bridged token + /// + /// fee_amount = price_per_gas_unit * eth_tx_gas_limit + #[payable("*")] + #[endpoint(createRefundTransaction)] + fn create_refund_transaction( &self, to: EthAddress, opt_refund_info: OptionalValue>, ) { - let transaction_details = self.create_transaction_common(to, opt_refund_info); - - if !transaction_details.is_refund_tx { - self.create_transaction_event( - transaction_details.batch_id, - transaction_details.tx_nonce, - transaction_details.payment_token, - transaction_details.actual_bridged_amount, - transaction_details.required_fee, - transaction_details - .refund_info - .address - .as_managed_buffer() - .clone(), - transaction_details.to_address, - ); - } else { - self.create_refund_transaction_event( - transaction_details.batch_id, - transaction_details.tx_nonce, - transaction_details.payment_token, - transaction_details.actual_bridged_amount, - transaction_details.required_fee, - transaction_details.refund_info.initial_batch_id, - transaction_details.refund_info.initial_nonce, - ); - } + let bridge_proxy_address = self.get_bridge_proxy_address(); + let caller = self.blockchain().get_caller(); + + require!( + bridge_proxy_address == caller, + "Only BridgeProxy SC can call this endpoint" + ); + + let transaction_details = self.create_transaction_common(to, OptionalValue::None); + let caller = self.blockchain().get_caller(); + + let refund_info = match opt_refund_info { + OptionalValue::Some(refund_info) => refund_info, + OptionalValue::None => RefundInfo { + address: caller, + initial_batch_id: 0u64, + initial_nonce: 0, + }, + }; + + self.create_refund_transaction_event( + transaction_details.batch_id, + transaction_details.tx_nonce, + transaction_details.payment_token, + transaction_details.actual_bridged_amount, + transaction_details.required_fee, + refund_info.initial_batch_id, + refund_info.initial_nonce, + ); } /// Claim funds for failed MultiversX -> Ethereum transactions. @@ -482,18 +516,40 @@ pub trait EsdtSafe: fn get_refund_amounts( &self, address: ManagedAddress, + opt_tokens: OptionalValue>>, ) -> MultiValueEncoded> { let mut refund_amounts = MultiValueEncoded::new(); - for token_id in self.token_whitelist().iter() { - let amount = self.refund_amount(&address, &token_id).get(); - if amount > 0u32 { - refund_amounts.push((token_id, amount).into()); + match opt_tokens { + OptionalValue::Some(tokens) => { + for token_id in tokens { + self.get_refund_amount_for_token(&address, token_id, &mut refund_amounts); + } + } + OptionalValue::None => { + for token_id in self.token_whitelist().iter() { + let amount = self.refund_amount(&address, &token_id).get(); + if amount > 0u32 { + refund_amounts.push((token_id, amount).into()); + } + } } } refund_amounts } + fn get_refund_amount_for_token( + &self, + address: &ManagedAddress, + token_id: TokenIdentifier, + refund_amounts: &mut MultiValueEncoded>, + ) { + let amount = self.refund_amount(address, &token_id).get(); + if amount > 0u32 { + refund_amounts.push((token_id, amount).into()); + } + } + // views #[view(getTotalRefundAmounts)] @@ -648,4 +704,7 @@ pub trait EsdtSafe: address: &ManagedAddress, token_id: &TokenIdentifier, ) -> SingleValueMapper; + + #[storage_mapper("failedRefunds")] + fn failed_refunds(&self) -> VecMapper>; } diff --git a/esdt-safe/tests/esdt_safe_blackbox_test.rs b/esdt-safe/tests/esdt_safe_blackbox_test.rs index bf6ca44f..c3e3e06e 100644 --- a/esdt-safe/tests/esdt_safe_blackbox_test.rs +++ b/esdt-safe/tests/esdt_safe_blackbox_test.rs @@ -257,19 +257,6 @@ impl EsdtSafeTestState { OptionalValue::Some(BigUint::from(0u64)), ) .run(); - - self.esdt_raw_transaction() - .add_token_to_whitelist( - TOKEN_ID, - "TOKEN_ID", - false, - true, - BigUint::from(0u64), - BigUint::from(0u64), - BigUint::from(0u64), - OptionalValue::Some(BigUint::from(0u64)), - ) - .run(); } fn init_supply_should_fail( @@ -360,8 +347,10 @@ impl EsdtSafeTestState { ) { self.esdt_raw_transaction() .create_transaction( - EthAddress::zero(), - OptionalValue::None::>, + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + OptionalValue::>::None, ) .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(token_id), @@ -375,8 +364,10 @@ impl EsdtSafeTestState { fn single_transaction_should_work(&mut self, token_id: TestTokenIdentifier, amount: u64) { self.esdt_raw_transaction() .create_transaction( - EthAddress::zero(), - OptionalValue::None::>, + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + OptionalValue::>::None, ) .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(token_id), @@ -595,25 +586,6 @@ fn init_supply_test_mint_burn() { .with_result(ExpectError(ERROR, "Token not in whitelist")) .run(); - state - .esdt_raw_transaction() - .init_supply_mint_burn(TOKEN_ID, BigUint::from(10_000u64), BigUint::from(10_000u64)) - .with_result(ExpectError( - ERROR, - "Cannot init for non mintable/burnable tokens", - )) - .run(); - - state - .esdt_raw_transaction() - .init_supply_mint_burn( - TOKEN_WITH_BURN_ROLE, - BigUint::from(10_000u64), - BigUint::from(10_000u64), - ) - .with_result(ReturnsResult) - .run(); - let total_minted = state .world .query() @@ -625,7 +597,7 @@ fn init_supply_test_mint_burn() { assert_eq!( total_minted, - BigUint::from(10_000u64), + BigUint::from(0u64), "Total supply should be 10,000" ); @@ -640,7 +612,7 @@ fn init_supply_test_mint_burn() { assert_eq!( total_burned, - BigUint::from(10_000u64), + BigUint::from(0u64), "Total supply should be 10,000" ); } @@ -660,19 +632,6 @@ fn set_transaction_batch_status_test() { let mut tx_statuses_invalid = MultiValueEncoded::::new(); tx_statuses_invalid.push(TransactionStatus::Pending); - state - .world - .tx() - .from(MULTISIG_ADDRESS) - .to(ESDT_SAFE_ADDRESS) - .typed(esdt_safe_proxy::EsdtSafeProxy) - .init_supply_mint_burn( - TOKEN_WITH_BURN_ROLE, - BigUint::from(100_000u64), - BigUint::from(10_000u64), - ) - .run(); - state .world .tx() @@ -762,22 +721,13 @@ fn esdt_safe_create_transaction() { .from(BRIDGE_PROXY_ADDRESS) .to(ESDT_SAFE_ADDRESS) .typed(esdt_safe_proxy::EsdtSafeProxy) - .create_transaction(EthAddress::zero(), OptionalValue::Some(refund_info.clone())) - .single_esdt(&TOKEN_ID.into(), 0, &BigUint::from(10u64)) - .run(); - - state - .world - .tx() - .from(OWNER_ADDRESS) - .to(ESDT_SAFE_ADDRESS) - .typed(esdt_safe_proxy::EsdtSafeProxy) - .create_transaction(EthAddress::zero(), OptionalValue::Some(refund_info.clone())) + .create_refund_transaction( + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + OptionalValue::Some(refund_info.clone()), + ) .single_esdt(&TOKEN_ID.into(), 0, &BigUint::from(10u64)) - .returns(ExpectError( - ERROR, - "Cannot specify a refund address from this caller", - )) .run(); state @@ -786,7 +736,12 @@ fn esdt_safe_create_transaction() { .from(BRIDGED_TOKENS_WRAPPER_ADDRESS) .to(ESDT_SAFE_ADDRESS) .typed(esdt_safe_proxy::EsdtSafeProxy) - .create_transaction(EthAddress::zero(), OptionalValue::Some(refund_info.clone())) + .create_transaction( + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + OptionalValue::>::None, + ) .single_esdt(&TOKEN_ID.into(), 0, &BigUint::from(10u64)) .run(); @@ -835,6 +790,44 @@ fn esdt_safe_create_transaction() { assert_eq!(total_balances, 120000u64); } +#[test] +fn create_refund_transaction_not_from_bridge_proxy_test() { + let mut state = EsdtSafeTestState::new(); + state.multisig_deploy(); + state.safe_deploy(); + + state.world.set_esdt_balance( + MULTISIG_ADDRESS, + b"TOKEN-WITH", + BigUint::from(10_000_000u64), + ); + + let refund_info = sc_proxies::esdt_safe_proxy::RefundInfo:: { + address: ManagedAddress::from(OWNER_ADDRESS.eval_to_array()), + initial_batch_id: 1u64, + initial_nonce: 1u64, + }; + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .create_refund_transaction( + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + OptionalValue::Some(refund_info.clone()), + ) + .single_esdt(&TOKEN_ID.into(), 0, &BigUint::from(10u64)) + .returns(ExpectError( + ERROR, + "Only BridgeProxy SC can call this endpoint", + )) + .run(); +} + #[test] fn add_refund_batch_test() { let mut state = EsdtSafeTestState::new(); @@ -1096,12 +1089,14 @@ fn claim_refund_test() { state.single_transaction_should_work(MINT_BURN_TOKEN, 1000u64); state.set_transaction_batch_status_should_work(1, tx_statuses.clone()); + let opt_tokens: OptionalValue>> = + OptionalValue::None; let refund = state .world .query() .to(ESDT_SAFE_ADDRESS) .typed(esdt_safe_proxy::EsdtSafeProxy) - .get_refund_amounts(MULTISIG_ADDRESS) + .get_refund_amounts(MULTISIG_ADDRESS, opt_tokens.clone()) .returns(ReturnsResult) .run(); @@ -1123,7 +1118,7 @@ fn claim_refund_test() { .query() .to(ESDT_SAFE_ADDRESS) .typed(esdt_safe_proxy::EsdtSafeProxy) - .get_refund_amounts(OWNER_ADDRESS) + .get_refund_amounts(OWNER_ADDRESS, opt_tokens) .returns(ReturnsResult) .run(); assert!(refund_after.is_empty()); @@ -1287,7 +1282,23 @@ fn withdraw_transaction_fees_test() { .typed(esdt_safe_proxy::EsdtSafeProxy) .create_transaction( EthAddress::zero(), - OptionalValue::None::>, + OptionalValue::>::None, + ) + .single_esdt(&TOKEN_ID.into(), 0, &BigUint::from(1_000_000u64)) + .returns(ExpectError(ERROR, "Cannot send to an empty address")) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .create_transaction( + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + OptionalValue::>::None, ) .single_esdt(&TOKEN_ID.into(), 0, &BigUint::from(1_000_000u64)) .returns(ReturnsResult) diff --git a/esdt-safe/wasm/src/lib.rs b/esdt-safe/wasm/src/lib.rs index e4c10874..e5efcd8c 100644 --- a/esdt-safe/wasm/src/lib.rs +++ b/esdt-safe/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 43 +// Endpoints: 45 // Async Callback (empty): 1 -// Total number of exported functions: 46 +// Total number of exported functions: 48 #![no_std] @@ -22,7 +22,9 @@ multiversx_sc_wasm_adapter::endpoints! { upgrade => upgrade setTransactionBatchStatus => set_transaction_batch_status addRefundBatch => add_refund_batch + addRefundBatchForFailedTx => add_refund_batch_for_failed_tx createTransaction => create_transaction + createRefundTransaction => create_refund_transaction claimRefund => claim_refund withdrawRefundFeesForEthereum => withdraw_refund_fees_for_ethereum withdrawTransactionFees => withdraw_transaction_fees diff --git a/multi-transfer-esdt/src/lib.rs b/multi-transfer-esdt/src/lib.rs index 70a1c3bf..9d9ff858 100644 --- a/multi-transfer-esdt/src/lib.rs +++ b/multi-transfer-esdt/src/lib.rs @@ -55,6 +55,19 @@ pub trait MultiTransferEsdt: let safe_address = self.get_esdt_safe_address(); for eth_tx in transfers { + let token_roles = self + .blockchain() + .get_esdt_local_roles(ð_tx.token_id.clone()); + if token_roles.has_role(&EsdtLocalRole::Transfer) { + // let refund_tx = self.convert_to_refund_tx(eth_tx.clone()); + // refund_tx_list.push(refund_tx); + // self.token_with_transfer_role_event(eth_tx.token_id); + + self.add_eth_tx_to_refund_tx_list(eth_tx.clone(), &mut refund_tx_list); + self.token_with_transfer_role_event(eth_tx.token_id); + continue; + } + let is_success: bool = self .tx() .to(safe_address.clone()) @@ -63,38 +76,50 @@ pub trait MultiTransferEsdt: .returns(ReturnsResult) .sync_call(); - require!(is_success, "Invalid token or amount"); + if !is_success { + self.add_eth_tx_to_refund_tx_list(eth_tx, &mut refund_tx_list); + continue; + } let universal_token = self.get_universal_token(eth_tx.clone()); - let mut must_refund = false; if eth_tx.to.is_zero() { - self.transfer_failed_invalid_destination(batch_id, eth_tx.tx_nonce); - must_refund = true; - } else if self.is_above_max_amount(ð_tx.token_id, ð_tx.amount) { - self.transfer_over_max_amount(batch_id, eth_tx.tx_nonce); - must_refund = true; - } else if self.is_account_same_shard_frozen(sc_shard, ð_tx.to, &universal_token) { - self.transfer_failed_frozen_destination_account(batch_id, eth_tx.tx_nonce); - must_refund = true; + self.add_eth_tx_to_refund_tx_list(eth_tx.clone(), &mut refund_tx_list); + self.transfer_failed_invalid_destination_event(batch_id, eth_tx.tx_nonce); + continue; } - - if must_refund { - let refund_tx = self.convert_to_refund_tx(eth_tx); - refund_tx_list.push(refund_tx); - + if self.is_above_max_amount(ð_tx.token_id, ð_tx.amount) { + self.add_eth_tx_to_refund_tx_list(eth_tx.clone(), &mut refund_tx_list); + self.transfer_over_max_amount_event(batch_id, eth_tx.tx_nonce); + continue; + } + if self.is_account_same_shard_frozen(sc_shard, ð_tx.to, &universal_token) { + self.add_eth_tx_to_refund_tx_list(eth_tx.clone(), &mut refund_tx_list); + self.transfer_failed_frozen_destination_account_event(batch_id, eth_tx.tx_nonce); continue; } // emit event before the actual transfer so we don't have to save the tx_nonces as well - self.transfer_performed_event( - batch_id, - eth_tx.from.clone(), - eth_tx.to.clone(), - eth_tx.token_id.clone(), - eth_tx.amount.clone(), - eth_tx.tx_nonce, - ); + // emit events only for non-SC destinations + if self.blockchain().is_smart_contract(ð_tx.to) { + self.transfer_performed_sc_event( + batch_id, + eth_tx.from.clone(), + eth_tx.to.clone(), + eth_tx.token_id.clone(), + eth_tx.amount.clone(), + eth_tx.tx_nonce, + ); + } else { + self.transfer_performed_event( + batch_id, + eth_tx.from.clone(), + eth_tx.to.clone(), + eth_tx.token_id.clone(), + eth_tx.amount.clone(), + eth_tx.tx_nonce, + ); + } valid_tx_list.push(eth_tx.clone()); valid_payments_list.push(EsdtTokenPayment::new(eth_tx.token_id, 0, eth_tx.amount)); @@ -128,6 +153,10 @@ pub trait MultiTransferEsdt: refund_batch.push(Transaction::from(tx_fields)); refund_payments.push(EsdtTokenPayment::new(token_identifier, 0, amount)); } else { + require!( + self.unprocessed_refund_txs(tx_nonce).is_empty(), + "This transcation is already marked as unprocessed" + ); self.unprocessed_refund_txs(tx_nonce) .set(Transaction::from(tx_fields)); @@ -160,14 +189,27 @@ pub trait MultiTransferEsdt: // private + fn add_eth_tx_to_refund_tx_list( + &self, + eth_tx: EthTransaction, + refund_tx_list: &mut ManagedVec>, + ) { + let refund_tx = self.convert_to_refund_tx(eth_tx); + refund_tx_list.push(refund_tx); + } + fn is_refund_valid(&self, token_id: &TokenIdentifier) -> bool { let esdt_safe_addr = self.get_esdt_safe_address(); let own_sc_address = self.blockchain().get_sc_address(); let sc_shard = self.blockchain().get_shard_of_address(&own_sc_address); + let token_roles = self.blockchain().get_esdt_local_roles(token_id); - if self.is_account_same_shard_frozen(sc_shard, &esdt_safe_addr, token_id) { + if self.is_account_same_shard_frozen(sc_shard, &esdt_safe_addr, token_id) + || token_roles.has_role(&EsdtLocalRole::Transfer) + { return false; } + return true; } @@ -281,21 +323,39 @@ pub trait MultiTransferEsdt: #[indexed] tx_id: TxNonce, ); + #[event("transferPerformedSCEvent")] + fn transfer_performed_sc_event( + &self, + #[indexed] batch_id: u64, + #[indexed] from: EthAddress, + #[indexed] to: ManagedAddress, + #[indexed] token_id: TokenIdentifier, + #[indexed] amount: BigUint, + #[indexed] tx_id: TxNonce, + ); + #[event("transferFailedInvalidDestination")] - fn transfer_failed_invalid_destination(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + fn transfer_failed_invalid_destination_event( + &self, + #[indexed] batch_id: u64, + #[indexed] tx_id: u64, + ); + + #[event("tokenWithTransferRole")] + fn token_with_transfer_role_event(&self, #[indexed] token_id: TokenIdentifier); #[event("transferFailedInvalidToken")] - fn transfer_failed_invalid_token(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + fn transfer_failed_invalid_token_event(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); #[event("transferFailedFrozenDestinationAccount")] - fn transfer_failed_frozen_destination_account( + fn transfer_failed_frozen_destination_account_event( &self, #[indexed] batch_id: u64, #[indexed] tx_id: u64, ); #[event("transferOverMaxAmount")] - fn transfer_over_max_amount(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + fn transfer_over_max_amount_event(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); #[event("unprocessedRefundTxs")] fn unprocessed_refund_txs_event(&self, #[indexed] tx_id: u64); diff --git a/multi-transfer-esdt/tests/multi_transfer_blackbox_test.rs b/multi-transfer-esdt/tests/multi_transfer_blackbox_test.rs index a1fb4d3c..5e193701 100644 --- a/multi-transfer-esdt/tests/multi_transfer_blackbox_test.rs +++ b/multi-transfer-esdt/tests/multi_transfer_blackbox_test.rs @@ -151,7 +151,7 @@ impl MultiTransferTestState { .account(USER2_ADDRESS) .nonce(1); - let roles = vec![ + let roles = [ "ESDTRoleLocalMint".to_string(), "ESDTRoleLocalBurn".to_string(), ]; @@ -295,7 +295,7 @@ impl MultiTransferTestState { b"TOKEN-123456", &[EsdtLocalRole::Mint, EsdtLocalRole::Burn], ); - self.esdt_raw_transaction() + self.esdt_raw_transaction_esdt_safe() .add_token_to_whitelist( TokenIdentifier::from_esdt_bytes("BRIDGE-123456"), "BRIDGE", @@ -308,7 +308,7 @@ impl MultiTransferTestState { ) .run(); - self.esdt_raw_transaction() + self.esdt_raw_transaction_esdt_safe() .add_token_to_whitelist( TokenIdentifier::from_esdt_bytes("TOKEN"), "TOKEN", @@ -334,21 +334,10 @@ impl MultiTransferTestState { .set_max_bridged_amount(TOKEN_TICKER, MAX_AMOUNT - 1) .run(); - self.esdt_raw_transaction() - .add_token_to_whitelist( - TokenIdentifier::from_esdt_bytes("WRAPPED-123456"), - "BRIDGE2", - true, - false, - BigUint::zero(), - BigUint::zero(), - BigUint::zero(), - OptionalValue::Some(BigUint::from(ESDT_SAFE_ETH_TX_GAS_LIMIT)), - ) + self.esdt_raw_transaction_esdt_safe() + .unpause_endpoint() .run(); - self.esdt_raw_transaction().unpause_endpoint().run(); - self.world .tx() .from(MULTISIG_ADDRESS) @@ -432,22 +421,7 @@ impl MultiTransferTestState { BigUint::from(0u64), ) .run(); - self.world - .tx() - .from(MULTISIG_ADDRESS) - .to(ESDT_SAFE_ADDRESS) - .typed(esdt_safe_proxy::EsdtSafeProxy) - .add_token_to_whitelist( - TokenIdentifier::from_esdt_bytes("WRAPPED-123456"), - "BRIDGE2", - true, - false, - BigUint::zero(), - BigUint::zero(), - BigUint::zero(), - OptionalValue::Some(BigUint::from(ESDT_SAFE_ETH_TX_GAS_LIMIT)), - ) - .run(); + self.world .tx() .from(MULTISIG_ADDRESS) @@ -538,7 +512,7 @@ impl MultiTransferTestState { self.bridged_tokens_wrapper_deploy(); } - fn config_esdtsafe(&mut self) { + fn config_esdt_safe(&mut self) { self.world.set_esdt_local_roles( ESDT_SAFE_ADDRESS, b"TOKEN-123456", @@ -549,7 +523,7 @@ impl MultiTransferTestState { b"TOKEN-123456", BigUint::from(10_000_000u64), ); - self.esdt_raw_transaction() + self.esdt_raw_transaction_esdt_safe() .add_token_to_whitelist( TOKEN_ID, "TOKEN", @@ -561,7 +535,7 @@ impl MultiTransferTestState { OptionalValue::Some(BigUint::from(10u64)), ) .run(); - self.esdt_raw_transaction() + self.esdt_raw_transaction_esdt_safe() .add_token_to_whitelist( TOKEN_WITH_BURN_ROLE, "TKN", @@ -573,7 +547,7 @@ impl MultiTransferTestState { OptionalValue::Some(BigUint::from(0u64)), ) .run(); - self.esdt_raw_transaction() + self.esdt_raw_transaction_esdt_safe() .add_token_to_whitelist( TOKEN_WITHOUT_BURN_ROLE, "TKNW", @@ -585,9 +559,22 @@ impl MultiTransferTestState { OptionalValue::Some(BigUint::from(0u64)), ) .run(); + self.esdt_raw_transaction_esdt_safe() + .add_token_to_whitelist( + TokenIdentifier::from_esdt_bytes("WRAPPED-123456"), + "BRIDGE2", + true, + false, + BigUint::zero(), + BigUint::zero(), + BigUint::zero(), + OptionalValue::Some(BigUint::from(ESDT_SAFE_ETH_TX_GAS_LIMIT)), + ) + .run(); + self.world .tx() - .from(OWNER_ADDRESS) + .from(MULTISIG_ADDRESS) .to(ESDT_SAFE_ADDRESS) .typed(esdt_safe_proxy::EsdtSafeProxy) .init_supply_mint_burn( @@ -597,16 +584,17 @@ impl MultiTransferTestState { ) .run(); } + fn single_transaction_should_fail( &mut self, token_id: TestTokenIdentifier, amount: u64, expected_error: &str, ) { - self.esdt_raw_transaction() + self.esdt_raw_transaction_esdt_safe() .create_transaction( EthAddress::zero(), - OptionalValue::None::>, + OptionalValue::>::None, ) .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(token_id), @@ -618,10 +606,10 @@ impl MultiTransferTestState { } fn single_transaction_should_work(&mut self, token_id: TestTokenIdentifier, amount: u64) { - self.esdt_raw_transaction() + self.esdt_raw_transaction_esdt_safe() .create_transaction( EthAddress::zero(), - OptionalValue::None::>, + OptionalValue::>::None, ) .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(token_id), @@ -639,7 +627,7 @@ impl MultiTransferTestState { expected_status: u64, expected_error: &str, ) { - self.esdt_raw_transaction() + self.esdt_raw_transaction_esdt_safe() .set_transaction_batch_status(batch_id, statuses) .returns(ExpectError(expected_status, expected_error)) .run(); @@ -650,7 +638,7 @@ impl MultiTransferTestState { batch_id: u32, statuses: MultiValueEncoded, ) { - self.esdt_raw_transaction() + self.esdt_raw_transaction_esdt_safe() .set_transaction_batch_status(batch_id, statuses) .returns(ReturnsResult) .run(); @@ -699,7 +687,7 @@ impl MultiTransferTestState { .run(); } - fn esdt_raw_transaction( + fn esdt_raw_transaction_esdt_safe( &mut self, ) -> EsdtSafeProxyMethods, TestSCAddress, TestSCAddress, ()> { self.world @@ -715,6 +703,7 @@ fn test_config() { let mut state = MultiTransferTestState::new(); state.deploy_contracts(); + state.config_esdt_safe(); state.config_multi_transfer(); state.config_bridged_tokens_wrapper(); } @@ -778,12 +767,58 @@ fn basic_transfer_test() { .esdt_balance(BRIDGE_TOKEN_ID, token_amount); } +#[test] +fn basic_transfer_smart_contract_dest_test() { + let mut state = MultiTransferTestState::new(); + let token_amount = BigUint::from(500u64); + + state.deploy_contracts(); + state.config_multi_transfer(); + + let call_data = ManagedBuffer::from(b"add"); + call_data + .clone() + .concat(ManagedBuffer::from(GAS_LIMIT.to_string())); + call_data.clone().concat(ManagedBuffer::default()); + + let eth_tx = EthTransaction { + from: EthAddress { + raw_addr: ManagedByteArray::default(), + }, + to: ManagedAddress::from(MULTISIG_ADDRESS.eval_to_array()), + token_id: TokenIdentifier::from(BRIDGE_TOKEN_ID), + amount: token_amount.clone(), + tx_nonce: 1u64, + call_data: ManagedOption::some(call_data), + }; + + let mut transfers: MultiValueEncoded> = + MultiValueEncoded::new(); + transfers.push(eth_tx); + + state + .world + .tx() + .from(MULTISIG_ADDRESS) + .to(MULTI_TRANSFER_ADDRESS) + .typed(multi_transfer_esdt_proxy::MultiTransferEsdtProxy) + .batch_transfer_esdt_token(1u32, transfers) + .run(); + + state + .world + .check_account(BRIDGE_PROXY_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, token_amount); +} + #[test] fn batch_transfer_both_executed_test() { let mut state = MultiTransferTestState::new(); let token_amount = BigUint::from(500u64); state.deploy_contracts(); + state.config_esdt_safe(); + state.config_multi_transfer(); let mut args = ManagedVec::new(); @@ -1005,7 +1040,7 @@ fn test_unwrap_token_create_transaction_paused() { let mut state = MultiTransferTestState::new(); state.deploy_contracts(); - + state.config_esdt_safe(); state.config_bridged_tokens_wrapper(); state @@ -1018,6 +1053,7 @@ fn test_unwrap_token_create_transaction_paused() { TokenIdentifier::from(UNIVERSAL_TOKEN_IDENTIFIER), ESDT_SAFE_ADDRESS.to_address(), EthAddress::zero(), + OptionalValue::>::None, ) .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(UNIVERSAL_TOKEN_IDENTIFIER), @@ -1032,6 +1068,7 @@ fn test_unwrap_token_create_transaction_paused() { fn test_unwrap_token_create_transaction_insufficient_liquidity() { let mut state = MultiTransferTestState::new(); state.deploy_contracts(); + state.config_esdt_safe(); state.config_multi_transfer(); state.config_bridged_tokens_wrapper(); @@ -1068,7 +1105,12 @@ fn test_unwrap_token_create_transaction_insufficient_liquidity() { .from(USER1_ADDRESS) .to(BRIDGED_TOKENS_WRAPPER_ADDRESS) .typed(bridged_tokens_wrapper_proxy::BridgedTokensWrapperProxy) - .unwrap_token_create_transaction(WRAPPED_TOKEN_ID, ESDT_SAFE_ADDRESS.to_address(), EthAddress::zero()) + .unwrap_token_create_transaction( + WRAPPED_TOKEN_ID, + ESDT_SAFE_ADDRESS.to_address(), + EthAddress::zero(), + OptionalValue::>::None, + ) .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(UNIVERSAL_TOKEN_IDENTIFIER), 0u64, @@ -1084,13 +1126,14 @@ fn test_unwrap_token_create_transaction_should_work() { state.deploy_contracts(); + state.config_esdt_safe(); state.config_multi_transfer(); state.config_bridged_tokens_wrapper(); state .world .tx() - .from(OWNER_ADDRESS) + .from(MULTISIG_ADDRESS) .to(BRIDGED_TOKENS_WRAPPER_ADDRESS) .typed(bridged_tokens_wrapper_proxy::BridgedTokensWrapperProxy) .deposit_liquidity() @@ -1108,7 +1151,7 @@ fn test_unwrap_token_create_transaction_should_work() { state.check_balances_on_safe( WRAPPED_TOKEN_ID, BigUint::zero(), - BigUint::from(600000u64), + BigUint::from(600_000u64), BigUint::zero(), ); @@ -1118,7 +1161,7 @@ fn test_unwrap_token_create_transaction_should_work() { .to(BRIDGED_TOKENS_WRAPPER_ADDRESS) .typed(bridged_tokens_wrapper_proxy::BridgedTokensWrapperProxy) .token_liquidity(WRAPPED_TOKEN_ID) - .returns(ExpectValue(BigUint::from(1000u64))) + .returns(ExpectValue(BigUint::from(1_000u64))) .run(); state @@ -1127,7 +1170,14 @@ fn test_unwrap_token_create_transaction_should_work() { .from(USER1_ADDRESS) .to(BRIDGED_TOKENS_WRAPPER_ADDRESS) .typed(bridged_tokens_wrapper_proxy::BridgedTokensWrapperProxy) - .unwrap_token_create_transaction(WRAPPED_TOKEN_ID, ESDT_SAFE_ADDRESS.to_address(), EthAddress::zero()) + .unwrap_token_create_transaction( + WRAPPED_TOKEN_ID, + ESDT_SAFE_ADDRESS.to_address(), + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + OptionalValue::>::None, + ) .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(UNIVERSAL_TOKEN_IDENTIFIER), 0u64, @@ -1157,7 +1207,7 @@ fn test_unwrap_token_create_transaction_should_fail() { let mut state = MultiTransferTestState::new(); state.deploy_contracts(); - + state.config_esdt_safe(); state.config_multi_transfer(); state.config_bridged_tokens_wrapper(); @@ -1171,7 +1221,12 @@ fn test_unwrap_token_create_transaction_should_fail() { .from(USER1_ADDRESS) .to(BRIDGED_TOKENS_WRAPPER_ADDRESS) .typed(bridged_tokens_wrapper_proxy::BridgedTokensWrapperProxy) - .unwrap_token_create_transaction(WRAPPED_TOKEN_ID, ESDT_SAFE_ADDRESS.to_address(), EthAddress::zero()) + .unwrap_token_create_transaction( + WRAPPED_TOKEN_ID, + ESDT_SAFE_ADDRESS.to_address(), + EthAddress::zero(), + OptionalValue::>::None, + ) .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(TOKEN_TICKER), 0u64, @@ -1186,7 +1241,7 @@ fn test_unwrap_token_create_transaction_amount_zero() { let mut state = MultiTransferTestState::new(); state.deploy_contracts(); - + state.config_esdt_safe(); state.config_multi_transfer(); state.config_bridged_tokens_wrapper(); state @@ -1199,6 +1254,7 @@ fn test_unwrap_token_create_transaction_amount_zero() { TokenIdentifier::from(WRAPPED_TOKEN_ID), ESDT_SAFE_ADDRESS.to_address(), EthAddress::zero(), + OptionalValue::>::None, ) .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(UNIVERSAL_TOKEN_IDENTIFIER), @@ -1257,6 +1313,7 @@ fn add_refund_batch_test_should_work() { .typed(multi_transfer_esdt_proxy::MultiTransferEsdtProxy) .batch_transfer_esdt_token(1u32, transfers) .run(); + state.check_balances_on_safe( TOKEN_TICKER, BigUint::zero(), diff --git a/multisig/scenarios/ethereum_to_multiversx_relayer_call_data_several_tx_test.scen.json b/multisig/scenarios/ethereum_to_multiversx_relayer_call_data_several_tx_test.scen.json index be6db6b8..e5305f1a 100644 --- a/multisig/scenarios/ethereum_to_multiversx_relayer_call_data_several_tx_test.scen.json +++ b/multisig/scenarios/ethereum_to_multiversx_relayer_call_data_several_tx_test.scen.json @@ -386,8 +386,8 @@ "gasLimit": "5,000,000" }, "expect": { - "status": "4", - "message": "str:Invalid token or amount" + "out": [], + "status": "0" } } ] diff --git a/multisig/scenarios/ethereum_to_multiversx_relayer_query2_test.scen.json b/multisig/scenarios/ethereum_to_multiversx_relayer_query2_test.scen.json index 2ded2f90..7434eed1 100644 --- a/multisig/scenarios/ethereum_to_multiversx_relayer_query2_test.scen.json +++ b/multisig/scenarios/ethereum_to_multiversx_relayer_query2_test.scen.json @@ -414,8 +414,8 @@ "gasLimit": "5,000,000" }, "expect": { - "status": "4", - "message": "str:Invalid token or amount" + "out": [], + "status": "0" } } ] diff --git a/multisig/scenarios/execute_multiversx_to_ethereum_tx_batch.scen.json b/multisig/scenarios/execute_multiversx_to_ethereum_tx_batch.scen.json index c965c992..a71e0268 100644 --- a/multisig/scenarios/execute_multiversx_to_ethereum_tx_batch.scen.json +++ b/multisig/scenarios/execute_multiversx_to_ethereum_tx_batch.scen.json @@ -38,7 +38,7 @@ "nonce": "*", "balance": "*", "storage": { - "str:action_data.item|u32:1": { + "str:actionData.item|u32:1": { "1-action_type": "u8:1", "2-batch_id": "u64:1", "3-tx_batch_status_len": "u32:2", @@ -51,7 +51,6 @@ "+": {} } }, - { "step": "scCall", "txId": "second-relayer-sign", @@ -257,4 +256,4 @@ } } ] -} +} \ No newline at end of file diff --git a/multisig/scenarios/reject_multiversx_to_ethereum_tx_batch.scen.json b/multisig/scenarios/reject_multiversx_to_ethereum_tx_batch.scen.json index 122fabab..033bd8ab 100644 --- a/multisig/scenarios/reject_multiversx_to_ethereum_tx_batch.scen.json +++ b/multisig/scenarios/reject_multiversx_to_ethereum_tx_batch.scen.json @@ -38,7 +38,7 @@ "nonce": "*", "balance": "*", "storage": { - "str:action_data.item|u32:1": { + "str:actionData.item|u32:1": { "1-action_type": "u8:1", "2-batch_id": "u64:1", "3-tx_batch_status_len": "u32:2", @@ -286,4 +286,4 @@ } } ] -} +} \ No newline at end of file diff --git a/multisig/scenarios/setup.scen.json b/multisig/scenarios/setup.scen.json index 56e2e062..e96de844 100644 --- a/multisig/scenarios/setup.scen.json +++ b/multisig/scenarios/setup.scen.json @@ -75,11 +75,6 @@ "str:tokenTicker|nested:str:GWEI": "str:GWEI", "str:tokenTicker|nested:str:WEGLD-123456": "str:WEGLD", "str:tokenTicker|nested:str:ETH-123456": "str:ETH", - "str:tokenWhitelist.index|nested:str:WEGLD-123456": "1", - "str:tokenWhitelist.item|u32:1": "str:WEGLD-123456", - "str:tokenWhitelist.index|nested:str:ETH-123456": "2", - "str:tokenWhitelist.item|u32:2": "str:ETH-123456", - "str:tokenWhitelist.len": "2", "str:mintBalances|nested:str:WEGLD-123456": "500,000,000,000", "str:mintBalances|nested:str:ETH-123456": "500,000,000,000" }, @@ -106,6 +101,11 @@ "balance": "1000", "storage": {} }, + "address:relayer3": { + "nonce": "0", + "balance": "1000", + "storage": {} + }, "address:user": { "nonce": "0", "balance": "0", @@ -170,12 +170,12 @@ "str:proxyAddress": "sc:bridge_proxy", "str:bridgedTokensWrapperAddress": "sc:bridged_tokens_wrapper", "str:feeEstimatorAddress": "sc:price_aggregator", - "str:num_board_members": "2", + "str:numBoardMembers": "2", "str:quorum": "2", "str:requiredStakeAmount": "1000", "str:slashAmount": "500", - "str:user_role|u32:1": "1", - "str:user_role|u32:2": "1", + "str:userRole|u32:1": "1", + "str:userRole|u32:2": "1", "str:user_address_to_id|address:relayer1": "1", "str:user_address_to_id|address:relayer2": "2", "str:user_count": "2", @@ -287,20 +287,6 @@ ] } }, - { - "step": "scQuery", - "txId": "get-all-known-tokens", - "tx": { - "to": "sc:esdt_safe", - "function": "getAllKnownTokens" - }, - "expect": { - "out": [ - "str:WEGLD-123456", - "str:ETH-123456" - ] - } - }, { "step": "scCall", "txId": "unpause multisig", @@ -375,6 +361,20 @@ "gas": "*", "refund": "*" } + }, + { + "step": "scQuery", + "txId": "get-all-known-tokens", + "tx": { + "to": "sc:esdt_safe", + "function": "getAllKnownTokens" + }, + "expect": { + "out": [ + "str:WEGLD-123456", + "str:ETH-123456" + ] + } } ] } \ No newline at end of file diff --git a/multisig/scenarios/unstake.scen.json b/multisig/scenarios/unstake.scen.json index ab177b2a..af19caaf 100644 --- a/multisig/scenarios/unstake.scen.json +++ b/multisig/scenarios/unstake.scen.json @@ -42,6 +42,67 @@ "gasLimit": "35,000,000", "gasPrice": "0" }, + "expect": { + "status": "4", + "message": "str:Quorum size not appropriate", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "third-relayer-stake", + "tx": { + "from": "address:relayer3", + "to": "sc:multisig", + "value": "1000", + "function": "stake", + "arguments": [], + "gasLimit": "35,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Only board members can stake", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "add-board-member", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "function": "addBoardMember", + "arguments": [ + "address:relayer3" + ], + "gasLimit": "35,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "third-relayer-stake", + "tx": { + "from": "address:relayer3", + "to": "sc:multisig", + "value": "1,000", + "function": "stake", + "arguments": [], + "gasLimit": "35,000,000", + "gasPrice": "0" + }, "expect": { "status": "0", "message": "", @@ -50,17 +111,52 @@ "refund": "*" } }, + { + "step": "scQuery", + "txId": "getAllStakedRelayers - only one staked", + "tx": { + "to": "sc:multisig", + "function": "getAllStakedRelayers", + "arguments": [] + }, + "expect": { + "status": "0", + "message": "", + "out": [ + "address:relayer1", + "address:relayer2", + "address:relayer3" + ] + } + }, { "step": "checkState", "accounts": { + "address:relayer1": { + "nonce": "2", + "balance": "0", + "storage": {} + }, + "address:relayer2": { + "nonce": "1", + "balance": "0", + "storage": {} + }, + "address:relayer3": { + "nonce": "2", + "balance": "0", + "storage": {} + }, "sc:multisig": { "nonce": "*", - "balance": "*", + "balance": "3000", "storage": { - "str:quorum": "1", + "str:amountStaked|address:relayer1": "1000", + "str:amountStaked|address:relayer2": "1000", + "str:amountStaked|address:relayer3": "1000", "+": "" }, - "code": "*" + "code": "file:../output/multisig.wasm" }, "+": {} } @@ -92,11 +188,12 @@ "accounts": { "sc:multisig": { "nonce": "*", - "balance": "2000", + "balance": "3000", "storage": { - "str:quorum": "1", - "str:user_role|u32:1": "1", + "str:quorum": "2", + "str:user_role|u32:1": "0", "str:user_role|u32:2": "0", + "str:user_role|u32:3": "0", "+": "" }, "code": "*" @@ -158,11 +255,12 @@ }, "sc:multisig": { "nonce": "*", - "balance": "1000", + "balance": "2000", "storage": { - "str:quorum": "1", - "str:user_role|u32:1": "1", + "str:quorum": "2", + "str:user_role|u32:1": "0", "str:user_role|u32:2": "0", + "str:user_role|u32:3": "0", "+": "" }, "code": "*" diff --git a/multisig/src/esdt_safe_proxy.rs b/multisig/src/esdt_safe_proxy.rs deleted file mode 100644 index 59e564f3..00000000 --- a/multisig/src/esdt_safe_proxy.rs +++ /dev/null @@ -1,828 +0,0 @@ -// Code generated by the multiversx-sc proxy generator. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -#![allow(dead_code)] -#![allow(clippy::all)] - -use multiversx_sc::proxy_imports::*; - -pub struct EsdtSafeProxy; - -impl TxProxyTrait for EsdtSafeProxy -where - Env: TxEnv, - From: TxFrom, - To: TxTo, - Gas: TxGas, -{ - type TxProxyMethods = EsdtSafeProxyMethods; - - fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { - EsdtSafeProxyMethods { wrapped_tx: tx } - } -} - -pub struct EsdtSafeProxyMethods -where - Env: TxEnv, - From: TxFrom, - To: TxTo, - Gas: TxGas, -{ - wrapped_tx: Tx, -} - -#[rustfmt::skip] -impl EsdtSafeProxyMethods -where - Env: TxEnv, - Env::Api: VMApi, - From: TxFrom, - Gas: TxGas, -{ - /// fee_estimator_contract_address - The address of a Price Aggregator contract, - /// which will get the price of token A in token B - /// - /// eth_tx_gas_limit - The gas limit that will be used for transactions on the ETH side. - /// Will be used to compute the fees for the transfer - pub fn init< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - Arg2: ProxyArg>, - >( - self, - fee_estimator_contract_address: Arg0, - multi_transfer_contract_address: Arg1, - eth_tx_gas_limit: Arg2, - ) -> TxTypedDeploy { - self.wrapped_tx - .payment(NotPayable) - .raw_deploy() - .argument(&fee_estimator_contract_address) - .argument(&multi_transfer_contract_address) - .argument(ð_tx_gas_limit) - .original_result() - } -} - -#[rustfmt::skip] -impl EsdtSafeProxyMethods -where - Env: TxEnv, - Env::Api: VMApi, - From: TxFrom, - To: TxTo, - Gas: TxGas, -{ - pub fn upgrade< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - Arg2: ProxyArg>, - Arg3: ProxyArg>, - >( - self, - fee_estimator_contract_address: Arg0, - multi_transfer_contract_address: Arg1, - bridge_proxy_contract_address: Arg2, - eth_tx_gas_limit: Arg3, - ) -> TxTypedUpgrade { - self.wrapped_tx - .payment(NotPayable) - .raw_upgrade() - .argument(&fee_estimator_contract_address) - .argument(&multi_transfer_contract_address) - .argument(&bridge_proxy_contract_address) - .argument(ð_tx_gas_limit) - .original_result() - } -} - -#[rustfmt::skip] -impl EsdtSafeProxyMethods -where - Env: TxEnv, - Env::Api: VMApi, - From: TxFrom, - To: TxTo, - Gas: TxGas, -{ - /// Sets the statuses for the transactions, after they were executed on the Ethereum side. - /// - /// Only TransactionStatus::Executed (3) and TransactionStatus::Rejected (4) values are allowed. - /// Number of provided statuses must be equal to number of transactions in the batch. - pub fn set_transaction_batch_status< - Arg0: ProxyArg, - Arg1: ProxyArg>, - >( - self, - batch_id: Arg0, - tx_statuses: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setTransactionBatchStatus") - .argument(&batch_id) - .argument(&tx_statuses) - .original_result() - } - - /// Converts failed Ethereum -> MultiversX transactions to MultiversX -> Ethereum transaction. - /// This is done every now and then to refund the tokens. - /// - /// As with normal MultiversX -> Ethereum transactions, a part of the tokens will be - /// subtracted to pay for the fees - pub fn add_refund_batch< - Arg0: ProxyArg>>, - >( - self, - refund_transactions: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .raw_call("addRefundBatch") - .argument(&refund_transactions) - .original_result() - } - - /// Create an MultiversX -> Ethereum transaction. Only fungible tokens are accepted. - /// - /// Every transfer will have a part of the tokens subtracted as fees. - /// The fee amount depends on the global eth_tx_gas_limit - /// and the current GWEI price, respective to the bridged token - /// - /// fee_amount = price_per_gas_unit * eth_tx_gas_limit - pub fn create_transaction< - Arg0: ProxyArg>, - Arg1: ProxyArg>>, - >( - self, - to: Arg0, - opt_refund_info: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .raw_call("createTransaction") - .argument(&to) - .argument(&opt_refund_info) - .original_result() - } - - pub fn create_transaction_sc_call< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - Arg2: ProxyArg>>, - >( - self, - to: Arg0, - data: Arg1, - opt_refund_address: Arg2, - ) -> TxTypedCall { - self.wrapped_tx - .raw_call("createTransactionSCCall") - .argument(&to) - .argument(&data) - .argument(&opt_refund_address) - .original_result() - } - - /// Claim funds for failed MultiversX -> Ethereum transactions. - /// These are not sent automatically to prevent the contract getting stuck. - /// For example, if the receiver is a SC, a frozen account, etc. - pub fn claim_refund< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("claimRefund") - .argument(&token_id) - .original_result() - } - - pub fn set_bridged_tokens_wrapper_contract_address< - Arg0: ProxyArg>>, - >( - self, - opt_address: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setBridgedTokensWrapperAddress") - .argument(&opt_address) - .original_result() - } - - pub fn set_bridge_proxy_contract_address< - Arg0: ProxyArg>>, - >( - self, - opt_new_address: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setBridgeProxyContractAddress") - .argument(&opt_new_address) - .original_result() - } - - pub fn withdraw_refund_fees_for_ethereum< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - >( - self, - token_id: Arg0, - multisig_owner: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("withdrawRefundFeesForEthereum") - .argument(&token_id) - .argument(&multisig_owner) - .original_result() - } - - pub fn withdraw_transaction_fees< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - >( - self, - token_id: Arg0, - multisig_owner: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("withdrawTransactionFees") - .argument(&token_id) - .argument(&multisig_owner) - .original_result() - } - - pub fn compute_total_amounts_from_index< - Arg0: ProxyArg, - Arg1: ProxyArg, - >( - self, - start_index: Arg0, - end_index: Arg1, - ) -> TxTypedCall>> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("computeTotalAmmountsFromIndex") - .argument(&start_index) - .argument(&end_index) - .original_result() - } - - /// Query function that lists all refund amounts for a user. - /// Useful for knowing which token IDs to pass to the claimRefund endpoint. - pub fn get_refund_amounts< - Arg0: ProxyArg>, - >( - self, - address: Arg0, - ) -> TxTypedCall, BigUint>>> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getRefundAmounts") - .argument(&address) - .original_result() - } - - pub fn get_total_refund_amounts( - self, - ) -> TxTypedCall, BigUint>>> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getTotalRefundAmounts") - .original_result() - } - - pub fn get_refund_fees_for_ethereum< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getRefundFeesForEthereum") - .argument(&token_id) - .original_result() - } - - pub fn get_transaction_fees< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getTransactionFees") - .argument(&token_id) - .original_result() - } - - pub fn bridged_tokens_wrapper_address( - self, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getBridgedTokensWrapperAddress") - .original_result() - } - - pub fn bridge_proxy_contract_address( - self, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getBridgeProxyContractAddress") - .original_result() - } - - pub fn set_fee_estimator_contract_address< - Arg0: ProxyArg>, - >( - self, - new_address: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setFeeEstimatorContractAddress") - .argument(&new_address) - .original_result() - } - - pub fn set_eth_tx_gas_limit< - Arg0: ProxyArg>, - >( - self, - new_limit: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setEthTxGasLimit") - .argument(&new_limit) - .original_result() - } - - /// Default price being used if the aggregator lacks a mapping for this token - /// or the aggregator address is not set - pub fn set_default_price_per_gas_unit< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - >( - self, - token_id: Arg0, - default_price_per_gas_unit: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setDefaultPricePerGasUnit") - .argument(&token_id) - .argument(&default_price_per_gas_unit) - .original_result() - } - - /// Token ticker being used when querying the aggregator for GWEI prices - pub fn set_token_ticker< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - >( - self, - token_id: Arg0, - ticker: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setTokenTicker") - .argument(&token_id) - .argument(&ticker) - .original_result() - } - - /// Returns the fee for the given token ID (the fee amount is in the given token) - pub fn calculate_required_fee< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("calculateRequiredFee") - .argument(&token_id) - .original_result() - } - - pub fn fee_estimator_contract_address( - self, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getFeeEstimatorContractAddress") - .original_result() - } - - pub fn default_price_per_gas_unit< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getDefaultPricePerGasUnit") - .argument(&token_id) - .original_result() - } - - pub fn eth_tx_gas_limit( - self, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getEthTxGasLimit") - .original_result() - } - - /// Distributes the accumulated fees to the given addresses. - /// Expected arguments are pairs of (address, percentage), - /// where percentages must add up to the PERCENTAGE_TOTAL constant - pub fn distribute_fees< - Arg0: ProxyArg>>, - >( - self, - address_percentage_pairs: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("distributeFees") - .argument(&address_percentage_pairs) - .original_result() - } - - pub fn add_token_to_whitelist< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - Arg2: ProxyArg, - Arg3: ProxyArg, - Arg4: ProxyArg>, - Arg5: ProxyArg>, - Arg6: ProxyArg>, - Arg7: ProxyArg>>, - >( - self, - token_id: Arg0, - ticker: Arg1, - mint_burn_token: Arg2, - native_token: Arg3, - total_balance: Arg4, - mint_balance: Arg5, - burn_balance: Arg6, - opt_default_price_per_gas_unit: Arg7, - ) -> TxTypedCall { - self.wrapped_tx - .raw_call("addTokenToWhitelist") - .argument(&token_id) - .argument(&ticker) - .argument(&mint_burn_token) - .argument(&native_token) - .argument(&total_balance) - .argument(&mint_balance) - .argument(&burn_balance) - .argument(&opt_default_price_per_gas_unit) - .original_result() - } - - pub fn remove_token_from_whitelist< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("removeTokenFromWhitelist") - .argument(&token_id) - .original_result() - } - - pub fn get_tokens< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - >( - self, - token_id: Arg0, - amount: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getTokens") - .argument(&token_id) - .argument(&amount) - .original_result() - } - - pub fn init_supply< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - >( - self, - token_id: Arg0, - amount: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .raw_call("initSupply") - .argument(&token_id) - .argument(&amount) - .original_result() - } - - pub fn init_supply_mint_burn< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - Arg2: ProxyArg>, - >( - self, - token_id: Arg0, - mint_amount: Arg1, - burn_amount: Arg2, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("initSupplyMintBurn") - .argument(&token_id) - .argument(&mint_amount) - .argument(&burn_amount) - .original_result() - } - - pub fn set_multi_transfer_contract_address< - Arg0: ProxyArg>>, - >( - self, - opt_new_address: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setMultiTransferContractAddress") - .argument(&opt_new_address) - .original_result() - } - - pub fn token_whitelist( - self, - ) -> TxTypedCall>> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getAllKnownTokens") - .original_result() - } - - pub fn native_token< - Arg0: ProxyArg>, - >( - self, - token: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("isNativeToken") - .argument(&token) - .original_result() - } - - pub fn mint_burn_token< - Arg0: ProxyArg>, - >( - self, - token: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("isMintBurnToken") - .argument(&token) - .original_result() - } - - pub fn multi_transfer_contract_address( - self, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getMultiTransferContractAddress") - .original_result() - } - - pub fn accumulated_transaction_fees< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getAccumulatedTransactionFees") - .argument(&token_id) - .original_result() - } - - pub fn total_balances< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getTotalBalances") - .argument(&token_id) - .original_result() - } - - pub fn mint_balances< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getMintBalances") - .argument(&token_id) - .original_result() - } - - pub fn burn_balances< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getBurnBalances") - .argument(&token_id) - .original_result() - } - - pub fn set_max_tx_batch_size< - Arg0: ProxyArg, - >( - self, - new_max_tx_batch_size: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setMaxTxBatchSize") - .argument(&new_max_tx_batch_size) - .original_result() - } - - pub fn set_max_tx_batch_block_duration< - Arg0: ProxyArg, - >( - self, - new_max_tx_batch_block_duration: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setMaxTxBatchBlockDuration") - .argument(&new_max_tx_batch_block_duration) - .original_result() - } - - pub fn get_current_tx_batch( - self, - ) -> TxTypedCall, ManagedBuffer, TokenIdentifier, BigUint>>>>> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getCurrentTxBatch") - .original_result() - } - - pub fn get_first_batch_any_status( - self, - ) -> TxTypedCall, ManagedBuffer, TokenIdentifier, BigUint>>>>> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getFirstBatchAnyStatus") - .original_result() - } - - pub fn get_batch< - Arg0: ProxyArg, - >( - self, - batch_id: Arg0, - ) -> TxTypedCall, ManagedBuffer, TokenIdentifier, BigUint>>>>> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getBatch") - .argument(&batch_id) - .original_result() - } - - pub fn get_batch_status< - Arg0: ProxyArg, - >( - self, - batch_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getBatchStatus") - .argument(&batch_id) - .original_result() - } - - pub fn first_batch_id( - self, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getFirstBatchId") - .original_result() - } - - pub fn last_batch_id( - self, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getLastBatchId") - .original_result() - } - - pub fn set_max_bridged_amount< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - >( - self, - token_id: Arg0, - max_amount: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setMaxBridgedAmount") - .argument(&token_id) - .argument(&max_amount) - .original_result() - } - - pub fn max_bridged_amount< - Arg0: ProxyArg>, - >( - self, - token_id: Arg0, - ) -> TxTypedCall> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getMaxBridgedAmount") - .argument(&token_id) - .original_result() - } - - pub fn pause_endpoint( - self, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("pause") - .original_result() - } - - pub fn unpause_endpoint( - self, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("unpause") - .original_result() - } - - pub fn paused_status( - self, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("isPaused") - .original_result() - } -} - -#[type_abi] -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, Clone, ManagedVecItem, PartialEq)] -pub struct RefundInfo -where - Api: ManagedTypeApi, -{ - pub address: ManagedAddress, - pub initial_batch_id: u64, - pub initial_nonce: u64, -} diff --git a/multisig/src/lib.rs b/multisig/src/lib.rs index 87c526bb..893dd424 100644 --- a/multisig/src/lib.rs +++ b/multisig/src/lib.rs @@ -21,6 +21,8 @@ use user_role::UserRole; use multiversx_sc::imports::*; +const MAX_ACTIONS_INTER: usize = 10; + /// Multi-signature smart contract implementation. /// Acts like a wallet that needs multiple signers for any action performed. #[multiversx_sc::contract] @@ -189,10 +191,12 @@ pub trait Multisig: INVALID_PERCENTAGE_SUM_OVER_ERR_MSG ); let esdt_safe_addr = self.esdt_safe_address().get(); + let opt_tokens_to_distribute: OptionalValue>> = + OptionalValue::None; self.tx() .to(esdt_safe_addr) .typed(esdt_safe_proxy::EsdtSafeProxy) - .distribute_fees(args) + .distribute_fees(args, opt_tokens_to_distribute) .sync_call(); } @@ -269,7 +273,7 @@ pub trait Multisig: "Action already proposed" ); - let current_batch_len = current_batch_transactions.raw_len() / TX_MULTIRESULT_NR_FIELDS; + let current_batch_len = current_batch_transactions.len(); let status_batch_len = statuses_vec.len(); require!( current_batch_len == status_batch_len, @@ -416,7 +420,7 @@ pub trait Multisig: #[endpoint(performAction)] fn perform_action_endpoint(&self, action_id: usize) { require!( - !self.action_mapper().item_is_empty(action_id), + !self.executed_actions().contains(&action_id), "Action was already executed" ); @@ -433,6 +437,7 @@ pub trait Multisig: require!(self.not_paused(), "No actions may be executed while paused"); self.perform_action(action_id); + self.executed_actions().insert(action_id); } // private @@ -453,7 +458,7 @@ pub trait Multisig: // if there's only one proposed action, // the action was already cleared at the beginning of this function if action_ids_mapper.len() > 1 { - for act_id in action_ids_mapper.values() { + for act_id in action_ids_mapper.values().take(MAX_ACTIONS_INTER) { self.clear_action(act_id); } } @@ -478,7 +483,7 @@ pub trait Multisig: // if there's only one proposed action, // the action was already cleared at the beginning of this function if action_ids_mapper.len() > 1 { - for act_id in action_ids_mapper.values() { + for act_id in action_ids_mapper.values().take(MAX_ACTIONS_INTER) { self.clear_action(act_id); } } @@ -502,4 +507,19 @@ pub trait Multisig: } } } + + #[endpoint(clearActionsForBatchId)] + fn clear_actions_for_batch_id(&self, eth_batch_id: u64) { + let last_executed_eth_batch_id = self.last_executed_eth_batch_id().get(); + require!( + eth_batch_id < last_executed_eth_batch_id, + "Batch needs to be already executed" + ); + + let action_ids_mapper = self.batch_id_to_action_id_mapping(eth_batch_id); + + for act_id in action_ids_mapper.values().take(MAX_ACTIONS_INTER) { + self.clear_action(act_id); + } + } } diff --git a/multisig/src/queries.rs b/multisig/src/queries.rs index ba5f3fc6..d9efd368 100644 --- a/multisig/src/queries.rs +++ b/multisig/src/queries.rs @@ -56,15 +56,9 @@ pub trait QueriesModule: crate::storage::StorageModule + crate::util::UtilModule .sync_call() } - /// Actions are cleared after execution, so an empty entry means the action was executed already - /// Returns "false" if the action ID is invalid #[view(wasActionExecuted)] fn was_action_executed(&self, action_id: usize) -> bool { - if self.is_valid_action_id(action_id) { - self.action_mapper().item_is_empty(action_id) - } else { - false - } + self.executed_actions().contains(&action_id) } /// Used for Ethereum -> MultiversX batches. diff --git a/multisig/src/setup.rs b/multisig/src/setup.rs index 9bf99bd7..8e90cdf1 100644 --- a/multisig/src/setup.rs +++ b/multisig/src/setup.rs @@ -3,6 +3,8 @@ use multiversx_sc::imports::*; use eth_address::EthAddress; use sc_proxies::{bridge_proxy_contract_proxy, esdt_safe_proxy, multi_transfer_esdt_proxy}; +const MAX_BOARD_MEMBERS: usize = 40; + #[multiversx_sc::module] pub trait SetupModule: crate::multisig_general::MultisigGeneralModule @@ -17,14 +19,9 @@ pub trait SetupModule: &self, child_sc_address: ManagedAddress, source_address: ManagedAddress, - is_payable: bool, init_args: MultiValueEncoded, ) { - let mut metadata = CodeMetadata::UPGRADEABLE; - if is_payable { - // TODO: Replace with PayableBySc when it's available - metadata |= CodeMetadata::PAYABLE; - } + let metadata = CodeMetadata::UPGRADEABLE; let gas = self.blockchain().get_gas_left(); self.send_raw().upgrade_from_source_contract( @@ -66,7 +63,7 @@ pub trait SetupModule: fn slash_board_member(&self, board_member: ManagedAddress) { let slash_amount = self.slash_amount().get(); - // remove slashed amount from user stake amountself + // remove slashed amount from user stake amount self self.amount_staked(&board_member) .update(|stake| *stake -= &slash_amount); @@ -79,7 +76,28 @@ pub trait SetupModule: #[endpoint(changeQuorum)] fn change_quorum(&self, new_quorum: usize) { require!( - new_quorum <= self.num_board_members().get(), + new_quorum <= MAX_BOARD_MEMBERS && new_quorum > 1, + "Quorum size not appropriate" + ); + let total_users = self.user_mapper().get_user_count(); + let mut board_member_with_valid_stake: usize = 0; + + for user_id in 1..total_users + 1 { + let user_role = self.user_id_to_role(user_id).get(); + + if user_role.is_board_member() { + if let Some(board_member_addr) = self.user_mapper().get_user_address(user_id) { + let amount_staked = self.amount_staked(&board_member_addr).get(); + let required_stake_amount = self.required_stake_amount().get(); + if amount_staked >= required_stake_amount { + board_member_with_valid_stake += 1; + } + } + } + } + + require!( + new_quorum <= board_member_with_valid_stake, "quorum cannot exceed board size" ); self.quorum().set(new_quorum); diff --git a/multisig/src/storage.rs b/multisig/src/storage.rs index 3823f73a..1f3478b8 100644 --- a/multisig/src/storage.rs +++ b/multisig/src/storage.rs @@ -18,19 +18,19 @@ pub trait StorageModule { #[storage_mapper("user")] fn user_mapper(&self) -> UserMapper; - #[storage_mapper("user_role")] + #[storage_mapper("userRole")] fn user_id_to_role(&self, user_id: usize) -> SingleValueMapper; /// Denormalized board member count. /// It is kept in sync with the user list by the contract. #[view(getNumBoardMembers)] - #[storage_mapper("num_board_members")] + #[storage_mapper("numBoardMembers")] fn num_board_members(&self) -> SingleValueMapper; - #[storage_mapper("action_data")] + #[storage_mapper("actionData")] fn action_mapper(&self) -> VecMapper>; - #[storage_mapper("action_signer_ids")] + #[storage_mapper("actionSignerIds")] fn action_signer_ids(&self, action_id: usize) -> UnorderedSetMapper; /// The required amount to stake for accepting relayer position @@ -73,6 +73,9 @@ pub trait StorageModule { esdt_safe_batch_id: u64, ) -> MapMapper, usize>; + #[storage_mapper("executedActions")] + fn executed_actions(&self) -> UnorderedSetMapper; + /// Mapping between ERC20 Ethereum address and MultiversX ESDT Token Identifiers #[view(getErc20AddressForTokenId)] diff --git a/multisig/tests/multisig_blackbox_test.rs b/multisig/tests/multisig_blackbox_test.rs index b1af3e82..36849c9a 100644 --- a/multisig/tests/multisig_blackbox_test.rs +++ b/multisig/tests/multisig_blackbox_test.rs @@ -415,8 +415,10 @@ impl MultiTransferTestState { .to(ESDT_SAFE_ADDRESS) .typed(esdt_safe_proxy::EsdtSafeProxy) .create_transaction( - EthAddress::zero(), - OptionalValue::None::>, + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + OptionalValue::>::None, ) .single_esdt( &TokenIdentifier::from(WEGLD_TOKEN_ID), @@ -431,8 +433,10 @@ impl MultiTransferTestState { .to(ESDT_SAFE_ADDRESS) .typed(esdt_safe_proxy::EsdtSafeProxy) .create_transaction( - EthAddress::zero(), - OptionalValue::None::>, + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + OptionalValue::>::None, ) .single_esdt( &TokenIdentifier::from(WEGLD_TOKEN_ID), @@ -700,7 +704,6 @@ fn ethereum_to_multiversx_relayer_call_data_several_tx_test() { .to(MULTISIG_ADDRESS) .typed(multisig_proxy::MultisigProxy) .perform_action_endpoint(1usize) - .returns(ExpectError(4, "Invalid token or amount")) .run(); state.world.write_scenario_trace( @@ -862,7 +865,6 @@ fn ethereum_to_multiversx_relayer_query2_test() { .to(MULTISIG_ADDRESS) .typed(multisig_proxy::MultisigProxy) .perform_action_endpoint(1usize) - .returns(ExpectError(4, "Invalid token or amount")) .run(); state @@ -1695,7 +1697,6 @@ fn test_upgrade_child_contract_from_source_success() { .upgrade_child_contract_from_source( child_sc_address.clone(), MOCK_MULTI_TRANSFER_ADDRESS, - false, init_args.clone(), ) .run(); @@ -1709,7 +1710,6 @@ fn test_upgrade_child_contract_from_source_success() { .upgrade_child_contract_from_source( child_sc_address.clone(), MOCK_MULTI_TRANSFER_ADDRESS, - true, init_args.clone(), ) .run(); @@ -1763,59 +1763,6 @@ fn test_remove_user_success() { state.assert_board_member_count(2usize); } -#[test] -fn test_remove_user_cannot_remove_all() { - let mut state = MultiTransferTestState::new(); - - let mut board: MultiValueEncoded> = - MultiValueEncoded::new(); - board.push(RELAYER1_ADDRESS.to_managed_address()); - board.push(RELAYER2_ADDRESS.to_managed_address()); - state - .world - .tx() - .from(OWNER_ADDRESS) - .typed(multisig_proxy::MultisigProxy) - .init( - ESDT_SAFE_ADDRESS, - MULTI_TRANSFER_ADDRESS, - BRIDGE_PROXY_ADDRESS, - BRIDGED_TOKENS_WRAPPER_ADDRESS, - PRICE_AGGREGATOR_ADDRESS, - 1_000u64, - 500u64, - 1usize, - board, - ) - .code(MULTISIG_CODE_PATH) - .new_address(MULTISIG_ADDRESS) - .run(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); - - state - .world - .tx() - .from(OWNER_ADDRESS) - .to(MULTISIG_ADDRESS) - .typed(multisig_proxy::MultisigProxy) - .remove_user(RELAYER1_ADDRESS.to_managed_address()) - .run(); - - state - .world - .tx() - .from(OWNER_ADDRESS) - .to(MULTISIG_ADDRESS) - .typed(multisig_proxy::MultisigProxy) - .remove_user(RELAYER2_ADDRESS.to_managed_address()) - .returns(ExpectError(4u64, "cannot remove all board members")) - .run(); -} - #[test] fn test_remove_user_quorum_exceed_board_size() { let mut state = MultiTransferTestState::new(); @@ -1845,7 +1792,7 @@ fn test_change_quorum_success() { state.assert_board_member_count(2usize); - let new_quorum = 1usize; + let new_quorum = 2usize; state .world .tx() @@ -1875,7 +1822,9 @@ fn test_add_mapping_success() { let token_id = TokenIdentifier::from(WEGLD_TOKEN_ID); - let erc20_address = EthAddress::zero(); + let erc20_address = EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }; state .world @@ -1913,7 +1862,9 @@ fn test_add_mapping_token_id_already_mapped() { let token_id = TokenIdentifier::from(WEGLD_TOKEN_ID); - let erc20_address = EthAddress::zero(); + let erc20_address = EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }; state .world @@ -1943,7 +1894,9 @@ fn test_add_mapping_erc20_address_already_mapped() { let token_id = TokenIdentifier::from(WEGLD_TOKEN_ID); - let erc20_address = EthAddress::zero(); + let erc20_address = EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }; state .world @@ -1973,7 +1926,9 @@ fn test_clear_mapping_success() { let token_id = TokenIdentifier::from(WEGLD_TOKEN_ID); - let erc20_address = EthAddress::zero(); + let erc20_address = EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }; state .world @@ -2376,7 +2331,7 @@ fn test_esdt_safe_settings_management() { let esdt_safe_address = ESDT_SAFE_ADDRESS; - let new_max_tx_batch_size = 100usize; + let new_max_tx_batch_size = 50usize; let new_max_tx_batch_block_duration = 600u64; let token_id = TokenIdentifier::from("TEST-123456"); let max_bridged_amount = BigUint::from(1_000_000u64); @@ -2428,7 +2383,7 @@ fn test_multi_transfer_esdt_settings_management() { let token_id = TokenIdentifier::from("TEST-123456"); let max_bridged_amount = BigUint::from(1_000_000u64); - let new_max_refund_tx_batch_size = 100usize; + let new_max_refund_tx_batch_size = 50usize; let new_max_refund_tx_batch_block_duration = 600u64; state diff --git a/multisig/wasm/Cargo.lock b/multisig/wasm/Cargo.lock index 5af96c1d..9993d89a 100644 --- a/multisig/wasm/Cargo.lock +++ b/multisig/wasm/Cargo.lock @@ -265,6 +265,9 @@ dependencies = [ "eth-address", "fee-estimator-module", "max-bridged-amount-module", + "mock-esdt-safe", + "mock-multi-transfer-esdt", + "mock-price-aggregator", "multi-transfer-esdt", "multiversx-price-aggregator-sc", "multiversx-sc", diff --git a/multisig/wasm/src/lib.rs b/multisig/wasm/src/lib.rs index dba3a844..45ec811e 100644 --- a/multisig/wasm/src/lib.rs +++ b/multisig/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 72 +// Endpoints: 73 // Async Callback (empty): 1 -// Total number of exported functions: 75 +// Total number of exported functions: 76 #![no_std] @@ -32,6 +32,7 @@ multiversx_sc_wasm_adapter::endpoints! { withdrawTransactionFees => withdraw_transaction_fees withdrawSlashedAmount => withdraw_slashed_amount performAction => perform_action_endpoint + clearActionsForBatchId => clear_actions_for_batch_id sign => sign upgradeChildContractFromSource => upgrade_child_contract_from_source addBoardMember => add_board_member_endpoint