diff --git a/.gitmodules b/.gitmodules index 5274dcbda..8deb9b9ed 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,3 +15,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "lib/foundry-deployment-kit"] + path = lib/foundry-deployment-kit + url = https://github.com/axieinfinity/foundry-deployment-kit diff --git a/lib/foundry-deployment-kit b/lib/foundry-deployment-kit new file mode 160000 index 000000000..e17e26992 --- /dev/null +++ b/lib/foundry-deployment-kit @@ -0,0 +1 @@ +Subproject commit e17e2699290d58f6474087c4ecbe50ea38c2c19e diff --git a/remappings.txt b/remappings.txt index f56bd7f74..c1cb927bf 100644 --- a/remappings.txt +++ b/remappings.txt @@ -6,4 +6,5 @@ hardhat/=node_modules/hardhat/ @ronin/test/=test/ @prb/test/=lib/prb-test/src/ @prb/math/=lib/prb-math/ -solady/=lib/solady/src/ \ No newline at end of file +solady/=lib/solady/src/ +foundry-deployment-kit/=lib/foundry-deployment-kit/script/ diff --git a/run.sh b/run.sh new file mode 100755 index 000000000..931b150b7 --- /dev/null +++ b/run.sh @@ -0,0 +1 @@ +source lib/foundry-deployment-kit/run.sh \ No newline at end of file diff --git a/script/20231218-maptoken/20231218-maptoken-mainchain.s.sol b/script/20231218-maptoken/20231218-maptoken-mainchain.s.sol new file mode 100644 index 000000000..7c14fb132 --- /dev/null +++ b/script/20231218-maptoken/20231218-maptoken-mainchain.s.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 } from "forge-std/console2.sol"; +import { StdStyle } from "forge-std/StdStyle.sol"; +import { BaseMigration } from "foundry-deployment-kit/BaseMigration.s.sol"; +import { RoninBridgeManager } from "@ronin/contracts/ronin/gateway/RoninBridgeManager.sol"; +import { IMainchainGatewayV3 } from "@ronin/contracts/interfaces/IMainchainGatewayV3.sol"; +import { GlobalProposal } from "@ronin/contracts/libraries/GlobalProposal.sol"; +import { Token } from "@ronin/contracts/libraries/Token.sol"; +import { Contract } from "../utils/Contract.sol"; +import { BridgeMigration } from "../BridgeMigration.sol"; +import { Network } from "../utils/Network.sol"; +import { Contract } from "../utils/Contract.sol"; +import { IGeneralConfigExtended } from "../IGeneralConfigExtended.sol"; + +contract Migration__20231215_MapTokenMainchain is BridgeMigration { + RoninBridgeManager internal _roninBridgeManager; + address constant _aggRoninToken = address(0x294311a8C37F0744F99EB152c419D4D3D6FEC1C7); + address constant _aggMainchainToken = address(0xFB0489e9753B045DdB35e39c6B0Cc02EC6b99AC5); + address internal _mainchainGatewayV3; + + // The decimal of AGG token is 18 + uint256 constant _highTierThreshold = 200_000_000 ether; + uint256 constant _lockedThreshold = 800_000_000 ether; + // The MAX_PERCENTAGE is 1_000_000 + uint256 constant _unlockFeePercentages = 10; + uint256 constant _dailyWithdrawalLimit = 500_000_000 ether; + + function setUp() public override { + super.setUp(); + + _roninBridgeManager = RoninBridgeManager(_config.getAddressFromCurrentNetwork(Contract.RoninBridgeManager.key())); + _mainchainGatewayV3 = _config.getAddress( + _config.getCompanionNetwork(_config.getNetworkByChainId(block.chainid)).key(), + Contract.MainchainGatewayV3.key() + ); + } + + function run() public { + address[] memory mainchainTokens = new address[](1); + mainchainTokens[0] = _aggMainchainToken; + address[] memory roninTokens = new address[](1); + roninTokens[0] = _aggRoninToken; + Token.Standard[] memory standards = new Token.Standard[](1); + standards[0] = Token.Standard.ERC20; + uint256[][4] memory thresholds; + // highTierThreshold + thresholds[0] = new uint256[](1); + thresholds[0][0] = _highTierThreshold; + // lockedThreshold + thresholds[1] = new uint256[](1); + thresholds[1][0] = _lockedThreshold; + // unlockFeePercentages + thresholds[2] = new uint256[](1); + thresholds[2][0] = _unlockFeePercentages; + // dailyWithdrawalLimit + thresholds[3] = new uint256[](1); + thresholds[3][0] = _dailyWithdrawalLimit; + + // function mapTokensAndThresholds( + // address[] calldata _mainchainTokens, + // address[] calldata _roninTokens, + // Token.Standard[] calldata _standards, + // uint256[][4] calldata _thresholds + // ) + + bytes memory innerData = abi.encodeCall(IMainchainGatewayV3.mapTokensAndThresholds, ( + mainchainTokens, + roninTokens, + standards, + thresholds + )); + bytes memory proxyData = abi.encodeWithSignature("functionDelegateCall(bytes)", innerData); + + uint256 expiredTime = block.timestamp + 10 days; + address[] memory targets = new address[](1); + targets[0] = _mainchainGatewayV3; + uint256[] memory values = new uint256[](1); + values[0] = 0; + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = proxyData; + uint256[] memory gasAmounts = new uint256[](1); + gasAmounts[0] = 1_000_000; + + _verifyMainchainProposalGasAmount(targets, values, calldatas, gasAmounts); + + uint256 chainId = _config.getCompanionNetwork(_config.getNetworkByChainId(block.chainid)).chainId(); + + vm.broadcast(sender()); + _roninBridgeManager.propose( + chainId, + expiredTime, + targets, + values, + calldatas, + gasAmounts + ); + } +} diff --git a/script/20231218-maptoken/20231218-maptoken-roninchain.s.sol b/script/20231218-maptoken/20231218-maptoken-roninchain.s.sol new file mode 100644 index 000000000..a7bc37af9 --- /dev/null +++ b/script/20231218-maptoken/20231218-maptoken-roninchain.s.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 } from "forge-std/console2.sol"; +import { StdStyle } from "forge-std/StdStyle.sol"; +import { BaseMigration } from "foundry-deployment-kit/BaseMigration.s.sol"; +import { RoninBridgeManager } from "@ronin/contracts/ronin/gateway/RoninBridgeManager.sol"; +import { IRoninGatewayV3 } from "@ronin/contracts/interfaces/IRoninGatewayV3.sol"; +import { Token } from "@ronin/contracts/libraries/Token.sol"; +import { Contract } from "../utils/Contract.sol"; +import { BridgeMigration } from "../BridgeMigration.sol"; +import { Network } from "../utils/Network.sol"; +import { Contract } from "../utils/Contract.sol"; +import { IGeneralConfigExtended } from "../IGeneralConfigExtended.sol"; + +contract Migration__20231215_MapTokenRoninchain is BridgeMigration { + RoninBridgeManager internal _roninBridgeManager; + address constant _aggRoninToken = address(0x294311a8C37F0744F99EB152c419D4D3D6FEC1C7); + address constant _aggMainchainToken = address(0xFB0489e9753B045DdB35e39c6B0Cc02EC6b99AC5); + address internal _roninGatewayV3; + + function setUp() public override { + super.setUp(); + _roninBridgeManager = RoninBridgeManager(_config.getAddressFromCurrentNetwork(Contract.RoninBridgeManager.key())); + _roninGatewayV3 = _config.getAddressFromCurrentNetwork(Contract.RoninGatewayV3.key()); + } + + function run() public { + address[] memory roninTokens = new address[](1); + roninTokens[0] = _aggRoninToken; + address[] memory mainchainTokens = new address[](1); + mainchainTokens[0] = _aggMainchainToken; + uint256[] memory chainIds = new uint256[](1); + chainIds[0] = _config.getCompanionNetwork(_config.getNetworkByChainId(block.chainid)).chainId(); + Token.Standard[] memory standards = new Token.Standard[](1); + standards[0] = Token.Standard.ERC20; + + // function mapTokens( + // address[] calldata _roninTokens, + // address[] calldata _mainchainTokens, + // uint256[] calldata chainIds, + // Token.Standard[] calldata _standards + // ) + bytes memory innerData = abi.encodeCall(IRoninGatewayV3.mapTokens, ( + roninTokens, + mainchainTokens, + chainIds, + standards + )); + bytes memory proxyData = abi.encodeWithSignature("functionDelegateCall(bytes)", innerData); + + uint256 expiredTime = block.timestamp + 10 days; + address[] memory targets = new address[](1); + targets[0] = _roninGatewayV3; + uint256[] memory values = new uint256[](1); + values[0] = 0; + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = proxyData; + uint256[] memory gasAmounts = new uint256[](1); + gasAmounts[0] = 1_000_000; + + _verifyRoninProposalGasAmount(targets, values, calldatas, gasAmounts); + + vm.broadcast(sender()); + _roninBridgeManager.propose( + block.chainid, + expiredTime, + targets, + values, + calldatas, + gasAmounts + ); + } +} diff --git a/script/BridgeMigration.sol b/script/BridgeMigration.sol new file mode 100644 index 000000000..2f8662ba1 --- /dev/null +++ b/script/BridgeMigration.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 } from "forge-std/console2.sol"; +import { BaseMigration } from "foundry-deployment-kit/BaseMigration.s.sol"; +import { GeneralConfigExtended } from "./GeneralConfigExtended.sol"; +import { RoninBridgeManager } from "@ronin/contracts/ronin/gateway/RoninBridgeManager.sol"; +import { ErrorHandler } from "@ronin/contracts/libraries/ErrorHandler.sol"; +import { IGeneralConfigExtended } from "./IGeneralConfigExtended.sol"; +import { Network } from "./utils/Network.sol"; +import { Contract } from "./utils/Contract.sol"; +import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; + +contract BridgeMigration is BaseMigration { + using ErrorHandler for bool; + + error ErrProposalOutOfGas(bytes4 sig, uint256 expectedGas); + + IGeneralConfigExtended internal constant _config = IGeneralConfigExtended(address(CONFIG)); + + function _configByteCode() internal virtual override returns (bytes memory) { + return abi.encodePacked(type(GeneralConfigExtended).creationCode); + } + + function _sharedArguments() internal virtual override returns (bytes memory rawArgs) { + return ""; + } + + function _verifyRoninProposalGasAmount( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + uint256[] memory gasAmounts + ) internal { + address roninBridgeManager = _config.getAddressFromCurrentNetwork(Contract.RoninBridgeManager.key()); + uint256 snapshotId = vm.snapshot(); + vm.startPrank(address(roninBridgeManager)); + _verifyProposalGasAmount(roninBridgeManager, targets, values, calldatas, gasAmounts); + vm.stopPrank(); + vm.revertTo(snapshotId); + } + + function _verifyMainchainProposalGasAmount( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + uint256[] memory gasAmounts + ) internal { + _config.createFork(Network.EthMainnet.key()); + _config.switchTo(Network.EthMainnet.key()); + + address mainchainBridgeManager = _config.getAddressFromCurrentNetwork(Contract.MainchainBridgeManager.key()); + uint256 snapshotId = vm.snapshot(); + + vm.startPrank(address(mainchainBridgeManager)); + _verifyProposalGasAmount(mainchainBridgeManager, targets, values, calldatas, gasAmounts); + vm.stopPrank(); + vm.revertTo(snapshotId); + + _config.switchTo(DefaultNetwork.RoninMainnet.key()); + } + + function _verifyProposalGasAmount( + address bridgeManager, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + uint256[] memory gasAmounts + ) private { + for (uint256 i; i < targets.length; i++) { + vm.deal(address(bridgeManager), values[i]); + uint256 gasUsed = gasleft(); + (bool success, bytes memory returnOrRevertData) = targets[i].call{value: values[i]}(calldatas[i]); + gasUsed = gasUsed - gasleft(); + success.handleRevert(bytes4(calldatas[i]), returnOrRevertData); + + console2.log("Call", i, ": gasUsed", gasUsed); + if (gasUsed > gasAmounts[i]) { + revert ErrProposalOutOfGas(bytes4(calldatas[i]), gasUsed); + } + } + } +} diff --git a/script/GeneralConfigExtended.sol b/script/GeneralConfigExtended.sol new file mode 100644 index 000000000..1d2f5ccb9 --- /dev/null +++ b/script/GeneralConfigExtended.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 as console } from "forge-std/console2.sol"; +import { TContract } from "foundry-deployment-kit/types/Types.sol"; +import { BaseGeneralConfig } from "foundry-deployment-kit/BaseGeneralConfig.sol"; +import { TNetwork } from "foundry-deployment-kit/types/Types.sol"; +import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; +import { Network } from "./utils/Network.sol"; +import { Contract } from "./utils/Contract.sol"; + +contract GeneralConfigExtended is BaseGeneralConfig { + constructor() BaseGeneralConfig("", "deployments/") { } + + function _setUpNetworks() internal virtual override { + setNetworkInfo( + Network.Goerli.chainId(), + Network.Goerli.key(), + Network.Goerli.chainAlias(), + Network.Goerli.deploymentDir(), + Network.Goerli.envLabel(), + Network.Goerli.explorer() + ); + setNetworkInfo( + Network.EthMainnet.chainId(), + Network.EthMainnet.key(), + Network.EthMainnet.chainAlias(), + Network.EthMainnet.deploymentDir(), + Network.EthMainnet.envLabel(), + Network.EthMainnet.explorer() + ); + } + + function _setUpContracts() internal virtual override { + _mapContractname(Contract.BridgeReward); + _mapContractname(Contract.BridgeSlash); + _mapContractname(Contract.BridgeTracking); + _mapContractname(Contract.RoninBridgeManager); + _mapContractname(Contract.RoninGatewayV3); + _mapContractname(Contract.MainchainBridgeManager); + _mapContractname(Contract.MainchainGatewayV3); + } + + function _mapContractname(Contract contractEnum) internal { + _contractNameMap[contractEnum.key()] = contractEnum.name(); + } + + function getCompanionNetwork(TNetwork network) external pure returns (Network) { + if (network == DefaultNetwork.RoninTestnet.key()) return Network.Goerli; + if (network == DefaultNetwork.RoninMainnet.key()) return Network.EthMainnet; + revert("Network: Unknown companion network"); + } +} diff --git a/script/IGeneralConfigExtended.sol b/script/IGeneralConfigExtended.sol new file mode 100644 index 000000000..78a02582a --- /dev/null +++ b/script/IGeneralConfigExtended.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IGeneralConfig } from "foundry-deployment-kit/interfaces/IGeneralConfig.sol"; +import { TNetwork } from "foundry-deployment-kit/types/Types.sol"; +import { Network } from "./utils/Network.sol"; + +interface IGeneralConfigExtended is IGeneralConfig { + /** + * @dev Returns the companion mainchain network of a roninchain network + * + * Input: roninchain network + * Output: companion mainchain network of roninchain + * + */ + function getCompanionNetwork(TNetwork network) external pure returns (Network); +} diff --git a/script/utils/Contract.sol b/script/utils/Contract.sol new file mode 100644 index 000000000..3403c5b2f --- /dev/null +++ b/script/utils/Contract.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { LibString, TContract } from "foundry-deployment-kit/types/Types.sol"; + +enum Contract { + BridgeReward, + BridgeSlash, + BridgeTracking, + RoninBridgeManager, + RoninGatewayV3, + MainchainBridgeManager, + MainchainGatewayV3 +} + +using { key, name } for Contract global; + +function key(Contract contractEnum) pure returns (TContract) { + return TContract.wrap(LibString.packOne(name(contractEnum))); +} + +function name(Contract contractEnum) pure returns (string memory) { + if (contractEnum == Contract.BridgeReward) return "BridgeReward"; + if (contractEnum == Contract.BridgeSlash) return "BridgeSlash"; + if (contractEnum == Contract.BridgeTracking) return "BridgeTracking"; + if (contractEnum == Contract.RoninBridgeManager) return "RoninBridgeManager"; + if (contractEnum == Contract.RoninGatewayV3) return "RoninGatewayV3"; + if (contractEnum == Contract.MainchainBridgeManager) return "MainchainBridgeManager"; + if (contractEnum == Contract.MainchainGatewayV3) return "MainchainGatewayV3"; + revert("Contract: Unknown contract"); +} diff --git a/script/utils/Network.sol b/script/utils/Network.sol new file mode 100644 index 000000000..6c312a97a --- /dev/null +++ b/script/utils/Network.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { LibString, TNetwork } from "foundry-deployment-kit/types/Types.sol"; + +enum Network { + Goerli, + EthMainnet +} + +using { key, name, chainId, chainAlias, envLabel, deploymentDir, explorer } for Network global; + +function chainId(Network network) pure returns (uint256) { + if (network == Network.Goerli) return 5; + if (network == Network.EthMainnet) return 1; + revert("Network: Unknown chain id"); +} + +function key(Network network) pure returns (TNetwork) { + return TNetwork.wrap(LibString.packOne(name(network))); +} + +function explorer(Network network) pure returns (string memory link) { + if (network == Network.Goerli) return "https://goerli.etherscan.io/"; + if (network == Network.EthMainnet) return "https://etherscan.io/"; +} + +function name(Network network) pure returns (string memory) { + if (network == Network.Goerli) return "Goerli"; + if (network == Network.EthMainnet) return "EthMainnet"; + revert("Network: Unknown network name"); +} + +function deploymentDir(Network network) pure returns (string memory) { + if (network == Network.Goerli) return "goerli/"; + if (network == Network.EthMainnet) return "ethereum/"; + revert("Network: Unknown network deployment directory"); +} + +function envLabel(Network network) pure returns (string memory) { + if (network == Network.Goerli) return "TESTNET_PK"; + if (network == Network.EthMainnet) return "MAINNET_PK"; + revert("Network: Unknown private key env label"); +} + +function chainAlias(Network network) pure returns (string memory) { + if (network == Network.Goerli) return "goerli"; + if (network == Network.EthMainnet) return "ethereum"; + revert("Network: Unknown network alias"); +}