diff --git a/logs/contract-code-sizes.log b/logs/contract-code-sizes.log index d4d56b11..940034ef 100644 --- a/logs/contract-code-sizes.log +++ b/logs/contract-code-sizes.log @@ -13,6 +13,7 @@ | ERC1967Proxy | 0.177 | 24.399 | | ERC20 | 2.173 | 22.403 | | ERC20PresetMinterPauser | 6.368 | 18.208 | +| ERC721 | 4.363 | 20.213 | | EnumerableSet | 0.086 | 24.49 | | ErrorHandler | 0.086 | 24.49 | | GeneralConfig | 20.136 | 4.44 | @@ -39,7 +40,7 @@ | MockBridgeSlash | 1.388 | 23.188 | | MockBridgeTracking | 1.897 | 22.679 | | MockERC20 | 2.442 | 22.134 | -| MockERC721 | 3.827 | 20.749 | +| MockERC721 | 4.741 | 19.835 | | MockGatewayForTracking | 1.616 | 22.96 | | MockRoninBridgeManager | 24.601 | -0.025 | | MockRoninGatewayV3Extended | 20.048 | 4.528 | diff --git a/logs/storage/ERC721.sol:ERC721.log b/logs/storage/ERC721.sol:ERC721.log new file mode 100644 index 00000000..bda99eff --- /dev/null +++ b/logs/storage/ERC721.sol:ERC721.log @@ -0,0 +1,6 @@ +lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol:ERC721:_name (storage_slot: 0) (offset: 0) (type: string) (numberOfBytes: 32) +lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol:ERC721:_symbol (storage_slot: 1) (offset: 0) (type: string) (numberOfBytes: 32) +lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol:ERC721:_owners (storage_slot: 2) (offset: 0) (type: mapping(uint256 => address)) (numberOfBytes: 32) +lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol:ERC721:_balances (storage_slot: 3) (offset: 0) (type: mapping(address => uint256)) (numberOfBytes: 32) +lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol:ERC721:_tokenApprovals (storage_slot: 4) (offset: 0) (type: mapping(uint256 => address)) (numberOfBytes: 32) +lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol:ERC721:_operatorApprovals (storage_slot: 5) (offset: 0) (type: mapping(address => mapping(address => bool))) (numberOfBytes: 32) \ No newline at end of file diff --git a/logs/storage/MockERC721.sol:MockERC721.log b/logs/storage/MockERC721.sol:MockERC721.log index b189dbef..2b9833e8 100644 --- a/logs/storage/MockERC721.sol:MockERC721.log +++ b/logs/storage/MockERC721.sol:MockERC721.log @@ -1,7 +1,6 @@ -lib/foundry-deployment-kit/lib/forge-std/src/mocks/MockERC721.sol:MockERC721:name (storage_slot: 0) (offset: 0) (type: string) (numberOfBytes: 32) -lib/foundry-deployment-kit/lib/forge-std/src/mocks/MockERC721.sol:MockERC721:symbol (storage_slot: 1) (offset: 0) (type: string) (numberOfBytes: 32) -lib/foundry-deployment-kit/lib/forge-std/src/mocks/MockERC721.sol:MockERC721:_ownerOf (storage_slot: 2) (offset: 0) (type: mapping(uint256 => address)) (numberOfBytes: 32) -lib/foundry-deployment-kit/lib/forge-std/src/mocks/MockERC721.sol:MockERC721:_balanceOf (storage_slot: 3) (offset: 0) (type: mapping(address => uint256)) (numberOfBytes: 32) -lib/foundry-deployment-kit/lib/forge-std/src/mocks/MockERC721.sol:MockERC721:getApproved (storage_slot: 4) (offset: 0) (type: mapping(uint256 => address)) (numberOfBytes: 32) -lib/foundry-deployment-kit/lib/forge-std/src/mocks/MockERC721.sol:MockERC721:isApprovedForAll (storage_slot: 5) (offset: 0) (type: mapping(address => mapping(address => bool))) (numberOfBytes: 32) -lib/foundry-deployment-kit/lib/forge-std/src/mocks/MockERC721.sol:MockERC721:initialized (storage_slot: 6) (offset: 0) (type: bool) (numberOfBytes: 1) \ No newline at end of file +src/mocks/token/MockERC721.sol:MockERC721:_name (storage_slot: 0) (offset: 0) (type: string) (numberOfBytes: 32) +src/mocks/token/MockERC721.sol:MockERC721:_symbol (storage_slot: 1) (offset: 0) (type: string) (numberOfBytes: 32) +src/mocks/token/MockERC721.sol:MockERC721:_owners (storage_slot: 2) (offset: 0) (type: mapping(uint256 => address)) (numberOfBytes: 32) +src/mocks/token/MockERC721.sol:MockERC721:_balances (storage_slot: 3) (offset: 0) (type: mapping(address => uint256)) (numberOfBytes: 32) +src/mocks/token/MockERC721.sol:MockERC721:_tokenApprovals (storage_slot: 4) (offset: 0) (type: mapping(uint256 => address)) (numberOfBytes: 32) +src/mocks/token/MockERC721.sol:MockERC721:_operatorApprovals (storage_slot: 5) (offset: 0) (type: mapping(address => mapping(address => bool))) (numberOfBytes: 32) \ No newline at end of file diff --git a/script/GeneralConfig.sol b/script/GeneralConfig.sol index 0d85a792..2651f1f0 100644 --- a/script/GeneralConfig.sol +++ b/script/GeneralConfig.sol @@ -47,6 +47,7 @@ contract GeneralConfig is BaseGeneralConfig, Utils { _mapContractName(Contract.RoninBridgeManager); _mapContractName(Contract.MainchainGatewayV3); _mapContractName(Contract.MainchainBridgeManager); + _mapContractName(Contract.MockERC721); _contractNameMap[Contract.WETH.key()] = "MockWrappedToken"; _contractNameMap[Contract.WRON.key()] = "MockWrappedToken"; diff --git a/script/Migration.s.sol b/script/Migration.s.sol index c77df0a1..ecec4fa8 100644 --- a/script/Migration.s.sol +++ b/script/Migration.s.sol @@ -43,6 +43,8 @@ contract Migration is BaseMigrationV2, Utils { param.slp.symbol = "SLP"; param.usdc.name = "USD Coin"; param.usdc.symbol = "USDC"; + param.mockErc721.name = "Mock ERC721"; + param.mockErc721.symbol = "M_ERC721"; uint256 num = 6; address[] memory operatorAddrs = new address[](num); diff --git a/script/contracts/token/MockERC721Deploy.s.sol b/script/contracts/token/MockERC721Deploy.s.sol new file mode 100644 index 00000000..f310a1b9 --- /dev/null +++ b/script/contracts/token/MockERC721Deploy.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { MockERC721 } from "@ronin/contracts/mocks/token/MockERC721.sol"; +import { Contract } from "../../utils/Contract.sol"; +import { ISharedArgument } from "../../interfaces/ISharedArgument.sol"; +import { Migration } from "../../Migration.s.sol"; + +contract MockERC721Deploy is Migration { + function _defaultArguments() internal virtual override returns (bytes memory args) { + ISharedArgument.MockERC721Param memory param = config.sharedArguments().mockErc721; + + args = abi.encode(param.name, param.symbol); + } + + function run() public virtual returns (MockERC721) { + return MockERC721(_deployImmutable(Contract.MockERC721.key())); + } +} diff --git a/script/interfaces/ISharedArgument.sol b/script/interfaces/ISharedArgument.sol index 6ae10e68..37971d4d 100644 --- a/script/interfaces/ISharedArgument.sol +++ b/script/interfaces/ISharedArgument.sol @@ -93,6 +93,11 @@ interface ISharedArgument is IGeneralConfig { string symbol; } + struct MockERC721Param { + string name; + string symbol; + } + struct UnitTestParam { address proxyAdmin; uint256 numberOfBlocksInEpoch; @@ -119,6 +124,7 @@ interface ISharedArgument is IGeneralConfig { MockERC20Param axs; MockERC20Param slp; MockERC20Param usdc; + MockERC721Param mockErc721; UnitTestParam test; } diff --git a/script/utils/Contract.sol b/script/utils/Contract.sol index 9578abc9..e94823f6 100644 --- a/script/utils/Contract.sol +++ b/script/utils/Contract.sol @@ -9,6 +9,7 @@ enum Contract { AXS, SLP, USDC, + MockERC721, BridgeTracking, BridgeSlash, BridgeReward, @@ -32,6 +33,7 @@ function name(Contract contractEnum) pure returns (string memory) { if (contractEnum == Contract.AXS) return "AXS"; if (contractEnum == Contract.SLP) return "SLP"; if (contractEnum == Contract.USDC) return "USDC"; + if (contractEnum == Contract.MockERC721) return "MockERC721"; if (contractEnum == Contract.BridgeTracking) return "BridgeTracking"; if (contractEnum == Contract.BridgeSlash) return "BridgeSlash"; diff --git a/src/mocks/token/MockERC721.sol b/src/mocks/token/MockERC721.sol new file mode 100644 index 00000000..4ce9bf29 --- /dev/null +++ b/src/mocks/token/MockERC721.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract MockERC721 is ERC721 { + constructor(string memory name, string memory symbol) ERC721(name, symbol) { } + + function mint(address to, uint256 id) external { + _mint(to, id); + } +} diff --git a/test/bridge/integration/BaseIntegration.t.sol b/test/bridge/integration/BaseIntegration.t.sol index 535bf093..3d34ca72 100644 --- a/test/bridge/integration/BaseIntegration.t.sol +++ b/test/bridge/integration/BaseIntegration.t.sol @@ -17,6 +17,8 @@ import { BridgeReward } from "@ronin/contracts/ronin/gateway/BridgeReward.sol"; import { MainchainGatewayV3 } from "@ronin/contracts/mainchain/MainchainGatewayV3.sol"; import { MainchainBridgeManager } from "@ronin/contracts/mainchain/MainchainBridgeManager.sol"; import { MockERC20 } from "@ronin/contracts/mocks/token/MockERC20.sol"; +import { MockERC721 } from "@ronin/contracts/mocks/token/MockERC721.sol"; + import { MockWrappedToken } from "@ronin/contracts/mocks/token/MockWrappedToken.sol"; import { GlobalProposal } from "@ronin/contracts/libraries/GlobalProposal.sol"; import { Proposal } from "@ronin/contracts/libraries/Proposal.sol"; @@ -50,6 +52,7 @@ import { WRONDeploy } from "@ronin/script/contracts/token/WRONDeploy.s.sol"; import { AXSDeploy } from "@ronin/script/contracts/token/AXSDeploy.s.sol"; import { SLPDeploy } from "@ronin/script/contracts/token/SLPDeploy.s.sol"; import { USDCDeploy } from "@ronin/script/contracts/token/USDCDeploy.s.sol"; +import { MockERC721Deploy } from "@ronin/script/contracts/token/MockERC721Deploy.s.sol"; import { RoninBridgeAdminUtils } from "test/helpers/RoninBridgeAdminUtils.t.sol"; import { MainchainBridgeAdminUtils } from "test/helpers/MainchainBridgeAdminUtils.t.sol"; @@ -74,12 +77,13 @@ contract BaseIntegration_Test is Base_Test { MockERC20 _roninAxs; MockERC20 _roninSlp; MockERC20 _roninUsdc; + MockERC721 _roninMockERC721; MockWrappedToken _mainchainWeth; - MockWrappedToken _mainchainWron; MockERC20 _mainchainAxs; MockERC20 _mainchainSlp; MockERC20 _mainchainUsdc; + MockERC721 _mainchainMockERC721; MockValidatorContract_OnlyTiming_ForHardhatTest _validatorSet; @@ -117,6 +121,7 @@ contract BaseIntegration_Test is Base_Test { _roninAxs = new AXSDeploy().run(); _roninSlp = new SLPDeploy().run(); _roninUsdc = new USDCDeploy().run(); + _roninMockERC721 = new MockERC721Deploy().run(); _param = ISharedArgument(LibSharedAddress.CONFIG).sharedArguments(); _roninProposalUtils = @@ -130,10 +135,10 @@ contract BaseIntegration_Test is Base_Test { _mainchainBridgeManager = new MainchainBridgeManagerDeploy().run(); _mainchainWeth = new WETHDeploy().run(); - _mainchainWron = new WRONDeploy().run(); _mainchainAxs = new AXSDeploy().run(); _mainchainSlp = new SLPDeploy().run(); _mainchainUsdc = new USDCDeploy().run(); + _mainchainMockERC721 = new MockERC721Deploy().run(); _param = ISharedArgument(LibSharedAddress.CONFIG).sharedArguments(); _mainchainProposalUtils = new MainchainBridgeAdminUtils( @@ -160,28 +165,6 @@ contract BaseIntegration_Test is Base_Test { _mainchainGatewayV3Initialize(); } - function _getMainchainAndRoninTokens() - internal - view - returns (address[] memory mainchainTokens, address[] memory roninTokens) - { - uint256 tokenNum = 6; - mainchainTokens = new address[](tokenNum); - roninTokens = new address[](tokenNum); - - mainchainTokens[0] = address(_mainchainWeth); - mainchainTokens[1] = address(_mainchainWron); - mainchainTokens[2] = address(_mainchainAxs); - mainchainTokens[3] = address(_mainchainSlp); - mainchainTokens[4] = address(_mainchainUsdc); - - roninTokens[0] = address(_roninWeth); - roninTokens[1] = address(_roninWron); - roninTokens[2] = address(_roninAxs); - roninTokens[3] = address(_roninSlp); - roninTokens[4] = address(_roninUsdc); - } - function _bridgeRewardInitialize() internal { // Bridge rewards _param.bridgeReward.validatorSetContract = address(_validatorSet); @@ -246,14 +229,16 @@ contract BaseIntegration_Test is Base_Test { function _roninGatewayV3Initialize() internal { (address[] memory mainchainTokens, address[] memory roninTokens) = _getMainchainAndRoninTokens(); - uint256 tokenNum = mainchainTokens.length; + uint256 tokenNum = mainchainTokens.length; // reserve slot for ERC721Tokens uint256[] memory minimumThreshold = new uint256[](tokenNum); uint256[] memory chainIds = new uint256[](tokenNum); Token.Standard[] memory standards = new Token.Standard[](tokenNum); for (uint256 i; i < tokenNum; i++) { - minimumThreshold[i] = 0; + bool isERC721 = i == mainchainTokens.length - 1; // last item is ERC721 + + minimumThreshold[i] = 20; chainIds[i] = block.chainid; - standards[i] = Token.Standard.ERC20; + standards[i] = isERC721 ? Token.Standard.ERC721 : Token.Standard.ERC20; } // Ronin Gateway V3 @@ -447,20 +432,22 @@ contract BaseIntegration_Test is Base_Test { uint256[] memory lockedThreshold = new uint256[](tokenNum); uint256[] memory unlockFeePercentages = new uint256[](tokenNum); uint256[] memory dailyWithdrawalLimits = new uint256[](tokenNum); - - highTierThreshold[0] = 10; - lockedThreshold[0] = 20; - unlockFeePercentages[0] = 100_000; - dailyWithdrawalLimits[0] = 12; - Token.Standard[] memory standards = new Token.Standard[](tokenNum); + for (uint256 i; i < tokenNum; i++) { - standards[i] = Token.Standard.ERC20; + bool isERC721 = i == mainchainTokens.length - 1; // last item is ERC721 + + highTierThreshold[i] = 10; + lockedThreshold[i] = 20; + unlockFeePercentages[i] = 100_000; + dailyWithdrawalLimits[i] = 12; + standards[i] = isERC721 ? Token.Standard.ERC721 : Token.Standard.ERC20; } // Mainchain Gateway V3 - _param.mainchainGatewayV3.addresses[0] = mainchainTokens; - _param.mainchainGatewayV3.addresses[1] = roninTokens; + _param.mainchainGatewayV3.wrappedToken = address(_mainchainWeth); + _param.mainchainGatewayV3.addresses[0] = mainchainTokens; // (ERC20 + ERC721) + _param.mainchainGatewayV3.addresses[1] = roninTokens; // (ERC20 + ERC721) _param.mainchainGatewayV3.addresses[2] = getEmptyAddressArray(); _param.mainchainGatewayV3.thresholds[0] = highTierThreshold; _param.mainchainGatewayV3.thresholds[1] = lockedThreshold; @@ -493,6 +480,28 @@ contract BaseIntegration_Test is Base_Test { _mainchainPauseEnforcer.initialize(IPauseTarget(param.target), param.admin, param.sentries); } + function _getMainchainAndRoninTokens() + internal + view + returns (address[] memory mainchainTokens, address[] memory roninTokens) + { + uint256 tokenNum = 5; + mainchainTokens = new address[](tokenNum); + roninTokens = new address[](tokenNum); + + mainchainTokens[0] = address(_mainchainWeth); + mainchainTokens[1] = address(_mainchainAxs); + mainchainTokens[2] = address(_mainchainSlp); + mainchainTokens[3] = address(_mainchainUsdc); + mainchainTokens[4] = address(_mainchainMockERC721); + + roninTokens[0] = address(_roninWeth); + roninTokens[1] = address(_roninAxs); + roninTokens[2] = address(_roninSlp); + roninTokens[3] = address(_roninUsdc); + roninTokens[4] = address(_roninMockERC721); + } + function _changeAdminOnRonin() internal { vm.startPrank(_param.test.proxyAdmin); TransparentUpgradeableProxyV2(payable(address(_roninGatewayV3))).changeAdmin(address(_roninBridgeManager)); @@ -557,6 +566,4 @@ contract BaseIntegration_Test is Base_Test { vm.warp(nextDayTimestamp); vm.roll(epochEndingBlockNumber); } - - function test_setUpIntegration() public { } } diff --git a/test/bridge/integration/bridge-manager/propose-and-cast-vote/voteBridgeOperator.RoninBridgeManager.t.sol b/test/bridge/integration/bridge-manager/propose-and-cast-vote/voteBridgeOperator.RoninBridgeManager.t.sol index 63d56e58..5d45e546 100644 --- a/test/bridge/integration/bridge-manager/propose-and-cast-vote/voteBridgeOperator.RoninBridgeManager.t.sol +++ b/test/bridge/integration/bridge-manager/propose-and-cast-vote/voteBridgeOperator.RoninBridgeManager.t.sol @@ -54,6 +54,7 @@ contract VoteBridgeOperator_RoninBridgeManager_Test is BaseIntegration_Test { _generateAddingOperators(_addingOperatorNum); } + // Should be able to vote bridge operators function test_voteAddBridgeOperatorsProposal() public { _globalProposal = _roninProposalUtils.createGlobalProposal({ expiryTimestamp: block.timestamp + _proposalExpiryDuration, @@ -83,6 +84,7 @@ contract VoteBridgeOperator_RoninBridgeManager_Test is BaseIntegration_Test { assertEq(_roninBridgeManager.getBridgeOperators(), _afterRelayedOperators); } + // Should be able relay the vote of bridge operators function test_relayAddBridgeOperator() public { test_voteAddBridgeOperatorsProposal(); @@ -98,6 +100,7 @@ contract VoteBridgeOperator_RoninBridgeManager_Test is BaseIntegration_Test { assertEq(_mainchainBridgeManager.getBridgeOperators(), _afterRelayedOperators); } + // Should not able to relay again function test_RevertWhen_RelayAgain() public { test_relayAddBridgeOperator(); @@ -109,6 +112,7 @@ contract VoteBridgeOperator_RoninBridgeManager_Test is BaseIntegration_Test { _mainchainBridgeManager.relayGlobalProposal(_globalProposal, _supports, _signatures); } + // Should be able to vote for a larger number of bridge operators function test_voteForLargeNumberOfOperators(uint256 seed) public { uint256 numAddingOperators = seed % 10 + 10; _generateAddingOperators(numAddingOperators); @@ -141,6 +145,7 @@ contract VoteBridgeOperator_RoninBridgeManager_Test is BaseIntegration_Test { assertEq(_roninBridgeManager.getBridgeOperators(), _afterRelayedOperators); } + // Should the approved proposal can be relayed on mainchain (even when the time of expiry is passed) function test_relayExpiredProposal() public { test_voteAddBridgeOperatorsProposal(); diff --git a/test/bridge/integration/mainchain-gateway/requestDepositFor.MainchainGatewayV3.t.sol b/test/bridge/integration/mainchain-gateway/requestDepositFor.MainchainGatewayV3.t.sol new file mode 100644 index 00000000..3245b550 --- /dev/null +++ b/test/bridge/integration/mainchain-gateway/requestDepositFor.MainchainGatewayV3.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 as console } from "forge-std/console2.sol"; +import { Transfer as LibTransfer } from "@ronin/contracts/libraries/Transfer.sol"; +import { Token } from "@ronin/contracts/libraries/Token.sol"; + +import "../BaseIntegration.t.sol"; + +interface IERC721 { + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); +} + +interface IERC20 { + event Transfer(address indexed from, address indexed to, uint256 value); +} + +contract RequestDepositFor_MainchainGatewayV3_Test is BaseIntegration_Test { + event DepositRequested(bytes32 receiptHash, LibTransfer.Receipt receipt); + + using LibTransfer for LibTransfer.Request; + using LibTransfer for LibTransfer.Receipt; + + LibTransfer.Request _depositRequest; + + address _sender; + uint256 _quantity; + + function setUp() public virtual override { + super.setUp(); + + _sender = makeAddr("sender"); + _quantity = 10; + + _depositRequest.recipientAddr = makeAddr("recipient"); + _depositRequest.tokenAddr = address(0); + _depositRequest.info.erc = Token.Standard.ERC20; + _depositRequest.info.id = 0; + _depositRequest.info.quantity = _quantity; + + vm.deal(_sender, 10 ether); + } + + // test deposit > should be able to deposit eth + function test_depositNative() public { + _depositRequest.tokenAddr = address(0); + + LibTransfer.Request memory cachedRequest = _depositRequest; + cachedRequest.tokenAddr = address(_mainchainWeth); + + vm.expectEmit(address(_mainchainGatewayV3)); + LibTransfer.Receipt memory receipt = cachedRequest.into_deposit_receipt( + _sender, _mainchainGatewayV3.depositCount(), address(_roninWeth), block.chainid + ); + emit DepositRequested(receipt.hash(), receipt); + + vm.prank(_sender); + _mainchainGatewayV3.requestDepositFor{ value: _quantity }(_depositRequest); + + assertEq(address(_mainchainGatewayV3).balance, _quantity); + assertEq(_mainchainGatewayV3.depositCount(), 1); + } + + // test deposit > should be able to deposit ERC20 + function test_depositERC20() public { + _mainchainAxs.mint(_sender, _quantity); + vm.prank(_sender); + _mainchainAxs.approve(address(_mainchainGatewayV3), _quantity); + + _depositRequest.tokenAddr = address(_mainchainAxs); + + vm.expectEmit(address(_mainchainGatewayV3)); + LibTransfer.Receipt memory receipt = _depositRequest.into_deposit_receipt( + _sender, _mainchainGatewayV3.depositCount(), address(_roninAxs), block.chainid + ); + emit DepositRequested(receipt.hash(), receipt); + + vm.prank(_sender); + _mainchainGatewayV3.requestDepositFor(_depositRequest); + + assertEq(_mainchainAxs.balanceOf(address(_mainchainGatewayV3)), _quantity); + assertEq(_mainchainGatewayV3.depositCount(), 1); + } + + // test deposit > should be able to deposit weth and gateway receive eth + function test_depositERC721() public { + uint256 tokenId = 22; + _mainchainMockERC721.mint(_sender, tokenId); + vm.prank(_sender); + _mainchainMockERC721.approve(address(_mainchainGatewayV3), tokenId); + + _depositRequest.tokenAddr = address(_mainchainMockERC721); + _depositRequest.info.erc = Token.Standard.ERC721; + _depositRequest.info.id = tokenId; + _depositRequest.info.quantity = 0; + + LibTransfer.Receipt memory receipt = _depositRequest.into_deposit_receipt( + _sender, _mainchainGatewayV3.depositCount(), address(_roninMockERC721), block.chainid + ); + vm.expectEmit(address(_mainchainGatewayV3)); + emit DepositRequested(receipt.hash(), receipt); + + assertEq(_mainchainMockERC721.ownerOf(tokenId), _sender); + + vm.prank(_sender); + _mainchainGatewayV3.requestDepositFor(_depositRequest); + + assertEq(_mainchainMockERC721.ownerOf(tokenId), address(_mainchainGatewayV3)); + assertEq(_mainchainGatewayV3.depositCount(), 1); + } +} diff --git a/test/bridge/integration/mainchain-gateway/submit-withdrawal/submitWithdrawal.MainchainGatewayV3.t.sol b/test/bridge/integration/mainchain-gateway/submit-withdrawal/submitWithdrawal.MainchainGatewayV3.t.sol deleted file mode 100644 index eb60743e..00000000 --- a/test/bridge/integration/mainchain-gateway/submit-withdrawal/submitWithdrawal.MainchainGatewayV3.t.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import { console2 as console } from "forge-std/console2.sol"; -import { ContractType } from "@ronin/contracts/utils/ContractType.sol"; -import { Transfer } from "@ronin/contracts/libraries/Transfer.sol"; -import { Token } from "@ronin/contracts/libraries/Token.sol"; -import { SignatureConsumer } from "@ronin/contracts/interfaces/consumers/SignatureConsumer.sol"; -import "../../BaseIntegration.t.sol"; - -contract SubmitWithdrawal_MainchainGatewayV3_Test is BaseIntegration_Test { - using Transfer for Transfer.Receipt; - - Transfer.Receipt _withdrawalReceipt; - bytes32 _domainSeparator; - - function setUp() public virtual override { - super.setUp(); - - _domainSeparator = _mainchainGatewayV3.DOMAIN_SEPARATOR(); - - _withdrawalReceipt.id = 0; - _withdrawalReceipt.kind = Transfer.Kind.Withdrawal; - _withdrawalReceipt.ronin.addr = makeAddr("requester"); - _withdrawalReceipt.ronin.tokenAddr = address(_roninWeth); - _withdrawalReceipt.ronin.chainId = block.chainid; - _withdrawalReceipt.mainchain.addr = makeAddr("recipient"); - _withdrawalReceipt.mainchain.tokenAddr = address(_mainchainWeth); - _withdrawalReceipt.mainchain.chainId = block.chainid; - _withdrawalReceipt.info.erc = Token.Standard.ERC20; - _withdrawalReceipt.info.id = 0; - _withdrawalReceipt.info.quantity = 0; - - vm.deal(address(_mainchainGatewayV3), 10 ether); - vm.prank(address(_mainchainGatewayV3)); - _mainchainWeth.deposit{ value: 10 ether }(); - } - - function test_submitWithdrawal() public { - _withdrawalReceipt.info.quantity = 10; - - SignatureConsumer.Signature[] memory signatures = - _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs); - - _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); - } - - function _generateSignaturesFor(Transfer.Receipt memory receipt, uint256[] memory signerPKs) - internal - view - returns (SignatureConsumer.Signature[] memory sigs) - { - sigs = new SignatureConsumer.Signature[](signerPKs.length); - - for (uint256 i; i < signerPKs.length; i++) { - bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _domainSeparator, receipt.hash())); - - sigs[i] = _sign(signerPKs[i], digest); - } - } - - function _sign(uint256 pk, bytes32 digest) internal pure returns (SignatureConsumer.Signature memory sig) { - (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); - sig.v = v; - sig.r = r; - sig.s = s; - } -} diff --git a/test/bridge/integration/mainchain-gateway/submitWithdrawal.MainchainGatewayV3.t.sol b/test/bridge/integration/mainchain-gateway/submitWithdrawal.MainchainGatewayV3.t.sol new file mode 100644 index 00000000..fd18b7db --- /dev/null +++ b/test/bridge/integration/mainchain-gateway/submitWithdrawal.MainchainGatewayV3.t.sol @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 as console } from "forge-std/console2.sol"; +import { Transfer as LibTransfer } from "@ronin/contracts/libraries/Transfer.sol"; +import { Token } from "@ronin/contracts/libraries/Token.sol"; +import { SignatureConsumer } from "@ronin/contracts/interfaces/consumers/SignatureConsumer.sol"; +import { IMainchainGatewayV3 } from "@ronin/contracts/interfaces/IMainchainGatewayV3.sol"; +import "../BaseIntegration.t.sol"; + +interface IERC721 { + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); +} + +interface IERC20 { + event Transfer(address indexed from, address indexed to, uint256 value); +} + +contract SubmitWithdrawal_MainchainGatewayV3_Test is BaseIntegration_Test { + event Withdrew(bytes32 receiptHash, LibTransfer.Receipt receipt); + + error ErrInvalidOrder(bytes4); + error ErrQueryForApprovedWithdrawal(); + error ErrReachedDailyWithdrawalLimit(); + error ErrQueryForProcessedWithdrawal(); + error ErrQueryForInsufficientVoteWeight(); + + using LibTransfer for LibTransfer.Receipt; + + LibTransfer.Receipt _withdrawalReceipt; + bytes32 _domainSeparator; + + function setUp() public virtual override { + super.setUp(); + + _domainSeparator = _mainchainGatewayV3.DOMAIN_SEPARATOR(); + + _withdrawalReceipt.id = 0; + _withdrawalReceipt.kind = LibTransfer.Kind.Withdrawal; + _withdrawalReceipt.ronin.addr = makeAddr("requester"); + _withdrawalReceipt.ronin.tokenAddr = address(_roninWeth); + _withdrawalReceipt.ronin.chainId = block.chainid; + _withdrawalReceipt.mainchain.addr = makeAddr("recipient"); + _withdrawalReceipt.mainchain.tokenAddr = address(_mainchainWeth); + _withdrawalReceipt.mainchain.chainId = block.chainid; + _withdrawalReceipt.info.erc = Token.Standard.ERC20; + _withdrawalReceipt.info.id = 0; + _withdrawalReceipt.info.quantity = 10; + + vm.deal(address(_mainchainGatewayV3), 10 ether); + } + + // test withdrawal > should not be able to withdraw without enough signature + function test_RevertWhen_NotEnoughSignatures() public { + SignatureConsumer.Signature[] memory signatures = + _generateSignaturesFor(_withdrawalReceipt, wrapUint(_param.test.operatorPKs[0])); + + vm.expectRevert(ErrQueryForInsufficientVoteWeight.selector); + + _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); + } + + // test withdrawal > should not be able to withdraw eth with wrong order of signatures + function test_RevertWhen_InvalidOrderSignatures() public { + require(_param.test.operatorPKs.length > 1, "Amounts of operators too small"); + + // swap order of signatures (operator 0 <-> operator 1) + uint256 tempPK = _param.test.operatorPKs[0]; + _param.test.operatorPKs[0] = _param.test.operatorPKs[1]; + _param.test.operatorPKs[1] = tempPK; + + SignatureConsumer.Signature[] memory signatures = + _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs); + + vm.expectRevert(abi.encodeWithSelector(ErrInvalidOrder.selector, IMainchainGatewayV3.submitWithdrawal.selector)); + + _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); + } + + // test withdrawal > should be able to withdraw eth + function test_WithdrawNative_OnMainchain() public { + SignatureConsumer.Signature[] memory signatures = + _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs); + + uint256 balanceBefore = _withdrawalReceipt.mainchain.addr.balance; + + vm.expectEmit(address(_mainchainGatewayV3)); + emit Withdrew(_withdrawalReceipt.hash(), _withdrawalReceipt); + + _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); + + uint256 balanceAfter = _withdrawalReceipt.mainchain.addr.balance; + assertEq(balanceAfter, balanceBefore + _withdrawalReceipt.info.quantity); + } + + // test withdrawal > should not able to withdraw with same withdrawalId + function test_RevertWhen_WithdrawWithSameId() public { + test_WithdrawNative_OnMainchain(); + + SignatureConsumer.Signature[] memory signatures = + _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs); + + vm.expectRevert(ErrQueryForProcessedWithdrawal.selector); + + _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); + } + + // test withdrawal > should be able to withdraw for self + function test_WithdrawForSelf() public { + address sender = makeAddr("sender"); + _withdrawalReceipt.mainchain.addr = sender; + + SignatureConsumer.Signature[] memory signatures = + _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs); + + vm.expectEmit(address(_mainchainGatewayV3)); + emit Withdrew(_withdrawalReceipt.hash(), _withdrawalReceipt); + + vm.prank(sender); + _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); + + assertEq(sender.balance, _withdrawalReceipt.info.quantity); + } + + // test withdrawal > should be able to withdraw locked erc20 + function test_WithdrawLockedERC20() public { + address recipient = _withdrawalReceipt.mainchain.addr; + uint256 quantity = _withdrawalReceipt.info.quantity; + + _mainchainAxs.mint(address(_mainchainGatewayV3), quantity); + + _withdrawalReceipt.mainchain.tokenAddr = address(_mainchainAxs); + _withdrawalReceipt.ronin.tokenAddr = address(_roninAxs); + + SignatureConsumer.Signature[] memory signatures = + _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs); + + vm.expectEmit(address(_mainchainAxs)); + emit IERC20.Transfer(address(_mainchainGatewayV3), recipient, quantity); + vm.expectEmit(address(_mainchainGatewayV3)); + emit Withdrew(_withdrawalReceipt.hash(), _withdrawalReceipt); + + uint256 balanceBefore = _mainchainAxs.balanceOf(recipient); + + _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); + + uint256 balanceAfter = _mainchainAxs.balanceOf(recipient); + + assertEq(balanceAfter, balanceBefore + quantity); + } + + // test withdraw > should be able to mint new erc20 token when withdrawing + function test_MintTokenWhileWithdrawing() public { + address recipient = _withdrawalReceipt.mainchain.addr; + uint256 quantity = _withdrawalReceipt.info.quantity; + + uint256 balanceBefore = _mainchainSlp.balanceOf(recipient); + _withdrawalReceipt.mainchain.tokenAddr = address(_mainchainSlp); + _withdrawalReceipt.ronin.tokenAddr = address(_roninSlp); + + SignatureConsumer.Signature[] memory signatures = + _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs); + + vm.expectEmit(address(_mainchainSlp)); + emit IERC20.Transfer(address(0), address(_mainchainGatewayV3), quantity); + + vm.expectEmit(address(_mainchainSlp)); + emit IERC20.Transfer(address(_mainchainGatewayV3), recipient, quantity); + + vm.expectEmit(address(_mainchainGatewayV3)); + emit Withdrew(_withdrawalReceipt.hash(), _withdrawalReceipt); + + _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); + + uint256 balanceAfter = _mainchainSlp.balanceOf(recipient); + + assertEq(balanceAfter, balanceBefore + quantity); + } + + // test withdraw > should be able to withdraw locked erc721 + function test_WithdrawERC721Token() public { + address recipient = _withdrawalReceipt.mainchain.addr; + + uint256 tokenId = 22; + _mainchainMockERC721.mint(address(_mainchainGatewayV3), tokenId); + + _withdrawalReceipt.mainchain.tokenAddr = address(_mainchainMockERC721); + _withdrawalReceipt.ronin.tokenAddr = address(_roninMockERC721); + _withdrawalReceipt.info.id = tokenId; + _withdrawalReceipt.info.erc = Token.Standard.ERC721; + _withdrawalReceipt.info.quantity = 0; + + SignatureConsumer.Signature[] memory signatures = + _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs); + + vm.expectEmit(address(_mainchainMockERC721)); + emit IERC721.Transfer(address(_mainchainGatewayV3), recipient, tokenId); + + vm.expectEmit(address(_mainchainGatewayV3)); + emit Withdrew(_withdrawalReceipt.hash(), _withdrawalReceipt); + + assertEq(_mainchainMockERC721.ownerOf(tokenId), address(_mainchainGatewayV3)); + + _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); + + assertEq(_mainchainMockERC721.ownerOf(tokenId), recipient); + } + + // test withdraw > should be able to mint new erc721 when withdrawing + function test_MintERC721TokenWhileWithdrawing() public { + address recipient = _withdrawalReceipt.mainchain.addr; + + uint256 tokenId = 22; + + _withdrawalReceipt.mainchain.tokenAddr = address(_mainchainMockERC721); + _withdrawalReceipt.ronin.tokenAddr = address(_roninMockERC721); + _withdrawalReceipt.info.id = tokenId; + _withdrawalReceipt.info.erc = Token.Standard.ERC721; + _withdrawalReceipt.info.quantity = 0; + + SignatureConsumer.Signature[] memory signatures = + _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs); + + vm.expectEmit(address(_mainchainMockERC721)); + emit IERC721.Transfer(address(0), recipient, tokenId); + + vm.expectEmit(address(_mainchainGatewayV3)); + emit Withdrew(_withdrawalReceipt.hash(), _withdrawalReceipt); + + _mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures); + + assertEq(_mainchainMockERC721.ownerOf(tokenId), recipient); + } + + function _generateSignaturesFor(LibTransfer.Receipt memory receipt, uint256[] memory signerPKs) + internal + view + returns (SignatureConsumer.Signature[] memory sigs) + { + sigs = new SignatureConsumer.Signature[](signerPKs.length); + + for (uint256 i; i < signerPKs.length; i++) { + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _domainSeparator, receipt.hash())); + + sigs[i] = _sign(signerPKs[i], digest); + } + } + + function _sign(uint256 pk, bytes32 digest) internal pure returns (SignatureConsumer.Signature memory sig) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); + sig.v = v; + sig.r = r; + sig.s = s; + } +} diff --git a/test/bridge/integration/pause-enforcer/accessControl.PauseEnforcer.t.sol b/test/bridge/integration/pause-enforcer/accessControl.PauseEnforcer.t.sol index ec307524..d4d6dbd9 100644 --- a/test/bridge/integration/pause-enforcer/accessControl.PauseEnforcer.t.sol +++ b/test/bridge/integration/pause-enforcer/accessControl.PauseEnforcer.t.sol @@ -8,6 +8,7 @@ contract AccessControl_PauseEnforcer_Test is BaseIntegration_Test { super.setUp(); } + // Access control > Should admin of pause enforcer can be change function test_changeAdmin_OfPauseEnforcer() public { address newEnforcerAdmin = makeAddr("new-enforcer-admin"); @@ -17,6 +18,7 @@ contract AccessControl_PauseEnforcer_Test is BaseIntegration_Test { assertEq(_roninPauseEnforcer.hasRole(0x0, newEnforcerAdmin), true); } + // Access control > Should previous admin of pause enforcer can be revoked function test_renounceAdminRole_PreviousAdmin() public { test_changeAdmin_OfPauseEnforcer(); diff --git a/test/bridge/integration/pause-enforcer/emergencyAction.PauseEnforcer.t.sol b/test/bridge/integration/pause-enforcer/emergencyAction.PauseEnforcer.t.sol index d161dc78..fec87e77 100644 --- a/test/bridge/integration/pause-enforcer/emergencyAction.PauseEnforcer.t.sol +++ b/test/bridge/integration/pause-enforcer/emergencyAction.PauseEnforcer.t.sol @@ -12,7 +12,7 @@ contract EmergencyAction_PauseEnforcer_Test is BaseIntegration_Test { super.setUp(); } - // Should be able to emergency pause + // Emergency pause & emergency unpause > Should be able to emergency pause function test_EmergencyPause_RoninGatewayV3() public { vm.prank(_param.roninPauseEnforcer.sentries[0]); _roninPauseEnforcer.triggerPause(); @@ -21,7 +21,7 @@ contract EmergencyAction_PauseEnforcer_Test is BaseIntegration_Test { assertEq(_roninGatewayV3.paused(), true); } - // Should the gateway cannot interacted when on pause + // Emergency pause & emergency unpause > Should the gateway cannot interacted when on pause function test_RevertWhen_InteractWithGateway_AfterPause() public { test_EmergencyPause_RoninGatewayV3(); Transfer.Receipt memory receipt = Transfer.Receipt({ @@ -37,7 +37,7 @@ contract EmergencyAction_PauseEnforcer_Test is BaseIntegration_Test { _roninGatewayV3.depositFor(receipt); } - // Should not be able to emergency pause for a second time + // Emergency pause & emergency unpause > Should not be able to emergency pause for a second time function test_RevertWhen_PauseAgain() public { test_EmergencyPause_RoninGatewayV3(); @@ -47,7 +47,7 @@ contract EmergencyAction_PauseEnforcer_Test is BaseIntegration_Test { _roninPauseEnforcer.triggerPause(); } - // Should be able to emergency unpause + // Emergency pause & emergency unpause > Should be able to emergency unpause function test_EmergencyUnpause_RoninGatewayV3() public { test_EmergencyPause_RoninGatewayV3(); @@ -58,7 +58,7 @@ contract EmergencyAction_PauseEnforcer_Test is BaseIntegration_Test { assertEq(_roninGatewayV3.paused(), false); } - // Should the gateway can be interacted after unpause + // Emergency pause & emergency unpause > Should the gateway can be interacted after unpause function test_InteractWithGateway_AfterUnpause() public { test_EmergencyUnpause_RoninGatewayV3(); Transfer.Receipt memory receipt = Transfer.Receipt({ diff --git a/test/bridge/integration/pause-enforcer/normalPause.GatewayV3.t.sol b/test/bridge/integration/pause-enforcer/normalPause.GatewayV3.t.sol index 2f4d7192..c3ce75a1 100644 --- a/test/bridge/integration/pause-enforcer/normalPause.GatewayV3.t.sol +++ b/test/bridge/integration/pause-enforcer/normalPause.GatewayV3.t.sol @@ -13,7 +13,7 @@ contract NormalPause_GatewayV3_Test is BaseIntegration_Test { super.setUp(); } - // Should gateway admin can pause the gateway through voting + // Normal pause & emergency unpause > Should gateway admin can pause the gateway through voting function test_GovernanceAdmin_PauseGateway_ThroughoutVoting() public { bytes memory calldata_ = abi.encodeCall(GatewayV3.pause, ()); _roninProposalUtils.functionDelegateCall(address(_roninGatewayV3), calldata_); @@ -22,7 +22,7 @@ contract NormalPause_GatewayV3_Test is BaseIntegration_Test { assertEq(_roninGatewayV3.paused(), true); } - // Should not be able to emergency unpause + // Normal pause & emergency unpause > Should not be able to emergency unpause function test_RevertWhen_EmergencyUnpause() public { test_GovernanceAdmin_PauseGateway_ThroughoutVoting(); @@ -32,7 +32,7 @@ contract NormalPause_GatewayV3_Test is BaseIntegration_Test { _roninPauseEnforcer.triggerUnpause(); } - // Should not be able to override by emergency pause and emergency unpause + // Normal pause & emergency unpause > Should not be able to override by emergency pause and emergency unpause function test_RevertWhen_OverrideByEmergencyPauseOrUnPause() public { test_GovernanceAdmin_PauseGateway_ThroughoutVoting(); @@ -47,7 +47,7 @@ contract NormalPause_GatewayV3_Test is BaseIntegration_Test { _roninPauseEnforcer.triggerUnpause(); } - // Should gateway admin can unpause the gateway through voting + // Normal pause & emergency unpause > Should gateway admin can unpause the gateway through voting function test_GovernanceAdmin_UnPauseGateway_ThroughoutVoting() public { test_GovernanceAdmin_PauseGateway_ThroughoutVoting();