diff --git a/script/GeneralConfig.sol b/script/GeneralConfig.sol index 2651f1f0..f66ed598 100644 --- a/script/GeneralConfig.sol +++ b/script/GeneralConfig.sol @@ -63,6 +63,12 @@ contract GeneralConfig is BaseGeneralConfig, Utils { _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"); + } + function getSender() public view virtual override returns (address payable sender) { sender = _option.trezor ? payable(_trezorSender) : payable(_envSender); bool isLocalNetwork = getCurrentNetwork() == DefaultNetwork.Local.key(); diff --git a/script/Migration.s.sol b/script/Migration.s.sol index ecec4fa8..0e0466f1 100644 --- a/script/Migration.s.sol +++ b/script/Migration.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import { BaseMigrationV2 } from "./BaseMigrationV2.sol"; +import { BaseMigration } from "./BaseMigration.sol"; import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; import { GeneralConfig } from "./GeneralConfig.sol"; import { ISharedArgument } from "./interfaces/ISharedArgument.sol"; @@ -12,7 +12,7 @@ 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 BaseMigrationV2, Utils { +contract Migration is BaseMigration, Utils { ISharedArgument public constant config = ISharedArgument(address(CONFIG)); function _configByteCode() internal virtual override returns (bytes memory) { diff --git a/script/PostChecker.sol b/script/PostChecker.sol new file mode 100644 index 00000000..bd3c85b8 --- /dev/null +++ b/script/PostChecker.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +abstract contract PostChecker { + +} \ No newline at end of file diff --git a/script/libraries/LibArray.sol b/script/libraries/LibArray.sol index d9630afa..39bf8c55 100644 --- a/script/libraries/LibArray.sol +++ b/script/libraries/LibArray.sol @@ -11,18 +11,121 @@ library LibArray { */ error LengthMismatch(); + function toSingletonArray(address self) internal pure returns (address[] memory arr) { + arr = new address[](1); + arr[0] = self; + } + + function toSingletonArray(bytes32 self) internal pure returns (bytes32[] memory arr) { + arr = new bytes32[](1); + arr[0] = self; + } + + function toSingletonArray(uint256 self) internal pure returns (uint256[] memory arr) { + arr = new uint256[](1); + arr[0] = self; + } + + /** + * @dev Returns whether or not there's a duplicate. Runs in O(n^2). + * @param arr Array to search + * @return Returns true if duplicate, false otherwise + */ + function hasDuplicate(uint256[] memory arr) internal pure returns (bool) { + if (arr.length == 0) return false; + + unchecked { + for (uint256 i; i < arr.length - 1; ++i) { + for (uint256 j = i + 1; j < arr.length; ++j) { + if (arr[i] == arr[j]) return true; + } + } + } + + return false; + } + + /** + * @dev Returns whether two arrays of addresses are equal or not. + */ + function isEqual(uint256[] memory self, uint256[] memory other) internal pure returns (bool yes) { + yes = hash(self) == hash(other); + } + + /** + * @dev Return the concatenated array from a and b. + */ + function extend(address[] memory a, address[] memory b) internal pure returns (address[] memory c) { + unchecked { + uint256 lengthA = a.length; + uint256 lengthB = b.length; + c = new address[](lengthA + lengthB); + + uint256 i; + for (; i < lengthA;) { + c[i] = a[i]; + ++i; + } + for (uint256 j; j < lengthB;) { + c[i] = b[j]; + ++i; + ++j; + } + } + } + + /** + * @dev Converts an array of uint8 to an array of uint256. + * @param self The array of uint8. + * @return uint256s The resulting array of uint256s. + */ function toUint256s(uint8[] memory self) internal pure returns (uint256[] memory uint256s) { assembly ("memory-safe") { uint256s := self } } + /** + * @dev Converts an array of uint96 to an array of uint256. + * @param self The array of uint96. + * @return uint256s The resulting array of uint256s. + */ + function toUint256s(uint96[] memory self) internal pure returns (uint256[] memory uint256s) { + assembly ("memory-safe") { + uint256s := self + } + } + + /** + * @dev Down cast an array of uint256 to an array of uint8. + * @param self The array of uint256. + * @return uint8s The resulting array of uint256s. + * This function will result in invalid data if value in array is greater than 255. + * Use it as caution. + */ function toUint8sUnsafe(uint256[] memory self) internal pure returns (uint8[] memory uint8s) { assembly ("memory-safe") { uint8s := self } } + function toUint96sUnsafe(uint256[] memory self) internal pure returns (uint96[] memory uint96s) { + assembly ("memory-safe") { + uint96s := self + } + } + + function toAddressesUnsafe(uint256[] memory self) internal pure returns (address[] memory addrs) { + assembly ("memory-safe") { + addrs := self + } + } + + /** + * @dev Create an array of indices with provided range. + * @param length The array size + * @return data an array of indices + */ function arange(uint256 length) internal pure returns (uint256[] memory data) { data = new uint256[](length); for (uint256 i; i < length; ++i) { @@ -41,16 +144,21 @@ library LibArray { } } - function hash(uint256[] memory data) internal pure returns (bytes32 digest) { + /** + * @dev Hash dynamic size array + * @param self The array of uint256 + * @return digest The hash result of the array + */ + function hash(uint256[] memory self) internal pure returns (bytes32 digest) { assembly ("memory-safe") { - digest := keccak256(add(data, 0x20), mload(data)) + digest := keccak256(add(self, 0x20), mul(mload(self), 0x20)) } } /** * @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. + * @return result The sum of the provided array. */ function sum(uint256[] memory data) internal pure returns (uint256 result) { assembly ("memory-safe") { @@ -78,7 +186,7 @@ library LibArray { /** * @dev Converts an array of uint64 to an array of uint256. - * @param self The array of bytes32. + * @param self The array of uint64. * @return uint256s The resulting array of uint256. */ function toUint256s(uint64[] memory self) internal pure returns (uint256[] memory uint256s) { @@ -87,17 +195,6 @@ library LibArray { } } - /** - * @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. diff --git a/script/post-check/BasePostCheck.s.sol b/script/post-check/BasePostCheck.s.sol new file mode 100644 index 00000000..05ce6d3e --- /dev/null +++ b/script/post-check/BasePostCheck.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Vm } from "forge-std/Vm.sol"; +import { StdStyle } from "forge-std/StdStyle.sol"; +import { console2 as console } from "forge-std/console2.sol"; +import { BaseMigration } from "foundry-deployment-kit/BaseMigration.s.sol"; +import { IBridgeManager } from "@ronin/contracts/interfaces/bridge/IBridgeManager.sol"; + +abstract contract BasePostCheck is BaseMigration { + using StdStyle for *; + + uint256 internal seed = vm.unixTime(); + address internal _bridgeSlash; + address internal _bridgeReward; + address internal _bridgeTracking; + mapping(uint256 chainId => address manager) internal _manager; + mapping(uint256 chainId => address gateway) internal _gateway; + + modifier onPostCheck(string memory postCheckLabel) { + uint256 snapshotId = _beforePostCheck(postCheckLabel); + _; + _afterPostCheck(postCheckLabel, snapshotId); + } + + function _beforePostCheck(string memory postCheckLabel) private returns (uint256 snapshotId) { + snapshotId = vm.snapshot(); + console.log("\n> ".cyan(), postCheckLabel.blue().italic(), "..."); + } + + function _afterPostCheck(string memory postCheckLabel, uint256 snapshotId) private { + console.log(string.concat("Postcheck", postCheckLabel.italic(), "successful!\n").green()); + vm.revertTo(snapshotId); + } +} diff --git a/script/post-check/PostCheck_StorageSlots.s.sol b/script/post-check/PostCheck_StorageSlots.s.sol new file mode 100644 index 00000000..d61a60bf --- /dev/null +++ b/script/post-check/PostCheck_StorageSlots.s.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { BasePostCheck } from "./BasePostCheck.s.sol"; + +abstract contract PostCheck_StorageSlot is BasePostCheck { + function validate_BridgeManager_StorageSlot() internal onPostCheck("validate_BridgeManager_StorageSlot") { } + + function validate_BridgeReward_StorageSlot() internal onPostCheck("validate_BridgeReward_StorageSlot") { } + + function validate_BridgeSlash_StorageSlot() internal onPostCheck("validateBridgeSlash_StorageSlot") { } + + function validate_BridgeTracking_StorageSlot() internal onPostCheck("validate_BridgeTracking_StorageSlot") { } +} diff --git a/script/post-check/bridge-manager/BridgeManager.post_check.tree b/script/post-check/bridge-manager/BridgeManager.post_check.tree new file mode 100644 index 00000000..9731e52f --- /dev/null +++ b/script/post-check/bridge-manager/BridgeManager.post_check.tree @@ -0,0 +1,91 @@ +BridgeManager + CRUD + add + when not self-called + it should revert + when self-called + when the list has duplicate + it should revert + when the list has null value + it should revert + when three input array mismatch + it should revert + when vote weight is zero + it should revert + when the valid input + governor should be in governor list + it should increase the total weight + governor should have expected weight + operator should have expected weight + it should map governor => bridge operator + it should map bridge operator => governor + bridge operator should be in operator list + it should notify proxy contract with valid data + it should notify immutable contract with valid data + remove + when not self-called + it should revert + when self-called + when the list has duplicate + it should revert + when the list has null value + it should revert + when operator is not in the operator list + it should not remove + when operator is in the list + governor should have 0 weight + it should decrease the total weight + bridge operator should have 0 weight + orders of other governors must unchanged + orders of other bridge operators must unchanged + it should notify proxy contract with valid data + it should notify immutable contract with valid data + it should remove the operator from the operator list + it should remove corresponding governor from the list + it should remove the mapping governor => bridge operator + it should remove the mapping bridge operator => governor + + update + when self-called + it should revert + when caller is not governor + it should revert + when caller is governor + when new bridge operator is null + it should revert + when new bridge operator mapped to another governor + it should revert + when current bridge operator == new bridge operator + it should revert + when new bridge operator is not in the list + total weight must not change + old bridge operator should have 0 weight + orders of other governors must unchanged + orders of other bridge operators must unchanged + it should notify proxy contract with valid data + it should notify immutable contract with valid data + it should add the mapping governor => new bridge operator + it should add the mapping new bridge operator => governor + new bridge operator should has old bridge operator weight + it should remove the mapping old bridge operator => governor + it should remove the mapping governor => old bridge operator + + Proposal + GlobalProposal + when the nonce is invalid + it should revert + when the caller is not the governor + it should verify signature with correct governor + when the target is BridgeManager + it should self call + ProposalForCurrentNetwork + when the nonce is invalid + it should revert + when the caller is not the governor + when the calldata includes signature + it should verify correct signature + when the calldata not includes signature + it should revert + when the target is BridgeManager + it should self call + \ No newline at end of file diff --git a/script/post-check/bridge-manager/PostCheck_BridgeManager.s.sol b/script/post-check/bridge-manager/PostCheck_BridgeManager.s.sol new file mode 100644 index 00000000..899bfad7 --- /dev/null +++ b/script/post-check/bridge-manager/PostCheck_BridgeManager.s.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +abstract contract PostCheck_BridgeManager { + +} \ No newline at end of file diff --git a/script/post-check/bridge-manager/crud/PostCheck_BridgeManager_CRUD_addBridgeOperators.s.sol b/script/post-check/bridge-manager/crud/PostCheck_BridgeManager_CRUD_addBridgeOperators.s.sol new file mode 100644 index 00000000..cf49e64b --- /dev/null +++ b/script/post-check/bridge-manager/crud/PostCheck_BridgeManager_CRUD_addBridgeOperators.s.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IBridgeManager } from "@ronin/contracts/interfaces/bridge/IBridgeManager.sol"; +import { BasePostCheck } from "script/post-check/BasePostCheck.s.sol"; +import { LibArray } from "script/libraries/LibArray.sol"; + +abstract contract PostCheck_BridgeManager_CRUD_AddBridgeOperators is BasePostCheck { + using LibArray for *; + + uint256 private voteWeight = 100; + string private seedStr = vm.toString(seed); + address private any = makeAddr(string.concat("any", seedStr)); + address private operator = makeAddr(string.concat("operator-", seedStr)); + address private governor = makeAddr(string.concat("governor-", seedStr)); + + function validate_RevertWhen_NotSelfCalled_addBridgeOperators() private { + vm.prank(any); + bool[] memory addeds = IBridgeManager(_manager[block.chainid]).addBridgeOperators( + voteWeight.toSingletonArray().toUint96sUnsafe(), operator.toSingletonArray(), governor.toSingletonArray() + ); + assertFalse(addeds[0], "addeds[0] == true"); + } + + function validate_RevertWhen_SelfCalled_TheListHasDuplicate_addBridgeOperators() private { + vm.prank(_manager[block.chainid]); + bool[] memory addeds = IBridgeManager(_manager[block.chainid]).addBridgeOperators( + voteWeight.toSingletonArray().toUint96sUnsafe(), operator.toSingletonArray(), operator.toSingletonArray() + ); + assertFalse(addeds[0], "addeds[0] == true"); + + vm.prank(_manager[block.chainid]); + addeds = IBridgeManager(_manager[block.chainid]).addBridgeOperators( + voteWeight.toSingletonArray().toUint96sUnsafe(), governor.toSingletonArray(), governor.toSingletonArray() + ); + assertFalse(addeds[0], "addeds[0] == true"); + + vm.prank(_manager[block.chainid]); + addeds = IBridgeManager(_manager[block.chainid]).addBridgeOperators( + voteWeight.toSingletonArray().toUint96sUnsafe(), + governor.toSingletonArray().extend(operator.toSingletonArray()), + operator.toSingletonArray().extend(governor.toSingletonArray()) + ); + assertFalse(addeds[0], "addeds[0] == true"); + } + + function validate_RevertWhen_SelfCalled_InputArrayLengthMismatch_addBridgeOperators() private { + vm.prank(_manager[block.chainid]); + vm.expectRevert(); + IBridgeManager(_manager[block.chainid]).addBridgeOperators( + voteWeight.toSingletonArray().toUint96sUnsafe(), + governor.toSingletonArray(), + operator.toSingletonArray().extend(governor.toSingletonArray()) + ); + } + + function validate_RevertWhen_SelfCalled_ContainsNullVoteWeight_addBridgeOperators() private { + vm.prank(_manager[block.chainid]); + vm.expectRevert(); + IBridgeManager(_manager[block.chainid]).addBridgeOperators( + uint256(0).toSingletonArray().toUint96sUnsafe(), + governor.toSingletonArray(), + operator.toSingletonArray().extend(governor.toSingletonArray()) + ); + } + + function validate_addBridgeOperators() private { + + } +} diff --git a/script/post-check/bridge-manager/crud/PostCheck_BridgeManager_CRUD_removeBridgeOperators.s.sol b/script/post-check/bridge-manager/crud/PostCheck_BridgeManager_CRUD_removeBridgeOperators.s.sol new file mode 100644 index 00000000..e69de29b diff --git a/script/post-check/bridge-manager/crud/PostCheck_BridgeManager_CRUD_updateBridgeOperator.s.sol b/script/post-check/bridge-manager/crud/PostCheck_BridgeManager_CRUD_updateBridgeOperator.s.sol new file mode 100644 index 00000000..e69de29b diff --git a/script/post-check/bridge-reward/BridgeReward.post_check.tree b/script/post-check/bridge-reward/BridgeReward.post_check.tree new file mode 100644 index 00000000..e69de29b diff --git a/script/post-check/bridge-reward/PostCheck_BridgeReward.s.sol b/script/post-check/bridge-reward/PostCheck_BridgeReward.s.sol new file mode 100644 index 00000000..e69de29b diff --git a/script/post-check/bridge-slash/BridgeSlash.post_check.tree b/script/post-check/bridge-slash/BridgeSlash.post_check.tree new file mode 100644 index 00000000..e69de29b diff --git a/script/post-check/bridge-slash/PostCheck_BridgeSlash.s.sol b/script/post-check/bridge-slash/PostCheck_BridgeSlash.s.sol new file mode 100644 index 00000000..e69de29b diff --git a/script/post-check/bridge-tracking/BridgeTracking.post_check.tree b/script/post-check/bridge-tracking/BridgeTracking.post_check.tree new file mode 100644 index 00000000..e69de29b diff --git a/script/post-check/bridge-tracking/PostCheck_BridgeTracking.s.sol b/script/post-check/bridge-tracking/PostCheck_BridgeTracking.s.sol new file mode 100644 index 00000000..e69de29b diff --git a/script/post-check/gateway/Gateway.post_check.tree b/script/post-check/gateway/Gateway.post_check.tree new file mode 100644 index 00000000..2166352d --- /dev/null +++ b/script/post-check/gateway/Gateway.post_check.tree @@ -0,0 +1,5 @@ +Gateway + deposit + when user deposit + withdraw + when user withdraw \ No newline at end of file diff --git a/script/post-check/gateway/PostCheck_Gateway.s.sol b/script/post-check/gateway/PostCheck_Gateway.s.sol new file mode 100644 index 00000000..7570920e --- /dev/null +++ b/script/post-check/gateway/PostCheck_Gateway.s.sol @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; \ No newline at end of file