diff --git a/test/defaultMessageReceiver/executeProposal.js b/test/defaultMessageReceiver/executeProposal.js new file mode 100644 index 00000000..91bc2762 --- /dev/null +++ b/test/defaultMessageReceiver/executeProposal.js @@ -0,0 +1,211 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +const TruffleAssert = require("truffle-assertions"); +const Ethers = require("ethers"); + +const Helpers = require("../helpers"); + +const DefaultMessageReceiverContract = artifacts.require("DefaultMessageReceiver"); +const NativeTokenHandlerContract = artifacts.require("NativeTokenHandler"); +const NativeTokenAdapterContract = artifacts.require("NativeTokenAdapter"); +const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler"); +const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); +const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); + +contract("Bridge - [execute proposal - native token]", async (accounts) => { + const originDomainID = 1; + const destinationDomainID = 2; + const adminAddress = accounts[0]; + const depositorAddress = accounts[1]; + const evmRecipientAddress = accounts[2]; + const relayer1Address = accounts[3]; + + const expectedDepositNonce = 1; + const emptySetResourceData = "0x"; + const resourceID = "0x0000000000000000000000000000000000000000000000000000000000000650"; + const depositAmount = Ethers.utils.parseEther("1"); + const fee = Ethers.utils.parseEther("0.1"); + const transferredAmount = depositAmount.sub(fee); + const transactionId = "0x0000000000000000000000000000000000000000000000000000000000000001"; + const executionGasAmount = 30000000; + + let BridgeInstance; + let DefaultMessageReceiverInstance; + let NativeTokenHandlerInstance; + let BasicFeeHandlerInstance; + let FeeHandlerRouterInstance; + let NativeTokenAdapterInstance; + let ERC20MintableInstance; + let proposal; + let depositProposalData; + let message; + + beforeEach(async () => { + await Promise.all([ + (BridgeInstance = await Helpers.deployBridge( + destinationDomainID, + adminAddress + )), + ]); + + + FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( + BridgeInstance.address + ); + BasicFeeHandlerInstance = await BasicFeeHandlerContract.new( + BridgeInstance.address, + FeeHandlerRouterInstance.address + ); + NativeTokenAdapterInstance = await NativeTokenAdapterContract.new( + BridgeInstance.address, + resourceID + ); + DefaultMessageReceiverInstance = await DefaultMessageReceiverContract.new([], 100000); + NativeTokenHandlerInstance = await NativeTokenHandlerContract.new( + BridgeInstance.address, + NativeTokenAdapterInstance.address, + DefaultMessageReceiverInstance.address, + ); + await DefaultMessageReceiverInstance.grantRole( + await DefaultMessageReceiverInstance.SYGMA_HANDLER_ROLE(), + NativeTokenHandlerInstance.address + ); + ERC20MintableInstance = await ERC20MintableContract.new( + "token", + "TOK" + ); + + await BridgeInstance.adminSetResource( + NativeTokenHandlerInstance.address, + resourceID, + Ethers.constants.AddressZero, + emptySetResourceData + ); + await BasicFeeHandlerInstance.changeFee(destinationDomainID, resourceID, fee); + await BridgeInstance.adminChangeFeeHandler(FeeHandlerRouterInstance.address), + await FeeHandlerRouterInstance.adminSetResourceHandler( + originDomainID, + resourceID, + BasicFeeHandlerInstance.address + ), + + await ERC20MintableInstance.grantRole( + await ERC20MintableInstance.MINTER_ROLE(), + DefaultMessageReceiverInstance.address + ); + + const mintableERC20Iface = new Ethers.utils.Interface(["function mint(address to, uint256 amount)"]); + const actions = [{ + nativeValue: 0, + callTo: ERC20MintableInstance.address, + approveTo: Ethers.constants.AddressZero, + tokenSend: Ethers.constants.AddressZero, + tokenReceive: Ethers.constants.AddressZero, + data: mintableERC20Iface.encodeFunctionData("mint", [evmRecipientAddress, "20"]), + }] + message = Helpers.createMessageCallData( + transactionId, + actions, + evmRecipientAddress + ); + + depositProposalData = Helpers.createOptionalContractCallDepositData( + transferredAmount, + Ethers.constants.AddressZero, + executionGasAmount, + message + ); + + proposal = { + originDomainID: originDomainID, + depositNonce: expectedDepositNonce, + resourceID: resourceID, + data: depositProposalData + }; + + // set MPC address to unpause the Bridge + await BridgeInstance.endKeygen(Helpers.mpcAddress); + }); + + it("should revert if handler does not have SYGMA_HANDLER_ROLE", async () => { + await DefaultMessageReceiverInstance.revokeRole( + await DefaultMessageReceiverInstance.SYGMA_HANDLER_ROLE(), + NativeTokenHandlerInstance.address + ); + const proposalSignedData = await Helpers.signTypedProposal( + BridgeInstance.address, + [proposal] + ); + + // depositorAddress makes initial deposit of depositAmount + assert.isFalse(await BridgeInstance.paused()); + await TruffleAssert.passes( + NativeTokenAdapterInstance.depositToEVMWithMessage( + originDomainID, + Ethers.constants.AddressZero, + executionGasAmount, + message, + { + from: depositorAddress, + value: depositAmount + }) + ); + + const executeTx = await BridgeInstance.executeProposal( + proposal, + proposalSignedData, + { + from: relayer1Address, + gas: executionGasAmount + } + ); + + TruffleAssert.eventEmitted(executeTx, "FailedHandlerExecution", (event) => { + return ( + event.originDomainID.toNumber() === originDomainID && + event.depositNonce.toNumber() === expectedDepositNonce && + event.lowLevelData === "0xdeda9030" // InsufficientPermission() + ); + }); + }); + + it("should revert if insufficient gas limit left for executing action", async () => { + const insufficientExecutionGasAmount = 100000; + const proposalSignedData = await Helpers.signTypedProposal( + BridgeInstance.address, + [proposal] + ); + + // depositorAddress makes initial deposit of depositAmount + assert.isFalse(await BridgeInstance.paused()); + await TruffleAssert.passes( + NativeTokenAdapterInstance.depositToEVMWithMessage( + originDomainID, + Ethers.constants.AddressZero, + insufficientExecutionGasAmount, + message, + { + from: depositorAddress, + value: depositAmount + }) + ); + + const executeTx = await BridgeInstance.executeProposal( + proposal, + proposalSignedData, + { + from: relayer1Address, + gas: insufficientExecutionGasAmount + } + ); + + TruffleAssert.eventEmitted(executeTx, "FailedHandlerExecution", (event) => { + return ( + event.originDomainID.toNumber() === originDomainID && + event.depositNonce.toNumber() === expectedDepositNonce && + event.lowLevelData === "0x60ee1247" // InsufficientGasLimit() + ); + }); + }); +});