diff --git a/Cargo.lock b/Cargo.lock index a604f13b..868a8f8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -608,9 +608,10 @@ dependencies = [ name = "mock-esdt-safe" version = "0.0.0" dependencies = [ + "eth-address", "multiversx-sc", + "multiversx-sc-modules", "multiversx-sc-scenario", - "num-bigint", ] [[package]] diff --git a/bridge-proxy/tests/bridge_proxy_blackbox_test.rs b/bridge-proxy/tests/bridge_proxy_blackbox_test.rs index 327cd5b8..20e287fc 100644 --- a/bridge-proxy/tests/bridge_proxy_blackbox_test.rs +++ b/bridge-proxy/tests/bridge_proxy_blackbox_test.rs @@ -44,6 +44,8 @@ const BRIDGE_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("BRIDGE-12 const WBRIDGE_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("WBRIDGE-123456"); 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 OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); @@ -544,10 +546,115 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { test.deploy_crowdfunding(); test.config_bridge(); + let eth_tx = EthTransaction { + from: EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + to: ManagedAddress::from(NO_INIT_SC_ADDRESS.eval_to_array()), + token_id: BRIDGE_TOKEN_ID.into(), + amount: BigUint::from(500u64), + tx_nonce: 1u64, + call_data: ManagedOption::none(), + }; + + 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)) + .run(); + + test.world + .tx() + .from(OWNER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .execute(1u32) + .run(); + + // Refund: Funds are transfered to BridgedTokensWrapper + test.world + .check_account(BRIDGED_TOKENS_WRAPPER_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, amount.clone()); + + // Wrong endpoint for callData let mut args = ManagedVec::new(); + let call_data: CallData = CallData { + endpoint: ManagedBuffer::from(b"nofunc"), + 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: amount.clone(), + tx_nonce: 2u64, + call_data: ManagedOption::some(call_data), + }; + + 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(2u32) + .returns(ExpectValue(eth_tx)) + .run(); + + test.world + .tx() + .from(OWNER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .execute(2u32) + .run(); + + // Refund: Funds are transfered to BridgedTokensWrapper + test.world + .check_account(BRIDGED_TOKENS_WRAPPER_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, amount.clone() * 2u64); + + // Wrong args + let mut args = ManagedVec::new(); + args.push(ManagedBuffer::from(b"wrongargs")); let call_data: CallData = CallData { - endpoint: ManagedBuffer::from("fund"), + endpoint: ManagedBuffer::from(b"fund"), gas_limit: GAS_LIMIT, args: ManagedOption::some(args), }; @@ -559,13 +666,82 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { from: EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - to: ManagedAddress::from(NO_INIT_SC_ADDRESS.eval_to_array()), + to: ManagedAddress::from(CROWDFUNDING_ADDRESS.eval_to_array()), + token_id: BRIDGE_TOKEN_ID.into(), + amount: amount.clone(), + tx_nonce: 3u64, + call_data: ManagedOption::some(call_data), + }; + + 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(3u32) + .returns(ExpectValue(eth_tx)) + .run(); + + test.world + .tx() + .from(OWNER_ADDRESS) + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .execute(3u32) + .run(); + + // Refund: Funds are transfered to BridgedTokensWrapper + test.world + .check_account(BRIDGED_TOKENS_WRAPPER_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, amount * 3u64); +} + +#[test] +fn bridge_proxy_too_small_gas_sc_call_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::from(b"fund"), + gas_limit: TOO_SMALL_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::none(), + call_data: ManagedOption::some(call_data), }; + let amount = BigUint::from(500u64); + // Destination is not an initialized contract test.world .tx() .from(MULTI_TRANSFER_ADDRESS) @@ -575,7 +751,7 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { .egld_or_single_esdt( &EgldOrEsdtTokenIdentifier::esdt(BRIDGE_TOKEN_ID), 0, - &BigUint::from(500u64), + &amount, ) .run(); @@ -595,8 +771,8 @@ fn bridge_proxy_wrong_formatting_sc_call_test() { .execute(1u32) .run(); - // Refund: Funds are transfered to BridgedTokensWrapper + // Refund: Funds are transfered to EsdtSafe test.world - .check_account(BRIDGED_TOKENS_WRAPPER_ADDRESS) - .esdt_balance(BRIDGE_TOKEN_ID, BigUint::from(500u64)); + .check_account(ESDT_SAFE_ADDRESS) + .esdt_balance(BRIDGE_TOKEN_ID, amount.clone()); } diff --git a/common/mock-contracts/mock-esdt-safe/Cargo.toml b/common/mock-contracts/mock-esdt-safe/Cargo.toml index 68932d4a..b08e4109 100644 --- a/common/mock-contracts/mock-esdt-safe/Cargo.toml +++ b/common/mock-contracts/mock-esdt-safe/Cargo.toml @@ -9,10 +9,13 @@ authors = ["you"] path = "src/mock_esdt_safe.rs" [dependencies.multiversx-sc] -version = "0.53.2" +version = "=0.53.2" -[dev-dependencies] -num-bigint = "0.4" +[dependencies.multiversx-sc-modules] +version = "=0.53.2" [dev-dependencies.multiversx-sc-scenario] -version = "0.53.2" \ No newline at end of file +version = "=0.53.2" + +[dependencies.eth-address] +path = "../../eth-address" \ No newline at end of file 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 51a6bbe1..991fc205 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 @@ -1,7 +1,16 @@ #![no_std] +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); -#[allow(unused_imports)] -use multiversx_sc::imports::*; +use eth_address::EthAddress; + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, Clone, ManagedVecItem, PartialEq)] +pub struct RefundInfo { + pub address: ManagedAddress, + pub initial_batch_id: u64, + pub initial_nonce: u64, +} /// An empty contract. To be used as a template when starting a new contract from scratch. #[multiversx_sc::contract] @@ -17,4 +26,13 @@ pub trait MockEsdtSafe { #[upgrade] fn upgrade(&self) {} + + #[payable("*")] + #[endpoint(createTransaction)] + fn create_transaction( + &self, + _to: EthAddress, + _opt_refund_info: OptionalValue>, + ) { + } } diff --git a/multisig/tests/multisig_blackbox_test.rs b/multisig/tests/multisig_blackbox_test.rs index a08697c6..b297eaf1 100644 --- a/multisig/tests/multisig_blackbox_test.rs +++ b/multisig/tests/multisig_blackbox_test.rs @@ -37,7 +37,10 @@ use sc_proxies::{ multi_transfer_esdt_proxy, multisig_proxy, }; use token_module::ProxyTrait as _; -use transaction::{CallData, EthTransaction, EthTxAsMultiValue, TxBatchSplitInFields}; +use transaction::{ + transaction_status::TransactionStatus, CallData, EthTransaction, EthTxAsMultiValue, + TxBatchSplitInFields, +}; const WEGLD_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("WEGLD-123456"); const ETH_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("ETH-123456"); @@ -69,6 +72,7 @@ const ORACLE_ADDRESS: TestAddress = TestAddress::new("oracle"); const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); const USER1_ADDRESS: TestAddress = TestAddress::new("user1"); const USER2_ADDRESS: TestAddress = TestAddress::new("user2"); +const NON_BOARD_MEMEBER_ADDRESS: TestAddress = TestAddress::new("non-board-member"); const RELAYER1_ADDRESS: TestAddress = TestAddress::new("relayer1"); const RELAYER2_ADDRESS: TestAddress = TestAddress::new("relayer2"); @@ -125,7 +129,9 @@ impl MultiTransferTestState { .balance(1_000u64) .account(RELAYER2_ADDRESS) .nonce(1) - .balance(1_000u64); + .balance(1_000u64) + .account(NON_BOARD_MEMEBER_ADDRESS) + .nonce(1); let roles = vec![ "ESDTRoleLocalMint".to_string(), @@ -889,3 +895,158 @@ fn ethereum_to_multiversx_tx_batch_rejected_test() { .move_refund_batch_to_safe_from_child_contract() .run(); } + +#[test] +fn multisig_non_board_member_interaction_test() { + let mut state = MultiTransferTestState::new(); + let token_amount = BigUint::from(76_000_000_000u64); + + state.multisig_deploy(); + state.safe_deploy(); + state.multi_transfer_deploy(); + state.bridge_proxy_deploy(); + state.bridged_tokens_wrapper_deploy(); + state.config_multisig(); + + let eth_tx = EthTxAsMultiValue::::from(( + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + TokenIdentifier::from(WEGLD_TOKEN_ID), + token_amount.clone(), + 1u64, + ManagedOption::none(), + )); + + let mut transfers: MultiValueEncoded> = + MultiValueEncoded::new(); + transfers.push(eth_tx); + + state + .world + .tx() + .from(NON_BOARD_MEMEBER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .propose_multi_transfer_esdt_batch(1u32, transfers.clone()) + .returns(ExpectError(4, "only board members can propose")) + .run(); + + let mut tx_batch_status: MultiValueEncoded = + MultiValueEncoded::::new(); + tx_batch_status.push(TransactionStatus::InProgress); + + state + .world + .tx() + .from(RELAYER1_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .propose_multi_transfer_esdt_batch(1u32, transfers) + .run(); +} + +#[test] +fn multisig_insuficient_signatures_test() { + let mut state = MultiTransferTestState::new(); + let token_amount = BigUint::from(76_000_000_000u64); + + state.multisig_deploy(); + state.safe_deploy(); + state.multi_transfer_deploy(); + state.bridge_proxy_deploy(); + state.bridged_tokens_wrapper_deploy(); + state.config_multisig(); + + let eth_tx = EthTxAsMultiValue::::from(( + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + TokenIdentifier::from(WEGLD_TOKEN_ID), + token_amount.clone(), + 1u64, + ManagedOption::none(), + )); + + let mut transfers: MultiValueEncoded> = + MultiValueEncoded::new(); + transfers.push(eth_tx); + + state + .world + .tx() + .from(RELAYER1_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .propose_multi_transfer_esdt_batch(1u32, transfers) + .run(); + + state + .world + .tx() + .from(RELAYER1_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .perform_action_endpoint(1usize) + .returns(ExpectError(4, "quorum has not been reached")) + .run(); +} + +#[test] +fn multisig_non_board_member_sign_test() { + let mut state = MultiTransferTestState::new(); + let token_amount = BigUint::from(76_000_000_000u64); + + state.multisig_deploy(); + state.safe_deploy(); + state.multi_transfer_deploy(); + state.bridge_proxy_deploy(); + state.bridged_tokens_wrapper_deploy(); + state.config_multisig(); + + let eth_tx = EthTxAsMultiValue::::from(( + EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }, + ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + TokenIdentifier::from(WEGLD_TOKEN_ID), + token_amount.clone(), + 1u64, + ManagedOption::none(), + )); + + let mut transfers: MultiValueEncoded> = + MultiValueEncoded::new(); + transfers.push(eth_tx); + + state + .world + .tx() + .from(RELAYER1_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .propose_multi_transfer_esdt_batch(1u32, transfers) + .run(); + + state + .world + .tx() + .from(NON_BOARD_MEMEBER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .sign(1usize) + .returns(ExpectError(4, "only board members can sign")) + .run(); + + state + .world + .tx() + .from(RELAYER1_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .perform_action_endpoint(1usize) + .returns(ExpectError(4, "quorum has not been reached")) + .run(); +}