diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad49e528b..1b97da344 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,5 +42,7 @@ jobs: - name: Run Forge tests run: | + anvil --chain-id 2024 --port 8546 & + anvil --chain-id 2 --port 8547 & forge test -vvv id: test diff --git a/foundry.toml b/foundry.toml index afeff1aae..e20e1029f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,6 +3,7 @@ src = 'src' out = 'out' optimizer = true optimizer_runs = 200 +ffi = true libs = [ 'lib', @@ -51,4 +52,6 @@ ethereum = "https://eth.llamarpc.com" goerli = "https://ethereum-goerli.publicnode.com" ronin-mainnet = "https://api-partner.roninchain.com/rpc" ronin-testnet = "https://saigon-archive.roninchain.com/rpc" -localhost = "http://localhost:8545" + +ronin-local = "http://localhost:8546" +ethereum-local = "http://localhost:8547" diff --git a/lib/foundry-deployment-kit b/lib/foundry-deployment-kit index 68b8e5a83..8c7284248 160000 --- a/lib/foundry-deployment-kit +++ b/lib/foundry-deployment-kit @@ -1 +1 @@ -Subproject commit 68b8e5a83113275cae9f4332cc99924c18779887 +Subproject commit 8c728424858f8c8358f2e0f9374a6ea581f46486 diff --git a/logs/contract-code-sizes.log b/logs/contract-code-sizes.log index 9bbea1e67..81d81e1d7 100644 --- a/logs/contract-code-sizes.log +++ b/logs/contract-code-sizes.log @@ -4,7 +4,7 @@ | AddressArrayUtils | 0.086 | 24.49 | | ArtifactFactory | 9.155 | 15.421 | | Ballot | 0.086 | 24.49 | -| BaseGeneralConfig | 20.129 | 4.447 | +| BaseGeneralConfig | 20.128 | 4.448 | | BridgeOperatorsBallot | 0.166 | 24.41 | | BridgeReward | 6.032 | 18.544 | | BridgeSlash | 5.639 | 18.937 | @@ -15,17 +15,19 @@ | ERC20PresetMinterPauser | 6.368 | 18.208 | | EnumerableSet | 0.086 | 24.49 | | ErrorHandler | 0.086 | 24.49 | -| GeneralConfig | 20.129 | 4.447 | -| GeneralConfigExtended | 20.351 | 4.225 | +| GeneralConfig | 20.616 | 3.96 | +| GeneralConfigExtended | 20.35 | 4.226 | | GlobalProposal | 0.166 | 24.41 | | HasBridgeDeprecated | 0.063 | 24.513 | | HasValidatorDeprecated | 0.063 | 24.513 | | IsolatedGovernance | 0.086 | 24.49 | | JSONParserLib | 0.086 | 24.49 | +| LibArray | 0.086 | 24.49 | | LibArrayUtils | 0.086 | 24.49 | | LibErrorHandler | 0.086 | 24.49 | | LibProxy | 0.086 | 24.49 | | LibSharedAddress | 0.086 | 24.49 | +| LibSort | 0.086 | 24.49 | | LibString | 0.086 | 24.49 | | LibTUint256Slot | 0.086 | 24.49 | | MainchainBridgeManager | 19.361 | 5.215 | diff --git a/script/GeneralConfig.sol b/script/GeneralConfig.sol index 33df668db..ccf4b9a80 100644 --- a/script/GeneralConfig.sol +++ b/script/GeneralConfig.sol @@ -36,6 +36,24 @@ contract GeneralConfig is BaseGeneralConfig, Utils { Network.RoninDevnet.envLabel(), Network.RoninDevnet.explorer() ); + + setNetworkInfo( + Network.RoninLocal.chainId(), + Network.RoninLocal.key(), + Network.RoninLocal.chainAlias(), + Network.RoninLocal.deploymentDir(), + Network.RoninLocal.envLabel(), + Network.RoninLocal.explorer() + ); + + setNetworkInfo( + Network.EthLocal.chainId(), + Network.EthLocal.key(), + Network.EthLocal.chainAlias(), + Network.EthLocal.deploymentDir(), + Network.EthLocal.envLabel(), + Network.EthLocal.explorer() + ); } function _setUpContracts() internal virtual override { @@ -53,32 +71,17 @@ contract GeneralConfig is BaseGeneralConfig, Utils { _contractNameMap[Contract.AXS.key()] = "MockERC20"; _contractNameMap[Contract.SLP.key()] = "MockERC20"; _contractNameMap[Contract.USDC.key()] = "MockERC20"; - - if (getCurrentNetwork() == DefaultNetwork.Local.key()) { - address deployer = getSender(); - - // ronin bridge contracts - setAddress(DefaultNetwork.Local.key(), Contract.RoninGatewayV3.key(), vm.computeCreateAddress(deployer, 4)); - setAddress(DefaultNetwork.Local.key(), Contract.BridgeTracking.key(), vm.computeCreateAddress(deployer, 6)); - setAddress(DefaultNetwork.Local.key(), Contract.BridgeSlash.key(), vm.computeCreateAddress(deployer, 8)); - setAddress(DefaultNetwork.Local.key(), Contract.BridgeReward.key(), vm.computeCreateAddress(deployer, 10)); - setAddress(DefaultNetwork.Local.key(), Contract.RoninBridgeManager.key(), vm.computeCreateAddress(deployer, 11)); - - //mainchain bridge contracts - setAddress(DefaultNetwork.Local.key(), Contract.MainchainGatewayV3.key(), vm.computeCreateAddress(deployer, 13)); - setAddress( - DefaultNetwork.Local.key(), Contract.MainchainBridgeManager.key(), vm.computeCreateAddress(deployer, 14) - ); - - // ronin tokens - setAddress(DefaultNetwork.Local.key(), Contract.WETH.key(), vm.computeCreateAddress(deployer, 15)); - setAddress(DefaultNetwork.Local.key(), Contract.AXS.key(), vm.computeCreateAddress(deployer, 16)); - setAddress(DefaultNetwork.Local.key(), Contract.SLP.key(), vm.computeCreateAddress(deployer, 17)); - setAddress(DefaultNetwork.Local.key(), Contract.USDC.key(), vm.computeCreateAddress(deployer, 18)); - } } function _mapContractName(Contract contractEnum) internal { _contractNameMap[contractEnum.key()] = contractEnum.name(); } + + function getSender() public view virtual override returns (address payable sender) { + sender = _option.trezor ? payable(_trezorSender) : payable(_envSender); + bool isLocalNetwork = getCurrentNetwork() == DefaultNetwork.Local.key() + || getCurrentNetwork() == Network.RoninLocal.key() || getCurrentNetwork() == Network.EthLocal.key(); + if (sender == address(0x0) && isLocalNetwork) sender = payable(DEFAULT_SENDER); + require(sender != address(0x0), "GeneralConfig: Sender is address(0x0)"); + } } diff --git a/script/Migration.s.sol b/script/Migration.s.sol index fed07af80..0a53c03f6 100644 --- a/script/Migration.s.sol +++ b/script/Migration.s.sol @@ -8,9 +8,9 @@ import { ISharedArgument } from "./interfaces/ISharedArgument.sol"; import { Network } from "./utils/Network.sol"; import { Utils } from "./utils/Utils.sol"; import { Contract } from "./utils/Contract.sol"; - import { GlobalProposal } from "@ronin/contracts/libraries/GlobalProposal.sol"; import { Token } from "@ronin/contracts/libraries/Token.sol"; +import { LibArray } from "./libraries/LibArray.sol"; contract Migration is BaseMigration, Utils { ISharedArgument public constant config = ISharedArgument(address(CONFIG)); @@ -21,152 +21,98 @@ contract Migration is BaseMigration, Utils { function _sharedArguments() internal virtual override returns (bytes memory rawArgs) { ISharedArgument.SharedParameter memory param; + param.test.proxyAdmin = makeAddr("proxy-admin"); + param.test.dposGA = makeAddr("governance-admin"); + param.test.mainchainChainId = Network.EthLocal.chainId(); + param.test.roninChainId = Network.RoninLocal.chainId(); + + // tokens + param.weth.name = "Wrapped WETH"; + param.weth.symbol = "WETH"; + param.wron.name = "Wrapped RON"; + param.wron.symbol = "WRON"; + param.axs.name = "Axie Infinity Shard"; + param.axs.symbol = "AXS"; + param.slp.name = "Smooth Love Potion"; + param.slp.symbol = "SLP"; + param.usdc.name = "USD Coin"; + param.usdc.symbol = "USDC"; + + uint256 num = 6; + address[] memory operatorAddrs = new address[](num); + address[] memory governorAddrs = new address[](num); + uint256[] memory operatorPKs = new uint256[](num); + uint256[] memory governorPKs = new uint256[](num); + uint96[] memory voteWeights = new uint96[](num); + GlobalProposal.TargetOption[] memory options = new GlobalProposal.TargetOption[](0); + address[] memory targets = new address[](0); + + for (uint256 i; i < num; i++) { + (address addrOperator, uint256 pkOperator) = makeAddrAndKey(string.concat("operator-", vm.toString(i + 1))); + (address addrGovernor, uint256 pkGovernor) = makeAddrAndKey(string.concat("governor-", vm.toString(i + 1))); + + operatorAddrs[i] = addrOperator; + governorAddrs[i] = addrGovernor; + operatorPKs[i] = pkOperator; + governorPKs[i] = pkGovernor; + voteWeights[i] = 100; + } + + LibArray.inlineSortByValue(operatorPKs, LibArray.toUint256s(operatorAddrs)); + LibArray.inlineSortByValue(governorPKs, LibArray.toUint256s(governorAddrs)); + + param.test.operatorPKs = operatorPKs; + param.test.governorPKs = governorPKs; if (network() == Network.Goerli.key()) { // Undefined } else if (network() == DefaultNetwork.RoninTestnet.key()) { // Undefined - } else if (network() == DefaultNetwork.Local.key()) { - uint256 num = 6; - address[] memory operatorAddrs = new address[](num); - address[] memory governorAddrs = new address[](num); - uint256[] memory operatorPKs = new uint256[](num); - uint256[] memory governorPKs = new uint256[](num); - uint96[] memory voteWeights = new uint96[](num); - for (uint256 i; i < num; i++) { - (address addrOperator, uint256 pkOperator) = makeAddrAndKey(string.concat("operator-", vm.toString(i + 1))); - (address addrGovernor, uint256 pkGovernor) = makeAddrAndKey(string.concat("governor-", vm.toString(i + 1))); - operatorAddrs[i] = addrOperator; - governorAddrs[i] = addrGovernor; - operatorPKs[i] = pkOperator; - governorPKs[i] = pkGovernor; - voteWeights[i] = 100; - } - - address governanceAdmin = makeAddr("governance-admin"); - address validatorSetContract = makeAddr("validator-set-contract"); - Token.Standard[] memory standards = new Token.Standard[](1); - standards[0] = Token.Standard.ERC20; - - GlobalProposal.TargetOption[] memory options = new GlobalProposal.TargetOption[](4); - address[] memory targets = new address[](4); - - options[0] = GlobalProposal.TargetOption.GatewayContract; - targets[0] = loadContract(Contract.RoninGatewayV3.key()); - - options[1] = GlobalProposal.TargetOption.BridgeReward; - targets[1] = loadContract(Contract.BridgeReward.key()); - - options[2] = GlobalProposal.TargetOption.BridgeSlash; - targets[2] = loadContract(Contract.BridgeSlash.key()); - - options[3] = GlobalProposal.TargetOption.BridgeTracking; - targets[3] = loadContract(Contract.BridgeTracking.key()); - - // test - param.test.proxyAdmin = makeAddr("proxy-admin"); - param.test.operatorPKs = operatorPKs; - param.test.governorPKs = governorPKs; - + } else if (network() == Network.RoninLocal.key() || network() == Network.EthLocal.key()) { // Bridge rewards - param.bridgeReward.bridgeManagerContract = loadContract(Contract.RoninBridgeManager.key()); - param.bridgeReward.bridgeTrackingContract = loadContract(Contract.BridgeTracking.key()); - param.bridgeReward.bridgeSlashContract = loadContract(Contract.BridgeSlash.key()); - param.bridgeReward.validatorSetContract = validatorSetContract; - param.bridgeReward.dposGA = governanceAdmin; + param.bridgeReward.dposGA = param.test.dposGA; param.bridgeReward.rewardPerPeriod = 5_000; // Bridge Slash - param.bridgeSlash.validatorContract = validatorSetContract; - param.bridgeSlash.bridgeManagerContract = loadContract(Contract.RoninBridgeManager.key()); - param.bridgeSlash.bridgeTrackingContract = loadContract(Contract.BridgeTracking.key()); - param.bridgeSlash.dposGA = governanceAdmin; + param.bridgeSlash.dposGA = param.test.dposGA; // Bridge Tracking - param.bridgeTracking.bridgeContract = loadContract(Contract.RoninGatewayV3.key()); - param.bridgeTracking.validatorContract = validatorSetContract; + + // Ronin Gateway V3 + param.roninGatewayV3.numerator = 3; + param.roninGatewayV3.denominator = 6; + param.roninGatewayV3.trustedNumerator = 2; + param.roninGatewayV3.trustedDenominator = 3; // Ronin Bridge Manager param.roninBridgeManager.num = 2; param.roninBridgeManager.denom = 4; param.roninBridgeManager.roninChainId = 0; + param.roninBridgeManager.roninChainId = param.test.roninChainId; param.roninBridgeManager.expiryDuration = 60 * 60 * 24 * 14; // 14 days - param.roninBridgeManager.bridgeContract = loadContract(Contract.RoninGatewayV3.key()); - param.roninBridgeManager.callbackRegisters = wrapAddress(loadContract(Contract.BridgeSlash.key())); param.roninBridgeManager.bridgeOperators = operatorAddrs; param.roninBridgeManager.governors = governorAddrs; param.roninBridgeManager.voteWeights = voteWeights; param.roninBridgeManager.targetOptions = options; param.roninBridgeManager.targets = targets; - - // Ronin Gateway V3 - param.roninGatewayV3.roleSetter = address(0); - param.roninGatewayV3.numerator = 3; - param.roninGatewayV3.denominator = 6; - param.roninGatewayV3.trustedNumerator = 2; - param.roninGatewayV3.trustedDenominator = 3; - param.roninGatewayV3.withdrawalMigrators = getEmptyAddressArray(); - param.roninGatewayV3.packedAddresses[0] = wrapAddress(address(0)); - param.roninGatewayV3.packedAddresses[1] = wrapAddress(address(0)); - param.roninGatewayV3.packedNumbers[0] = wrapUint(1); - param.roninGatewayV3.packedNumbers[1] = wrapUint(0); - param.roninGatewayV3.standards = standards; + // Mainchain Gateway V3 + param.mainchainGatewayV3.roninChainId = param.test.roninChainId; + param.mainchainGatewayV3.numerator = 1; + param.mainchainGatewayV3.highTierVWNumerator = 10; + param.mainchainGatewayV3.denominator = 10; // Mainchain Bridge Manager - delete options; - delete targets; - - options = new GlobalProposal.TargetOption[](1); - targets = new address[](1); - - options[0] = GlobalProposal.TargetOption.GatewayContract; - targets[0] = loadContract(Contract.MainchainGatewayV3.key()); - param.mainchainBridgeManager.num = 2; param.mainchainBridgeManager.denom = 4; param.mainchainBridgeManager.roninChainId = 0; - param.mainchainBridgeManager.bridgeContract = loadContract(Contract.MainchainGatewayV3.key()); - param.mainchainBridgeManager.callbackRegisters = getEmptyAddressArray(); + param.mainchainBridgeManager.roninChainId = param.test.roninChainId; param.mainchainBridgeManager.bridgeOperators = operatorAddrs; param.mainchainBridgeManager.governors = governorAddrs; param.mainchainBridgeManager.voteWeights = voteWeights; param.mainchainBridgeManager.targetOptions = options; param.mainchainBridgeManager.targets = targets; - - // Mainchain Gateway V3 - delete standards; - standards = new Token.Standard[](2); - - standards[0] = Token.Standard.ERC20; - standards[1] = Token.Standard.ERC20; - - param.mainchainGatewayV3.roleSetter = address(0); - param.mainchainGatewayV3.roninChainId = 0; - param.mainchainGatewayV3.numerator = 1; - param.mainchainGatewayV3.highTierVWNumerator = 10; - param.mainchainGatewayV3.denominator = 10; - param.mainchainGatewayV3.addresses[0] = - wrapAddress(loadContract(Contract.WETH.key()), loadContract(Contract.USDC.key())); // mainchain tokens - param.mainchainGatewayV3.addresses[1] = - wrapAddress(loadContract(Contract.WETH.key()), loadContract(Contract.USDC.key())); // ronin tokens - param.mainchainGatewayV3.addresses[2] = getEmptyAddressArray(); //withdrawalUnlockers - param.mainchainGatewayV3.thresholds[0] = wrapUint(10, 0); // highTierThreshold - param.mainchainGatewayV3.thresholds[1] = wrapUint(20, 0); // lockedThreshold - param.mainchainGatewayV3.thresholds[2] = wrapUint(100_000, 0); // unlockFeePercentages - param.mainchainGatewayV3.thresholds[3] = wrapUint(12, 0); // dailyWithdrawalLimits - param.mainchainGatewayV3.standards = standards; - - // tokens - param.weth.name = "Wrapped WETH"; - param.weth.symbol = "WETH"; - param.wron.name = "Wrapped RON"; - param.wron.symbol = "WRON"; - param.axs.name = "Axie Infinity Shard"; - param.axs.symbol = "AXS"; - param.slp.name = "Smooth Love Potion"; - param.slp.symbol = "SLP"; - param.usdc.name = "USD Coin"; - param.usdc.symbol = "USDC"; + } else if (network() == DefaultNetwork.Local.key()) { + // Undefined } else { revert("Migration: Network Unknown Shared Parameters Unimplemented!"); } @@ -175,8 +121,8 @@ contract Migration is BaseMigration, Utils { } function _getProxyAdmin() internal virtual override returns (address payable) { - return network() == DefaultNetwork.Local.key() - ? payable(config.sharedArguments().test.proxyAdmin) - : super._getProxyAdmin(); + bool isLocalNetwork = network() == DefaultNetwork.Local.key() || network() == Network.RoninLocal.key() + || network() == Network.EthLocal.key(); + return isLocalNetwork ? payable(config.sharedArguments().test.proxyAdmin) : super._getProxyAdmin(); } } diff --git a/script/contracts/BridgeRewardDeploy.s.sol b/script/contracts/BridgeRewardDeploy.s.sol index 05d17af5f..1413fc3d2 100644 --- a/script/contracts/BridgeRewardDeploy.s.sol +++ b/script/contracts/BridgeRewardDeploy.s.sol @@ -11,23 +11,7 @@ import { RoninBridgeManagerDeploy } from "./RoninBridgeManagerDeploy.s.sol"; import { BridgeSlashDeploy } from "./BridgeSlashDeploy.s.sol"; contract BridgeRewardDeploy is Migration { - function _defaultArguments() internal virtual override returns (bytes memory args) { - ISharedArgument.BridgeRewardParam memory param = config.sharedArguments().bridgeReward; - - args = abi.encodeCall( - BridgeReward.initialize, - ( - param.bridgeManagerContract, - param.bridgeTrackingContract, - param.bridgeSlashContract, - param.validatorSetContract, - param.dposGA, - param.rewardPerPeriod - ) - ); - } - function run() public virtual returns (BridgeReward) { - return BridgeReward(_deployProxy(Contract.BridgeReward.key())); + return BridgeReward(_deployProxy(Contract.BridgeReward.key(), EMPTY_ARGS)); } } diff --git a/script/contracts/BridgeSlashDeploy.s.sol b/script/contracts/BridgeSlashDeploy.s.sol index c3a101a3c..067b842f1 100644 --- a/script/contracts/BridgeSlashDeploy.s.sol +++ b/script/contracts/BridgeSlashDeploy.s.sol @@ -10,16 +10,7 @@ import { BridgeTrackingDeploy } from "./BridgeTrackingDeploy.s.sol"; import { RoninBridgeManagerDeploy } from "./RoninBridgeManagerDeploy.s.sol"; contract BridgeSlashDeploy is Migration { - function _defaultArguments() internal virtual override returns (bytes memory args) { - ISharedArgument.BridgeSlashParam memory param = config.sharedArguments().bridgeSlash; - - args = abi.encodeCall( - BridgeSlash.initialize, - (param.validatorContract, param.bridgeManagerContract, param.bridgeTrackingContract, param.dposGA) - ); - } - function run() public virtual returns (BridgeSlash) { - return BridgeSlash(_deployProxy(Contract.BridgeSlash.key())); + return BridgeSlash(_deployProxy(Contract.BridgeSlash.key(), EMPTY_ARGS)); } } diff --git a/script/contracts/BridgeTrackingDeploy.s.sol b/script/contracts/BridgeTrackingDeploy.s.sol index 022cf154b..fb5e6227b 100644 --- a/script/contracts/BridgeTrackingDeploy.s.sol +++ b/script/contracts/BridgeTrackingDeploy.s.sol @@ -9,14 +9,7 @@ import { Migration } from "../Migration.s.sol"; import { RoninGatewayV3Deploy } from "./RoninGatewayV3Deploy.s.sol"; contract BridgeTrackingDeploy is Migration { - function _defaultArguments() internal virtual override returns (bytes memory args) { - ISharedArgument.BridgeTrackingParam memory param = config.sharedArguments().bridgeTracking; - - args = - abi.encodeCall(BridgeTracking.initialize, (param.bridgeContract, param.validatorContract, param.startedAtBlock)); - } - function run() public virtual returns (BridgeTracking) { - return BridgeTracking(_deployProxy(Contract.BridgeTracking.key())); + return BridgeTracking(_deployProxy(Contract.BridgeTracking.key(), EMPTY_ARGS)); } } diff --git a/script/contracts/MainchainGatewayV3Deploy.s.sol b/script/contracts/MainchainGatewayV3Deploy.s.sol index 61412d00a..bb4c2e074 100644 --- a/script/contracts/MainchainGatewayV3Deploy.s.sol +++ b/script/contracts/MainchainGatewayV3Deploy.s.sol @@ -8,26 +8,7 @@ import { ISharedArgument } from "../interfaces/ISharedArgument.sol"; import { Migration } from "../Migration.s.sol"; contract MainchainGatewayV3Deploy is Migration { - function _defaultArguments() internal virtual override returns (bytes memory args) { - ISharedArgument.MainchainGatewayV3Param memory param = config.sharedArguments().mainchainGatewayV3; - - args = abi.encodeCall( - MainchainGatewayV3.initialize, - ( - param.roleSetter, - IWETH(param.wrappedToken), - param.roninChainId, - param.numerator, - param.highTierVWNumerator, - param.denominator, - param.addresses, - param.thresholds, - param.standards - ) - ); - } - function run() public virtual returns (MainchainGatewayV3) { - return MainchainGatewayV3(_deployProxy(Contract.MainchainGatewayV3.key())); + return MainchainGatewayV3(_deployProxy(Contract.MainchainGatewayV3.key(), EMPTY_ARGS)); } } diff --git a/script/contracts/RoninGatewayV3Deploy.s.sol b/script/contracts/RoninGatewayV3Deploy.s.sol index c252eafb7..60db9ae0d 100644 --- a/script/contracts/RoninGatewayV3Deploy.s.sol +++ b/script/contracts/RoninGatewayV3Deploy.s.sol @@ -8,26 +8,7 @@ import { ISharedArgument } from "../interfaces/ISharedArgument.sol"; import { Migration } from "../Migration.s.sol"; contract RoninGatewayV3Deploy is Migration { - function _defaultArguments() internal virtual override returns (bytes memory args) { - ISharedArgument.RoninGatewayV3Param memory param = config.sharedArguments().roninGatewayV3; - - args = abi.encodeCall( - RoninGatewayV3.initialize, - ( - param.roleSetter, - param.numerator, - param.denominator, - param.trustedNumerator, - param.trustedDenominator, - param.withdrawalMigrators, - param.packedAddresses, - param.packedNumbers, - param.standards - ) - ); - } - function run() public virtual returns (RoninGatewayV3) { - return RoninGatewayV3(_deployProxy(Contract.RoninGatewayV3.key())); + return RoninGatewayV3(_deployProxy(Contract.RoninGatewayV3.key(), EMPTY_ARGS)); } } diff --git a/script/interfaces/ISharedArgument.sol b/script/interfaces/ISharedArgument.sol index 7bce9174b..eb5508f6e 100644 --- a/script/interfaces/ISharedArgument.sol +++ b/script/interfaces/ISharedArgument.sol @@ -89,6 +89,9 @@ interface ISharedArgument is IGeneralConfig { struct UnitTestParam { address proxyAdmin; + address dposGA; + uint256 mainchainChainId; + uint256 roninChainId; uint256[] operatorPKs; uint256[] governorPKs; } diff --git a/script/libraries/LibArray.sol b/script/libraries/LibArray.sol new file mode 100644 index 000000000..d9630afad --- /dev/null +++ b/script/libraries/LibArray.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title LibArray + * @dev A library for array-related utility functions in Solidity. + */ +library LibArray { + /** + * @dev Error indicating a length mismatch between two arrays. + */ + error LengthMismatch(); + + function toUint256s(uint8[] memory self) internal pure returns (uint256[] memory uint256s) { + assembly ("memory-safe") { + uint256s := self + } + } + + function toUint8sUnsafe(uint256[] memory self) internal pure returns (uint8[] memory uint8s) { + assembly ("memory-safe") { + uint8s := self + } + } + + function arange(uint256 length) internal pure returns (uint256[] memory data) { + data = new uint256[](length); + for (uint256 i; i < length; ++i) { + data[i] = i; + } + } + + /** + * @dev Converts an array of uint256 to an array of bytes32. + * @param self The array of uint256. + * @return bytes32s The resulting array of bytes32. + */ + function toBytes32s(uint256[] memory self) internal pure returns (bytes32[] memory bytes32s) { + assembly ("memory-safe") { + bytes32s := self + } + } + + function hash(uint256[] memory data) internal pure returns (bytes32 digest) { + assembly ("memory-safe") { + digest := keccak256(add(data, 0x20), mload(data)) + } + } + + /** + * @dev Calculates the sum of an array of uint256 values. + * @param data The array of uint256 values for which the sum is calculated. + * @return result The sum of the provided array of uint256 values. + */ + function sum(uint256[] memory data) internal pure returns (uint256 result) { + assembly ("memory-safe") { + // Load the length (first 32 bytes) + let len := mload(data) + let dataElementLocation := add(data, 0x20) + + // Iterate until the bound is not met. + for { let end := add(dataElementLocation, mul(len, 0x20)) } lt(dataElementLocation, end) { + dataElementLocation := add(dataElementLocation, 0x20) + } { result := add(result, mload(dataElementLocation)) } + } + } + + /** + * @dev Converts an array of bytes32 to an array of uint256. + * @param self The array of bytes32. + * @return uint256s The resulting array of uint256. + */ + function toUint256s(bytes32[] memory self) internal pure returns (uint256[] memory uint256s) { + assembly ("memory-safe") { + uint256s := self + } + } + + /** + * @dev Converts an array of uint64 to an array of uint256. + * @param self The array of bytes32. + * @return uint256s The resulting array of uint256. + */ + function toUint256s(uint64[] memory self) internal pure returns (uint256[] memory uint256s) { + assembly ("memory-safe") { + uint256s := self + } + } + + /** + * @dev Converts an array of address to an array of uint256. + * @param self The array of address. + * @return uint256s The resulting array of uint256. + */ + function toUint256s(address[] memory self) internal pure returns (uint256[] memory uint256s) { + assembly ("memory-safe") { + uint256s := self + } + } + + /** + * @dev Sorts an array of uint256 values based on a corresponding array of values using the specified sorting mode. + * @param self The array to be sorted. + * @param values The corresponding array of values used for sorting. + * @notice This function modify `self` and `values` + * @return sorted The sorted array. + */ + function inlineSortByValue(uint256[] memory self, uint256[] memory values) + internal + pure + returns (uint256[] memory sorted) + { + return inlineQuickSortByValue(self, values); + } + + /** + * @dev Sorts an array of uint256 based on a corresponding array of values. + * @param self The array to be sorted. + * @param values The corresponding array of values used for sorting. + * @notice This function modify `self` and `values` + * @return sorted The sorted array. + */ + function inlineQuickSortByValue(uint256[] memory self, uint256[] memory values) + internal + pure + returns (uint256[] memory sorted) + { + uint256 length = self.length; + if (length != values.length) revert LengthMismatch(); + unchecked { + if (length > 1) inlineQuickSortByValue(self, values, 0, int256(length - 1)); + } + + assembly ("memory-safe") { + sorted := self + } + } + + /** + * @dev Internal function to perform quicksort on an array of uint256 values based on a corresponding array of values. + * @param arr The array to be sorted. + * @param values The corresponding array of values used for sorting. + * @param left The left index of the subarray to be sorted. + * @param right The right index of the subarray to be sorted. + * @notice This function modify `arr` and `values` + */ + function inlineQuickSortByValue(uint256[] memory arr, uint256[] memory values, int256 left, int256 right) + private + pure + { + unchecked { + if (left == right) return; + int256 i = left; + int256 j = right; + uint256 pivot = values[uint256(left + right) >> 1]; + + while (i <= j) { + while (pivot > values[uint256(i)]) ++i; + while (pivot < values[uint256(j)]) --j; + + if (i <= j) { + (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); + (values[uint256(i)], values[uint256(j)]) = (values[uint256(j)], values[uint256(i)]); + ++i; + --j; + } + } + + if (left < j) inlineQuickSortByValue(arr, values, left, j); + if (i < right) inlineQuickSortByValue(arr, values, i, right); + } + } +} diff --git a/script/utils/Network.sol b/script/utils/Network.sol index aa817211a..b98bd4454 100644 --- a/script/utils/Network.sol +++ b/script/utils/Network.sol @@ -6,7 +6,9 @@ import { LibString, TNetwork } from "foundry-deployment-kit/types/Types.sol"; enum Network { Goerli, EthMainnet, - RoninDevnet + RoninDevnet, + RoninLocal, + EthLocal } using { key, name, chainId, chainAlias, envLabel, deploymentDir, explorer } for Network global; @@ -15,6 +17,9 @@ function chainId(Network network) pure returns (uint256) { if (network == Network.Goerli) return 5; if (network == Network.EthMainnet) return 1; if (network == Network.RoninDevnet) return 2022; + if (network == Network.RoninLocal) return 2024; + if (network == Network.EthLocal) return 2; + revert("Network: Unknown chain id"); } @@ -31,6 +36,9 @@ function name(Network network) pure returns (string memory) { if (network == Network.Goerli) return "Goerli"; if (network == Network.RoninDevnet) return "RoninDevnet"; if (network == Network.EthMainnet) return "EthMainnet"; + if (network == Network.RoninLocal) return "RoninLocal"; + if (network == Network.EthLocal) return "EthLocal"; + revert("Network: Unknown network name"); } @@ -38,6 +46,9 @@ function deploymentDir(Network network) pure returns (string memory) { if (network == Network.Goerli) return "goerli/"; if (network == Network.EthMainnet) return "ethereum/"; if (network == Network.RoninDevnet) return "ronin-devnet/"; + if (network == Network.RoninLocal) return ""; + if (network == Network.EthLocal) return ""; + revert("Network: Unknown network deployment directory"); } @@ -45,6 +56,8 @@ function envLabel(Network network) pure returns (string memory) { if (network == Network.Goerli) return "TESTNET_PK"; if (network == Network.RoninDevnet) return "DEVNET_PK"; if (network == Network.EthMainnet) return "MAINNET_PK"; + if (network == Network.RoninLocal) return "DEVNET_PK"; + if (network == Network.EthLocal) return "DEVNET_PK"; revert("Network: Unknown private key env label"); } @@ -52,5 +65,7 @@ function chainAlias(Network network) pure returns (string memory) { if (network == Network.Goerli) return "goerli"; if (network == Network.EthMainnet) return "ethereum"; if (network == Network.RoninDevnet) return "ronin-devnet"; + if (network == Network.RoninLocal) return "ronin-local"; + if (network == Network.EthLocal) return "ethereum-local"; revert("Network: Unknown network alias"); } diff --git a/script/utils/Utils.sol b/script/utils/Utils.sol index 91ae0431b..f0ca0dd76 100644 --- a/script/utils/Utils.sol +++ b/script/utils/Utils.sol @@ -22,6 +22,11 @@ contract Utils { arr[0] = val; } + function wrapBytes(bytes memory val) internal pure returns (bytes[] memory arr) { + arr = new bytes[](1); + arr[0] = val; + } + function wrapAddress(address val1, address val2, address val3) internal pure returns (address[] memory arr) { arr = new address[](3); arr[0] = val1; diff --git a/src/mainchain/MainchainGatewayV3.sol b/src/mainchain/MainchainGatewayV3.sol index 89a408a7b..68be6019a 100644 --- a/src/mainchain/MainchainGatewayV3.sol +++ b/src/mainchain/MainchainGatewayV3.sol @@ -93,7 +93,7 @@ contract MainchainGatewayV3 is } // Grant role for withdrawal unlocker - for (uint256 _i; _i < _addresses[2].length; ) { + for (uint256 _i; _i < _addresses[2].length;) { _grantRole(WITHDRAWAL_UNLOCKER_ROLE, _addresses[2][_i]); unchecked { @@ -109,7 +109,7 @@ contract MainchainGatewayV3 is /** * @dev Receives ether without doing anything. Use this function to topup native token. */ - function receiveEther() external payable {} + function receiveEther() external payable { } /** * @inheritdoc IMainchainGatewayV3 @@ -135,10 +135,12 @@ contract MainchainGatewayV3 is /** * @inheritdoc IMainchainGatewayV3 */ - function submitWithdrawal( - Transfer.Receipt calldata _receipt, - Signature[] calldata _signatures - ) external virtual whenNotPaused returns (bool _locked) { + function submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] calldata _signatures) + external + virtual + whenNotPaused + returns (bool _locked) + { return _submitWithdrawal(_receipt, _signatures); } @@ -227,10 +229,11 @@ contract MainchainGatewayV3 is address[] calldata _roninTokens, Token.Standard[] calldata _standards ) internal virtual { - if (!(_mainchainTokens.length == _roninTokens.length && _mainchainTokens.length == _standards.length)) + if (!(_mainchainTokens.length == _roninTokens.length && _mainchainTokens.length == _standards.length)) { revert ErrLengthMismatch(msg.sig); + } - for (uint256 _i; _i < _mainchainTokens.length; ) { + for (uint256 _i; _i < _mainchainTokens.length;) { _roninToken[_mainchainTokens[_i]].tokenAddr = _roninTokens[_i]; _roninToken[_mainchainTokens[_i]].erc = _standards[_i]; @@ -256,10 +259,11 @@ contract MainchainGatewayV3 is * Emits the `Withdrew` once the assets are released. * */ - function _submitWithdrawal( - Transfer.Receipt calldata _receipt, - Signature[] memory _signatures - ) internal virtual returns (bool _locked) { + function _submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] memory _signatures) + internal + virtual + returns (bool _locked) + { uint256 _id = _receipt.id; uint256 _quantity = _receipt.info.quantity; address _tokenAddr = _receipt.mainchain.tokenAddr; @@ -293,7 +297,7 @@ contract MainchainGatewayV3 is address _lastSigner; Signature memory _sig; uint256 _weight; - for (uint256 _i; _i < _signatures.length; ) { + for (uint256 _i; _i < _signatures.length;) { _sig = _signatures[_i]; _signer = ecrecover(_receiptDigest, _sig.v, _sig.r, _sig.s); if (_lastSigner >= _signer) revert ErrInvalidOrder(msg.sig); @@ -339,16 +343,16 @@ contract MainchainGatewayV3 is */ function _requestDepositFor(Transfer.Request memory _request, address _requester) internal virtual { MappedToken memory _token; - address _weth = address(wrappedNativeToken); + address _roninWeth = address(wrappedNativeToken); _request.info.validate(); if (_request.tokenAddr == address(0)) { if (_request.info.quantity != msg.value) revert ErrInvalidRequest(); - _token = getRoninToken(_weth); + _token = getRoninToken(_roninWeth); if (_token.erc != _request.info.erc) revert ErrInvalidTokenStandard(); - _request.tokenAddr = _weth; + _request.tokenAddr = _roninWeth; } else { if (msg.value != 0) revert ErrInvalidRequest(); @@ -357,18 +361,14 @@ contract MainchainGatewayV3 is _request.info.transferFrom(_requester, address(this), _request.tokenAddr); // Withdraw if token is WETH - if (_weth == _request.tokenAddr) { - IWETH(_weth).withdraw(_request.info.quantity); + if (_roninWeth == _request.tokenAddr) { + IWETH(_roninWeth).withdraw(_request.info.quantity); } } uint256 _depositId = depositCount++; - Transfer.Receipt memory _receipt = _request.into_deposit_receipt( - _requester, - _depositId, - _token.tokenAddr, - roninChainId - ); + Transfer.Receipt memory _receipt = + _request.into_deposit_receipt(_requester, _depositId, _token.tokenAddr, roninChainId); emit DepositRequested(_receipt.hash(), _receipt); } @@ -376,11 +376,11 @@ contract MainchainGatewayV3 is /** * @dev Returns the minimum vote weight for the token. */ - function _computeMinVoteWeight( - Token.Standard _erc, - address _token, - uint256 _quantity - ) internal virtual returns (uint256 _weight, bool _locked) { + function _computeMinVoteWeight(Token.Standard _erc, address _token, uint256 _quantity) + internal + virtual + returns (uint256 _weight, bool _locked) + { uint256 _totalWeight = _getTotalWeight(); _weight = _minimumVoteWeight(_totalWeight); if (_erc == Token.Standard.ERC20) { diff --git a/test/bridge/integration/BaseIntegration.t.sol b/test/bridge/integration/BaseIntegration.t.sol index dfb752953..665d17110 100644 --- a/test/bridge/integration/BaseIntegration.t.sol +++ b/test/bridge/integration/BaseIntegration.t.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.19; import { Base_Test } from "../../Base.t.sol"; import { LibSharedAddress } from "foundry-deployment-kit/libraries/LibSharedAddress.sol"; import { ISharedArgument } from "@ronin/script/interfaces/ISharedArgument.sol"; +import { IGeneralConfig } from "foundry-deployment-kit/interfaces/IGeneralConfig.sol"; +import { GeneralConfig } from "@ronin/script/GeneralConfig.sol"; +import { Network } from "@ronin/script/utils/Network.sol"; import { RoninBridgeManager } from "@ronin/contracts/ronin/gateway/RoninBridgeManager.sol"; import { RoninGatewayV3 } from "@ronin/contracts/ronin/gateway/RoninGatewayV3.sol"; @@ -14,6 +17,14 @@ import { MainchainGatewayV3 } from "@ronin/contracts/mainchain/MainchainGatewayV import { MainchainBridgeManager } from "@ronin/contracts/mainchain/MainchainBridgeManager.sol"; import { MockERC20 } from "@ronin/contracts/mocks/token/MockERC20.sol"; import { MockWrappedToken } from "@ronin/contracts/mocks/token/MockWrappedToken.sol"; +import { GlobalProposal } from "@ronin/contracts/libraries/GlobalProposal.sol"; +import { Token } from "@ronin/contracts/libraries/Token.sol"; +import { IWETH } from "@ronin/contracts/interfaces/IWETH.sol"; +import { SignatureConsumer } from "@ronin/contracts/interfaces/consumers/SignatureConsumer.sol"; +import { Ballot } from "@ronin/contracts/libraries/Ballot.sol"; +import { GlobalCoreGovernance } from "@ronin/contracts/extensions/sequential-governance/GlobalCoreGovernance.sol"; +import { IHasContracts } from "@ronin/contracts/interfaces/collections/IHasContracts.sol"; +import { ContractType } from "@ronin/contracts/utils/ContractType.sol"; import { RoninBridgeManagerDeploy } from "@ronin/script/contracts/RoninBridgeManagerDeploy.s.sol"; import { RoninGatewayV3Deploy } from "@ronin/script/contracts/RoninGatewayV3Deploy.s.sol"; @@ -23,11 +34,16 @@ import { BridgeRewardDeploy } from "@ronin/script/contracts/BridgeRewardDeploy.s import { MainchainGatewayV3Deploy } from "@ronin/script/contracts/MainchainGatewayV3Deploy.s.sol"; import { MainchainBridgeManagerDeploy } from "@ronin/script/contracts/MainchainBridgeManagerDeploy.s.sol"; import { WETHDeploy } from "@ronin/script/contracts/token/WETHDeploy.s.sol"; +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 { ProposalUtils } from "test/helpers/ProposalUtils.t.sol"; +import { MockValidatorSet_ForFoundryTest } from "test/mocks/MockValidatorSet_ForFoundryTest.sol"; + contract BaseIntegration_Test is Base_Test { + IGeneralConfig _config; ISharedArgument.SharedParameter _param; RoninBridgeManager _roninBridgeManager; @@ -38,26 +54,372 @@ contract BaseIntegration_Test is Base_Test { MainchainGatewayV3 _mainchainGatewayV3; MainchainBridgeManager _mainchainBridgeManager; - MockWrappedToken _weth; - MockERC20 _axs; - MockERC20 _slp; - MockERC20 _usdc; + MockWrappedToken _roninWeth; + MockWrappedToken _roninWron; + MockERC20 _roninAxs; + MockERC20 _roninSlp; + MockERC20 _roninUsdc; + + MockWrappedToken _mainchainWeth; + MockWrappedToken _mainchainWron; + MockERC20 _mainchainAxs; + MockERC20 _mainchainSlp; + MockERC20 _mainchainUsdc; + + MockValidatorSet_ForFoundryTest _validatorSet; + + ProposalUtils _roninProposalUtils; + ProposalUtils _mainchainProposalUtils; + + uint256 _roninNonce = 1; + uint256 _mainchainNonce = 1; function setUp() public virtual { + _deployGeneralConfig(); + + _deployContractsOnRonin(); + _deployContractsOnMainchain(); + + _initializeRonin(); + _initializeMainchain(); + } + + function _deployContractsOnRonin() internal { + _config.createFork(Network.RoninLocal.key()); + _config.switchTo(Network.RoninLocal.key()); + _roninGatewayV3 = new RoninGatewayV3Deploy().run(); _bridgeTracking = new BridgeTrackingDeploy().run(); _bridgeSlash = new BridgeSlashDeploy().run(); _bridgeReward = new BridgeRewardDeploy().run(); _roninBridgeManager = new RoninBridgeManagerDeploy().run(); + _roninWeth = new WETHDeploy().run(); + _roninWron = new WRONDeploy().run(); + _roninAxs = new AXSDeploy().run(); + _roninSlp = new SLPDeploy().run(); + _roninUsdc = new USDCDeploy().run(); + + _validatorSet = new MockValidatorSet_ForFoundryTest(); + + _param = ISharedArgument(LibSharedAddress.CONFIG).sharedArguments(); + _roninProposalUtils = new ProposalUtils(_param.test.roninChainId); + } + + function _deployContractsOnMainchain() internal { + _config.createFork(Network.EthLocal.key()); + _config.switchTo(Network.EthLocal.key()); + _mainchainGatewayV3 = new MainchainGatewayV3Deploy().run(); _mainchainBridgeManager = new MainchainBridgeManagerDeploy().run(); - _weth = new WETHDeploy().run(); - _axs = new AXSDeploy().run(); - _slp = new SLPDeploy().run(); - _usdc = new USDCDeploy().run(); + _mainchainWeth = new WETHDeploy().run(); + _mainchainWron = new WRONDeploy().run(); + _mainchainAxs = new AXSDeploy().run(); + _mainchainSlp = new SLPDeploy().run(); + _mainchainUsdc = new USDCDeploy().run(); _param = ISharedArgument(LibSharedAddress.CONFIG).sharedArguments(); + _mainchainProposalUtils = new ProposalUtils(_param.test.roninChainId); + } + + function _initializeRonin() internal { + _config.switchTo(Network.RoninLocal.key()); + + _bridgeRewardInitialize(); + _bridgeTrackingInitialize(); + _bridgeSlashInitialize(); + _roninGatewayV3Initialize(); + _constructForRoninBridgeManager(); + } + + function _initializeMainchain() internal { + _config.switchTo(Network.EthLocal.key()); + + _constructForMainchainBridgeManager(); + _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[4] = address(_roninSlp); + roninTokens[3] = address(_roninUsdc); + } + + function _bridgeRewardInitialize() internal { + // Bridge rewards + _param.bridgeReward.validatorSetContract = address(_validatorSet); + _param.bridgeReward.bridgeManagerContract = address(_roninBridgeManager); + _param.bridgeReward.bridgeTrackingContract = address(_bridgeTracking); + _param.bridgeReward.bridgeSlashContract = address(_bridgeSlash); + + ISharedArgument.BridgeRewardParam memory param = _param.bridgeReward; + + _bridgeReward.initialize( + param.bridgeManagerContract, + param.bridgeTrackingContract, + param.bridgeSlashContract, + param.validatorSetContract, + param.dposGA, + param.rewardPerPeriod + ); + + _validatorSet.setCurrentPeriod(10); + vm.prank(_param.test.dposGA); + _bridgeReward.initializeREP2(); + } + + function _bridgeTrackingInitialize() internal { + // Bridge Tracking + _param.bridgeTracking.validatorContract = address(_validatorSet); + _param.bridgeTracking.bridgeContract = address(_roninGatewayV3); + + ISharedArgument.BridgeTrackingParam memory param = _param.bridgeTracking; + + _bridgeTracking.initialize(param.bridgeContract, param.validatorContract, param.startedAtBlock); + _bridgeTracking.initializeV2(); + _bridgeTracking.initializeV3( + address(_roninBridgeManager), address(_bridgeSlash), address(_bridgeReward), _param.test.dposGA + ); + } + + function _bridgeSlashInitialize() internal { + // Bridge Slash + _param.bridgeSlash.validatorContract = address(_validatorSet); + _param.bridgeSlash.bridgeManagerContract = address(_roninBridgeManager); + _param.bridgeSlash.bridgeTrackingContract = address(_bridgeTracking); + + ISharedArgument.BridgeSlashParam memory param = _param.bridgeSlash; + + _bridgeSlash.initialize( + param.validatorContract, param.bridgeManagerContract, param.bridgeTrackingContract, param.dposGA + ); + + vm.prank(_param.test.dposGA); + _bridgeSlash.initializeREP2(); + } + + function _roninGatewayV3Initialize() internal { + (address[] memory mainchainTokens, address[] memory roninTokens) = _getMainchainAndRoninTokens(); + uint256 tokenNum = mainchainTokens.length; + 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; + chainIds[i] = _param.test.mainchainChainId; + standards[i] = Token.Standard.ERC20; + } + + // Ronin Gateway V3 + _param.roninGatewayV3.packedAddresses[0] = roninTokens; + _param.roninGatewayV3.packedAddresses[1] = mainchainTokens; + _param.roninGatewayV3.packedNumbers[0] = chainIds; + _param.roninGatewayV3.packedNumbers[1] = minimumThreshold; + _param.roninGatewayV3.standards = standards; + + ISharedArgument.RoninGatewayV3Param memory param = _param.roninGatewayV3; + + _roninGatewayV3.initialize( + param.roleSetter, + param.numerator, + param.denominator, + param.trustedNumerator, + param.trustedDenominator, + param.withdrawalMigrators, + param.packedAddresses, + param.packedNumbers, + param.standards + ); + + _roninGatewayV3.initializeV2(); + _roninGatewayV3.initializeV3(address(_roninBridgeManager)); + } + + function _constructForRoninBridgeManager() internal { + GlobalProposal.TargetOption[] memory options = new GlobalProposal.TargetOption[](4); + address[] memory targets = new address[](4); + + options[0] = GlobalProposal.TargetOption.GatewayContract; + targets[0] = address(_roninGatewayV3); + + options[1] = GlobalProposal.TargetOption.BridgeReward; + targets[1] = address(_bridgeReward); + + options[2] = GlobalProposal.TargetOption.BridgeSlash; + targets[2] = address(_bridgeSlash); + + options[3] = GlobalProposal.TargetOption.BridgeTracking; + targets[3] = address(_bridgeTracking); + + // Ronin Bridge Manager + _param.roninBridgeManager.bridgeContract = address(_roninGatewayV3); + _param.roninBridgeManager.callbackRegisters = wrapAddress(address(_bridgeSlash)); + _param.roninBridgeManager.targetOptions = options; + _param.roninBridgeManager.targets = targets; + + ISharedArgument.BridgeManagerParam memory param = _param.roninBridgeManager; + uint256 length = param.governors.length; + Ballot.VoteType[] memory supports_ = new Ballot.VoteType[](length); + for (uint256 i; i < length; i++) { + supports_[i] = Ballot.VoteType.For; + } + { + // set targets + GlobalProposal.GlobalProposalDetail memory globalProposal = _roninProposalUtils.createGlobalProposal({ + expiryTimestamp: block.timestamp + 10, + targetOption: GlobalProposal.TargetOption.BridgeManager, + value: 0, + calldata_: abi.encodeCall(GlobalCoreGovernance.updateManyTargetOption, (param.targetOptions, param.targets)), + gasAmount: 500_000, + nonce: _roninNonce++ + }); + + SignatureConsumer.Signature[] memory signatures = + _roninProposalUtils.generateSignaturesGlobal(globalProposal, _param.test.governorPKs); + + vm.prank(_param.roninBridgeManager.governors[0]); + _roninBridgeManager.proposeGlobalProposalStructAndCastVotes(globalProposal, supports_, signatures); + } + { + // set bridge contract + GlobalProposal.GlobalProposalDetail memory globalProposal = _roninProposalUtils.createGlobalProposal({ + expiryTimestamp: block.timestamp + 10, + targetOption: GlobalProposal.TargetOption.BridgeManager, + value: 0, + calldata_: abi.encodeCall(IHasContracts.setContract, (ContractType.BRIDGE, param.bridgeContract)), + gasAmount: 500_000, + nonce: _roninNonce++ + }); + + SignatureConsumer.Signature[] memory signatures = + _roninProposalUtils.generateSignaturesGlobal(globalProposal, _param.test.governorPKs); + + vm.prank(_param.roninBridgeManager.governors[0]); + _roninBridgeManager.proposeGlobalProposalStructAndCastVotes(globalProposal, supports_, signatures); + } + } + + function _constructForMainchainBridgeManager() internal { + GlobalProposal.TargetOption[] memory options = new GlobalProposal.TargetOption[](1); + address[] memory targets = new address[](1); + + options[0] = GlobalProposal.TargetOption.GatewayContract; + targets[0] = address(_mainchainGatewayV3); + + // Mainchain Bridge Manager + _param.mainchainBridgeManager.bridgeContract = address(_mainchainGatewayV3); + _param.mainchainBridgeManager.callbackRegisters = getEmptyAddressArray(); + _param.mainchainBridgeManager.targetOptions = options; + _param.mainchainBridgeManager.targets = targets; + + ISharedArgument.BridgeManagerParam memory param = _param.mainchainBridgeManager; + uint256 length = param.governors.length; + Ballot.VoteType[] memory supports_ = new Ballot.VoteType[](length); + for (uint256 i; i < length; i++) { + supports_[i] = Ballot.VoteType.For; + } + { + // set targets + GlobalProposal.GlobalProposalDetail memory globalProposal = _mainchainProposalUtils.createGlobalProposal({ + expiryTimestamp: block.timestamp + 10, + targetOption: GlobalProposal.TargetOption.BridgeManager, + value: 0, + calldata_: abi.encodeCall(GlobalCoreGovernance.updateManyTargetOption, (param.targetOptions, param.targets)), + gasAmount: 500_000, + nonce: _mainchainNonce++ + }); + + SignatureConsumer.Signature[] memory signatures = + _mainchainProposalUtils.generateSignaturesGlobal(globalProposal, _param.test.governorPKs); + + vm.prank(_param.roninBridgeManager.governors[0]); + _mainchainBridgeManager.relayGlobalProposal(globalProposal, supports_, signatures); + } + { + // set bridge contract + GlobalProposal.GlobalProposalDetail memory globalProposal = _mainchainProposalUtils.createGlobalProposal({ + expiryTimestamp: block.timestamp + 10, + targetOption: GlobalProposal.TargetOption.BridgeManager, + value: 0, + calldata_: abi.encodeCall(IHasContracts.setContract, (ContractType.BRIDGE, param.bridgeContract)), + gasAmount: 500_000, + nonce: _mainchainNonce++ + }); + + SignatureConsumer.Signature[] memory signatures = + _mainchainProposalUtils.generateSignaturesGlobal(globalProposal, _param.test.governorPKs); + + vm.prank(_param.roninBridgeManager.governors[0]); + _mainchainBridgeManager.relayGlobalProposal(globalProposal, supports_, signatures); + } + } + + function _mainchainGatewayV3Initialize() internal { + (address[] memory mainchainTokens, address[] memory roninTokens) = _getMainchainAndRoninTokens(); + uint256 tokenNum = mainchainTokens.length; + uint256[] memory highTierThreshold = new uint256[](tokenNum); + 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; + } + + // Mainchain Gateway V3 + _param.mainchainGatewayV3.addresses[0] = mainchainTokens; + _param.mainchainGatewayV3.addresses[1] = roninTokens; + _param.mainchainGatewayV3.addresses[2] = getEmptyAddressArray(); + _param.mainchainGatewayV3.thresholds[0] = highTierThreshold; + _param.mainchainGatewayV3.thresholds[1] = lockedThreshold; + _param.mainchainGatewayV3.thresholds[2] = unlockFeePercentages; + _param.mainchainGatewayV3.thresholds[3] = dailyWithdrawalLimits; + _param.mainchainGatewayV3.standards = standards; + + ISharedArgument.MainchainGatewayV3Param memory param = _param.mainchainGatewayV3; + + _mainchainGatewayV3.initialize( + param.roleSetter, + IWETH(param.wrappedToken), + param.roninChainId, + param.numerator, + param.highTierVWNumerator, + param.denominator, + param.addresses, + param.thresholds, + param.standards + ); + + _mainchainGatewayV3.initializeV2(address(_mainchainBridgeManager)); + } + + function _deployGeneralConfig() internal { + vm.makePersistent(LibSharedAddress.CONFIG); + vm.allowCheatcodes(LibSharedAddress.CONFIG); + deployCodeTo("GeneralConfig.sol", type(GeneralConfig).creationCode, LibSharedAddress.CONFIG); + _config = IGeneralConfig(LibSharedAddress.CONFIG); } } 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 new file mode 100644 index 000000000..ac06d9ed8 --- /dev/null +++ b/test/bridge/integration/bridge-manager/propose-and-cast-vote/voteBridgeOperator.RoninBridgeManager.t.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 as console } from "forge-std/console2.sol"; +import { GlobalProposal } from "@ronin/contracts/libraries/GlobalProposal.sol"; +import { Ballot } from "@ronin/contracts/libraries/Ballot.sol"; +import { ContractType } from "@ronin/contracts/utils/ContractType.sol"; +import { IBridgeManager } from "@ronin/contracts/interfaces/bridge/IBridgeManager.sol"; +import { SignatureConsumer } from "@ronin/contracts/interfaces/consumers/SignatureConsumer.sol"; +import { LibSort } from "solady/utils/LibSort.sol"; +import "../../BaseIntegration.t.sol"; + +contract VoteBridgeOperator_RoninBridgeManager_Test is BaseIntegration_Test { + using LibSort for address[]; + + uint256 _proposalExpiryDuration; + uint256 _addingOperatorNum; + address[] _addingOperators; + address[] _addingGovernors; + uint96[] _voteWeights; + + address[] _beforeRelayedOperators; + address[] _beforeRelayedGovernors; + + Ballot.VoteType[] _supports; + + function setUp() public virtual override { + super.setUp(); + _config.switchTo(Network.RoninLocal.key()); + + _proposalExpiryDuration = 60; + _addingOperatorNum = 3; + + _beforeRelayedOperators = _param.roninBridgeManager.bridgeOperators; + _beforeRelayedGovernors = _param.roninBridgeManager.governors; + + _supports = new Ballot.VoteType[](_beforeRelayedOperators.length); + for (uint256 i; i < _beforeRelayedGovernors.length; i++) { + _supports[i] = Ballot.VoteType.For; + } + + for (uint256 i; i < _addingOperatorNum; i++) { + _addingOperators.push(makeAddr(string.concat("adding-operator", vm.toString(i)))); + _addingGovernors.push(makeAddr(string.concat("adding-governor", vm.toString(i)))); + _voteWeights.push(uint96(uint256(100))); + } + } + + function test_voteBridgeOperators() public { + GlobalProposal.GlobalProposalDetail memory globalProposal = _roninProposalUtils.createGlobalProposal({ + expiryTimestamp: block.timestamp + _proposalExpiryDuration, + targetOption: GlobalProposal.TargetOption.BridgeManager, + value: 0, + calldata_: abi.encodeCall(IBridgeManager.addBridgeOperators, (_voteWeights, _addingOperators, _addingGovernors)), + gasAmount: 500_000, + nonce: _roninNonce++ + }); + + SignatureConsumer.Signature[] memory signatures = + _roninProposalUtils.generateSignaturesGlobal(globalProposal, _param.test.governorPKs); + + vm.prank(_param.roninBridgeManager.governors[0]); + _roninBridgeManager.proposeGlobalProposalStructAndCastVotes(globalProposal, _supports, signatures); + } +} diff --git a/test/bridge/integration/bridge-manager/set-config/setConfig.MainchainManager.t.sol b/test/bridge/integration/bridge-manager/set-config/setConfig.MainchainManager.t.sol index 358c48104..311aefff8 100644 --- a/test/bridge/integration/bridge-manager/set-config/setConfig.MainchainManager.t.sol +++ b/test/bridge/integration/bridge-manager/set-config/setConfig.MainchainManager.t.sol @@ -8,6 +8,7 @@ import "../../BaseIntegration.t.sol"; contract SetConfig_MainchainManager_Test is BaseIntegration_Test { function setUp() public virtual override { super.setUp(); + _config.switchTo(Network.EthLocal.key()); } function test_configBridgeContractCorrectly() external { diff --git a/test/bridge/integration/bridge-manager/set-config/setConfig.RoninBridgeManager.t.sol b/test/bridge/integration/bridge-manager/set-config/setConfig.RoninBridgeManager.t.sol index d27aa9d40..887ca08d0 100644 --- a/test/bridge/integration/bridge-manager/set-config/setConfig.RoninBridgeManager.t.sol +++ b/test/bridge/integration/bridge-manager/set-config/setConfig.RoninBridgeManager.t.sol @@ -8,6 +8,7 @@ import "../../BaseIntegration.t.sol"; contract SetConfig_RoninBridgeManager_Test is BaseIntegration_Test { function setUp() public virtual override { super.setUp(); + _config.switchTo(Network.RoninLocal.key()); } function test_configBridgeContractCorrectly() external { 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 new file mode 100644 index 000000000..ea51c8b99 --- /dev/null +++ b/test/bridge/integration/mainchain-gateway/submit-withdrawal/submitWithdrawal.MainchainGatewayV3.t.sol @@ -0,0 +1,69 @@ +// 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(); + _config.switchTo(Network.EthLocal.key()); + + _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 = _param.test.roninChainId; + _withdrawalReceipt.mainchain.addr = makeAddr("recipient"); + _withdrawalReceipt.mainchain.tokenAddr = address(_mainchainWeth); + _withdrawalReceipt.mainchain.chainId = _param.test.mainchainChainId; + _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/helpers/ProposalUtils.t.sol b/test/helpers/ProposalUtils.t.sol new file mode 100644 index 000000000..8e4be0793 --- /dev/null +++ b/test/helpers/ProposalUtils.t.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { Test } from "forge-std/Test.sol"; +import { GlobalProposal } from "@ronin/contracts/libraries/GlobalProposal.sol"; +import { Proposal } from "@ronin/contracts/libraries/Proposal.sol"; +import { Token } from "@ronin/contracts/libraries/Token.sol"; +import { Ballot } from "@ronin/contracts/libraries/Ballot.sol"; +import { SignatureConsumer } from "@ronin/contracts/interfaces/consumers/SignatureConsumer.sol"; +import { Utils } from "script/utils/Utils.sol"; + +contract ProposalUtils is Utils, Test { + using ECDSA for bytes32; + using GlobalProposal for GlobalProposal.GlobalProposalDetail; + using Proposal for Proposal.ProposalDetail; + + uint256 _roninChainId; + bytes32 _domain; + + constructor(uint256 roninChainId) { + _roninChainId = roninChainId; + _domain = getBridgeManagerDomain(roninChainId); + } + + function createProposal( + uint256 expiryTimestamp, + address target, + uint256 value, + bytes memory calldata_, + uint256 gasAmount, + uint256 nonce + ) public view returns (Proposal.ProposalDetail memory proposal) { + proposal = Proposal.ProposalDetail({ + nonce: nonce, + chainId: _roninChainId, + expiryTimestamp: expiryTimestamp, + targets: wrapAddress(target), + values: wrapUint(value), + calldatas: wrapBytes(calldata_), + gasAmounts: wrapUint(gasAmount) + }); + } + + function createGlobalProposal( + uint256 expiryTimestamp, + GlobalProposal.TargetOption targetOption, + uint256 value, + bytes memory calldata_, + uint256 gasAmount, + uint256 nonce + ) public pure returns (GlobalProposal.GlobalProposalDetail memory proposal) { + GlobalProposal.TargetOption[] memory targetOptions = new GlobalProposal.TargetOption[](1); + targetOptions[0] = targetOption; + + proposal = GlobalProposal.GlobalProposalDetail({ + nonce: nonce, + expiryTimestamp: expiryTimestamp, + targetOptions: targetOptions, + values: wrapUint(value), + calldatas: wrapBytes(calldata_), + gasAmounts: wrapUint(gasAmount) + }); + } + + function generateSignatures( + Proposal.ProposalDetail memory proposal, + uint256[] memory signerPKs, + Ballot.VoteType support + ) public view returns (SignatureConsumer.Signature[] memory sigs) { + bytes32 proposalHash = proposal.hash(); + return generateSignaturesFor(proposalHash, signerPKs, support); + } + + function generateSignatures(Proposal.ProposalDetail memory proposal, uint256[] memory signerPKs) + public + view + returns (SignatureConsumer.Signature[] memory sigs) + { + return generateSignatures(proposal, signerPKs, Ballot.VoteType.For); + } + + function generateSignaturesGlobal( + GlobalProposal.GlobalProposalDetail memory proposal, + uint256[] memory signerPKs, + Ballot.VoteType support + ) public view returns (SignatureConsumer.Signature[] memory sigs) { + bytes32 proposalHash = proposal.hash(); + return generateSignaturesFor(proposalHash, signerPKs, support); + } + + function generateSignaturesGlobal(GlobalProposal.GlobalProposalDetail memory proposal, uint256[] memory signerPKs) + public + view + returns (SignatureConsumer.Signature[] memory sigs) + { + return generateSignaturesGlobal(proposal, signerPKs, Ballot.VoteType.For); + } + + function defaultExpiryTimestamp() public view returns (uint256) { } + + function functionDelegateCallGlobal() public view { } + function upgradeGlobal() public view { } + + function getBridgeManagerDomain(uint256 roninChainId) public pure returns (bytes32) { + return keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,bytes32 salt)"), + keccak256("BridgeAdmin"), // name hash + keccak256("2"), // version hash + keccak256(abi.encode("BRIDGE_ADMIN", roninChainId)) // salt + ) + ); + } + + function generateSignaturesFor(bytes32 proposalHash, uint256[] memory signerPKs, Ballot.VoteType support) + public + view + returns (SignatureConsumer.Signature[] memory sigs) + { + sigs = new SignatureConsumer.Signature[](signerPKs.length); + + for (uint256 i; i < signerPKs.length; i++) { + bytes32 digest = _domain.toTypedDataHash(Ballot.hash(proposalHash, support)); + 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/utils/Utils.sol b/test/utils/Utils.sol index 3048d22c9..4ca554e3f 100644 --- a/test/utils/Utils.sol +++ b/test/utils/Utils.sol @@ -6,7 +6,12 @@ import { StdUtils } from "forge-std/StdUtils.sol"; import { PRBMathUtils } from "@prb/math/src/test/Utils.sol"; abstract contract Utils is StdUtils, PRBMathUtils { - function getEmptyAddressArray() internal pure returns (address[] memory arr) {} + function getEmptyAddressArray() internal pure returns (address[] memory arr) { } + + function wrapUint(uint256 val) internal pure returns (uint256[] memory arr) { + arr = new uint256[](1); + arr[0] = val; + } function wrapAddress(address val) internal pure returns (address[] memory arr) { arr = new address[](1);