From 85635da922f4ec71d033070048947c78ecc765be Mon Sep 17 00:00:00 2001 From: huyhuynh3103 Date: Sun, 28 Jan 2024 13:02:41 +0700 Subject: [PATCH] test(PauseEnforcer): done test --- test/bridge/integration/BaseIntegration.t.sol | 40 +++---- .../accessControl.PauseEnforcer.t.sol | 31 +++++ .../emergencyAction.PauseEnforcer.t.sol | 88 ++++++++++++++ .../set-config/normalPause.GatewayV3.t.sol | 61 ++++++++++ .../depositVote.RoninGatewayV3.t.sol | 4 - test/helpers/MainchainBridgeAdminUtils.t.sol | 113 ++++++++++++++++++ test/helpers/ProposalUtils.t.sol | 2 +- 7 files changed, 314 insertions(+), 25 deletions(-) create mode 100644 test/bridge/integration/pause-enforcer/set-config/accessControl.PauseEnforcer.t.sol create mode 100644 test/bridge/integration/pause-enforcer/set-config/emergencyAction.PauseEnforcer.t.sol create mode 100644 test/bridge/integration/pause-enforcer/set-config/normalPause.GatewayV3.t.sol create mode 100644 test/helpers/MainchainBridgeAdminUtils.t.sol diff --git a/test/bridge/integration/BaseIntegration.t.sol b/test/bridge/integration/BaseIntegration.t.sol index 9737356e..47b1495d 100644 --- a/test/bridge/integration/BaseIntegration.t.sol +++ b/test/bridge/integration/BaseIntegration.t.sol @@ -51,8 +51,8 @@ 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 { ProposalUtils } from "test/helpers/ProposalUtils.t.sol"; import { RoninBridgeAdminUtils } from "test/helpers/RoninBridgeAdminUtils.t.sol"; +import { MainchainBridgeAdminUtils } from "test/helpers/MainchainBridgeAdminUtils.t.sol"; contract BaseIntegration_Test is Base_Test { IGeneralConfig _config; @@ -84,7 +84,7 @@ contract BaseIntegration_Test is Base_Test { MockValidatorContract_OnlyTiming_ForHardhatTest _validatorSet; RoninBridgeAdminUtils _roninProposalUtils; - ProposalUtils _mainchainProposalUtils; + MainchainBridgeAdminUtils _mainchainProposalUtils; function setUp() public virtual { _deployGeneralConfig(); @@ -100,6 +100,8 @@ contract BaseIntegration_Test is Base_Test { _configEmergencyPauserForRoninGateway(); _configEmergencyPauserForMainchainGateway(); + + _configBridgeTrackingForRoninGateway(); } function _deployContractsOnRonin() internal { @@ -141,7 +143,12 @@ contract BaseIntegration_Test is Base_Test { _mainchainUsdc = new USDCDeploy().run(); _param = ISharedArgument(LibSharedAddress.CONFIG).sharedArguments(); - _mainchainProposalUtils = new ProposalUtils(_param.test.roninChainId, _param.test.governorPKs); + _mainchainProposalUtils = new MainchainBridgeAdminUtils( + _param.test.roninChainId, + _param.test.governorPKs, + _mainchainBridgeManager, + _param.mainchainBridgeManager.governors[0] + ); } function _initializeRonin() internal { @@ -528,24 +535,17 @@ contract BaseIntegration_Test is Base_Test { _config.switchTo(Network.EthLocal.key()); bytes memory calldata_ = abi.encodeCall(GatewayV3.setEmergencyPauser, (address(_mainchainPauseEnforcer))); - Proposal.ProposalDetail memory proposal = _mainchainProposalUtils.createProposal({ - expiryTimestamp: block.timestamp + 1 minutes, - target: address(_mainchainGatewayV3), - value: 0, - calldata_: calldata_, - gasAmount: 1_000_000, - nonce: _mainchainBridgeManager.round(_param.test.mainchainChainId) + 1 - }); - - SignatureConsumer.Signature[] memory signatures = _mainchainProposalUtils.generateSignatures(proposal); - - Ballot.VoteType[] memory voteTypes = new Ballot.VoteType[](signatures.length); - for (uint256 i; i < signatures.length; i++) { - voteTypes[i] = Ballot.VoteType.For; - } + _mainchainProposalUtils.functionDelegateCall(address(_mainchainGatewayV3), calldata_); + } - vm.prank(_param.mainchainBridgeManager.governors[0]); - _mainchainBridgeManager.relayProposal(proposal, voteTypes, signatures); + function _configBridgeTrackingForRoninGateway() internal { + _config.switchTo(Network.RoninLocal.key()); + + bytes memory calldata_ = + abi.encodeCall(IHasContracts.setContract, (ContractType.BRIDGE_TRACKING, address(_bridgeTracking))); + _roninProposalUtils.functionDelegateCall(address(_roninGatewayV3), calldata_); + + _config.switchTo(Network.EthLocal.key()); } function _deployGeneralConfig() internal { diff --git a/test/bridge/integration/pause-enforcer/set-config/accessControl.PauseEnforcer.t.sol b/test/bridge/integration/pause-enforcer/set-config/accessControl.PauseEnforcer.t.sol new file mode 100644 index 00000000..250529ba --- /dev/null +++ b/test/bridge/integration/pause-enforcer/set-config/accessControl.PauseEnforcer.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "../../BaseIntegration.t.sol"; + +contract AccessControl_PauseEnforcer_Test is BaseIntegration_Test { + function setUp() public virtual override { + super.setUp(); + _config.switchTo(Network.RoninLocal.key()); + } + + function test_changeAdmin_OfPauseEnforcer() public { + address newEnforcerAdmin = makeAddr("new-enforcer-admin"); + + vm.prank(_param.roninPauseEnforcer.admin); + _roninPauseEnforcer.grantRole(0x0, newEnforcerAdmin); + + assertEq(_roninPauseEnforcer.hasRole(0x0, newEnforcerAdmin), true); + } + + function test_renounceAdminRole_PreviousAdmin() public { + test_changeAdmin_OfPauseEnforcer(); + + assertEq(_roninPauseEnforcer.hasRole(0x0, _param.roninPauseEnforcer.admin), true); + + vm.prank(_param.roninPauseEnforcer.admin); + _roninPauseEnforcer.renounceRole(0x0, _param.roninPauseEnforcer.admin); + + assertEq(_roninPauseEnforcer.hasRole(0x0, _param.roninPauseEnforcer.admin), false); + } +} diff --git a/test/bridge/integration/pause-enforcer/set-config/emergencyAction.PauseEnforcer.t.sol b/test/bridge/integration/pause-enforcer/set-config/emergencyAction.PauseEnforcer.t.sol new file mode 100644 index 00000000..04126e76 --- /dev/null +++ b/test/bridge/integration/pause-enforcer/set-config/emergencyAction.PauseEnforcer.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Transfer } from "@ronin/contracts/libraries/Transfer.sol"; +import { GatewayV3 } from "@ronin/contracts/extensions/GatewayV3.sol"; +import "../../BaseIntegration.t.sol"; + +contract EmergencyAction_PauseEnforcer_Test is BaseIntegration_Test { + error ErrTargetIsNotOnPaused(); + + function setUp() public virtual override { + super.setUp(); + } + + // Should be able to emergency pause + function test_EmergencyPause_RoninGatewayV3() public { + _config.switchTo(Network.RoninLocal.key()); + vm.prank(_param.roninPauseEnforcer.sentries[0]); + _roninPauseEnforcer.triggerPause(); + + assertEq(_roninPauseEnforcer.emergency(), true); + assertEq(_roninGatewayV3.paused(), true); + } + + // Should the gateway cannot interacted when on pause + function test_RevertWhen_InteractWithGateway_AfterPause() public { + test_EmergencyPause_RoninGatewayV3(); + Transfer.Receipt memory receipt = Transfer.Receipt({ + id: 0, + kind: Transfer.Kind.Deposit, + ronin: Token.Owner({ addr: makeAddr("recipient"), tokenAddr: address(_roninWeth), chainId: _param.test.roninChainId }), + mainchain: Token.Owner({ + addr: makeAddr("requester"), + tokenAddr: address(_mainchainWeth), + chainId: _param.test.mainchainChainId + }), + info: Token.Info({ erc: Token.Standard.ERC20, id: 0, quantity: 100 }) + }); + + vm.expectRevert("Pausable: paused"); + + _roninGatewayV3.depositFor(receipt); + } + + // Should not be able to emergency pause for a second time + function test_RevertWhen_PauseAgain() public { + test_EmergencyPause_RoninGatewayV3(); + + vm.expectRevert(ErrTargetIsNotOnPaused.selector); + + vm.prank(_param.roninPauseEnforcer.sentries[0]); + _roninPauseEnforcer.triggerPause(); + } + + // Should be able to emergency unpause + function test_EmergencyUnpause_RoninGatewayV3() public { + test_EmergencyPause_RoninGatewayV3(); + + vm.prank(_param.roninPauseEnforcer.sentries[0]); + _roninPauseEnforcer.triggerUnpause(); + + assertEq(_roninPauseEnforcer.emergency(), false); + assertEq(_roninGatewayV3.paused(), false); + } + + // Should the gateway can be interacted after unpause + function test_InteractWithGateway_AfterUnpause() public { + test_EmergencyUnpause_RoninGatewayV3(); + Transfer.Receipt memory receipt = Transfer.Receipt({ + id: 0, + kind: Transfer.Kind.Deposit, + ronin: Token.Owner({ addr: makeAddr("recipient"), tokenAddr: address(_roninWeth), chainId: _param.test.roninChainId }), + mainchain: Token.Owner({ + addr: makeAddr("requester"), + tokenAddr: address(_mainchainWeth), + chainId: _param.test.mainchainChainId + }), + info: Token.Info({ erc: Token.Standard.ERC20, id: 0, quantity: 100 }) + }); + + uint256 numOperatorsForVoteExecuted = + _param.roninBridgeManager.bridgeOperators.length * _param.roninBridgeManager.num / _param.roninBridgeManager.denom; + for (uint256 i; i < numOperatorsForVoteExecuted; i++) { + vm.prank(_param.roninBridgeManager.bridgeOperators[i]); + _roninGatewayV3.depositFor(receipt); + } + } +} diff --git a/test/bridge/integration/pause-enforcer/set-config/normalPause.GatewayV3.t.sol b/test/bridge/integration/pause-enforcer/set-config/normalPause.GatewayV3.t.sol new file mode 100644 index 00000000..77a3cb9a --- /dev/null +++ b/test/bridge/integration/pause-enforcer/set-config/normalPause.GatewayV3.t.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Transfer } from "@ronin/contracts/libraries/Transfer.sol"; +import { GatewayV3 } from "@ronin/contracts/extensions/GatewayV3.sol"; +import "../../BaseIntegration.t.sol"; + +contract NormalPause_GatewayV3_Test is BaseIntegration_Test { + error ErrNotOnEmergencyPause(); + error ErrTargetIsNotOnPaused(); + + function setUp() public virtual override { + super.setUp(); + _config.switchTo(Network.RoninLocal.key()); + } + + // 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_); + + assertEq(_roninPauseEnforcer.emergency(), false); + assertEq(_roninGatewayV3.paused(), true); + } + + // Should not be able to emergency unpause + function test_RevertWhen_EmergencyUnpause() public { + test_GovernanceAdmin_PauseGateway_ThroughoutVoting(); + + vm.expectRevert(ErrNotOnEmergencyPause.selector); + + vm.prank(_param.roninPauseEnforcer.sentries[0]); + _roninPauseEnforcer.triggerUnpause(); + } + + // Should not be able to override by emergency pause and emergency unpause + function test_RevertWhen_OverrideByEmergencyPauseOrUnPause() public { + test_GovernanceAdmin_PauseGateway_ThroughoutVoting(); + + vm.expectRevert(ErrTargetIsNotOnPaused.selector); + + vm.prank(_param.roninPauseEnforcer.sentries[0]); + _roninPauseEnforcer.triggerPause(); + + vm.expectRevert(ErrNotOnEmergencyPause.selector); + + vm.prank(_param.roninPauseEnforcer.sentries[0]); + _roninPauseEnforcer.triggerUnpause(); + } + + // Should gateway admin can unpause the gateway through voting + function test_GovernanceAdmin_UnPauseGateway_ThroughoutVoting() public { + test_GovernanceAdmin_PauseGateway_ThroughoutVoting(); + + bytes memory calldata_ = abi.encodeCall(GatewayV3.unpause, ()); + _roninProposalUtils.functionDelegateCall(address(_roninGatewayV3), calldata_); + + assertEq(_roninPauseEnforcer.emergency(), false); + assertEq(_roninGatewayV3.paused(), false); + } +} diff --git a/test/bridge/integration/ronin-gateway/depositVote.RoninGatewayV3.t.sol b/test/bridge/integration/ronin-gateway/depositVote.RoninGatewayV3.t.sol index 907554ba..b6da50d5 100644 --- a/test/bridge/integration/ronin-gateway/depositVote.RoninGatewayV3.t.sol +++ b/test/bridge/integration/ronin-gateway/depositVote.RoninGatewayV3.t.sol @@ -22,10 +22,6 @@ contract DepositVote_RoninGatewayV3_Test is BaseIntegration_Test { super.setUp(); _config.switchTo(Network.RoninLocal.key()); - bytes memory calldata_ = - abi.encodeCall(IHasContracts.setContract, (ContractType.BRIDGE_TRACKING, address(_bridgeTracking))); - _roninProposalUtils.functionDelegateCallGlobal(GlobalProposal.TargetOption.GatewayContract, calldata_); - vm.etch(address(_roninGatewayV3), address(new MockRoninGatewayV3Extended()).code); Transfer.Receipt memory receipt = Transfer.Receipt({ diff --git a/test/helpers/MainchainBridgeAdminUtils.t.sol b/test/helpers/MainchainBridgeAdminUtils.t.sol new file mode 100644 index 00000000..1d02105a --- /dev/null +++ b/test/helpers/MainchainBridgeAdminUtils.t.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { MainchainBridgeManager } from "@ronin/contracts/mainchain/MainchainBridgeManager.sol"; +import "./ProposalUtils.t.sol"; + +contract MainchainBridgeAdminUtils is ProposalUtils { + MainchainBridgeManager _contract; + address _sender; + + constructor(uint256 roninChainId, uint256[] memory signerPKs, MainchainBridgeManager contract_, address sender) + ProposalUtils(roninChainId, signerPKs) + { + _contract = contract_; + _sender = sender; + } + + function defaultExpiryTimestamp() public view returns (uint256) { + return block.timestamp + 10; + } + + function functionDelegateCall(address to, bytes memory data) public { + Proposal.ProposalDetail memory proposal = this.createProposal({ + expiryTimestamp: this.defaultExpiryTimestamp(), + target: to, + value: 0, + calldata_: abi.encodeWithSignature("functionDelegateCall(bytes)", data), + gasAmount: 2_000_000, + nonce: _contract.round(_roninChainId) + 1 + }); + + SignatureConsumer.Signature[] memory signatures = this.generateSignatures(proposal); + uint256 length = signatures.length; + Ballot.VoteType[] memory supports_ = new Ballot.VoteType[](length); + for (uint256 i; i < length; i++) { + supports_[i] = Ballot.VoteType.For; + } + vm.prank(_sender); + _contract.relayProposal(proposal, supports_, signatures); + } + + function functionDelegateCallGlobal(GlobalProposal.TargetOption target, bytes memory data) public { + GlobalProposal.GlobalProposalDetail memory proposal = this.createGlobalProposal({ + expiryTimestamp: this.defaultExpiryTimestamp(), + targetOption: target, + value: 0, + calldata_: abi.encodeWithSignature("functionDelegateCall(bytes)", data), + gasAmount: 2_000_000, + nonce: _contract.round(0) + 1 + }); + + SignatureConsumer.Signature[] memory signatures = this.generateSignaturesGlobal(proposal); + uint256 length = signatures.length; + Ballot.VoteType[] memory supports_ = new Ballot.VoteType[](length); + for (uint256 i; i < length; i++) { + supports_[i] = Ballot.VoteType.For; + } + vm.prank(_sender); + _contract.relayGlobalProposal(proposal, supports_, signatures); + } + + function functionDelegateCallsGlobal(GlobalProposal.TargetOption[] memory targetOptions, bytes[] memory datas) public { + uint256 length = targetOptions.length; + if (length != datas.length || length == 0) revert("Invalid length"); + + bytes[] memory calldatas = new bytes[](length); + uint256[] memory values = new uint256[](length); + uint256[] memory gasAmounts = new uint256[](length); + for (uint256 i; i < length; i++) { + calldatas[i] = abi.encodeWithSignature("functionDelegateCall(bytes)", datas[i]); + values[i] = 0; + gasAmounts[i] = 2_000_000; + } + + GlobalProposal.GlobalProposalDetail memory proposal = GlobalProposal.GlobalProposalDetail({ + nonce: _contract.round(0) + 1, + expiryTimestamp: this.defaultExpiryTimestamp(), + targetOptions: targetOptions, + values: values, + calldatas: calldatas, + gasAmounts: gasAmounts + }); + + SignatureConsumer.Signature[] memory signatures = this.generateSignaturesGlobal(proposal); + length = signatures.length; + Ballot.VoteType[] memory supports_ = new Ballot.VoteType[](length); + for (uint256 i; i < length; i++) { + supports_[i] = Ballot.VoteType.For; + } + vm.prank(_sender); + _contract.relayGlobalProposal(proposal, supports_, signatures); + } + + function upgradeGlobal(GlobalProposal.TargetOption targetOption, uint256 nonce, bytes memory data) public { + GlobalProposal.GlobalProposalDetail memory proposal = this.createGlobalProposal({ + expiryTimestamp: this.defaultExpiryTimestamp(), + targetOption: targetOption, + value: 0, + calldata_: abi.encodeWithSignature("upgradeTo(bytes)", data), + gasAmount: 2_000_000, + nonce: nonce + }); + + SignatureConsumer.Signature[] memory signatures = this.generateSignaturesGlobal(proposal); + uint256 length = signatures.length; + Ballot.VoteType[] memory supports_ = new Ballot.VoteType[](length); + for (uint256 i; i < length; i++) { + supports_[i] = Ballot.VoteType.For; + } + vm.prank(_sender); + _contract.relayGlobalProposal(proposal, supports_, signatures); + } +} diff --git a/test/helpers/ProposalUtils.t.sol b/test/helpers/ProposalUtils.t.sol index d5667a2d..ef504c8b 100644 --- a/test/helpers/ProposalUtils.t.sol +++ b/test/helpers/ProposalUtils.t.sol @@ -38,7 +38,7 @@ contract ProposalUtils is Utils, Test { ) public view returns (Proposal.ProposalDetail memory proposal) { proposal = Proposal.ProposalDetail({ nonce: nonce, - chainId: _roninChainId, + chainId: block.chainid, expiryTimestamp: expiryTimestamp, targets: wrapAddress(target), values: wrapUint(value),