From 06bc10ca0d90adc381eb005410043af5f8dd897a Mon Sep 17 00:00:00 2001 From: Iulian Pascalau <36724586+iulianpascalau@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:39:11 +0300 Subject: [PATCH 1/5] Feat/v3.5 (#64) * Add BridgeProxy contract and state variables * add deposit endpoint * add logic for deposit funds in Bridge contract * moved structs to SharedStructs * add counter for transactions * add execute endpoint * fixed BridgeMock bug * add executeCallback private method * add finishExecuteGracefully private method * add refund method and view functions * removed token payments mapping and refactored * removed token payment * fixed logic after removing token payments * add setter for Bridge address * refactored tests for Bridge, MintBurnERC20 and ERC20Safe contracts * refactored deposit endpoint * removed state variable bridgeAddress * small gas optimization for getPendingTransaction view function * lowestIndexId and calldata encoding fixes * add unit tests for BridgeProxy contract * refactored execute endpoint * add logic for deleting pending txns * add test for txns with endpoints with no args * made BridgeProxy contract upgradable * test fixes after merge * add test for BridgeProxy contract upgrade * add isPendingTransaction private function * temporar fixes * fixes after review * renamed BridgeProxy to BridgeExecutor * more fixes * refactor execute function * renamed variables * - fixes after merge --------- Co-authored-by: cosmatudor Co-authored-by: Rebegea Dragos-Alexandru <42241923+dragos-rebegea@users.noreply.github.com> Co-authored-by: Cosma Tudor-Mihai <111079498+cosmatudor@users.noreply.github.com> --- contracts/Bridge.sol | 92 +++- contracts/BridgeExecutor.sol | 139 ++++++ contracts/SharedStructs.sol | 9 + contracts/test/BridgeExecutorTestContract.sol | 27 ++ contracts/test/BridgeExecutorUpgrade.sol | 19 + contracts/test/BridgeMock.sol | 9 +- tasks/deploy/evil-erc.ts | 2 +- test/Bridge.test.js | 263 +++-------- test/BridgeExecutor.test.js | 447 ++++++++++++++++++ test/MintBurnERC20.test.js | 94 ++-- test/Safe.test.js | 15 +- test/utils/bridge.utils.js | 37 +- yarn.lock | 99 ++-- 13 files changed, 922 insertions(+), 330 deletions(-) create mode 100644 contracts/BridgeExecutor.sol create mode 100644 contracts/test/BridgeExecutorTestContract.sol create mode 100644 contracts/test/BridgeExecutorUpgrade.sol create mode 100644 test/BridgeExecutor.test.js diff --git a/contracts/Bridge.sol b/contracts/Bridge.sol index c1bacda..e0d28bc 100644 --- a/contracts/Bridge.sol +++ b/contracts/Bridge.sol @@ -8,6 +8,7 @@ import "./SharedStructs.sol"; import "./ERC20Safe.sol"; import "./access/RelayerRole.sol"; import "./lib/Pausable.sol"; +import "./BridgeExecutor.sol"; /** @title Bridge @@ -40,6 +41,7 @@ contract Bridge is Initializable, RelayerRole, Pausable { uint256[10] private __gap; ERC20Safe internal safe; + BridgeExecutor internal bridgeExecutor; mapping(uint256 => bool) public executedBatches; mapping(uint256 => CrossTransferStatus) public crossTransferStatuses; @@ -53,12 +55,22 @@ contract Bridge is Initializable, RelayerRole, Pausable { * - add/remove relayers * - add/remove tokens that can be bridged */ - function initialize(address[] memory board, uint256 initialQuorum, ERC20Safe erc20Safe) public virtual initializer { + function initialize( + address[] memory board, + uint256 initialQuorum, + ERC20Safe erc20Safe, + BridgeExecutor _bridgeExecutor + ) public virtual initializer { __RelayerRole_init(); - __Bridge__init_unchained(board, initialQuorum, erc20Safe); + __Bridge__init_unchained(board, initialQuorum, erc20Safe, _bridgeExecutor); } - function __Bridge__init_unchained(address[] memory board, uint256 initialQuorum, ERC20Safe erc20Safe) internal onlyInitializing { + function __Bridge__init_unchained( + address[] memory board, + uint256 initialQuorum, + ERC20Safe erc20Safe, + BridgeExecutor _bridgeExecutor + ) internal onlyInitializing { require(initialQuorum >= minimumQuorum, "Quorum is too low."); require(board.length >= initialQuorum, "The board should be at least the quorum size."); @@ -66,6 +78,7 @@ contract Bridge is Initializable, RelayerRole, Pausable { quorum = initialQuorum; safe = erc20Safe; + bridgeExecutor = _bridgeExecutor; batchSettleBlockCount = 40; } @@ -109,20 +122,19 @@ contract Bridge is Initializable, RelayerRole, Pausable { } /** - @notice Executes transfers that were signed by the relayers. - @dev This is for the MultiversX to Ethereum flow - @dev Arrays here try to mimmick the structure of a batch. A batch represents the values from the same index in all the arrays. - @param tokens Array containing all the token addresses that the batch interacts with. Can even contain duplicates. - @param recipients Array containing all the destinations from the batch. Can be duplicates. - @param amounts Array containing all the amounts that will be transfered. - @param batchNonceMvx Nonce for the batch. This identifies a batch created on the MultiversX chain that bridges tokens from MultiversX to Ethereum - @param signatures Signatures from all the relayers for the execution. This mimics a delegated multisig contract. For the execution to take place, there must be enough valid signatures to achieve quorum. + @notice Executes a batch of transfers + @param mvxTransactions List of transactions from MultiversX side. Each transaction consists of: + - token address + - sender + - recipient + - amount + - deposit nonce + - call data + @param batchNonceMvx Nonce for the batch + @param signatures List of signatures from the relayers */ function executeTransfer( - address[] calldata tokens, - address[] calldata recipients, - uint256[] calldata amounts, - uint256[] calldata depositNonces, + MvxTransaction[] calldata mvxTransactions, uint256 batchNonceMvx, bytes[] calldata signatures ) public whenNotPaused onlyRelayer { @@ -132,16 +144,12 @@ contract Bridge is Initializable, RelayerRole, Pausable { _validateQuorum( signatures, - _getHashedDepositData( - abi.encode(recipients, tokens, amounts, depositNonces, batchNonceMvx, executeTransferAction) - ) + _getHashedDepositData(abi.encode(mvxTransactions, batchNonceMvx, executeTransferAction)) ); - DepositStatus[] memory statuses = new DepositStatus[](tokens.length); - for (uint256 j = 0; j < tokens.length; j++) { - statuses[j] = safe.transfer(tokens[j], amounts[j], recipients[j]) - ? DepositStatus.Executed - : DepositStatus.Rejected; + DepositStatus[] memory statuses = new DepositStatus[](mvxTransactions.length); + for (uint256 j = 0; j < mvxTransactions.length; j++) { + statuses[j] = _processDeposit(mvxTransactions[j]); } CrossTransferStatus storage crossStatus = crossTransferStatuses[batchNonceMvx]; @@ -154,7 +162,9 @@ contract Bridge is Initializable, RelayerRole, Pausable { @param batchNonceMvx Nonce for the batch @return a list of statuses for each transfer in the batch provided and a boolean that indicates if the information is final */ - function getStatusesAfterExecution(uint256 batchNonceMvx) external view returns (DepositStatus[] memory, bool isFinal) { + function getStatusesAfterExecution( + uint256 batchNonceMvx + ) external view returns (DepositStatus[] memory, bool isFinal) { CrossTransferStatus memory crossStatus = crossTransferStatuses[batchNonceMvx]; return (crossStatus.statuses, _isMvxBatchFinal(crossStatus.createdBlockNumber)); } @@ -176,6 +186,40 @@ contract Bridge is Initializable, RelayerRole, Pausable { return keccak256(abi.encodePacked(prefix, keccak256(encodedData))); } + function _processDeposit(MvxTransaction calldata mvxTransaction) private returns (DepositStatus) { + address recipient; + bool isScCall = _isScCall(mvxTransaction.callData); + + if (isScCall) { + recipient = address(bridgeExecutor); + } else { + recipient = mvxTransaction.recipient; + } + + if (mvxTransaction.amount == 0) { + return DepositStatus.Rejected; + } + + bool transferSuccess = safe.transfer(mvxTransaction.token, mvxTransaction.amount, recipient); + if (!transferSuccess) { + return DepositStatus.Rejected; + } + + // If the recipient is a smart contract, attempt to deposit the funds in bridgeExecutor + if (isScCall) { + bool depositSuccess = bridgeExecutor.deposit(mvxTransaction); + if (!depositSuccess) { + return DepositStatus.Rejected; + } + } + + return DepositStatus.Executed; + } + + function _isScCall(bytes calldata _data) private pure returns (bool) { + return _data.length > 0; + } + function _validateQuorum(bytes[] memory signatures, bytes32 data) private view { uint256 signersCount; address[] memory validSigners = new address[](signatures.length); diff --git a/contracts/BridgeExecutor.sol b/contracts/BridgeExecutor.sol new file mode 100644 index 0000000..0b80932 --- /dev/null +++ b/contracts/BridgeExecutor.sol @@ -0,0 +1,139 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "./lib/Pausable.sol"; +import "./SharedStructs.sol"; +import "./lib/BoolTokenTransfer.sol"; +import "./access/AdminRole.sol"; +import "./access/BridgeRole.sol"; + +contract BridgeExecutor is Initializable, Pausable, BridgeRole { + using BoolTokenTransfer for IERC20; + + /*========================= CONTRACT STATE =========================*/ + uint128 public constant MIN_GAS_LIMIT_FOR_SC_CALL = 10_000_000; + uint128 public constant DEFAULT_GAS_LIMIT_FOR_REFUND_CALLBACK = 20_000_000; + uint256 private lowestTxId; + uint256 private currentTxId; + + mapping(uint256 => MvxTransaction) private pendingTransactions; + + /*========================= PUBLIC API =========================*/ + function initialize() public initializer { + __BridgeRole_init(); + __Pausable_init(); + } + + function __BridgeExecutor__init_unchained() internal onlyInitializing { + lowestTxId = 0; + currentTxId = 0; + } + + function deposit(MvxTransaction calldata txn) external payable whenNotPaused onlyBridge returns (bool) { + pendingTransactions[currentTxId] = txn; + currentTxId++; + + IERC20 token = IERC20(txn.token); + bool approvalSuccess = token.approve(txn.recipient, txn.amount); + + return approvalSuccess; + } + + function execute(uint256 txId) external whenNotPaused { + require(txId < currentTxId, "BridgeExecutor: Invalid transaction ID"); + MvxTransaction memory txn = pendingTransactions[txId]; + + require(txn.recipient != address(0), "BridgeExecutor: Transaction does not exist"); + + if (txn.callData.length == 0) { + _refundAndDeleteTxn(txId); + return; + } + + (bytes memory selector, uint256 gasLimit, bytes memory args) = abi.decode( + txn.callData, + (bytes, uint256, bytes) + ); + + if (selector.length == 0 || gasLimit == 0 || gasLimit < MIN_GAS_LIMIT_FOR_SC_CALL) { + _refundAndDeleteTxn(txId); + return; + } + + bytes memory data; + if (args.length > 0) { + data = abi.encodePacked(selector, args); + } else { + data = selector; + } + + _updateLowestTxId(); + + delete pendingTransactions[txId]; + + (bool success, ) = txn.recipient.call{ gas: gasLimit }(data); + + if (!success) { + _refundTransaction(txn.token, txn.amount); + return; + } + } + + /*========================= PRIVATE API =========================*/ + function _refundAndDeleteTxn(uint256 txId) private { + MvxTransaction memory txn = pendingTransactions[txId]; + _refundTransaction(txn.token, txn.amount); + + _updateLowestTxId(); + + delete pendingTransactions[txId]; + } + + function _refundTransaction(address token, uint256 amount) private { + IERC20 erc20 = IERC20(token); + bool transferExecuted = erc20.boolTransfer(this.bridge(), amount); + require(transferExecuted, "BridgeExecutor: Refund failed"); + } + + function _updateLowestTxId() private { + uint256 newLowestTxId = lowestTxId; + + while (newLowestTxId < currentTxId && pendingTransactions[newLowestTxId].amount == 0) { + newLowestTxId++; + } + + lowestTxId = newLowestTxId; + } + + function _isPendingTransaction(uint256 txId) private view returns (bool) { + return pendingTransactions[txId].amount != 0; + } + + /*========================= VIEW FUNCTIONS =========================*/ + function getPendingTransactionById(uint256 txId) public view returns (MvxTransaction memory) { + return pendingTransactions[txId]; + } + + function getPendingTransactions() public view returns (MvxTransaction[] memory) { + uint256 pendingTransactionsCount = currentTxId - lowestTxId; + MvxTransaction[] memory txns = new MvxTransaction[](pendingTransactionsCount); + uint256 index = 0; + + for (uint256 i = lowestTxId; i < currentTxId; i++) { + if (_isPendingTransaction(i)) { + txns[index] = pendingTransactions[i]; + index++; + } + } + + // Resize the array to the actual number of pending transactions + assembly { + mstore(txns, index) + } + + return txns; + } +} diff --git a/contracts/SharedStructs.sol b/contracts/SharedStructs.sol index dc1516d..289b678 100644 --- a/contracts/SharedStructs.sol +++ b/contracts/SharedStructs.sol @@ -34,3 +34,12 @@ struct Batch { struct DepositSCExtension { string depositData; } + +struct MvxTransaction { + address token; + bytes32 sender; + address recipient; + uint256 amount; + uint256 depositNonce; + bytes callData; +} diff --git a/contracts/test/BridgeExecutorTestContract.sol b/contracts/test/BridgeExecutorTestContract.sol new file mode 100644 index 0000000..e730f38 --- /dev/null +++ b/contracts/test/BridgeExecutorTestContract.sol @@ -0,0 +1,27 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.20; + +interface IERC20 { + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); +} + +contract BridgeExecutorTestContract { + uint256 public count; + address public bridgeExecutor; + + constructor(address _bridgeExecutor) { + count = 0; + bridgeExecutor = _bridgeExecutor; + } + + function increment() public { + count += 1; + } + + function withdraw(address tokenAddress, uint256 amount) external { + IERC20 token = IERC20(tokenAddress); + // The test contract should be approved to spend the tokens on behalf of the bridgeExecutor + require(token.transferFrom(bridgeExecutor, address(this), amount), "TransferFrom failed"); + } +} diff --git a/contracts/test/BridgeExecutorUpgrade.sol b/contracts/test/BridgeExecutorUpgrade.sol new file mode 100644 index 0000000..e115796 --- /dev/null +++ b/contracts/test/BridgeExecutorUpgrade.sol @@ -0,0 +1,19 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +import "../BridgeExecutor.sol"; + +contract BridgeExecutorUpgrade is Initializable, BridgeExecutor { + uint256 public someValue; + // New initialization function for the upgrade + function initializeV2(uint256 val) public reinitializer(2) { + someValue = val; + } + + function afterUpgrade() public view returns (uint256) { + return someValue; + } +} diff --git a/contracts/test/BridgeMock.sol b/contracts/test/BridgeMock.sol index 11c2249..b0f0576 100644 --- a/contracts/test/BridgeMock.sol +++ b/contracts/test/BridgeMock.sol @@ -5,8 +5,13 @@ pragma solidity ^0.8.20; import "../Bridge.sol"; contract BridgeMock is Bridge { - function initialize(address[] memory board, uint256 initialQuorum, ERC20Safe erc20Safe) public override initializer { - Bridge.initialize(board, initialQuorum, erc20Safe); + function initialize( + address[] memory board, + uint256 initialQuorum, + ERC20Safe erc20Safe, + BridgeExecutor bridgeExecutor + ) public override initializer { + Bridge.initialize(board, initialQuorum, erc20Safe, bridgeExecutor); } function proxyTransfer(address tokenAddress, uint256 amount, address recipientAddress) external returns (bool) { diff --git a/tasks/deploy/evil-erc.ts b/tasks/deploy/evil-erc.ts index ff6cae7..d723190 100644 --- a/tasks/deploy/evil-erc.ts +++ b/tasks/deploy/evil-erc.ts @@ -16,5 +16,5 @@ task("deploy-evil-erc", "Deploys EvilERC20 contract to use to test the bridge"). //whitelist tokens in safe console.log("Whitelisting token ", usdcContract.target); - await safe.whitelistToken(usdcContract.target, "25000000", "100000000000", false, true); + await safe.whitelistToken(usdcContract.target, "25000000", "100000000000", false, true, 0, 0, 0); }); diff --git a/test/Bridge.test.js b/test/Bridge.test.js index c80d869..4d6571e 100644 --- a/test/Bridge.test.js +++ b/test/Bridge.test.js @@ -8,17 +8,22 @@ describe("Bridge", async function () { let adminWallet, relayer1, relayer2, relayer3, relayer4, relayer5, relayer6, relayer7, relayer8, otherWallet; let boardMembers; const quorum = 7; - - let erc20Safe, bridge, genericErc20; + let erc20Safe, bridge, genericErc20, bridgeExecutor; async function setupContracts() { erc20Safe = await deployUpgradableContract(adminWallet, "ERC20Safe"); - bridge = await deployUpgradableContract(adminWallet, "Bridge", [boardMembers, quorum, erc20Safe.address]); + bridgeExecutor = await deployUpgradableContract(adminWallet, "BridgeExecutor"); + bridge = await deployUpgradableContract(adminWallet, "Bridge", [ + boardMembers, + quorum, + erc20Safe.address, + bridgeExecutor.address, + ]); await erc20Safe.setBridge(bridge.address); + await bridgeExecutor.setBridge(bridge.address); await bridge.unpause(); await setupErc20Token(); } - async function setupErc20Token() { genericErc20 = await deployContract(adminWallet, "GenericERC20", ["TSC", "TSC", 6]); await genericErc20.mint(adminWallet.address, 1000); @@ -27,8 +32,9 @@ describe("Bridge", async function () { await erc20Safe.unpause(); } - before(async function() { - [adminWallet, relayer1, relayer2, relayer3, relayer4, relayer5, relayer6, relayer7, relayer8, otherWallet] = await ethers.getSigners(); + before(async function () { + [adminWallet, relayer1, relayer2, relayer3, relayer4, relayer5, relayer6, relayer7, relayer8, otherWallet] = + await ethers.getSigners(); boardMembers = [adminWallet, relayer1, relayer2, relayer3, relayer5, relayer6, relayer7, relayer8].map( m => m.address, ); @@ -37,120 +43,102 @@ describe("Bridge", async function () { beforeEach(async function () { await setupContracts(); }); - it("sets creator as admin", async function () { expect(await bridge.admin()).to.equal(adminWallet.address); }); - it("sets the quorum", async function () { expect(await bridge.quorum()).to.equal(quorum); }); - it("Sets the board members with relayer rights", async function () { expect(await bridge.getRelayers()).to.eql(boardMembers); }); - describe("when initialized with a quorum that is lower than the minimum", async function () { it("reverts", async function () { const invalidQuorumValue = 1; await expect( - deployUpgradableContract(adminWallet, "Bridge", [boardMembers, invalidQuorumValue, erc20Safe.address]), + deployUpgradableContract(adminWallet, "Bridge", [ + boardMembers, + invalidQuorumValue, + erc20Safe.address, + bridgeExecutor.address, + ]), ).to.be.revertedWith("Quorum is too low."); }); }); - describe("addRelayer", async function () { it("reverts when called with an empty address", async function () { await expect(bridge.addRelayer(ethers.ZeroAddress)).to.be.revertedWith( "RelayerRole: account cannot be the 0 address", ); }); - it("reverts when not called by admin", async function () { nonAdminBridge = bridge.connect(otherWallet); await expect(nonAdminBridge.addRelayer(relayer4.address)).to.be.revertedWith( "Access Control: sender is not Admin", ); }); - it("adds the address as a relayer", async function () { await bridge.addRelayer(relayer4.address); - expect(await bridge.isRelayer(relayer4.address)).to.be.true; }); - it("emits event that a relayer was added", async function () { await expect(bridge.addRelayer(relayer4.address)) .to.emit(bridge, "RelayerAdded") .withArgs(relayer4.address, adminWallet.address); }); - it("reverts if new relayer is already a relayer", async function () { await bridge.addRelayer(relayer4.address); - await expect(bridge.addRelayer(relayer4.address)).to.be.revertedWith("RelayerRole: address is already a relayer"); }); }); - describe("removeRelayer", async function () { beforeEach(async function () { await bridge.addRelayer(relayer4.address); }); - it("removes the relayer", async function () { await bridge.removeRelayer(relayer4.address); - expect(await bridge.isRelayer(relayer4.address)).to.be.false; }); - it("emits an event", async function () { expect(await bridge.isRelayer(relayer4.address)).to.be.true; await expect(bridge.removeRelayer(relayer4.address)) .to.emit(bridge, "RelayerRemoved") .withArgs(relayer4.address, adminWallet.address); }); - it("reverts when not called by admin", async function () { nonAdminBridge = bridge.connect(otherWallet); await expect(nonAdminBridge.removeRelayer(relayer4.address)).to.be.revertedWith( "Access Control: sender is not Admin", ); }); - it("reverts if address is not already a relayer", async function () { await expect(bridge.removeRelayer(otherWallet.address)).to.be.revertedWith( "RelayerRole: address is not a relayer", ); }); }); - describe("setQuorum", async function () { const newQuorum = 8; - it("sets the quorum with the new value", async function () { await bridge.setQuorum(newQuorum); - expect(await bridge.quorum()).to.equal(newQuorum); }); - it("emits event", async function () { await expect(bridge.setQuorum(newQuorum)).to.emit(bridge, "QuorumChanged").withArgs(newQuorum); }); - it("reverts when not called by admin", async function () { nonAdminBridge = bridge.connect(otherWallet); await expect(nonAdminBridge.setQuorum(newQuorum)).to.be.revertedWith("Access Control: sender is not Admin"); }); - describe("when quorum is lower than the minimum", async function () { it("reverts", async function () { await expect(bridge.setQuorum(2)).to.be.revertedWith("Quorum is too low."); }); }); }); - describe("executeTransfer", async function () { let amount, batchNonce, signatures; + let mvxTxn; let dataToSign, signature1, signature2, @@ -170,71 +158,52 @@ describe("Bridge", async function () { Buffer.from("c0f0058cea88a2bc1240b60361efb965957038d05f916c42b3f23a2c38ced81e", "hex"), ); batchNonce = 42; - signatures = await getSignaturesForExecuteTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - [adminWallet, relayer1, relayer2, relayer3, relayer5, relayer6, relayer7, relayer8], - ); + mvxTxn = { + token: genericErc20.address, + sender: Buffer.from("c0f0058cea88a2bc1240b60361efb965957038d05f916c42b3f23a2c38ced81e", "hex"), + recipient: otherWallet.address, + amount: amount, + depositNonce: 1, + callData: "0x", + }; + signatures = await getSignaturesForExecuteTransfer([mvxTxn], batchNonce, [ + adminWallet, + relayer1, + relayer2, + relayer3, + relayer5, + relayer6, + relayer7, + relayer8, + ]); }); - describe("when quorum achieved", async function () { it("transfers tokens", async function () { - await expect(() => - bridge.executeTransfer([genericErc20.address], [otherWallet.address], [amount], [1], batchNonce, signatures), - ).to.changeTokenBalance(genericErc20, otherWallet, amount); + await expect(() => bridge.executeTransfer([mvxTxn], batchNonce, signatures)).to.changeTokenBalance( + genericErc20, + otherWallet, + amount, + ); }); - it("sets the wasBatchExecuted to true", async function () { - await bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signatures, - ); + await bridge.executeTransfer([mvxTxn], batchNonce, signatures); expect(await bridge.wasBatchExecuted(batchNonce)).to.be.true; }); - describe("but all signatures are from the same relayer", async function () { beforeEach(async function () { - dataToSign = await getExecuteTransferData( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - ); + dataToSign = await getExecuteTransferData([mvxTxn], batchNonce); signature1 = await adminWallet.signMessage(dataToSign); signatures = [signature1, signature1, signature1, signature1, signature1, signature1, signature1]; }); - it("reverts", async function () { - await expect( - bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signatures, - ), - ).to.be.revertedWith("Quorum was not met"); + await expect(bridge.executeTransfer([mvxTxn], batchNonce, signatures)).to.be.revertedWith( + "Quorum was not met", + ); }); }); - describe("but some signatures are from the same relayer", async function () { beforeEach(async function () { - dataToSign = await getExecuteTransferData( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - ); + dataToSign = await getExecuteTransferData([mvxTxn], batchNonce); signature1 = await adminWallet.signMessage(dataToSign); signature2 = await relayer1.signMessage(dataToSign); signature3 = await relayer2.signMessage(dataToSign); @@ -246,64 +215,33 @@ describe("Bridge", async function () { signaturesInvalid2 = [signature1, signature1, signature2, signature3, signature4, signature5, signature6]; signaturesValid = [signature1, signature2, signature3, signature4, signature5, signature6, signature7]; }); - it("reverts", async function () { - await expect( - bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signaturesInvalid, - ), - ).to.be.revertedWith("Quorum was not met"); - await expect( - bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signaturesInvalid2, - ), - ).to.be.revertedWith("Quorum was not met"); + await expect(bridge.executeTransfer([mvxTxn], batchNonce, signaturesInvalid)).to.be.revertedWith( + "Quorum was not met", + ); + await expect(bridge.executeTransfer([mvxTxn], batchNonce, signaturesInvalid2)).to.be.revertedWith( + "Quorum was not met", + ); }); - it("does not revert", async function () { - await expect(() => - bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signaturesValid, - ), - ).to.changeTokenBalance(genericErc20, otherWallet, amount); + await expect(() => bridge.executeTransfer([mvxTxn], batchNonce, signaturesValid)).to.changeTokenBalance( + genericErc20, + otherWallet, + amount, + ); }); }); }); - describe("not enough signatures for quorum", async function () { it("reverts", async function () { - await expect( - bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signatures.slice(0, -2), - ), - ).to.be.revertedWith("Not enough signatures to achieve quorum"); + await expect(bridge.executeTransfer([mvxTxn], batchNonce, signatures.slice(0, -2))).to.be.revertedWith( + "Not enough signatures to achieve quorum", + ); }); - it("does not set wasBatchExecuted", async function () { expect(await bridge.wasBatchExecuted(batchNonce)).to.be.false; }); }); - describe("trying to replay the batch", async function () { beforeEach(async function () { // add more funds in order to not fail because of insufficient balance @@ -312,21 +250,12 @@ describe("Bridge", async function () { amount, Buffer.from("c0f0058cea88a2bc1240b60361efb965957038d05f916c42b3f23a2c38ced81e", "hex"), ); - - await bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signatures, - ); + await bridge.executeTransfer([mvxTxn], batchNonce, signatures); }); - it("reverts", async function () { - await expect( - bridge.executeTransfer([genericErc20.address], [otherWallet.address], [amount], [1], batchNonce, signatures), - ).to.be.revertedWith("Batch already executed"); + await expect(bridge.executeTransfer([mvxTxn], batchNonce, signatures)).to.be.revertedWith( + "Batch already executed", + ); }); }); @@ -338,89 +267,53 @@ describe("Bridge", async function () { await bridge.unpause(); }); it("fails", async function () { - await expect( - bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signatures.slice(0, -2), - ), - ).to.be.revertedWith("Pausable: paused"); + await expect(bridge.executeTransfer([mvxTxn], batchNonce, signatures.slice(0, -2))).to.be.revertedWith( + "Pausable: paused", + ); }); - it("does not set wasBatchExecuted", async function () { expect(await bridge.wasBatchExecuted(batchNonce)).to.be.false; }); }); - describe("check execute transfer saves correct statuses", async function () { it("returns correct statuses", async function () { //TODO: implement this test - await bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signatures, - ); + await bridge.executeTransfer([mvxTxn], batchNonce, signatures); const settleBlockCount = await bridge.batchSettleBlockCount(); for (let i = 0; i < settleBlockCount - 1n; i++) { await network.provider.send("evm_mine"); } - const [firstStatuses, firstIsFinal] = await bridge.getStatusesAfterExecution(batchNonce); expect(firstStatuses).to.eql([3n]); - expect(firstIsFinal).to.be.false + expect(firstIsFinal).to.be.false; await network.provider.send("evm_mine"); - const [secondStatuses, secondIsFinal] = await bridge.getStatusesAfterExecution(batchNonce); expect(secondStatuses).to.eql([3n]); - expect(secondIsFinal).to.be.true + expect(secondIsFinal).to.be.true; }); - it("saves refund items", async function () { - await bridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signatures, - ); + await bridge.executeTransfer([mvxTxn], batchNonce, signatures); const settleBlockCount = await bridge.batchSettleBlockCount(); for (let i = 0; i < settleBlockCount - 1n; i++) { await network.provider.send("evm_mine"); } - const [firstStatuses, firstIsFinal] = await bridge.getStatusesAfterExecution(batchNonce); expect(firstStatuses).to.eql([3n]); - expect(firstIsFinal).to.be.false + expect(firstIsFinal).to.be.false; await network.provider.send("evm_mine"); - const [secondStatuses, secondIsFinal] = await bridge.getStatusesAfterExecution(batchNonce); expect(secondStatuses).to.eql([3n]); - expect(secondIsFinal).to.be.true + expect(secondIsFinal).to.be.true; }); }); - describe("called by a non relayer", async function () { it("reverts", async function () { const nonAdminBridge = bridge.connect(otherWallet); - await expect( - nonAdminBridge.executeTransfer( - [genericErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signatures, - ), - ).to.be.revertedWith("Access Control: sender is not Relayer"); + await expect(nonAdminBridge.executeTransfer([mvxTxn], batchNonce, signatures)).to.be.revertedWith( + "Access Control: sender is not Relayer", + ); }); }); }); diff --git a/test/BridgeExecutor.test.js b/test/BridgeExecutor.test.js new file mode 100644 index 0000000..cd2e77f --- /dev/null +++ b/test/BridgeExecutor.test.js @@ -0,0 +1,447 @@ +const { ethers } = require("hardhat"); +const { expect } = require("chai"); + +const { deployContract, deployUpgradableContract, upgradeContract } = require("./utils/deploy.utils"); +const { getSignaturesForExecuteTransfer } = require("./utils/bridge.utils"); + +describe("BridgeExecutor", function () { + let adminWallet, relayer1, relayer2, relayer3, relayer4, relayer5, relayer6, relayer7, relayer8, otherWallet; + let boardMembers; + const quorum = 7; + let erc20Safe, bridge, bridgeExecutor, genericErc20, testContract; + + async function setupContracts() { + erc20Safe = await deployUpgradableContract(adminWallet, "ERC20Safe"); + bridgeExecutor = await deployUpgradableContract(adminWallet, "BridgeExecutor"); + bridge = await deployUpgradableContract(adminWallet, "Bridge", [ + boardMembers, + quorum, + erc20Safe.address, + bridgeExecutor.address, + ]); + testContract = await deployContract(adminWallet, "BridgeExecutorTestContract", [bridgeExecutor.address]); + await erc20Safe.setBridge(bridge.address); + await bridgeExecutor.setBridge(bridge.address); + await bridge.unpause(); + await bridgeExecutor.unpause(); + await setupErc20Token(); + } + + async function setupErc20Token() { + genericErc20 = await deployContract(adminWallet, "GenericERC20", ["TSC", "TSC", 6]); + await genericErc20.mint(adminWallet.address, 1000); + await genericErc20.approve(erc20Safe.address, 1000); + await erc20Safe.whitelistToken(genericErc20.address, 0, 1000, false, true, 0, 0, 0); + await erc20Safe.unpause(); + } + + const prepareAndExecuteTransfer = async (amountForDeposit, batchNonce, arrayOfTxn) => { + await erc20Safe.deposit( + genericErc20.address, + amountForDeposit, + Buffer.from("c0f0058cea88a2bc1240b60361efb965957038d05f916c42b3f23a2c38ced81e", "hex"), + ); + + signatures = await getSignaturesForExecuteTransfer(arrayOfTxn, batchNonce, [ + adminWallet, + relayer1, + relayer2, + relayer3, + relayer5, + relayer6, + relayer7, + relayer8, + ]); + await bridge.executeTransfer(arrayOfTxn, batchNonce, signatures); + }; + + function generateCallData(functionSignature, gasLimit, argTypes, argValues) { + // Compute the function selector + const functionSelector = ethers.id(functionSignature).slice(0, 10); // First 4 bytes of the hash + + // Encode the function arguments + const coder = ethers.AbiCoder.defaultAbiCoder(); + const args = coder.encode(argTypes, argValues); + + // Encode the full calldata with function selector, gas limit, and arguments + const callDataEncoded = coder.encode(["bytes", "uint256", "bytes"], [functionSelector, gasLimit, args]); + + return callDataEncoded; + } + + const checkForEmptyTransaction = async pendingTxn => { + expect(pendingTxn[0]).to.equal(ethers.AddressZero); + expect(pendingTxn[1]).to.equal(ethers.HashZero); + expect(pendingTxn[2]).to.equal(ethers.AddressZero); + expect(pendingTxn[3]).to.equal(0); + expect(pendingTxn[4]).to.equal(0); + expect(pendingTxn[5]).to.equal("0x"); + }; + + before(async function () { + [adminWallet, relayer1, relayer2, relayer3, relayer4, relayer5, relayer6, relayer7, relayer8, otherWallet] = + await ethers.getSigners(); + boardMembers = [adminWallet, relayer1, relayer2, relayer3, relayer5, relayer6, relayer7, relayer8].map( + m => m.address, + ); + }); + + beforeEach(async function () { + await setupContracts(); + }); + it("sets creator as admin", async function () { + expect(await bridgeExecutor.admin()).to.equal(adminWallet.address); + }); + it("sets bridge address", async function () { + expect(await bridgeExecutor.bridge()).to.equal(bridge.address); + }); + + describe("deposit", function () { + let amount = 1000; + let batchNonce = 42; + let mvxTxn; + + beforeEach(async function () { + await erc20Safe.deposit( + genericErc20.address, + amount, + Buffer.from("c0f0058cea88a2bc1240b60361efb965957038d05f916c42b3f23a2c38ced81e", "hex"), + ); + mvxTxn = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: otherWallet.address, + amount: 80n, + depositNonce: 1, + callData: generateCallData("increment()", 11000000, [], []), + }; + + signatures = await getSignaturesForExecuteTransfer([mvxTxn], batchNonce, [ + adminWallet, + relayer1, + relayer2, + relayer3, + relayer5, + relayer6, + relayer7, + relayer8, + ]); + }); + + it("reverts when called with an address different than the bridge", async function () { + await expect(bridgeExecutor.connect(otherWallet).deposit(mvxTxn)).to.be.revertedWith( + "Access Control: sender is not Bridge", + ); + }); + it("reverts when contract is paused", async function () { + await bridgeExecutor.pause(); + await expect(bridge.executeTransfer([mvxTxn], batchNonce, signatures)).to.be.revertedWith("Pausable: paused"); + }); + it("should successfully deposit a pending transactions", async function () { + await bridge.executeTransfer([mvxTxn], batchNonce, signatures); + + const pendingTxn = await bridgeExecutor.getPendingTransactionById(0); + + expect(pendingTxn[0]).to.equal(mvxTxn.token); + expect(pendingTxn[1]).to.equal(mvxTxn.sender); + expect(pendingTxn[2]).to.equal(mvxTxn.recipient); + expect(pendingTxn[3]).to.equal(mvxTxn.amount); + expect(pendingTxn[4]).to.equal(mvxTxn.depositNonce); + expect(pendingTxn[5]).to.equal(mvxTxn.callData); + }); + }); + + describe("execute", function () { + let amount = 1000; + let batchNonce = 42; + let arrayOfTxn = []; + + beforeEach(async function () { + arrayOfTxn = []; // reset the array + }); + + it("reverts when contract is paused", async function () { + await bridgeExecutor.pause(); + await expect(bridgeExecutor.execute(0)).to.be.revertedWith("Pausable: paused"); + }); + it("reverts when called with bad transaction id", async function () { + await expect(bridgeExecutor.execute(arrayOfTxn.length + 1)).to.be.revertedWith( + "BridgeExecutor: Invalid transaction ID", + ); + }); + + it("should gracefully finish execution and refund if calldata is invalid", async function () { + const badFunctionSignature = "mint(uint256,uint256)"; + const gasLimit = 11000000; + const argTypes = ["uint256", "uint256"]; + const argValues = [0, 999]; + + const mvxTxnWithInvalidCalldata = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: genericErc20.address, + amount: 80n, + depositNonce: 2, + callData: generateCallData(badFunctionSignature, gasLimit, argTypes, argValues), + }; + arrayOfTxn.push(mvxTxnWithInvalidCalldata); + await prepareAndExecuteTransfer(amount, batchNonce, arrayOfTxn); + + const beforeBalance = await genericErc20.balanceOf(bridge.address); + await bridgeExecutor.execute(0); + const afterBalance = await genericErc20.balanceOf(bridge.address); + + expect(afterBalance).to.equal(beforeBalance + mvxTxnWithInvalidCalldata.amount); + + // check for pending transaction to be removed after refund + const pendingTxn = await bridgeExecutor.getPendingTransactionById(0); + checkForEmptyTransaction(pendingTxn); + }); + + it("should gracefully finish execution and refund if calldata exists but endpoint is empty or gas limit is invalid (zero or below minimum)", async function () { + const functionSignature = "mint(address,uint256)"; + const argTypes = ["address", "uint256"]; + const argValues = [otherWallet.address, 999]; + + const mvxTxnWithZeroGas = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: genericErc20.address, + amount: 80n, + depositNonce: 2, + callData: generateCallData(functionSignature, 0, argTypes, argValues), + }; + + const mvxTxnWithInsufficientGas = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: genericErc20.address, + amount: 80n, + depositNonce: 2, + callData: generateCallData(functionSignature, 2000000, argTypes, argValues), + }; + + const mvxTxnWithEmptyEndpoint = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: genericErc20.address, + amount: 80n, + depositNonce: 2, + callData: generateCallData("", 11000000, argTypes, argValues), + }; + + arrayOfTxn = [mvxTxnWithEmptyEndpoint, mvxTxnWithZeroGas, mvxTxnWithInsufficientGas]; + await prepareAndExecuteTransfer(240, batchNonce, arrayOfTxn); + + await bridgeExecutor.execute(0); + await bridgeExecutor.execute(1); + await bridgeExecutor.execute(2); + + // check for pending transaction to be removed after refund + checkForEmptyTransaction(bridgeExecutor.getPendingTransactionById(0)); + checkForEmptyTransaction(bridgeExecutor.getPendingTransactionById(1)); + checkForEmptyTransaction(bridgeExecutor.getPendingTransactionById(2)); + }); + + it("should successfully execute transaction with proper encoded calldata (with args)", async function () { + const amountToBeMinted = 999n; + const functionSignature = "mint(address,uint256)"; + const gasLimit = 11000000; + const argTypes = ["address", "uint256"]; + const argValues = [otherWallet.address, amountToBeMinted]; + mvxTxnWithCalldata = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: genericErc20.address, + amount: 80n, + depositNonce: 1, + callData: generateCallData(functionSignature, gasLimit, argTypes, argValues), + }; + arrayOfTxn.push(mvxTxnWithCalldata); + await prepareAndExecuteTransfer(amount, batchNonce, arrayOfTxn); + + // check the otherWallet' balance of the token that have been intended to be minted + const beforeBalance = await genericErc20.balanceOf(otherWallet.address); + await bridgeExecutor.execute(0); + const afterBalance = await genericErc20.balanceOf(otherWallet.address); + + expect(afterBalance).to.equal(beforeBalance + amountToBeMinted); + }); + + it("should successfully execute transaction with proper encoded calldata (without args)", async function () { + const functionSignature = "increment()"; + const gasLimit = 11000000; + mvxTxnWithCalldata = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: testContract.address, + amount: 80n, + depositNonce: 1, + callData: generateCallData(functionSignature, gasLimit, [], []), + }; + arrayOfTxn.push(mvxTxnWithCalldata); + await prepareAndExecuteTransfer(amount, batchNonce, arrayOfTxn); + + const counterBefore = await testContract.count(); + await bridgeExecutor.execute(0); + const counterAfter = await testContract.count(); + + expect(counterAfter).to.equal(counterBefore + 1n); + }); + + it("should transfer funds from bridge proxy contract after executing transaction", async function () { + const amount = 80n; + const functionSignature = "withdraw(address,uint256)"; + const gasLimit = 11000000; + const argTypes = ["address", "uint256"]; + const argValues = [genericErc20.address, amount]; + mvxTxnWithCalldata = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: testContract.address, + amount, + depositNonce: 1, + callData: generateCallData(functionSignature, gasLimit, argTypes, argValues), + }; + arrayOfTxn.push(mvxTxnWithCalldata); + await prepareAndExecuteTransfer(amount, batchNonce, arrayOfTxn); + + const beforeBalanceRecipient = await genericErc20.balanceOf(testContract.address); + const beforeBalanceBridgeExecutor = await genericErc20.balanceOf(bridgeExecutor.address); + + await bridgeExecutor.execute(0); + + const afterBalanceRecipient = await genericErc20.balanceOf(testContract.address); + const afterBalanceBridgeExecutor = await genericErc20.balanceOf(bridgeExecutor.address); + + expect(afterBalanceRecipient).to.equal(beforeBalanceRecipient + amount); + expect(afterBalanceBridgeExecutor).to.equal(beforeBalanceBridgeExecutor - amount); + }); + }); + + describe("getPendingTransactionById", function () { + let amount = 1000; + let batchNonce = 42; + let mvxTxn; + + beforeEach(async function () { + mvxTxn = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: otherWallet.address, + amount: 80n, + depositNonce: 1, + callData: generateCallData("increment()", 11000000, [], []), + }; + await prepareAndExecuteTransfer(amount, batchNonce, [mvxTxn]); + }); + + it("should return the transaction with the given id", async function () { + const pendingTxn = await bridgeExecutor.getPendingTransactionById(0); + + expect(pendingTxn[0]).to.equal(mvxTxn.token); + expect(pendingTxn[1]).to.equal(mvxTxn.sender); + expect(pendingTxn[2]).to.equal(mvxTxn.recipient); + expect(pendingTxn[3]).to.equal(mvxTxn.amount); + expect(pendingTxn[4]).to.equal(mvxTxn.depositNonce); + expect(pendingTxn[5]).to.equal(mvxTxn.callData); + }); + }); + + describe("getPendingTransactions", function () { + let amount = 1000; + let batchNonce = 42; + let mvxTxn1, mvxTxn2, mvxTxn3; + + beforeEach(async function () { + const functionSignature1 = "increment()"; + const gasLimit = 11000000; + mvxTxn1 = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: testContract.address, + amount: 80n, + depositNonce: 1, + callData: generateCallData(functionSignature1, gasLimit, [], []), + }; + + mvxTxn2 = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: otherWallet.address, + amount: 80n, + depositNonce: 2, + callData: generateCallData("increment()", 11000000, [], []), + }; + + const functionSignature2 = "mint(address,uint256)"; + const argTypes = ["address", "uint256"]; + const argValues = [otherWallet.address, 99]; + mvxTxn3 = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: genericErc20.address, + amount: 80n, + depositNonce: 3, + callData: generateCallData(functionSignature2, gasLimit, argTypes, argValues), + }; + + await prepareAndExecuteTransfer(amount, batchNonce, [mvxTxn1, mvxTxn2, mvxTxn3]); + }); + + it("should return all pending transactions", async function () { + const pendingTxns = await bridgeExecutor.getPendingTransactions(); + + expect(pendingTxns.length).to.equal(3); + expect(pendingTxns[0][4]).to.equal(mvxTxn1.depositNonce); + expect(pendingTxns[1][4]).to.equal(mvxTxn2.depositNonce); + expect(pendingTxns[2][4]).to.equal(mvxTxn3.depositNonce); + }); + }); + + describe("Upgrade works as expected", async function () { + let amount = 1000; + let batchNonce = 42; + let mvxTxn; + + beforeEach(async function () { + await erc20Safe.deposit( + genericErc20.address, + amount, + Buffer.from("c0f0058cea88a2bc1240b60361efb965957038d05f916c42b3f23a2c38ced81e", "hex"), + ); + mvxTxn = { + token: genericErc20.address, + sender: ethers.encodeBytes32String("senderAddress"), + recipient: otherWallet.address, + amount: 80n, + depositNonce: 1, + callData: generateCallData("increment()", 11000000, [], []), + }; + + signatures = await getSignaturesForExecuteTransfer([mvxTxn], batchNonce, [ + adminWallet, + relayer1, + relayer2, + relayer3, + relayer5, + relayer6, + relayer7, + relayer8, + ]); + }); + it("upgrades and has new functions", async function () { + let valueToCheckAgainst = 100n; + + // Make a deposit to check state persistence + await bridge.executeTransfer([mvxTxn], batchNonce, signatures); + + let newBridgeExecutor = await upgradeContract(adminWallet, bridgeExecutor.address, "BridgeExecutorUpgrade", [ + valueToCheckAgainst, + ]); + + expect(await newBridgeExecutor.afterUpgrade()).to.be.eq(valueToCheckAgainst); + expect((await newBridgeExecutor.getPendingTransactions()).length).to.be.eq(1); + }); + }); +}); diff --git a/test/MintBurnERC20.test.js b/test/MintBurnERC20.test.js index 77e3680..3ee9bf5 100644 --- a/test/MintBurnERC20.test.js +++ b/test/MintBurnERC20.test.js @@ -9,12 +9,19 @@ describe("ERC20Safe, MintBurnERC20, and Bridge Interaction", function () { let boardMembers; const quorum = 7; - let erc20Safe, bridge, mintBurnErc20; + let erc20Safe, bridge, mintBurnErc20, bridgeExecutor; async function setupContracts() { erc20Safe = await deployUpgradableContract(adminWallet, "ERC20Safe"); - bridge = await deployUpgradableContract(adminWallet, "Bridge", [boardMembers, quorum, erc20Safe.address]); + bridgeExecutor = await deployUpgradableContract(adminWallet, "BridgeExecutor"); + bridge = await deployUpgradableContract(adminWallet, "Bridge", [ + boardMembers, + quorum, + erc20Safe.address, + bridgeExecutor.address, + ]); await erc20Safe.setBridge(bridge.address); + await bridgeExecutor.setBridge(bridge.address); await bridge.unpause(); await setupErc20Token(); } @@ -26,18 +33,11 @@ describe("ERC20Safe, MintBurnERC20, and Bridge Interaction", function () { } before(async function () { - [adminWallet, relayer1, relayer2, relayer3, relayer4, relayer5, relayer6, relayer7, relayer8, otherWallet] = await ethers.getSigners(); - boardMembers = [ - adminWallet, - relayer1, - relayer2, - relayer3, - relayer4, - relayer5, - relayer6, - relayer7, - relayer8, - ].map(m => m.address); + [adminWallet, relayer1, relayer2, relayer3, relayer4, relayer5, relayer6, relayer7, relayer8, otherWallet] = + await ethers.getSigners(); + boardMembers = [adminWallet, relayer1, relayer2, relayer3, relayer4, relayer5, relayer6, relayer7, relayer8].map( + m => m.address, + ); }); beforeEach(async function () { @@ -45,18 +45,29 @@ describe("ERC20Safe, MintBurnERC20, and Bridge Interaction", function () { }); describe("should burn tokens on deposit to ERC20Safe", async function () { - let amount, batchNonce, signatures; + let amount, batchNonce, signatures, mvxTxn; beforeEach(async function () { amount = 80; + mvxTxn = { + token: mintBurnErc20.address, + sender: Buffer.from("c0f0058cea88a2bc1240b60361efb965957038d05f916c42b3f23a2c38ced81e", "hex"), + recipient: otherWallet.address, + amount: amount, + depositNonce: 1, + callData: "0x", + isScRecipient: false, + }; batchNonce = 42; - signatures = await getSignaturesForExecuteTransfer( - [mintBurnErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - [adminWallet, relayer1, relayer2, relayer3, relayer5, relayer6, relayer7, relayer8], - ); + signatures = await getSignaturesForExecuteTransfer([mvxTxn], batchNonce, [ + adminWallet, + relayer1, + relayer2, + relayer3, + relayer5, + relayer6, + relayer7, + relayer8, + ]); }); it("transfer is set as REJECTED when ERC20Safe does not have the minter role", async function () { @@ -65,14 +76,7 @@ describe("ERC20Safe, MintBurnERC20, and Bridge Interaction", function () { const initialBalanceOtherWallet = await mintBurnErc20.balanceOf(otherWallet.address); // Execute the transfer - await bridge.executeTransfer( - [mintBurnErc20.address], - [otherWallet.address], - [amount], - [1], - batchNonce, - signatures, - ); + await bridge.executeTransfer([mvxTxn], batchNonce, signatures); // Get final balance of the otherWallet const finalBalanceOtherWallet = await mintBurnErc20.balanceOf(otherWallet.address); @@ -88,14 +92,16 @@ describe("ERC20Safe, MintBurnERC20, and Bridge Interaction", function () { // check that the transfer is set as REJECTED const [transfers, isFinal] = await bridge.getStatusesAfterExecution(batchNonce); expect(transfers[0]).to.equal(4); - expect(isFinal).to.be.true + expect(isFinal).to.be.true; }); it("transfer is set as Executed when ERC20Safe does have the minter role", async function () { await mintBurnErc20.grantRole(await mintBurnErc20.MINTER_ROLE(), erc20Safe.address); - await expect(() => - bridge.executeTransfer([mintBurnErc20.address], [otherWallet.address], [amount], [1], batchNonce, signatures), - ).to.changeTokenBalance(mintBurnErc20, otherWallet, amount); + await expect(() => bridge.executeTransfer([mvxTxn], batchNonce, signatures)).to.changeTokenBalance( + mintBurnErc20, + otherWallet, + amount, + ); const settleBlockCount = await bridge.batchSettleBlockCount(); for (let i = 0; i < settleBlockCount; i++) { @@ -105,14 +111,16 @@ describe("ERC20Safe, MintBurnERC20, and Bridge Interaction", function () { // check that the transfer is set as Executed const [transfers, isFinal] = await bridge.getStatusesAfterExecution(batchNonce); expect(transfers[0]).to.equal(3); - expect(isFinal).to.be.true + expect(isFinal).to.be.true; }); it("deposit not should work when ERC20Safe does not have enough allowance", async function () { await mintBurnErc20.grantRole(await mintBurnErc20.MINTER_ROLE(), erc20Safe.address); - await expect(() => - bridge.executeTransfer([mintBurnErc20.address], [otherWallet.address], [amount], [1], batchNonce, signatures), - ).to.changeTokenBalance(mintBurnErc20, otherWallet, amount); + await expect(() => bridge.executeTransfer([mvxTxn], batchNonce, signatures)).to.changeTokenBalance( + mintBurnErc20, + otherWallet, + amount, + ); await expect( erc20Safe @@ -127,9 +135,11 @@ describe("ERC20Safe, MintBurnERC20, and Bridge Interaction", function () { it("deposit should work when ERC20Safe has enough allowance", async function () { await mintBurnErc20.grantRole(await mintBurnErc20.MINTER_ROLE(), erc20Safe.address); - await expect(() => - bridge.executeTransfer([mintBurnErc20.address], [otherWallet.address], [amount], [1], batchNonce, signatures), - ).to.changeTokenBalance(mintBurnErc20, otherWallet, amount); + await expect(() => bridge.executeTransfer([mvxTxn], batchNonce, signatures)).to.changeTokenBalance( + mintBurnErc20, + otherWallet, + amount, + ); await mintBurnErc20.connect(otherWallet).approve(erc20Safe.address, amount); await expect( diff --git a/test/Safe.test.js b/test/Safe.test.js index c684207..39b45ed 100644 --- a/test/Safe.test.js +++ b/test/Safe.test.js @@ -11,16 +11,22 @@ describe("ERC20Safe", function () { let adminWallet, otherWallet, simpleBoardMember; let boardMembers; - before(async function() { + before(async function () { [adminWallet, otherWallet, simpleBoardMember] = await ethers.getSigners(); boardMembers = [adminWallet, otherWallet, simpleBoardMember]; }); - let safe, genericERC20, bridge; + let safe, genericERC20, bridge, bridgeExecutor; beforeEach(async function () { genericERC20 = await deployContract(adminWallet, "GenericERC20", ["TSC", "TSC", 6]); safe = await deployUpgradableContract(adminWallet, "ERC20Safe"); - bridge = await deployUpgradableContract(adminWallet, "Bridge", [boardMembers.map(m => m.address), 3, safe.address]); + bridgeExecutor = await deployUpgradableContract(adminWallet, "BridgeExecutor"); + bridge = await deployUpgradableContract(adminWallet, "Bridge", [ + boardMembers.map(m => m.address), + 3, + safe.address, + bridgeExecutor.address, + ]); await genericERC20.approve(safe.address, 1000); await safe.setBridge(bridge.address); @@ -417,6 +423,7 @@ describe("ERC20Safe", function () { boardMembers.map(m => m.address), 3, safe.address, + bridgeExecutor.address, ]); await safe.setBridge(mockBridge.address); @@ -505,7 +512,7 @@ describe("ERC20Safe", function () { }); }); - describe("ERC20Safe - upgrade works as expected", async function() { + describe("ERC20Safe - upgrade works as expected", async function () { it("upgrades and has new functions", async function () { let valueToCheckAgainst = 100n; diff --git a/test/utils/bridge.utils.js b/test/utils/bridge.utils.js index 6153f4a..5bb31d8 100644 --- a/test/utils/bridge.utils.js +++ b/test/utils/bridge.utils.js @@ -1,15 +1,20 @@ const { ethers } = require("hardhat"); -function getExecuteTransferData(tokenAddresses, recipientAddresses, amounts, depositNonces, batchNonce) { - const signMessageDefinition = ["address[]", "address[]", "uint256[]", "uint256[]", "uint256", "string"]; - const signMessageData = [ - recipientAddresses, - tokenAddresses, - amounts, - depositNonces, - batchNonce, - "ExecuteBatchedTransfer", - ]; +function getExecuteTransferData(mvxTransactions, batchNonce) { + const mvxTransactionType = "address,bytes32,address,uint256,uint256,bytes"; + const mvxTransactionsType = `(${mvxTransactionType})[]`; + const signMessageDefinition = [mvxTransactionsType, "uint256", "string"]; + + // Prepare the data for encoding + const mvxTransactionsData = mvxTransactions.map(tx => [ + tx.token, + tx.sender, + tx.recipient, + tx.amount, + tx.depositNonce, + tx.callData, + ]); + const signMessageData = [mvxTransactionsData, batchNonce, "ExecuteBatchedTransfer"]; const abiCoder = new ethers.AbiCoder(); const bytesToSign = abiCoder.encode(signMessageDefinition, signMessageData); @@ -17,16 +22,8 @@ function getExecuteTransferData(tokenAddresses, recipientAddresses, amounts, dep return ethers.toBeArray(signData); } - -async function getSignaturesForExecuteTransfer( - tokenAddresses, - recipientAddresses, - amounts, - depositNonces, - batchNonce, - wallets, -) { - const dataToSign = getExecuteTransferData(tokenAddresses, recipientAddresses, amounts, depositNonces, batchNonce); +async function getSignaturesForExecuteTransfer(mvxTransactions, batchNonce, wallets) { + const dataToSign = getExecuteTransferData(mvxTransactions, batchNonce); let signatures = []; for (const wallet of wallets) { const signature = await wallet.signMessage(dataToSign); diff --git a/yarn.lock b/yarn.lock index 9c5adf5..c6f4eac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -806,7 +806,7 @@ "@noble/hashes@1.4.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0": version "1.4.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2": @@ -1404,11 +1404,6 @@ dependencies: "@types/chai" "*" -"@types/chai@*": - version "4.3.17" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.17.tgz#9195f9d242f2ac3b429908864b6b871a8f73f489" - integrity sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow== - "@types/chai@^4.2.21": version "4.3.16" resolved "https://registry.npmjs.org/@types/chai/-/chai-4.3.16.tgz" @@ -1436,11 +1431,23 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/http-cache-semantics@*": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + "@types/json-schema@^7.0.7": version "7.0.15" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/lru-cache@^5.1.0": version "5.1.1" resolved "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz" @@ -1479,9 +1486,9 @@ integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== "@types/node@^20.16.1": - version "20.16.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.1.tgz#0b44b15271d0e2191ca68faf1fbe506e06aed732" - integrity sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ== + version "20.16.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.2.tgz#9e388f503a5af306e8c63319334887390966a11e" + integrity sha512-91s/n4qUPV/wg8eE9KHYW1kouTfDk2FPGjXbBMfRWP/2vg1rCXNQL1OCabwGs0XSdukuK+MwCDXE30QpSeMUhQ== dependencies: undici-types "~6.19.2" @@ -1514,6 +1521,13 @@ dependencies: "@types/node" "*" +"@types/responselike@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" + integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== + dependencies: + "@types/node" "*" + "@types/secp256k1@^4.0.1": version "4.0.6" resolved "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.6.tgz" @@ -1900,9 +1914,16 @@ async-retry@^1.3.3: async@1.x: version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + resolved "https://registry.npmjs.org/async/-/async-1.5.2.tgz" integrity sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w== +async@^2.4.0: + version "2.6.4" + resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" @@ -2019,7 +2040,7 @@ bluebird@^3.5.0, bluebird@^3.5.3: bn.js@4.11.6: version "4.11.6" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz" integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9: @@ -3660,7 +3681,7 @@ fs.realpath@^1.0.0: fsevents@~2.3.2: version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: @@ -3770,7 +3791,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: glob@7.1.7: version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== dependencies: fs.realpath "^1.0.0" @@ -3818,7 +3839,7 @@ glob@^10.3.10: glob@^5.0.15: version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + resolved "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" integrity sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA== dependencies: inflight "^1.0.4" @@ -4022,9 +4043,9 @@ hardhat-tracer@^1.0.0-alpha.6: ethers "^5.6.1" hardhat@^2.22.8: - version "2.22.8" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.8.tgz#348dcdb48c44648ae7723f6efb511785e2b220c5" - integrity sha512-hPh2feBGRswkXkoXUFW6NbxgiYtEzp/3uvVFjYROy6fA9LH8BobUyxStlyhSKj4+v1Y23ZoUBOVWL84IcLACrA== + version "2.22.9" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.9.tgz#d8f2720561dc60f5cc0ee80c82f9b1907fd61c88" + integrity sha512-sWiuI/yRdFUPfndIvL+2H18Vs2Gav0XacCFYY5msT5dHOWkhLxESJySIk9j83mXL31aXL8+UMA9OgViFLexklg== dependencies: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" @@ -4556,7 +4577,7 @@ is-windows@^1.0.1: isarray@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isarray@^2.0.5: @@ -4881,7 +4902,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: +lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5161,7 +5182,7 @@ mocha@10.1.0: yargs-parser "20.2.4" yargs-unparser "2.0.0" -mocha@^10.0.0: +mocha@^10.0.0, mocha@^10.2.0: version "10.7.0" resolved "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz" integrity sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA== @@ -5187,32 +5208,6 @@ mocha@^10.0.0: yargs-parser "^20.2.9" yargs-unparser "^2.0.0" -mocha@^10.2.0: - version "10.7.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.3.tgz#ae32003cabbd52b59aece17846056a68eb4b0752" - integrity sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A== - dependencies: - ansi-colors "^4.1.3" - browser-stdout "^1.3.1" - chokidar "^3.5.3" - debug "^4.3.5" - diff "^5.2.0" - escape-string-regexp "^4.0.0" - find-up "^5.0.0" - glob "^8.1.0" - he "^1.2.0" - js-yaml "^4.1.0" - log-symbols "^4.1.0" - minimatch "^5.1.6" - ms "^2.1.3" - serialize-javascript "^6.0.2" - strip-json-comments "^3.1.1" - supports-color "^8.1.1" - workerpool "^6.5.1" - yargs "^16.2.0" - yargs-parser "^20.2.9" - yargs-unparser "^2.0.0" - ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" @@ -5948,11 +5943,6 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -retry@0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -6765,11 +6755,16 @@ tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.6.2: +tslib@^2, tslib@^2.1.0: version "2.6.3" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.3.1, tslib@^2.6.2: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + tsort@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz" From 3171f4c3b745c85b794d3a1cbbb9339b7ce4183b Mon Sep 17 00:00:00 2001 From: Iulian Pascalau Date: Wed, 18 Sep 2024 18:54:35 +0300 Subject: [PATCH 2/5] - added bridge executor to compile scripts --- docker/build.Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/build.Dockerfile b/docker/build.Dockerfile index 3de87f1..bacdb74 100644 --- a/docker/build.Dockerfile +++ b/docker/build.Dockerfile @@ -16,12 +16,14 @@ RUN jq -r '.abi' artifacts/contracts/Proxy.sol/Proxy.json > artifacts/contracts/ RUN jq -r '.abi' artifacts/contracts/ERC20Safe.sol/ERC20Safe.json > artifacts/contracts/ERC20Safe.sol/ERC20Safe.abi.json RUN jq -r '.abi' artifacts/contracts/GenericERC20.sol/GenericERC20.json > artifacts/contracts/GenericERC20.sol/GenericERC20.abi.json RUN jq -r '.abi' artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.json > artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.abi.json +RUN jq -r '.abi' artifacts/contracts/BridgeExecutor.sol/BridgeExecutor.json > artifacts/contracts/BridgeExecutor.sol/BridgeExecutor.abi.json RUN jq -r '.bytecode' artifacts/contracts/Bridge.sol/Bridge.json > artifacts/contracts/Bridge.sol/Bridge.hex RUN jq -r '.bytecode' artifacts/contracts/Proxy.sol/Proxy.json > artifacts/contracts/Proxy.sol/Proxy.hex RUN jq -r '.bytecode' artifacts/contracts/ERC20Safe.sol/ERC20Safe.json > artifacts/contracts/ERC20Safe.sol/ERC20Safe.hex RUN jq -r '.bytecode' artifacts/contracts/GenericERC20.sol/GenericERC20.json > artifacts/contracts/GenericERC20.sol/GenericERC20.hex RUN jq -r '.bytecode' artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.json > artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.hex +RUN jq -r '.bytecode' artifacts/contracts/BridgeExecutor.sol/BridgeExecutor.json > artifacts/contracts/BridgeExecutor.sol/BridgeExecutor.hex FROM golang:1.20.7-bookworm AS go-builder LABEL description="This Docker image creates the go-wrappers for the Solidity contracts" @@ -37,3 +39,4 @@ RUN abigen --abi=artifacts/contracts/Proxy.sol/Proxy.abi.json --pkg=contract --o RUN abigen --abi=artifacts/contracts/ERC20Safe.sol/ERC20Safe.abi.json --pkg=contract --out=artifacts/contracts/ERC20Safe.sol/ERC20Safe.go --type=ERC20Safe RUN abigen --abi=artifacts/contracts/GenericERC20.sol/GenericERC20.abi.json --pkg=contract --out=artifacts/contracts/GenericERC20.sol/GenericERC20.go --type=GenericERC20 RUN abigen --abi=artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.abi.json --pkg=contract --out=artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.go --type=MintBurnERC20 +RUN abigen --abi=artifacts/contracts/BridgeExecutor.sol/BridgeExecutor.abi.json --pkg=contract --out=artifacts/contracts/BridgeExecutor.sol/BridgeExecutor.go --type=BridgeExecutor From 43fa514926d99f38aabb4fc9b079d456d7a94a1e Mon Sep 17 00:00:00 2001 From: evelinemolnar Date: Thu, 19 Sep 2024 17:11:55 +0300 Subject: [PATCH 3/5] test-caller contract --- contracts/TestCaller.sol | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 contracts/TestCaller.sol diff --git a/contracts/TestCaller.sol b/contracts/TestCaller.sol new file mode 100644 index 0000000..93125ee --- /dev/null +++ b/contracts/TestCaller.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +struct CalledData { + uint256 size; + address addr; + address tokenIdentifier; +} + +contract TestCaller { + CalledData[] private calledDataParams; + + constructor() {} + + function upgrade() public {} + + function callPayable() public payable {} + + function callNonPayable() public {} + + function callPayableWithParams(uint256 size, address addr, address tokenIdentifier) public payable { + CalledData memory data = CalledData({ size: size, addr: addr, tokenIdentifier: tokenIdentifier }); + + calledDataParams.push(data); + } + + function getCalledDataParams() public view returns (CalledData[] memory) { + return calledDataParams; + } +} From b44eba99dd5e57da283f6851ec3aa651eafd557f Mon Sep 17 00:00:00 2001 From: evelinemolnar Date: Thu, 19 Sep 2024 18:09:01 +0300 Subject: [PATCH 4/5] update dockerfile --- docker/build.Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/build.Dockerfile b/docker/build.Dockerfile index 3de87f1..fd4f014 100644 --- a/docker/build.Dockerfile +++ b/docker/build.Dockerfile @@ -16,12 +16,14 @@ RUN jq -r '.abi' artifacts/contracts/Proxy.sol/Proxy.json > artifacts/contracts/ RUN jq -r '.abi' artifacts/contracts/ERC20Safe.sol/ERC20Safe.json > artifacts/contracts/ERC20Safe.sol/ERC20Safe.abi.json RUN jq -r '.abi' artifacts/contracts/GenericERC20.sol/GenericERC20.json > artifacts/contracts/GenericERC20.sol/GenericERC20.abi.json RUN jq -r '.abi' artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.json > artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.abi.json +RUN jq -r '.abi' artifacts/contracts/TestCaller.sol/TestCaller.json > artifacts/contracts/TestCaller.sol/TestCaller.abi.json RUN jq -r '.bytecode' artifacts/contracts/Bridge.sol/Bridge.json > artifacts/contracts/Bridge.sol/Bridge.hex RUN jq -r '.bytecode' artifacts/contracts/Proxy.sol/Proxy.json > artifacts/contracts/Proxy.sol/Proxy.hex RUN jq -r '.bytecode' artifacts/contracts/ERC20Safe.sol/ERC20Safe.json > artifacts/contracts/ERC20Safe.sol/ERC20Safe.hex RUN jq -r '.bytecode' artifacts/contracts/GenericERC20.sol/GenericERC20.json > artifacts/contracts/GenericERC20.sol/GenericERC20.hex RUN jq -r '.bytecode' artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.json > artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.hex +RUN jq -r '.bytecode' artifacts/contracts/TestCaller.sol/TestCaller.json > artifacts/contracts/TestCaller.sol/TestCaller.hex FROM golang:1.20.7-bookworm AS go-builder LABEL description="This Docker image creates the go-wrappers for the Solidity contracts" @@ -37,3 +39,4 @@ RUN abigen --abi=artifacts/contracts/Proxy.sol/Proxy.abi.json --pkg=contract --o RUN abigen --abi=artifacts/contracts/ERC20Safe.sol/ERC20Safe.abi.json --pkg=contract --out=artifacts/contracts/ERC20Safe.sol/ERC20Safe.go --type=ERC20Safe RUN abigen --abi=artifacts/contracts/GenericERC20.sol/GenericERC20.abi.json --pkg=contract --out=artifacts/contracts/GenericERC20.sol/GenericERC20.go --type=GenericERC20 RUN abigen --abi=artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.abi.json --pkg=contract --out=artifacts/contracts/MintBurnERC20.sol/MintBurnERC20.go --type=MintBurnERC20 +RUN abigen --abi=artifacts/contracts/TestCaller.sol/TestCaller.abi.json --pkg=contract --out=artifacts/contracts/TestCaller.sol/TestCaller.go --type=TestCaller From 64a850096444bf3554898528225cd1d5bac15e5e Mon Sep 17 00:00:00 2001 From: evelinemolnar Date: Wed, 2 Oct 2024 17:30:56 +0300 Subject: [PATCH 5/5] fixes --- contracts/TestCaller.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/TestCaller.sol b/contracts/TestCaller.sol index 93125ee..95600a7 100644 --- a/contracts/TestCaller.sol +++ b/contracts/TestCaller.sol @@ -14,11 +14,9 @@ contract TestCaller { function upgrade() public {} - function callPayable() public payable {} + function callPayable() public {} - function callNonPayable() public {} - - function callPayableWithParams(uint256 size, address addr, address tokenIdentifier) public payable { + function callPayableWithParams(uint256 size, address addr, address tokenIdentifier) public { CalledData memory data = CalledData({ size: size, addr: addr, tokenIdentifier: tokenIdentifier }); calledDataParams.push(data);