From b6121c85342f5bc748bec505da2f71ecf0f1bd3c Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Tue, 25 Feb 2025 20:13:23 +0000 Subject: [PATCH 1/9] (feat) Beginning of importing the UCCB contracts --- zilliqa/src/contracts/foundry.toml | 11 + .../tests/uccb/ChainDispatcher.t.sol | 361 ++++++++++++++++++ zilliqa/src/contracts/tests/uccb/Helpers.sol | 154 ++++++++ .../src/contracts/uccb/ChainDispatcher.sol | 200 ++++++++++ zilliqa/src/contracts/uccb/ChainGateway.sol | 50 +++ .../contracts/uccb/DispatchReplayChecker.sol | 88 +++++ zilliqa/src/contracts/uccb/Registry.sol | 105 +++++ zilliqa/src/contracts/uccb/Relayer.sol | 199 ++++++++++ .../src/contracts/uccb/ValidatorManager.sol | 170 +++++++++ 9 files changed, 1338 insertions(+) create mode 100644 zilliqa/src/contracts/foundry.toml create mode 100644 zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol create mode 100644 zilliqa/src/contracts/tests/uccb/Helpers.sol create mode 100644 zilliqa/src/contracts/uccb/ChainDispatcher.sol create mode 100644 zilliqa/src/contracts/uccb/ChainGateway.sol create mode 100644 zilliqa/src/contracts/uccb/DispatchReplayChecker.sol create mode 100644 zilliqa/src/contracts/uccb/Registry.sol create mode 100644 zilliqa/src/contracts/uccb/Relayer.sol create mode 100644 zilliqa/src/contracts/uccb/ValidatorManager.sol diff --git a/zilliqa/src/contracts/foundry.toml b/zilliqa/src/contracts/foundry.toml new file mode 100644 index 0000000000..da87407f4b --- /dev/null +++ b/zilliqa/src/contracts/foundry.toml @@ -0,0 +1,11 @@ +[profile.default] +src='.' +out='out' +libs=['vendor'] +test='tests' +cache_path='cache-foundry' +via_ir=true +auto_detect_solc=true +build_info=true +extra_ouptut=['storageLayout'] +solc='0.8.28' diff --git a/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol b/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol new file mode 100644 index 0000000000..da6549bf3e --- /dev/null +++ b/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {Target, ValidatorManagerFixture, IReentrancy} from "./Helpers.sol"; + +import {ChainDispatcher, IChainDispatcherEvents, IChainDispatcherErrors} from "contracts/core-upgradeable/ChainDispatcher.sol"; + +import {ISignatureValidatorErrors} from "../../SignatureValidator.sol"; +import {IDispatchReplayCheckerErrors} from "../../DispatchReplayChecker.sol"; + +import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +library DispatchArgsBuilder { + struct DispatchArgs { + uint sourceChainId; + address target; + bytes call; + uint gasLimit; + uint nonce; + } + + function instance( + address target + ) external pure returns (DispatchArgs memory args) { + args.sourceChainId = 1; + args.target = target; + args.call = abi.encodeWithSelector(Target.work.selector, uint(1)); + args.gasLimit = 1_000_000; + args.nonce = 1; + } + + function withCall( + DispatchArgs memory args, + bytes calldata call + ) external pure returns (DispatchArgs memory) { + args.call = call; + return args; + } +} + +contract ChainDispatcherHarness is + Initializable, + UUPSUpgradeable, + ChainDispatcherUpgradeable +{ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize( + address _owner, + address _validatorManager + ) external initializer { + __ChainDispatcher_init(_owner, _validatorManager); + } + + function _authorizeUpgrade(address) internal virtual override onlyOwner {} +} + +contract DispatcherFixture is IChainDispatcherEvents, ValidatorManagerFixture { + using MessageHashUtils for bytes; + using DispatchArgsBuilder for DispatchArgsBuilder.DispatchArgs; + + address owner = vm.createWallet("Owner").addr; + + Target internal immutable target = new Target(); + ChainDispatcherHarness dispatcher; + + constructor() ValidatorManagerFixture() {} + + function setUp() external { + address implementation = address(new ChainDispatcherHarness()); + address proxy = address( + new ERC1967Proxy( + implementation, + abi.encodeWithSelector( + ChainDispatcherHarness.initialize.selector, + owner, + address(validatorManager) + ) + ) + ); + dispatcher = ChainDispatcherHarness(proxy); + } + + function signDispatch( + DispatchArgsBuilder.DispatchArgs memory args + ) public returns (bytes[] memory signatures) { + bytes32 hashedMessage = abi + .encode( + args.sourceChainId, + block.chainid, + args.target, + args.call, + args.gasLimit, + args.nonce + ) + .toEthSignedMessageHash(); + + signatures = multiSign(sort(validators), hashedMessage); + } +} + +contract ChainDispatcherTests is DispatcherFixture { + using DispatchArgsBuilder for DispatchArgsBuilder.DispatchArgs; + + function test_happyPath() external { + DispatchArgsBuilder.DispatchArgs memory args = DispatchArgsBuilder + .instance(address(target)); + bytes[] memory signatures = signDispatch(args); + + vm.expectCall(address(target), args.call); + vm.expectEmit(address(dispatcher)); + emit Dispatched( + args.sourceChainId, + args.target, + true, + abi.encode(uint(2)), + args.nonce + ); + dispatcher.dispatch( + args.sourceChainId, + args.target, + args.call, + args.gasLimit, + args.nonce, + signatures + ); + assertEq(dispatcher.dispatched(args.sourceChainId, args.nonce), true); + } + + function testRevert_badSignature() external { + // Prepare call + DispatchArgsBuilder.DispatchArgs memory args = DispatchArgsBuilder + .instance(address(target)); + bytes[] memory signatures = signDispatch(args); + uint badNonce = args.nonce + 1; + + vm.expectRevert( + ISignatureValidatorErrors.InvalidValidatorOrSignatures.selector + ); + // Dispatch + dispatcher.dispatch( + args.sourceChainId, + args.target, + args.call, + args.gasLimit, + badNonce, + signatures + ); + assertEq(dispatcher.dispatched(args.sourceChainId, args.nonce), false); + } + + function testRevert_replay() external { + // Prepare call + DispatchArgsBuilder.DispatchArgs memory args = DispatchArgsBuilder + .instance(address(target)); + bytes[] memory signatures = signDispatch(args); + + // Dispatch + dispatcher.dispatch( + args.sourceChainId, + args.target, + args.call, + args.gasLimit, + args.nonce, + signatures + ); + assertEq(dispatcher.dispatched(args.sourceChainId, args.nonce), true); + // Replay + vm.expectRevert( + IDispatchReplayCheckerErrors.AlreadyDispatched.selector + ); + dispatcher.dispatch( + args.sourceChainId, + args.target, + args.call, + args.gasLimit, + args.nonce, + signatures + ); + assertEq(dispatcher.dispatched(args.sourceChainId, args.nonce), true); + } + + function test_failedCall() external { + uint num = 1000; + bytes memory failedCall = abi.encodeWithSelector( + target.work.selector, + num + ); + DispatchArgsBuilder.DispatchArgs memory args = DispatchArgsBuilder + .instance(address(target)) + .withCall(failedCall); + bytes[] memory signatures = signDispatch(args); + + // Dispatch + vm.expectCall(address(target), failedCall); + + bytes memory expectedError = abi.encodeWithSignature( + "Error(string)", + "Too large" + ); + vm.expectEmit(address(dispatcher)); + emit Dispatched( + args.sourceChainId, + args.target, + false, + expectedError, + args.nonce + ); + dispatcher.dispatch( + args.sourceChainId, + args.target, + args.call, + args.gasLimit, + args.nonce, + signatures + ); + assertEq(dispatcher.dispatched(args.sourceChainId, args.nonce), true); + } + + function test_nonContractCallerWithFailedCall() external { + DispatchArgsBuilder.DispatchArgs memory args = DispatchArgsBuilder + .instance(vm.addr(1001)); + bytes[] memory signatures = signDispatch(args); + + // Dispatch + bytes memory expectedError = abi.encodeWithSelector( + IChainDispatcherErrors.NonContractCaller.selector, + args.target + ); + vm.expectEmit(address(dispatcher)); + emit Dispatched( + args.sourceChainId, + args.target, + false, + expectedError, + args.nonce + ); + // Dispatch + dispatcher.dispatch( + args.sourceChainId, + args.target, + args.call, + args.gasLimit, + args.nonce, + signatures + ); + + assertEq(dispatcher.dispatched(args.sourceChainId, args.nonce), true); + } + + function test_outOfGasCall() external { + bytes memory call = abi.encodeWithSelector( + target.infiniteLoop.selector + ); + DispatchArgsBuilder.DispatchArgs memory args = DispatchArgsBuilder + .instance(address(target)) + .withCall(call); + bytes[] memory signatures = signDispatch(args); + + // Dispatch + vm.expectCall(address(target), args.call); + vm.expectEmit(address(dispatcher)); + emit Dispatched( + args.sourceChainId, + args.target, + false, + hex"", // denotes out of gas + args.nonce + ); + dispatcher.dispatch( + args.sourceChainId, + args.target, + args.call, + args.gasLimit, + args.nonce, + signatures + ); + + assertEq(dispatcher.dispatched(args.sourceChainId, args.nonce), true); + assertEq(target.c(), uint(0)); + } + + function test_reentrancy() external { + bytes memory call = abi.encodeWithSelector(target.reentrancy.selector); + DispatchArgsBuilder.DispatchArgs memory args = DispatchArgsBuilder + .instance(address(target)) + .withCall(call); + bytes[] memory signatures = signDispatch(args); + + target.setReentrancyConfig( + address(dispatcher), + abi.encodeWithSelector( + dispatcher.dispatch.selector, + args.sourceChainId, + args.target, + args.call, + args.gasLimit, + args.nonce, + signatures + ) + ); + + // Dispatch + bytes memory expectedError = abi.encodeWithSelector( + IReentrancy.ReentrancySafe.selector + ); + vm.expectEmit(address(dispatcher)); + emit Dispatched( + args.sourceChainId, + args.target, + false, + expectedError, + args.nonce + ); + dispatcher.dispatch( + args.sourceChainId, + args.target, + args.call, + args.gasLimit, + args.nonce, + signatures + ); + assertEq(dispatcher.dispatched(args.sourceChainId, args.nonce), true); + } + + function test_updateValidatorManager() external { + address newValidatorManager = vm + .createWallet("NewValidatorManager") + .addr; + + assertEq(dispatcher.validatorManager(), address(validatorManager)); + vm.prank(owner); + dispatcher.setValidatorManager(newValidatorManager); + assertEq(dispatcher.validatorManager(), newValidatorManager); + } + + function testRevert_updateValidatorManagerWhenNotOwner() external { + address newValidatorManager = vm + .createWallet("NewValidatorManager") + .addr; + address notOwner = vm.createWallet("notOwner").addr; + + assertEq(dispatcher.validatorManager(), address(validatorManager)); + vm.prank(notOwner); + vm.expectRevert( + abi.encodeWithSelector( + OwnableUpgradeable.OwnableUnauthorizedAccount.selector, + notOwner + ) + ); + dispatcher.setValidatorManager(newValidatorManager); + assertEq(dispatcher.validatorManager(), address(validatorManager)); + } +} diff --git a/zilliqa/src/contracts/tests/uccb/Helpers.sol b/zilliqa/src/contracts/tests/uccb/Helpers.sol new file mode 100644 index 0000000000..3b873bc802 --- /dev/null +++ b/zilliqa/src/contracts/tests/uccb/Helpers.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {ValidatorManager} from "../../uccb/ValidatorManager.sol"; +import {Tester, Vm} from "test/Tester.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; + +library UUPSUpgrader { + function deploy( + string memory location, + bytes memory initializerData + ) internal returns (address proxy) { + Options memory opts; + opts.unsafeSkipAllChecks = true; + + proxy = Upgrades.deployUUPSProxy(location, initializerData, opts); + } + + function upgrade( + address proxy, + string memory location, + bytes memory initializerData, + address deployer + ) internal { + Options memory opts; + opts.unsafeSkipAllChecks = true; + + Upgrades.upgradeProxy(proxy, location, initializerData, opts, deployer); + } +} + +contract TransferReentrancyTester { + address target; + bytes data; + bool public alreadyEntered = false; + + function reentrancyAttack( + address _target, + bytes calldata _data + ) external returns (bool) { + target = _target; + data = _data; + + (bool success, ) = target.call(data); + return success; + } + + receive() external payable { + if (address(target).balance > 0) { + (bool success, ) = target.call(data); + success; + } + } +} + +interface IReentrancy { + error ReentrancyVulnerability(); + error ReentrancySafe(); +} + +contract Target is IReentrancy { + uint public c = 0; + + function depositFee(uint amount) external payable { + amount; + } + + function work(uint num_) external pure returns (uint) { + require(num_ < 1000, "Too large"); + return num_ + 1; + } + + function infiniteLoop() public { + while (true) { + c = c + 1; + } + } + + function finish(bool success, bytes calldata res, uint nonce) external {} + + function finishRevert( + bool success, + bytes calldata res, + uint nonce + ) external pure { + success; + res; + nonce; + revert(); + } + + bool public alreadyEntered = false; + bytes public reentrancyCalldata; + address public reentrancyTarget; + + function setReentrancyConfig(address target, bytes calldata data) external { + reentrancyTarget = target; + reentrancyCalldata = data; + } + + function reentrancy() external { + if (alreadyEntered) { + revert IReentrancy.ReentrancyVulnerability(); + } + alreadyEntered = true; + (bool success, ) = reentrancyTarget.call(reentrancyCalldata); + if (success) { + revert IReentrancy.ReentrancyVulnerability(); + } + revert IReentrancy.ReentrancySafe(); + } +} + +abstract contract ValidatorManagerFixture is Tester { + uint constant VALIDATOR_COUNT = 10; + + ValidatorManager validatorManager; + Vm.Wallet[] public validators = new Vm.Wallet[](VALIDATOR_COUNT); + + function generateValidatorManager( + uint size + ) internal returns (Vm.Wallet[] memory, ValidatorManager) { + Vm.Wallet[] memory _validators = new Vm.Wallet[](size); + address[] memory validatorAddresses = new address[](size); + + for (uint i = 0; i < size; ++i) { + _validators[i] = vm.createWallet(i + 1); + validatorAddresses[i] = _validators[i].addr; + } + ValidatorManager _validatorManager = new ValidatorManager( + address(this) + ); + _validatorManager.initialize(validatorAddresses); + + return (_validators, _validatorManager); + } + + constructor() { + // Setup validator manager + ( + Vm.Wallet[] memory _validators, + ValidatorManager _validatorManager + ) = generateValidatorManager(VALIDATOR_COUNT); + validators = _validators; + validatorManager = _validatorManager; + } +} + +contract TestToken is ERC20 { + constructor(uint256 initialSupply) ERC20("Test", "T") { + _mint(msg.sender, initialSupply); + } +} diff --git a/zilliqa/src/contracts/uccb/ChainDispatcher.sol b/zilliqa/src/contracts/uccb/ChainDispatcher.sol new file mode 100644 index 0000000000..c199c45c4f --- /dev/null +++ b/zilliqa/src/contracts/uccb/ChainDispatcher.sol @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; + +import {IValidatorManager} from "uccb/ValidatorManager.sol"; +import {IDispatchReplayChecker, DispatchReplayChecker} from "uccb/DispatchReplayChecker.sol"; + +interface IChainDispatcherEvents { + /** + * @dev Triggered when an event enters this chain + */ + event Dispatched( + uint indexed sourceChainId, + address indexed target, + bool success, + bytes response, + uint indexed nonce + ); +} + +interface IChainDispatcherErrors { + /** + * @dev The target address being called must be a contract or the call will fall through + */ + error NonContractCaller(address target); +} + +interface IChainDispatcher is + IChainDispatcherEvents, + IChainDispatcherErrors, + IDispatchReplayChecker +{ + function validatorManager() external view returns (address); + + function setValidatorManager(address validatorManager) external; + + function dispatch( + uint sourceChainId, + address target, + bytes calldata call, + uint gasLimit, + uint nonce, + bytes[] calldata signatures + ) external; +} + +/** + * @title ChainDispatcher + * @notice Handles everything related to receiving messages from other chains to be dispatched + * @dev This contract should be used by inherited for cross-chain messaging. It is also made upgradeable. + * + * The `dispatch` function will dispatch a message sourcing from a different chain + * It is able to relay message to any arbitrary chain that is part of the UCCB network + */ +abstract contract ChainDispatcherUpgradeable is + IChainDispatcher, + Initializable, + Ownable2StepUpgradeable, + DispatchReplayCheckerUpgradeable +{ + using MessageHashUtils for bytes; + + /** + * @dev Storage of the initializable contract. + * + * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions + * when using with upgradeable contracts. + * + * @custom:storage-location erc7201:zilliqa.storage.ChainDispatcher + */ + struct ChainDispatcherStorage { + IValidatorManager validatorManager; + } + + // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.ChainDispatcher")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant CHAIN_DISPATCHER_STORAGE_POSITION = + 0x8cff60b14f9f959be48079fe56fd2ddb283fd144e381f4bd805400fbf1d0d600; + + /** + * @dev Returns a pointer to the storage namespace. + */ + function _getChainDispatcherStorage() + private + pure + returns (ChainDispatcherStorage storage $) + { + assembly { + $.slot := CHAIN_DISPATCHER_STORAGE_POSITION + } + } + + /** + * @dev Initializes the contracts with all the inherited contracts + */ + function __ChainDispatcher_init( + address _owner, + address _validatorManager + ) internal onlyInitializing { + __Ownable_init(_owner); + __ChainDispatcher_init_unchained(_validatorManager); + } + + /** + * @dev The unchained version is used to avoid repeated initializations down the inheritance path + */ + function __ChainDispatcher_init_unchained( + address _validatorManager + ) internal onlyInitializing { + _setValidatorManager(_validatorManager); + } + + /** + * @dev Returns the address of the validator manager used to validator messages + */ + function validatorManager() external view returns (address) { + ChainDispatcherStorage storage $ = _getChainDispatcherStorage(); + return address($.validatorManager); + } + + /** + * @dev Sets the validator manager + */ + function _setValidatorManager(address _validatorManager) internal { + ChainDispatcherStorage storage $ = _getChainDispatcherStorage(); + $.validatorManager = IValidatorManager(_validatorManager); + } + + /** + * @dev External function to set validator manager and permissioned by owner + */ + function setValidatorManager(address _validatorManager) external onlyOwner { + _setValidatorManager(_validatorManager); + } + + /** + * @dev Dispatches a message from another chain, it also verifies the signatures from the dispatchers + * + * The function will should not revert on the underlying call instruction made to the target contract + * and should catch all cases the it would fail. + * + * All other sources of transaction failure would come from the validation of the signatures of the call + * or due to cross-chain message replay, where the same nonce is being used repeatedly + * + * NOTE: The exception to reverting due to underlying call can be caused if the call is made to a scilla interoperability precompile + * where if this fails, it will revert the whole transaction + * + * @param sourceChainId the chainid where the message originated + * @param target the address of the contract to be called + * @param call the call data to be used in the call + * @param gasLimit the gas limit to be used in the call + * @param nonce the nonce from the relayer on the source chain + * @param signatures the signatures of the messages of the validator + */ + function dispatch( + uint sourceChainId, + address target, + bytes calldata call, + uint gasLimit, + uint nonce, + bytes[] calldata signatures + ) external replayDispatchGuard(sourceChainId, nonce) { + ChainDispatcherStorage storage $ = _getChainDispatcherStorage(); + + $.validatorManager.validateMessageWithSupermajority( + abi + .encode( + sourceChainId, + block.chainid, + target, + call, + gasLimit, + nonce + ) + .toEthSignedMessageHash(), + signatures + ); + + // If it is not a contract the call itself should not revert + if (target.code.length == 0) { + emit Dispatched( + sourceChainId, + target, + false, + abi.encodeWithSelector(NonContractCaller.selector, target), + nonce + ); + return; + } + + // This call will not revert the transaction + (bool success, bytes memory response) = (target).call{gas: gasLimit}( + call + ); + + emit Dispatched(sourceChainId, target, success, response, nonce); + } +} diff --git a/zilliqa/src/contracts/uccb/ChainGateway.sol b/zilliqa/src/contracts/uccb/ChainGateway.sol new file mode 100644 index 0000000000..0b98b72257 --- /dev/null +++ b/zilliqa/src/contracts/uccb/ChainGateway.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; + +import {IChainDispatcher, ChainDispatcher} from "contracts/uccb/ChainDispatcher.sol"; +import {IRelayer, RelayerUpgradeable} from "contracts/uccb/Relayer.sol"; + +interface IChainGateway is IRelayer, IChainDispatcher {} + +/** + * @title ChainGateway + * @notice The main core contract that is deployed on everychain to handle cross-chain messaging + * It inherits 2 important contracts Relayer and ChainDispatcher: + * The Relayer handles outbound messages with `relay` and `relayWithMetadata` functions. + * The ChainDispatcher serves for inbound messages. + * The contract is also UUPS upgradeable. + * For future upgrades of the contract refer to: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable + */ +contract ChainGatewayUpgradeable is + Initializable, + UUPSUpgradeable, + Ownable2StepUpgradeable, + RelayerUpgradeable, + ChainDispatcherUpgradeable +{ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /** + * @dev Initializer for the contract + */ + function initialize( + address _validatorManager, + address _owner + ) external initializer { + __Ownable_init(_owner); + __Relayer_init_unchained(); + __ChainDispatcher_init_unchained(_validatorManager); + } + + /** + * @dev Override used to secure the upgrade call to the contract owner + */ + function _authorizeUpgrade(address) internal virtual override onlyOwner {} +} diff --git a/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol b/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol new file mode 100644 index 0000000000..4fdbc8ebfe --- /dev/null +++ b/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +interface IDispatchReplayCheckerErrors { + /** + * @dev Triggered to when a repeated nonce is detected on dispatch request + */ + error AlreadyDispatched(); +} + +interface IDispatchReplayChecker is IDispatchReplayCheckerErrors { + function dispatched( + uint sourceChainId, + uint nonce + ) external view returns (bool); +} + +/** + * @title DispatchReplayChecker + * @notice Prevents dispatch replay attacks by keeping track of dispatched nonces + * @dev The contract has a modifier that can be used to protect functions from replay attacks + * essentially prevent the same message from being dispatched twice + * The combination of `(sourceChainId, nonce)` form a unique key pair. + */ +abstract contract DispatchReplayCheckerUpgradeable is IDispatchReplayChecker { + /** + * @dev Storage of the initializable contract. + * + * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions + * when using with upgradeable contracts. + * + * @custom:storage-location erc7201:zilliqa.storage.DispatchReplayChecker + */ + struct DispatchReplayCheckerStorage { + // sourceChainId => nonce => isDispatched + mapping(uint => mapping(uint => bool)) dispatched; + } + + // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.DispatchReplayChecker")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant DISPATCH_REPLAY_CHECKER_STORAGE_POSITION = + 0xf0d7858cd36fafa025d5af5f0a6a6196668a9b0994a77eee7583c69fc18dfb00; + + /** + * @dev Returns a pointer to the storage namespace. + */ + function _getDispatchReplayCheckerStorage() + private + pure + returns (DispatchReplayCheckerStorage storage $) + { + assembly { + $.slot := DISPATCH_REPLAY_CHECKER_STORAGE_POSITION + } + } + + /** + * @dev view function to verify if a message has been dispatched + */ + function dispatched( + uint sourceChainId, + uint nonce + ) external view returns (bool) { + DispatchReplayCheckerStorage + storage $ = _getDispatchReplayCheckerStorage(); + return $.dispatched[sourceChainId][nonce]; + } + + /** + * @dev Internal function handling the replay check and reverts if the message has been dispatched + */ + function _replayDispatchCheck(uint sourceChainId, uint nonce) internal { + DispatchReplayCheckerStorage + storage $ = _getDispatchReplayCheckerStorage(); + + if ($.dispatched[sourceChainId][nonce]) { + revert AlreadyDispatched(); + } + $.dispatched[sourceChainId][nonce] = true; + } + + /** + * @dev Modifier to protect functions from replay attacks and used by child contracts + */ + modifier replayDispatchGuard(uint sourceShardId, uint nonce) { + _replayDispatchCheck(sourceShardId, nonce); + _; + } +} diff --git a/zilliqa/src/contracts/uccb/Registry.sol b/zilliqa/src/contracts/uccb/Registry.sol new file mode 100644 index 0000000000..56a192fce1 --- /dev/null +++ b/zilliqa/src/contracts/uccb/Registry.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +interface IRegistryErrors { + /** + * @dev error thrown when modifier detects a unregistered address + */ + error NotRegistered(address targetAddress); +} + +interface IRegistryEvents { + /** + * @dev Triggered when a new address is registered + */ + event ContractRegistered(address target); + /** + * @dev Triggered when a address is removed + */ + event ContractUnregistered(address target); +} + +interface IRegistry is IRegistryErrors, IRegistryEvents { + function registered(address target) external view returns (bool); + + function register(address newTarget) external; + + function unregister(address removeTarget) external; +} + +/** + * @title Registry + * @notice Holds registered contracts that are allowed to be used by the + * contract that inherits this one + * Includes the `isRegistered` modifier that other contracts can leverage + */ +abstract contract RegistryUpgradeable is IRegistry { + /** + * @dev Storage of the initializable contract. + * + * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions + * when using with upgradeable contracts. + * + * @custom:storage-location erc7201:zilliqa.storage.Registry + */ + struct RegistryStorage { + mapping(address => bool) registered; + } + + // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.Registry")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant REGISTRY_STORAGE_POSITION = + 0x4432bdf0e567007e5ad3c8ad839a7f885ef69723eaa659dd9f06e98a97274300; + + /** + * @dev Returns a pointer to the storage namespace. + */ + function _getRegistryStorage() + private + pure + returns (RegistryStorage storage $) + { + assembly { + $.slot := REGISTRY_STORAGE_POSITION + } + } + + /** + * @dev modifier used by contracts that inherit from this one to check if + * the given `target` is part of the registry + */ + modifier isRegistered(address target) { + RegistryStorage storage $ = _getRegistryStorage(); + if (!registered(target)) { + revert NotRegistered(target); + } + _; + } + + /** + * @dev public function returns whether `target` is part of the registry + */ + function registered(address target) public view returns (bool) { + RegistryStorage storage $ = _getRegistryStorage(); + return $.registered[target]; + } + + /** + * @dev Internal function to register a new address + * Can be exposed through child contract + */ + function _register(address newTarget) internal { + RegistryStorage storage $ = _getRegistryStorage(); + $.registered[newTarget] = true; + emit ContractRegistered(newTarget); + } + + /** + * @dev Internal function to unregister an address + * Can be exposed through child contract + */ + function _unregister(address removeTarget) internal { + RegistryStorage storage $ = _getRegistryStorage(); + $.registered[removeTarget] = false; + emit ContractUnregistered(removeTarget); + } +} diff --git a/zilliqa/src/contracts/uccb/Relayer.sol b/zilliqa/src/contracts/uccb/Relayer.sol new file mode 100644 index 0000000000..f57fbd0e01 --- /dev/null +++ b/zilliqa/src/contracts/uccb/Relayer.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +import {RegistryUpgradeable, IRegistry} from "contracts/uccb/Registry.sol"; + +interface IRelayerEvents { + /** + * @dev Triggered when a outgoing message is relayed to another chain + */ + event Relayed( + uint indexed targetChainId, + address target, + bytes call, + uint gasLimit, + uint nonce + ); +} + +interface IRelayer is IRelayerEvents, IRegistry { + /** + * @dev Incorporates the extra metadata to add on relay + */ + struct CallMetadata { + uint sourceChainId; + address sender; + } + + function nonce(uint chainId) external view returns (uint); + + function relayWithMetadata( + uint targetChainId, + address target, + bytes4 callSelector, + bytes calldata callData, + uint gasLimit + ) external returns (uint); + + function relay( + uint targetChainId, + address target, + bytes calldata call, + uint gasLimit + ) external returns (uint); +} + +/** + * @title Relayer + * @notice Handles everything related to outgoing messages to be dispatched on other chains + * @dev This contract should be used by inherited for cross-chain messaging. It is also made upgradeable. + * + * It is able to relay message to any arbitrary chain that is part of the UCCB network + */ +abstract contract RelayerUpgradeable is + IRelayer, + Initializable, + Ownable2StepUpgradeable, + RegistryUpgradeable +{ + /** + * @dev Storage of the initializable contract. + * + * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions + * when using with upgradeable contracts. + * + * @custom:storage-location erc7201:zilliqa.storage.Relayer + */ + struct RelayerStorage { + // TargetChainId => Nonce + mapping(uint => uint) nonce; + } + + // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.Relayer")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant RELAYER_STORAGE_POSITION = + 0x814fccf6b0465c7c83d1a86cf4c4cdd0d8463969cbd4702358f5ae439f30a000; + + /** + * @dev Returns a pointer to the storage namespace. + */ + function _getRelayerStorage() + private + pure + returns (RelayerStorage storage $) + { + assembly { + $.slot := RELAYER_STORAGE_POSITION + } + } + + /** + * @dev Initializes the contracts with all the inherited contracts + */ + function __Relayer_init(address _owner) internal onlyInitializing { + __Ownable_init(_owner); + __Relayer_init_unchained(); + } + + /** + * @dev The unchained version is used to avoid repeated initializations down the inheritance path + */ + function __Relayer_init_unchained() internal onlyInitializing {} + + /** + * @dev Returns the nonce for a given chain + */ + function nonce(uint chainId) external view returns (uint) { + RelayerStorage storage $ = _getRelayerStorage(); + return $.nonce[chainId]; + } + + /** + * @dev internal relay function shared by the different implementations + * + * Nonces start counting from 1 + * It is also secured by the registry set. So only approved addresses can call relay + * Eventually we can remove `isRegistered` and allow it for public use. + * This requires a proper fee system to prevent abuse. + + */ + function _relay( + uint targetChainId, + address target, + bytes memory call, + uint gasLimit + ) internal isRegistered(_msgSender()) returns (uint) { + RelayerStorage storage $ = _getRelayerStorage(); + uint _nonce = ++$.nonce[targetChainId]; + + emit Relayed(targetChainId, target, call, gasLimit, _nonce); + return _nonce; + } + + /** + * @dev Basic relay called by contracts on the source chain to send message to target chain + * The sender needs to encode the call data and the target chain id with abi of the callee function + * + * @param targetChainId the chain id the message is intended to be sent to + * @param target the address of the contract on the target chain to execute the call + * @param call the encoded call data to be executed on the target address on the target chain + * @param gasLimit the gas limit for the call executed on the target chain + */ + function relay( + uint targetChainId, + address target, + bytes calldata call, + uint gasLimit + ) external returns (uint) { + return _relay(targetChainId, target, call, gasLimit); + } + + /** + * @dev Use this function to relay a call with metadata. This is useful when the dispatched function on the target chain requires the metadata + * For example they may need to verify the sender or the nonce of the transaction on the source chain. + * When packed here, we can ensure that the metadata is not tampered with + * + * NOTE: Ensure the target function conforms to the required abi format `function(CallMetadata, bytes)` + * + * @param targetChainId the chain id the message is intended to be sent to + * @param target the address of the contract on the target chain to execute the call + * @param callSelector the selector of the function to be called on target + * @param callData the calldata to be appended on the call selector + * @param gasLimit the gas limit for the call executed on the target chain + */ + function relayWithMetadata( + uint targetChainId, + address target, + bytes4 callSelector, + bytes calldata callData, + uint gasLimit + ) external returns (uint) { + return + _relay( + targetChainId, + target, + abi.encodeWithSelector( + callSelector, + CallMetadata(block.chainid, _msgSender()), + callData + ), + gasLimit + ); + } + + /** + * @dev Able to register new addresses that can call the relayer + */ + function register(address newTarget) external override onlyOwner { + _register(newTarget); + } + + /** + * @dev Removes an address from the registry. Thus preventing them to call relayer + */ + function unregister(address removeTarget) external override onlyOwner { + _unregister(removeTarget); + } +} diff --git a/zilliqa/src/contracts/uccb/ValidatorManager.sol b/zilliqa/src/contracts/uccb/ValidatorManager.sol new file mode 100644 index 0000000000..2f79d475db --- /dev/null +++ b/zilliqa/src/contracts/uccb/ValidatorManager.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +import {ISignatureValidatorErrors, SignatureValidator} from "contracts/uccb/SignatureValidator.sol"; + +interface IValidatorManager is ISignatureValidatorErrors { + function addValidator(address user) external returns (bool); + + function removeValidator(address user) external returns (bool); + + function getValidators() external view returns (address[] memory); + + function isValidator(address user) external view returns (bool); + + function validatorsSize() external view returns (uint); + + function validateMessageWithSupermajority( + bytes32 ethSignedMessageHash, + bytes[] calldata signatures + ) external view; +} + +/** + * @title ValidatorManager + * @notice Manages the validators for the UCCB network + * It can be used by `ChainGateway` contract to verify the signatures of the validators + * on incoming dispatch requests + */ +contract ValidatorManagerUpgradeable is + IValidatorManager, + Initializable, + UUPSUpgradeable, + Ownable2StepUpgradeable +{ + using EnumerableSet for EnumerableSet.AddressSet; + using SignatureValidator for EnumerableSet.AddressSet; + + /** + * @dev Storage of the initializable contract. + * + * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions + * when using with upgradeable contracts. + * + * @custom:storage-location erc7201:zilliqa.storage.ValidatorManager + */ + struct ValidatorManagerStorage { + EnumerableSet.AddressSet validators; + } + + // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.ValidatorManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant VALIDATOR_MANAGER_STORAGE_POSITION = + 0x7accde04f7b3831ef9580fa40c18d71adaa2564f23664e60f2464dcc899c5400; + + /** + * @dev Returns a pointer to the storage namespace. + */ + function _getValidatorManagerStorage() + private + pure + returns (ValidatorManagerStorage storage $) + { + assembly { + $.slot := VALIDATOR_MANAGER_STORAGE_POSITION + } + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /** + * @notice Initializes the contract with the owner and the initial validators + */ + function initialize( + address _owner, + address[] calldata validators + ) external initializer { + __Ownable_init(_owner); + + uint validatorsLength = validators.length; + for (uint i = 0; i < validatorsLength; ++i) { + _addValidator(validators[i]); + } + } + + /** + * @dev Restricts update to only the owner + */ + function _authorizeUpgrade(address) internal virtual override onlyOwner {} + + /** + * @dev Internal getter for validators + */ + function _validators() + internal + view + returns (EnumerableSet.AddressSet storage) + { + ValidatorManagerStorage storage $ = _getValidatorManagerStorage(); + return $.validators; + } + + /** + * @dev internal setter to add new validator + */ + function _addValidator(address user) internal returns (bool) { + return _validators().add(user); + } + + /** + * @dev external function to add new validator restricted to owner + */ + function addValidator(address user) public onlyOwner returns (bool) { + return _addValidator(user); + } + + /** + * @dev external function to remove validator restricted to owner + */ + function removeValidator(address user) external onlyOwner returns (bool) { + return _validators().remove(user); + } + + /** + * @dev external function to get all validators + * Expensive function, avoid calling on-chain. + * Should be used off-chain only. + */ + function getValidators() external view returns (address[] memory) { + return _validators().values(); + } + + /** + * @dev getter to check if the user is part of the validator set + */ + function isValidator(address user) external view returns (bool) { + return _validators().contains(user); + } + + /** + * @dev getter to get the size of the validator set + */ + function validatorsSize() external view returns (uint) { + return _validators().length(); + } + + /** + * @dev validators the signatures against the input hash + * Ensuring that all the signatures are from the validators + * and satisfies supermajority of the validators + * Signatures also have to be passed in ascending order of address + * No repeated signatures are allowed + * Function reverts if the signatures are not valid + */ + function validateMessageWithSupermajority( + bytes32 ethSignedMessageHash, + bytes[] calldata signatures + ) external view { + _validators().validateSignaturesWithSupermajority( + ethSignedMessageHash, + signatures + ); + } +} From 66cd26891b6754f3b1ac80b7d68aeaffca0412fa Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 3 Mar 2025 09:22:46 +0000 Subject: [PATCH 2/9] (fix) Continuing migration --- zilliqa/src/contracts/foundry.toml | 5 +- zilliqa/src/contracts/test/Tester.sol | 60 +++++++++++++++ .../tests/uccb/ChainDispatcher.t.sol | 6 +- zilliqa/src/contracts/tests/uccb/Helpers.sol | 2 +- .../src/contracts/uccb/SignatureValidator.sol | 75 +++++++++++++++++++ 5 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 zilliqa/src/contracts/test/Tester.sol create mode 100644 zilliqa/src/contracts/uccb/SignatureValidator.sol diff --git a/zilliqa/src/contracts/foundry.toml b/zilliqa/src/contracts/foundry.toml index da87407f4b..2c2cd2323b 100644 --- a/zilliqa/src/contracts/foundry.toml +++ b/zilliqa/src/contracts/foundry.toml @@ -1,11 +1,10 @@ [profile.default] -src='.' +src='zilliqa/src/contracts' out='out' -libs=['vendor'] test='tests' cache_path='cache-foundry' via_ir=true auto_detect_solc=true build_info=true -extra_ouptut=['storageLayout'] +extra_output=['storageLayout'] solc='0.8.28' diff --git a/zilliqa/src/contracts/test/Tester.sol b/zilliqa/src/contracts/test/Tester.sol new file mode 100644 index 0000000000..558013e155 --- /dev/null +++ b/zilliqa/src/contracts/test/Tester.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {Test, Vm} from "forge-std/Test.sol"; + +abstract contract Tester is Test { + modifier TODO() { + vm.skip(true); + _; + } + + function quickSort( + Vm.Wallet[] memory arr, + int left, + int right + ) private pure { + int i = left; + int j = right; + if (i == j) return; + Vm.Wallet memory pivot = arr[uint(left + (right - left) / 2)]; + while (i <= j) { + while (arr[uint(i)].addr < pivot.addr) i++; + while (pivot.addr < arr[uint(j)].addr) j--; + if (i <= j) { + (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]); + i++; + j--; + } + } + if (left < j) quickSort(arr, left, j); + if (i < right) quickSort(arr, i, right); + } + + function sign( + Vm.Wallet memory wallet, + bytes32 hashedMessage + ) public returns (bytes memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet, hashedMessage); + return abi.encodePacked(r, s, v); + } + + function multiSign( + Vm.Wallet[] memory wallet, + bytes32 hashedMessage + ) public returns (bytes[] memory) { + bytes[] memory signatures = new bytes[](wallet.length); + + for (uint i = 0; i < wallet.length; ++i) { + signatures[i] = sign(wallet[i], hashedMessage); + } + return signatures; + } + + function sort( + Vm.Wallet[] memory data + ) public pure returns (Vm.Wallet[] memory) { + quickSort(data, int(0), int(data.length - 1)); + return data; + } +} diff --git a/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol b/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol index da6549bf3e..b985e2b221 100644 --- a/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol +++ b/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.20; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {Target, ValidatorManagerFixture, IReentrancy} from "./Helpers.sol"; -import {ChainDispatcher, IChainDispatcherEvents, IChainDispatcherErrors} from "contracts/core-upgradeable/ChainDispatcher.sol"; +import {ChainDispatcher, IChainDispatcherEvents, IChainDispatcherErrors} from "../../uccb/ChainDispatcher.sol"; -import {ISignatureValidatorErrors} from "../../SignatureValidator.sol"; -import {IDispatchReplayCheckerErrors} from "../../DispatchReplayChecker.sol"; +import {ISignatureValidatorErrors} from "../../uccb/SignatureValidator.sol"; +import {IDispatchReplayCheckerErrors} from "../../uccb/DispatchReplayChecker.sol"; import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; diff --git a/zilliqa/src/contracts/tests/uccb/Helpers.sol b/zilliqa/src/contracts/tests/uccb/Helpers.sol index 3b873bc802..f5820b86de 100644 --- a/zilliqa/src/contracts/tests/uccb/Helpers.sol +++ b/zilliqa/src/contracts/tests/uccb/Helpers.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {ValidatorManager} from "../../uccb/ValidatorManager.sol"; -import {Tester, Vm} from "test/Tester.sol"; +import {Tester, Vm} from "../../test/Tester.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; diff --git a/zilliqa/src/contracts/uccb/SignatureValidator.sol b/zilliqa/src/contracts/uccb/SignatureValidator.sol new file mode 100644 index 0000000000..a7f40ea4e9 --- /dev/null +++ b/zilliqa/src/contracts/uccb/SignatureValidator.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +interface ISignatureValidatorErrors { + /** + * @dev Triggers when the signatures provided are either out of order or repeated + */ + error NonUniqueOrUnorderedSignatures(); + /** + * @dev Triggers when the signature does not match any validator + * It could be due to either the signature being wrong or validator invalid + */ + error InvalidValidatorOrSignatures(); + /** + * @dev not enough signatures are provided to reach supermajority + */ + error NoSupermajority(); +} + +/** + * @title SignatureValidator + * @notice Library used on enumerable set of validators to validate signatures + * It checks if the signatures are unique, ordered and valid against the provided message hash + */ +library SignatureValidator { + using ECDSA for bytes32; + using EnumerableSet for EnumerableSet.AddressSet; + + /** + * @dev Checks for strict supermajority + */ + function isSupermajority( + EnumerableSet.AddressSet storage self, + uint count + ) internal view returns (bool) { + return count * 3 > self.length() * 2; + } + + /** + * @dev Checks signatures are unique, ordered and valid against message hash + * and forms a supermajority + * errors [NonUniqueOrUnorderedSignatures, InvalidValidatorOrSignatures, NoSupermajority] + * NOTE: The signatures provided must be ordered by address ascendingly otherwise validation will fail + */ + function validateSignaturesWithSupermajority( + EnumerableSet.AddressSet storage self, + bytes32 ethSignedMessageHash, + bytes[] calldata signatures + ) internal view { + address lastSigner = address(0); + uint signaturesLength = signatures.length; + + for (uint i = 0; i < signaturesLength; ) { + address signer = ethSignedMessageHash.recover(signatures[i]); + if (signer <= lastSigner) { + revert ISignatureValidatorErrors + .NonUniqueOrUnorderedSignatures(); + } + if (!self.contains(signer)) { + revert ISignatureValidatorErrors.InvalidValidatorOrSignatures(); + } + lastSigner = signer; + unchecked { + ++i; + } + } + + if (!isSupermajority(self, signaturesLength)) { + revert ISignatureValidatorErrors.NoSupermajority(); + } + } +} From 34c1bf03ec9a3a939c11c46bcca8bd713952f4df Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 3 Mar 2025 11:37:55 +0000 Subject: [PATCH 3/9] (feat) Import forge-std --- .gitmodules | 5 ++++- vendor/forge-std | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 160000 vendor/forge-std diff --git a/.gitmodules b/.gitmodules index 6b95c696ae..2b657cf1ac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,7 @@ url = https://github.com/OpenZeppelin/openzeppelin-contracts.git [submodule "vendor/openzeppelin-contracts-upgradeable"] path = vendor/openzeppelin-contracts-upgradeable - url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git \ No newline at end of file + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git +[submodule "vendor/forge-std"] + path = vendor/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/vendor/forge-std b/vendor/forge-std new file mode 160000 index 0000000000..8ba9031ffc --- /dev/null +++ b/vendor/forge-std @@ -0,0 +1 @@ +Subproject commit 8ba9031ffcbe25aa0d1224d3ca263a995026e477 From dc3e50a03cf0043e514d4cb52ae9ea0de624630e Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 3 Mar 2025 11:54:26 +0000 Subject: [PATCH 4/9] (fix) Fix remappings.txt prior to importing openzeppelin-foundry-upgrades --- remappings.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/remappings.txt b/remappings.txt index 8aca4b6da6..f5e6d1a275 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,5 @@ @openzeppelin/contracts/=vendor/openzeppelin-contracts/contracts/ @openzeppelin/lib/=vendor/openzeppelin-contracts/lib/ -@openzeppelin/contracts-upgradeable/=vendor/openzeppelin-contracts-upgradeable/contracts/ \ No newline at end of file +@openzeppelin/contracts-upgradeable/=vendor/openzeppelin-contracts-upgradeable/contracts/ +forge-std=vendor/forge-std/src + From d8627b2dda0fcead7e5d80aa9f16639b566ddee5 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 3 Mar 2025 12:40:27 +0000 Subject: [PATCH 5/9] (feat) ChainDispatcher tests now pass --- .gitignore | 5 ++++- .gitmodules | 3 +++ .../src/contracts/foundry.toml => foundry.toml | 2 +- remappings.txt | 1 + vendor/openzeppelin-foundry-upgrades | 1 + .../contracts/tests/uccb/ChainDispatcher.t.sol | 2 +- zilliqa/src/contracts/tests/uccb/Helpers.sol | 18 +++++++++++------- zilliqa/src/contracts/uccb/ChainDispatcher.sol | 8 ++++---- zilliqa/src/contracts/uccb/ChainGateway.sol | 6 +++--- .../contracts/uccb/DispatchReplayChecker.sol | 2 +- zilliqa/src/contracts/uccb/Registry.sol | 2 +- zilliqa/src/contracts/uccb/Relayer.sol | 2 +- .../src/contracts/uccb/ValidatorManager.sol | 4 ++-- 13 files changed, 34 insertions(+), 22 deletions(-) rename zilliqa/src/contracts/foundry.toml => foundry.toml (93%) create mode 160000 vendor/openzeppelin-foundry-upgrades diff --git a/.gitignore b/.gitignore index 1c302326cf..071623075a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,7 @@ config.toml # Solidity Compiler files cache/ -out/ \ No newline at end of file +out/ + +# Foundry cache +cache-foundry/ diff --git a/.gitmodules b/.gitmodules index 2b657cf1ac..eb050c02f0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "vendor/forge-std"] path = vendor/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "vendor/openzeppelin-foundry-upgrades"] + path = vendor/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades diff --git a/zilliqa/src/contracts/foundry.toml b/foundry.toml similarity index 93% rename from zilliqa/src/contracts/foundry.toml rename to foundry.toml index 2c2cd2323b..4b7516516a 100644 --- a/zilliqa/src/contracts/foundry.toml +++ b/foundry.toml @@ -3,7 +3,7 @@ src='zilliqa/src/contracts' out='out' test='tests' cache_path='cache-foundry' -via_ir=true +viaIR=true auto_detect_solc=true build_info=true extra_output=['storageLayout'] diff --git a/remappings.txt b/remappings.txt index f5e6d1a275..d04ab694b7 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,6 @@ @openzeppelin/contracts/=vendor/openzeppelin-contracts/contracts/ @openzeppelin/lib/=vendor/openzeppelin-contracts/lib/ @openzeppelin/contracts-upgradeable/=vendor/openzeppelin-contracts-upgradeable/contracts/ +@openzeppelin/foundry-upgrades=vendor/openzeppelin-foundry-upgrades/src forge-std=vendor/forge-std/src diff --git a/vendor/openzeppelin-foundry-upgrades b/vendor/openzeppelin-foundry-upgrades new file mode 160000 index 0000000000..326d96b5d9 --- /dev/null +++ b/vendor/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit 326d96b5d9b5fa87b8cdb734f02a8f73324dad3e diff --git a/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol b/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol index b985e2b221..fdb8eafbbf 100644 --- a/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol +++ b/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol @@ -45,7 +45,7 @@ library DispatchArgsBuilder { contract ChainDispatcherHarness is Initializable, UUPSUpgradeable, - ChainDispatcherUpgradeable + ChainDispatcher { /// @custom:oz-upgrades-unsafe-allow constructor constructor() { diff --git a/zilliqa/src/contracts/tests/uccb/Helpers.sol b/zilliqa/src/contracts/tests/uccb/Helpers.sol index f5820b86de..9443507b78 100644 --- a/zilliqa/src/contracts/tests/uccb/Helpers.sol +++ b/zilliqa/src/contracts/tests/uccb/Helpers.sol @@ -4,7 +4,8 @@ pragma solidity ^0.8.20; import {ValidatorManager} from "../../uccb/ValidatorManager.sol"; import {Tester, Vm} from "../../test/Tester.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import {Upgrades, Options} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; library UUPSUpgrader { function deploy( @@ -128,12 +129,15 @@ abstract contract ValidatorManagerFixture is Tester { _validators[i] = vm.createWallet(i + 1); validatorAddresses[i] = _validators[i].addr; } - ValidatorManager _validatorManager = new ValidatorManager( - address(this) - ); - _validatorManager.initialize(validatorAddresses); - - return (_validators, _validatorManager); + address implementation = address(new ValidatorManager()); + address proxy = address(new ERC1967Proxy( + implementation, + abi.encodeWithSelector( + ValidatorManager.initialize.selector, + address(this), + validatorAddresses))); + + return (_validators, ValidatorManager(proxy)); } constructor() { diff --git a/zilliqa/src/contracts/uccb/ChainDispatcher.sol b/zilliqa/src/contracts/uccb/ChainDispatcher.sol index c199c45c4f..b04a5919b2 100644 --- a/zilliqa/src/contracts/uccb/ChainDispatcher.sol +++ b/zilliqa/src/contracts/uccb/ChainDispatcher.sol @@ -5,8 +5,8 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import {IValidatorManager} from "uccb/ValidatorManager.sol"; -import {IDispatchReplayChecker, DispatchReplayChecker} from "uccb/DispatchReplayChecker.sol"; +import {IValidatorManager} from "./ValidatorManager.sol"; +import {IDispatchReplayChecker, DispatchReplayChecker} from "./DispatchReplayChecker.sol"; interface IChainDispatcherEvents { /** @@ -55,11 +55,11 @@ interface IChainDispatcher is * The `dispatch` function will dispatch a message sourcing from a different chain * It is able to relay message to any arbitrary chain that is part of the UCCB network */ -abstract contract ChainDispatcherUpgradeable is +abstract contract ChainDispatcher is IChainDispatcher, Initializable, Ownable2StepUpgradeable, - DispatchReplayCheckerUpgradeable + DispatchReplayChecker { using MessageHashUtils for bytes; diff --git a/zilliqa/src/contracts/uccb/ChainGateway.sol b/zilliqa/src/contracts/uccb/ChainGateway.sol index 0b98b72257..4467e15390 100644 --- a/zilliqa/src/contracts/uccb/ChainGateway.sol +++ b/zilliqa/src/contracts/uccb/ChainGateway.sol @@ -5,8 +5,8 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import {IChainDispatcher, ChainDispatcher} from "contracts/uccb/ChainDispatcher.sol"; -import {IRelayer, RelayerUpgradeable} from "contracts/uccb/Relayer.sol"; +import {IChainDispatcher, ChainDispatcher} from "./ChainDispatcher.sol"; +import {IRelayer, RelayerUpgradeable} from "./Relayer.sol"; interface IChainGateway is IRelayer, IChainDispatcher {} @@ -19,7 +19,7 @@ interface IChainGateway is IRelayer, IChainDispatcher {} * The contract is also UUPS upgradeable. * For future upgrades of the contract refer to: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable */ -contract ChainGatewayUpgradeable is +contract ChainGateway is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable, diff --git a/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol b/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol index 4fdbc8ebfe..b29f3bd5ee 100644 --- a/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol +++ b/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol @@ -22,7 +22,7 @@ interface IDispatchReplayChecker is IDispatchReplayCheckerErrors { * essentially prevent the same message from being dispatched twice * The combination of `(sourceChainId, nonce)` form a unique key pair. */ -abstract contract DispatchReplayCheckerUpgradeable is IDispatchReplayChecker { +abstract contract DispatchReplayChecker is IDispatchReplayChecker { /** * @dev Storage of the initializable contract. * diff --git a/zilliqa/src/contracts/uccb/Registry.sol b/zilliqa/src/contracts/uccb/Registry.sol index 56a192fce1..31bfba20a8 100644 --- a/zilliqa/src/contracts/uccb/Registry.sol +++ b/zilliqa/src/contracts/uccb/Registry.sol @@ -33,7 +33,7 @@ interface IRegistry is IRegistryErrors, IRegistryEvents { * contract that inherits this one * Includes the `isRegistered` modifier that other contracts can leverage */ -abstract contract RegistryUpgradeable is IRegistry { +abstract contract Registry is IRegistry { /** * @dev Storage of the initializable contract. * diff --git a/zilliqa/src/contracts/uccb/Relayer.sol b/zilliqa/src/contracts/uccb/Relayer.sol index f57fbd0e01..7b6c847096 100644 --- a/zilliqa/src/contracts/uccb/Relayer.sol +++ b/zilliqa/src/contracts/uccb/Relayer.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {RegistryUpgradeable, IRegistry} from "contracts/uccb/Registry.sol"; +import {RegistryUpgradeable, IRegistry} from "./Registry.sol"; interface IRelayerEvents { /** diff --git a/zilliqa/src/contracts/uccb/ValidatorManager.sol b/zilliqa/src/contracts/uccb/ValidatorManager.sol index 2f79d475db..c0a8825e77 100644 --- a/zilliqa/src/contracts/uccb/ValidatorManager.sol +++ b/zilliqa/src/contracts/uccb/ValidatorManager.sol @@ -6,7 +6,7 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {ISignatureValidatorErrors, SignatureValidator} from "contracts/uccb/SignatureValidator.sol"; +import {ISignatureValidatorErrors, SignatureValidator} from "./SignatureValidator.sol"; interface IValidatorManager is ISignatureValidatorErrors { function addValidator(address user) external returns (bool); @@ -31,7 +31,7 @@ interface IValidatorManager is ISignatureValidatorErrors { * It can be used by `ChainGateway` contract to verify the signatures of the validators * on incoming dispatch requests */ -contract ValidatorManagerUpgradeable is +contract ValidatorManager is IValidatorManager, Initializable, UUPSUpgradeable, From 10601878ebcdbd3083f26920bcef7d5cfdac6bd7 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 3 Mar 2025 15:36:03 +0000 Subject: [PATCH 6/9] (feat) Add more tests. --- .../tests/uccb/DispatchReplayChecker.t.sol | 88 ++++++ .../src/contracts/tests/uccb/Relayer.t.sol | 281 ++++++++++++++++++ .../tests/uccb/SignatureValidator.t.sol | 272 +++++++++++++++++ .../tests/uccb/ValidatorManager.t.sol | 87 ++++++ zilliqa/src/contracts/uccb/Relayer.sol | 12 +- 5 files changed, 737 insertions(+), 3 deletions(-) create mode 100644 zilliqa/src/contracts/tests/uccb/DispatchReplayChecker.t.sol create mode 100644 zilliqa/src/contracts/tests/uccb/Relayer.t.sol create mode 100644 zilliqa/src/contracts/tests/uccb/SignatureValidator.t.sol create mode 100644 zilliqa/src/contracts/tests/uccb/ValidatorManager.t.sol diff --git a/zilliqa/src/contracts/tests/uccb/DispatchReplayChecker.t.sol b/zilliqa/src/contracts/tests/uccb/DispatchReplayChecker.t.sol new file mode 100644 index 0000000000..7a89f24922 --- /dev/null +++ b/zilliqa/src/contracts/tests/uccb/DispatchReplayChecker.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +import {Tester} from "../../test/Tester.sol"; +import {DispatchReplayChecker, IDispatchReplayCheckerErrors} from "../../uccb/DispatchReplayChecker.sol"; + +contract DispatchReplayCheckerHarness is + UUPSUpgradeable, + DispatchReplayChecker +{ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function _authorizeUpgrade(address) internal virtual override {} + + function exposed_replayDispatchCheck( + uint256 sourceShardId, + uint256 nonce + ) external { + _replayDispatchCheck(sourceShardId, nonce); + } +} + +contract DispatchReplayCheckerTests is Tester { + DispatchReplayCheckerHarness dispatchReplayChecker = + new DispatchReplayCheckerHarness(); + + function setUp() external { + address implementation = address(new DispatchReplayCheckerHarness()); + address proxy = address(new ERC1967Proxy(implementation, "")); + dispatchReplayChecker = DispatchReplayCheckerHarness(proxy); + } + + function test_happyPath() external { + uint256 sourceShardId = 0; + uint256 nonce = 0; + + dispatchReplayChecker.exposed_replayDispatchCheck(sourceShardId, nonce); + + assertEq( + dispatchReplayChecker.dispatched(sourceShardId, nonce), + true, + "should have marked dispatched" + ); + } + + function testRevert_whenAlreadyDispatched() external { + uint256 sourceShardId = 0; + uint256 nonce = 0; + + dispatchReplayChecker.exposed_replayDispatchCheck(sourceShardId, nonce); + assertEq( + dispatchReplayChecker.dispatched(sourceShardId, nonce), + true, + "should have marked dispatched" + ); + + vm.expectRevert( + IDispatchReplayCheckerErrors.AlreadyDispatched.selector + ); + dispatchReplayChecker.exposed_replayDispatchCheck(sourceShardId, nonce); + } + + function test_sameNonceDifferentSourceShard() external { + uint256 chain1 = 0; + uint256 chain2 = 1; + uint256 nonce = 0; + + dispatchReplayChecker.exposed_replayDispatchCheck(chain1, nonce); + assertEq( + dispatchReplayChecker.dispatched(chain1, nonce), + true, + "should have marked dispatched" + ); + + dispatchReplayChecker.exposed_replayDispatchCheck(chain2, nonce); + assertEq( + dispatchReplayChecker.dispatched(chain2, nonce), + true, + "should have marked dispatched" + ); + } +} diff --git a/zilliqa/src/contracts/tests/uccb/Relayer.t.sol b/zilliqa/src/contracts/tests/uccb/Relayer.t.sol new file mode 100644 index 0000000000..fdd36049fe --- /dev/null +++ b/zilliqa/src/contracts/tests/uccb/Relayer.t.sol @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {Tester} from "../../test/Tester.sol"; +import {Relayer, IRelayerEvents, IRelayer, CallMetadata} from "../../uccb/Relayer.sol"; +import {IRegistryErrors} from "../../uccb/Registry.sol"; + +import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +contract RelayerHarness is + Initializable, + UUPSUpgradeable, + Ownable2StepUpgradeable, + Relayer +{ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address _owner) external initializer { + __Ownable_init(_owner); + __Relayer_init_unchained(); + } + + function _authorizeUpgrade(address) internal virtual override onlyOwner {} +} + +interface ITest { + struct Args { + uint num; + } + + function foo() external; + + function fooWithMetadata( + CallMetadata calldata call, + Args calldata data + ) external; +} + +contract RelayerTests is Tester, IRelayerEvents { + RelayerHarness relayer; + address owner = vm.createWallet("Owner").addr; + address registered = vm.createWallet("Registered").addr; + + function setUp() external { + // Make deployment + address implementation = address(new RelayerHarness()); + address proxy = address( + new ERC1967Proxy( + implementation, + abi.encodeWithSelector( + RelayerHarness.initialize.selector, + owner + ) + ) + ); + relayer = RelayerHarness(proxy); + + // Preregister + vm.prank(owner); + relayer.register(registered); + + assertEq(relayer.registered(registered), true); + } + + function test_relay_happyPath() external { + uint nonce = 1; + uint targetChainId = 1; + address target = address(0x1); + bytes memory call = abi.encodeWithSelector(ITest.foo.selector); + uint gasLimit = 100_000; + + vm.expectEmit(address(relayer)); + vm.prank(registered); + emit IRelayerEvents.Relayed( + targetChainId, + target, + call, + gasLimit, + nonce + ); + uint result = relayer.relay(targetChainId, target, call, gasLimit); + + assertEq(result, nonce); + assertEq(relayer.nonce(targetChainId), nonce); + } + + function test_relay_identicalConsecutiveCallsHaveDifferentNonce() external { + uint nonce = 1; + uint targetChainId = 1; + address target = address(0x1); + bytes memory call = abi.encodeWithSelector(ITest.foo.selector); + uint gasLimit = 100_000; + + vm.expectEmit(address(relayer)); + vm.prank(registered); + emit IRelayerEvents.Relayed( + targetChainId, + target, + call, + gasLimit, + nonce + ); + uint result = relayer.relay(targetChainId, target, call, gasLimit); + + assertEq(result, nonce); + assertEq(relayer.nonce(targetChainId), nonce); + + nonce++; + + vm.expectEmit(address(relayer)); + emit IRelayerEvents.Relayed( + targetChainId, + target, + call, + gasLimit, + nonce + ); + vm.prank(registered); + result = relayer.relay(targetChainId, target, call, gasLimit); + assertEq(result, nonce); + assertEq(relayer.nonce(targetChainId), nonce); + } + + function test_relay_identicalConsecutiveCallsHaveDifferenceTargetChainId() + external + { + uint nonce = 1; + uint targetChainId = 1; + uint targetChainId2 = 2; + address target = address(0x1); + bytes memory call = abi.encodeWithSelector(ITest.foo.selector); + uint gasLimit = 100_000; + + vm.expectEmit(address(relayer)); + vm.prank(registered); + emit IRelayerEvents.Relayed( + targetChainId, + target, + call, + gasLimit, + nonce + ); + uint result = relayer.relay(targetChainId, target, call, gasLimit); + + assertEq(result, nonce); + assertEq(relayer.nonce(targetChainId), nonce); + + vm.expectEmit(address(relayer)); + emit IRelayerEvents.Relayed( + targetChainId2, + target, + call, + gasLimit, + nonce + ); + vm.prank(registered); + result = relayer.relay(targetChainId2, target, call, gasLimit); + + assertEq(result, nonce); + assertEq(relayer.nonce(targetChainId), nonce); + assertEq(relayer.nonce(targetChainId2), nonce); + } + + function test_relayWithMetadata_happyPath() external { + uint nonce = 1; + uint targetChainId = 1; + address target = address(0x1); + bytes4 callSelector = ITest.foo.selector; + bytes memory callData = abi.encode(ITest.Args(1)); + uint gasLimit = 100_000; + + bytes memory expectedCall = abi.encodeWithSelector( + callSelector, + CallMetadata(block.chainid, registered), + callData + ); + + vm.expectEmit(address(relayer)); + emit IRelayerEvents.Relayed( + targetChainId, + target, + expectedCall, + gasLimit, + nonce + ); + vm.prank(registered); + uint result = relayer.relayWithMetadata( + targetChainId, + target, + callSelector, + callData, + gasLimit + ); + + assertEq(result, nonce); + assertEq(relayer.nonce(targetChainId), nonce); + } + + function test_RevertNonRegisteredSender() external { + uint targetChainId = 1; + address target = address(0x1); + bytes memory call = abi.encodeWithSelector(ITest.foo.selector); + uint gasLimit = 100_000; + address notRegisteredSender = vm.addr(10); + + vm.prank(notRegisteredSender); + vm.expectRevert( + abi.encodeWithSelector( + IRegistryErrors.NotRegistered.selector, + notRegisteredSender + ) + ); + relayer.relay(targetChainId, target, call, gasLimit); + } + + function test_removeRegisteredSender() external { + uint targetChainId = 1; + address target = address(0x1); + bytes memory call = abi.encodeWithSelector(ITest.foo.selector); + uint gasLimit = 100_000; + + vm.prank(owner); + relayer.unregister(registered); + assertEq(relayer.registered(registered), false); + + vm.prank(registered); + vm.expectRevert( + abi.encodeWithSelector( + IRegistryErrors.NotRegistered.selector, + registered + ) + ); + relayer.relay(targetChainId, target, call, gasLimit); + } + + function test_RevertUnauthorizedRegister() external { + address notOwner = vm.createWallet("notOwner").addr; + address newRegistrant = vm.createWallet("newRegistrant").addr; + + vm.prank(notOwner); + vm.expectRevert( + abi.encodeWithSelector( + OwnableUpgradeable.OwnableUnauthorizedAccount.selector, + notOwner + ) + ); + relayer.register(newRegistrant); + } + + function test_RevertUnauthorizedUnregister() external { + address notOwner = vm.createWallet("notOwner").addr; + + vm.prank(notOwner); + vm.expectRevert( + abi.encodeWithSelector( + OwnableUpgradeable.OwnableUnauthorizedAccount.selector, + notOwner + ) + ); + relayer.unregister(registered); + } + + function test_transferOwnership() external { + address newOwner = vm.createWallet("newOwner").addr; + + vm.prank(owner); + relayer.transferOwnership(newOwner); + // Ownership should only be transferred after newOwner accepts + assertEq(relayer.owner(), owner); + + vm.prank(newOwner); + relayer.acceptOwnership(); + assertEq(relayer.owner(), newOwner); + } +} diff --git a/zilliqa/src/contracts/tests/uccb/SignatureValidator.t.sol b/zilliqa/src/contracts/tests/uccb/SignatureValidator.t.sol new file mode 100644 index 0000000000..9178a2cccb --- /dev/null +++ b/zilliqa/src/contracts/tests/uccb/SignatureValidator.t.sol @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {ISignatureValidatorErrors, SignatureValidator} from "../../uccb/SignatureValidator.sol"; +import {Tester, Vm} from "../../test/Tester.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +contract SignatureValidatorHarness is Tester { + using EnumerableSet for EnumerableSet.AddressSet; + using SignatureValidator for EnumerableSet.AddressSet; + + EnumerableSet.AddressSet private _validators; + + constructor(address[] memory validators) { + uint validatorsLength = validators.length; + for (uint i = 0; i < validatorsLength; ++i) { + _validators.add(validators[i]); + } + } + + function exposed_validateMessageWithSupermajority( + bytes32 ethSignedMessageHash, + bytes[] calldata signatures + ) external view { + _validators.validateSignaturesWithSupermajority( + ethSignedMessageHash, + signatures + ); + } +} + +abstract contract SignatureValidatorFixture is Tester { + using MessageHashUtils for bytes; + using EnumerableSet for EnumerableSet.AddressSet; + using SignatureValidator for EnumerableSet.AddressSet; + + uint constant validatorSize = 10; + SignatureValidatorHarness internal signatureValidator; + + Vm.Wallet[] validatorsWallets = new Vm.Wallet[](validatorSize); + + constructor() { + // Setup validator manager + ( + Vm.Wallet[] memory _validatorWallets, + SignatureValidatorHarness _signatureValidator + ) = generateValidators(validatorSize); + validatorsWallets = _validatorWallets; + signatureValidator = _signatureValidator; + } + + function generateValidators( + uint size + ) internal returns (Vm.Wallet[] memory, SignatureValidatorHarness) { + Vm.Wallet[] memory validatorWallets = new Vm.Wallet[](size); + address[] memory validatorAddresses = new address[](size); + for (uint i = 0; i < size; ++i) { + validatorWallets[i] = vm.createWallet(i + 1); + validatorAddresses[i] = validatorWallets[i].addr; + } + + return ( + validatorWallets, + new SignatureValidatorHarness(validatorAddresses) + ); + } + + function exactSupermajority( + uint size + ) internal pure returns (uint supermajority) { + supermajority = (size * 2) / 3 + 1; + } + + function getValidatorSubset( + Vm.Wallet[] memory _validators, + uint size + ) internal pure returns (Vm.Wallet[] memory subset) { + subset = new Vm.Wallet[](size); + for (uint i = 0; i < size; ++i) { + subset[i] = _validators[i]; + } + } +} + +contract SignatureValidatorTests is SignatureValidatorFixture { + using MessageHashUtils for bytes; + + function test_allValidatorsSign() external { + bytes32 messageHash = bytes("Hello world").toEthSignedMessageHash(); + bytes[] memory signatures = multiSign( + sort(validatorsWallets), + messageHash + ); + // If it works does not do anything + signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + function test_exactMajoritySign() external { + bytes32 messageHash = bytes("Hello world").toEthSignedMessageHash(); + uint exactSupermajoritySize = exactSupermajority(validatorSize); + Vm.Wallet[] memory exactSupermajorityValidators = getValidatorSubset( + validatorsWallets, + exactSupermajoritySize + ); + bytes[] memory signatures = multiSign( + sort(exactSupermajorityValidators), + messageHash + ); + // If it works does not do anything + signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + function testRevert_lessThanSupermajoritySign() external { + bytes32 messageHash = bytes("Hello world").toEthSignedMessageHash(); + uint exactSupermajoritySize = exactSupermajority(validatorSize) - 1; + Vm.Wallet[] memory exactSupermajorityValidators = getValidatorSubset( + validatorsWallets, + exactSupermajoritySize + ); + bytes[] memory signatures = multiSign( + sort(exactSupermajorityValidators), + messageHash + ); + // If it works does not do anything + vm.expectRevert(ISignatureValidatorErrors.NoSupermajority.selector); + signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + function testRevert_noSignatures() external { + bytes32 messageHash = bytes("Hello world").toEthSignedMessageHash(); + bytes[] memory signatures = new bytes[](0); + vm.expectRevert(ISignatureValidatorErrors.NoSupermajority.selector); + signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + function test_emptyMessage() external { + bytes32 messageHash; + bytes[] memory signatures = multiSign( + sort(validatorsWallets), + messageHash + ); + signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + function testRevert_invalidSignature() external { + bytes32 messageHash = bytes("Hello world").toEthSignedMessageHash(); + bytes[] memory signatures = multiSign( + sort(validatorsWallets), + messageHash + ); + // Manipulate one of the bytes in the first signature + signatures[0][0] = 0; + vm.expectRevert( + ISignatureValidatorErrors.InvalidValidatorOrSignatures.selector + ); + signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + function testRevert_unorderedSignatures() external { + bytes32 messageHash = bytes("Hello world").toEthSignedMessageHash(); + // Don't sort the validators by address + bytes[] memory signatures = multiSign(validatorsWallets, messageHash); + vm.expectRevert( + ISignatureValidatorErrors.NonUniqueOrUnorderedSignatures.selector + ); + signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + function testRevert_repeatedSigners() external { + bytes32 messageHash = bytes("Hello world").toEthSignedMessageHash(); + // Don't sort the validators by address + bytes[] memory signatures = multiSign( + sort(validatorsWallets), + messageHash + ); + // Repeat first and second validator + signatures[0] = signatures[1]; + vm.expectRevert( + ISignatureValidatorErrors.NonUniqueOrUnorderedSignatures.selector + ); + signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + function testFuzz_message(bytes memory message) external { + bytes32 messageHash = message.toEthSignedMessageHash(); + bytes[] memory signatures = multiSign( + sort(validatorsWallets), + messageHash + ); + + // Should work regardless of validators + signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + function test_largeValidatorSet() external { + uint _validatorSize = 25_000; + + ( + Vm.Wallet[] memory _validatorWallet, + SignatureValidatorHarness _signatureValidator + ) = generateValidators(_validatorSize); + + bytes32 messageHash = bytes("Hello World").toEthSignedMessageHash(); + bytes[] memory signatures = multiSign( + sort(_validatorWallet), + messageHash + ); + + // Should work regardless of validators + _signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } + + /// forge-config: default.fuzz.runs = 100 + function testFuzz_signatureCount(uint input) external { + uint size = 200; + uint exactSupermajoritySize = exactSupermajority(size); + uint signaturesCount = exactSupermajoritySize + + (input % (size - exactSupermajoritySize)); + + ( + Vm.Wallet[] memory _validatorWallet, + SignatureValidatorHarness _signatureValidator + ) = generateValidators(size); + Vm.Wallet[] memory validatorSubset = getValidatorSubset( + _validatorWallet, + signaturesCount + ); + bytes32 messageHash = bytes("Hello World").toEthSignedMessageHash(); + + bytes[] memory signatures = multiSign( + sort(validatorSubset), + messageHash + ); + + // Should work regardless of validators + _signatureValidator.exposed_validateMessageWithSupermajority( + messageHash, + signatures + ); + } +} diff --git a/zilliqa/src/contracts/tests/uccb/ValidatorManager.t.sol b/zilliqa/src/contracts/tests/uccb/ValidatorManager.t.sol new file mode 100644 index 0000000000..2af014431b --- /dev/null +++ b/zilliqa/src/contracts/tests/uccb/ValidatorManager.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {Tester} from "../../test/Tester.sol"; +import {ValidatorManager} from "../../uccb/ValidatorManager.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +contract ValidatorManagerTests is Tester { + address owner = vm.createWallet("Owner").addr; + address validator1 = vm.createWallet("Validator1").addr; + address validator2 = vm.createWallet("Validator2").addr; + ValidatorManager validatorManager; + + function setUp() external { + address[] memory validators = new address[](1); + validators[0] = validator1; + + vm.prank(owner); + address implementation = address(new ValidatorManager()); + address proxy = address(new ERC1967Proxy( + implementation, + abi.encodeWithSelector( + ValidatorManager.initialize.selector, + address(owner), + validators))); + validatorManager = ValidatorManager(proxy); + } + + function test_addValidator() external { + vm.startPrank(owner); + validatorManager.addValidator(validator2); + + assertEq(validatorManager.validatorsSize(), 2); + assertEq(validatorManager.isValidator(validator1), true); + assertEq(validatorManager.isValidator(validator2), true); + vm.stopPrank(); + } + + function test_removeValidator() external { + vm.startPrank(owner); + validatorManager.removeValidator(validator1); + + assertEq(validatorManager.validatorsSize(), 0); + assertEq(validatorManager.isValidator(validator1), false); + vm.stopPrank(); + } + + function test_revertAddValidatorIfNotOwner() external { + address nonOwner = vm.createWallet("NonOwner").addr; + + vm.prank(nonOwner); + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableUnauthorizedAccount.selector, + nonOwner + ) + ); + validatorManager.addValidator(validator2); + } + + function test_revertRemoveValidatorIfNotOwner() external { + address nonOwner = vm.createWallet("NonOwner").addr; + + vm.prank(nonOwner); + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableUnauthorizedAccount.selector, + nonOwner + ) + ); + validatorManager.removeValidator(validator2); + } + + function test_transferOwnership() external { + address newOwner = vm.createWallet("NewOwner").addr; + + vm.prank(owner); + validatorManager.transferOwnership(newOwner); + // Ownership should only be transferred after newOwner accepts + assertEq(validatorManager.owner(), owner); + + vm.prank(newOwner); + validatorManager.acceptOwnership(); + assertEq(validatorManager.owner(), newOwner); + } +} diff --git a/zilliqa/src/contracts/uccb/Relayer.sol b/zilliqa/src/contracts/uccb/Relayer.sol index 7b6c847096..c8973c1aa5 100644 --- a/zilliqa/src/contracts/uccb/Relayer.sol +++ b/zilliqa/src/contracts/uccb/Relayer.sol @@ -4,7 +4,8 @@ pragma solidity ^0.8.20; import {OwnableUpgradeable, Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {RegistryUpgradeable, IRegistry} from "./Registry.sol"; +import {Registry, IRegistry} from "./Registry.sol"; + interface IRelayerEvents { /** @@ -19,6 +20,11 @@ interface IRelayerEvents { ); } +struct CallMetadata { + uint sourceChainId; + address sender; +} + interface IRelayer is IRelayerEvents, IRegistry { /** * @dev Incorporates the extra metadata to add on relay @@ -53,11 +59,11 @@ interface IRelayer is IRelayerEvents, IRegistry { * * It is able to relay message to any arbitrary chain that is part of the UCCB network */ -abstract contract RelayerUpgradeable is +abstract contract Relayer is IRelayer, Initializable, Ownable2StepUpgradeable, - RegistryUpgradeable + Registry { /** * @dev Storage of the initializable contract. From b28f07290aeda51100b9f22e0b557490449bef84 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Tue, 4 Mar 2025 11:33:09 +0000 Subject: [PATCH 7/9] (fix) Add gas limit to recommended test line --- zilliqa/src/contracts/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zilliqa/src/contracts/README.md b/zilliqa/src/contracts/README.md index a63848fad3..15a6e70ec9 100644 --- a/zilliqa/src/contracts/README.md +++ b/zilliqa/src/contracts/README.md @@ -17,5 +17,5 @@ ZQ_CONTRACT_TEST_BLESS=1 cargo test --features test_contract_bytecode -- contrac ## Run Solidity tests ```sh - forge test -C zilliqa/src/contracts/tests -``` \ No newline at end of file + forge test -C zilliqa/src/contracts/tests --gas-limit 2000000000000 +``` From 5652d96889772f4c209d37469d4bc2c2d9ec13fd Mon Sep 17 00:00:00 2001 From: rrw-zilliqa <108257153+rrw-zilliqa@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:45:19 +0000 Subject: [PATCH 8/9] Apply formatting changes to solidity files --- zilliqa/src/contracts/test/Tester.sol | 16 +++--- .../tests/uccb/ChainDispatcher.t.sol | 14 +++--- zilliqa/src/contracts/tests/uccb/Helpers.sol | 32 ++++++------ .../src/contracts/tests/uccb/Relayer.t.sol | 44 ++++++++--------- .../tests/uccb/SignatureValidator.t.sol | 32 ++++++------ .../tests/uccb/ValidatorManager.t.sol | 16 +++--- .../src/contracts/uccb/ChainDispatcher.sol | 16 +++--- .../contracts/uccb/DispatchReplayChecker.sol | 14 +++--- zilliqa/src/contracts/uccb/Relayer.sol | 49 +++++++++---------- .../src/contracts/uccb/SignatureValidator.sol | 6 +-- .../src/contracts/uccb/ValidatorManager.sol | 8 +-- 11 files changed, 127 insertions(+), 120 deletions(-) diff --git a/zilliqa/src/contracts/test/Tester.sol b/zilliqa/src/contracts/test/Tester.sol index 558013e155..395af8f2c2 100644 --- a/zilliqa/src/contracts/test/Tester.sol +++ b/zilliqa/src/contracts/test/Tester.sol @@ -11,18 +11,18 @@ abstract contract Tester is Test { function quickSort( Vm.Wallet[] memory arr, - int left, - int right + int256 left, + int256 right ) private pure { - int i = left; - int j = right; + int256 i = left; + int256 j = right; if (i == j) return; - Vm.Wallet memory pivot = arr[uint(left + (right - left) / 2)]; + Vm.Wallet memory pivot = arr[uint256(left + (right - left) / 2)]; while (i <= j) { while (arr[uint(i)].addr < pivot.addr) i++; while (pivot.addr < arr[uint(j)].addr) j--; if (i <= j) { - (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]); + (arr[uint256(i)], arr[uint256uint256uint256(j)]) = (arr[uint(j)], arr[uint(i)]); i++; j--; } @@ -45,7 +45,7 @@ abstract contract Tester is Test { ) public returns (bytes[] memory) { bytes[] memory signatures = new bytes[](wallet.length); - for (uint i = 0; i < wallet.length; ++i) { + for (uint256 i = 0; i < wallet.length; ++i) { signatures[i] = sign(wallet[i], hashedMessage); } return signatures; @@ -54,7 +54,7 @@ abstract contract Tester is Test { function sort( Vm.Wallet[] memory data ) public pure returns (Vm.Wallet[] memory) { - quickSort(data, int(0), int(data.length - 1)); + quickSort(data, int256(0), int256(data.length - 1)); return data; } } diff --git a/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol b/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol index fdb8eafbbf..0b15107030 100644 --- a/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol +++ b/zilliqa/src/contracts/tests/uccb/ChainDispatcher.t.sol @@ -16,11 +16,11 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s library DispatchArgsBuilder { struct DispatchArgs { - uint sourceChainId; + uint256 sourceChainId; address target; bytes call; - uint gasLimit; - uint nonce; + uint256 gasLimit; + uint256 nonce; } function instance( @@ -28,7 +28,7 @@ library DispatchArgsBuilder { ) external pure returns (DispatchArgs memory args) { args.sourceChainId = 1; args.target = target; - args.call = abi.encodeWithSelector(Target.work.selector, uint(1)); + args.call = abi.encodeWithSelector(Target.work.selector, uint256(1)); args.gasLimit = 1_000_000; args.nonce = 1; } @@ -139,7 +139,7 @@ contract ChainDispatcherTests is DispatcherFixture { DispatchArgsBuilder.DispatchArgs memory args = DispatchArgsBuilder .instance(address(target)); bytes[] memory signatures = signDispatch(args); - uint badNonce = args.nonce + 1; + uint256 badNonce = args.nonce + 1; vm.expectRevert( ISignatureValidatorErrors.InvalidValidatorOrSignatures.selector @@ -188,7 +188,7 @@ contract ChainDispatcherTests is DispatcherFixture { } function test_failedCall() external { - uint num = 1000; + uint256 num = 1000; bytes memory failedCall = abi.encodeWithSelector( target.work.selector, num @@ -284,7 +284,7 @@ contract ChainDispatcherTests is DispatcherFixture { ); assertEq(dispatcher.dispatched(args.sourceChainId, args.nonce), true); - assertEq(target.c(), uint(0)); + assertEq(target.c(), uint256(0)); } function test_reentrancy() external { diff --git a/zilliqa/src/contracts/tests/uccb/Helpers.sol b/zilliqa/src/contracts/tests/uccb/Helpers.sol index 9443507b78..7de8228901 100644 --- a/zilliqa/src/contracts/tests/uccb/Helpers.sol +++ b/zilliqa/src/contracts/tests/uccb/Helpers.sol @@ -61,13 +61,13 @@ interface IReentrancy { } contract Target is IReentrancy { - uint public c = 0; + uint256 public c = 0; - function depositFee(uint amount) external payable { + function depositFee(uint256 amount) external payable { amount; } - function work(uint num_) external pure returns (uint) { + function work(uint256 num_) external pure returns (uint256) { require(num_ < 1000, "Too large"); return num_ + 1; } @@ -78,12 +78,12 @@ contract Target is IReentrancy { } } - function finish(bool success, bytes calldata res, uint nonce) external {} + function finish(bool success, bytes calldata res, uint256 nonce) external {} function finishRevert( bool success, bytes calldata res, - uint nonce + uint256 nonce ) external pure { success; res; @@ -114,28 +114,32 @@ contract Target is IReentrancy { } abstract contract ValidatorManagerFixture is Tester { - uint constant VALIDATOR_COUNT = 10; + uint256 constant VALIDATOR_COUNT = 10; ValidatorManager validatorManager; Vm.Wallet[] public validators = new Vm.Wallet[](VALIDATOR_COUNT); function generateValidatorManager( - uint size + uint256 size ) internal returns (Vm.Wallet[] memory, ValidatorManager) { Vm.Wallet[] memory _validators = new Vm.Wallet[](size); address[] memory validatorAddresses = new address[](size); - for (uint i = 0; i < size; ++i) { + for (uint256 i = 0; i < size; ++i) { _validators[i] = vm.createWallet(i + 1); validatorAddresses[i] = _validators[i].addr; } address implementation = address(new ValidatorManager()); - address proxy = address(new ERC1967Proxy( - implementation, - abi.encodeWithSelector( - ValidatorManager.initialize.selector, - address(this), - validatorAddresses))); + address proxy = address( + new ERC1967Proxy( + implementation, + abi.encodeWithSelector( + ValidatorManager.initialize.selector, + address(this), + validatorAddresses + ) + ) + ); return (_validators, ValidatorManager(proxy)); } diff --git a/zilliqa/src/contracts/tests/uccb/Relayer.t.sol b/zilliqa/src/contracts/tests/uccb/Relayer.t.sol index fdd36049fe..cf71d57dca 100644 --- a/zilliqa/src/contracts/tests/uccb/Relayer.t.sol +++ b/zilliqa/src/contracts/tests/uccb/Relayer.t.sol @@ -31,7 +31,7 @@ contract RelayerHarness is interface ITest { struct Args { - uint num; + uint256 num; } function foo() external; @@ -69,11 +69,11 @@ contract RelayerTests is Tester, IRelayerEvents { } function test_relay_happyPath() external { - uint nonce = 1; - uint targetChainId = 1; + uint256 nonce = 1; + uint256 targetChainId = 1; address target = address(0x1); bytes memory call = abi.encodeWithSelector(ITest.foo.selector); - uint gasLimit = 100_000; + uint256 gasLimit = 100_000; vm.expectEmit(address(relayer)); vm.prank(registered); @@ -84,18 +84,18 @@ contract RelayerTests is Tester, IRelayerEvents { gasLimit, nonce ); - uint result = relayer.relay(targetChainId, target, call, gasLimit); + uint256 result = relayer.relay(targetChainId, target, call, gasLimit); assertEq(result, nonce); assertEq(relayer.nonce(targetChainId), nonce); } function test_relay_identicalConsecutiveCallsHaveDifferentNonce() external { - uint nonce = 1; - uint targetChainId = 1; + uint256 nonce = 1; + uint256 targetChainId = 1; address target = address(0x1); bytes memory call = abi.encodeWithSelector(ITest.foo.selector); - uint gasLimit = 100_000; + uint256 gasLimit = 100_000; vm.expectEmit(address(relayer)); vm.prank(registered); @@ -106,7 +106,7 @@ contract RelayerTests is Tester, IRelayerEvents { gasLimit, nonce ); - uint result = relayer.relay(targetChainId, target, call, gasLimit); + uint256 result = relayer.relay(targetChainId, target, call, gasLimit); assertEq(result, nonce); assertEq(relayer.nonce(targetChainId), nonce); @@ -130,12 +130,12 @@ contract RelayerTests is Tester, IRelayerEvents { function test_relay_identicalConsecutiveCallsHaveDifferenceTargetChainId() external { - uint nonce = 1; - uint targetChainId = 1; - uint targetChainId2 = 2; + uint256 nonce = 1; + uint256 targetChainId = 1; + uint256 targetChainId2 = 2; address target = address(0x1); bytes memory call = abi.encodeWithSelector(ITest.foo.selector); - uint gasLimit = 100_000; + uint256 gasLimit = 100_000; vm.expectEmit(address(relayer)); vm.prank(registered); @@ -146,7 +146,7 @@ contract RelayerTests is Tester, IRelayerEvents { gasLimit, nonce ); - uint result = relayer.relay(targetChainId, target, call, gasLimit); + uint256 result = relayer.relay(targetChainId, target, call, gasLimit); assertEq(result, nonce); assertEq(relayer.nonce(targetChainId), nonce); @@ -168,12 +168,12 @@ contract RelayerTests is Tester, IRelayerEvents { } function test_relayWithMetadata_happyPath() external { - uint nonce = 1; - uint targetChainId = 1; + uint256 nonce = 1; + uint256 targetChainId = 1; address target = address(0x1); bytes4 callSelector = ITest.foo.selector; bytes memory callData = abi.encode(ITest.Args(1)); - uint gasLimit = 100_000; + uint256 gasLimit = 100_000; bytes memory expectedCall = abi.encodeWithSelector( callSelector, @@ -190,7 +190,7 @@ contract RelayerTests is Tester, IRelayerEvents { nonce ); vm.prank(registered); - uint result = relayer.relayWithMetadata( + uint256 result = relayer.relayWithMetadata( targetChainId, target, callSelector, @@ -203,10 +203,10 @@ contract RelayerTests is Tester, IRelayerEvents { } function test_RevertNonRegisteredSender() external { - uint targetChainId = 1; + uint256 targetChainId = 1; address target = address(0x1); bytes memory call = abi.encodeWithSelector(ITest.foo.selector); - uint gasLimit = 100_000; + uint256 gasLimit = 100_000; address notRegisteredSender = vm.addr(10); vm.prank(notRegisteredSender); @@ -220,10 +220,10 @@ contract RelayerTests is Tester, IRelayerEvents { } function test_removeRegisteredSender() external { - uint targetChainId = 1; + uint256 targetChainId = 1; address target = address(0x1); bytes memory call = abi.encodeWithSelector(ITest.foo.selector); - uint gasLimit = 100_000; + uint256 gasLimit = 100_000; vm.prank(owner); relayer.unregister(registered); diff --git a/zilliqa/src/contracts/tests/uccb/SignatureValidator.t.sol b/zilliqa/src/contracts/tests/uccb/SignatureValidator.t.sol index 9178a2cccb..afde94a922 100644 --- a/zilliqa/src/contracts/tests/uccb/SignatureValidator.t.sol +++ b/zilliqa/src/contracts/tests/uccb/SignatureValidator.t.sol @@ -13,8 +13,8 @@ contract SignatureValidatorHarness is Tester { EnumerableSet.AddressSet private _validators; constructor(address[] memory validators) { - uint validatorsLength = validators.length; - for (uint i = 0; i < validatorsLength; ++i) { + uint256 validatorsLength = validators.length; + for (uint256 i = 0; i < validatorsLength; ++i) { _validators.add(validators[i]); } } @@ -35,7 +35,7 @@ abstract contract SignatureValidatorFixture is Tester { using EnumerableSet for EnumerableSet.AddressSet; using SignatureValidator for EnumerableSet.AddressSet; - uint constant validatorSize = 10; + uint256 constant validatorSize = 10; SignatureValidatorHarness internal signatureValidator; Vm.Wallet[] validatorsWallets = new Vm.Wallet[](validatorSize); @@ -51,11 +51,11 @@ abstract contract SignatureValidatorFixture is Tester { } function generateValidators( - uint size + uint256 size ) internal returns (Vm.Wallet[] memory, SignatureValidatorHarness) { Vm.Wallet[] memory validatorWallets = new Vm.Wallet[](size); address[] memory validatorAddresses = new address[](size); - for (uint i = 0; i < size; ++i) { + for (uint256 i = 0; i < size; ++i) { validatorWallets[i] = vm.createWallet(i + 1); validatorAddresses[i] = validatorWallets[i].addr; } @@ -67,17 +67,17 @@ abstract contract SignatureValidatorFixture is Tester { } function exactSupermajority( - uint size - ) internal pure returns (uint supermajority) { + uint256 size + ) internal pure returns (uint256 supermajority) { supermajority = (size * 2) / 3 + 1; } function getValidatorSubset( Vm.Wallet[] memory _validators, - uint size + uint256 size ) internal pure returns (Vm.Wallet[] memory subset) { subset = new Vm.Wallet[](size); - for (uint i = 0; i < size; ++i) { + for (uint256 i = 0; i < size; ++i) { subset[i] = _validators[i]; } } @@ -101,7 +101,7 @@ contract SignatureValidatorTests is SignatureValidatorFixture { function test_exactMajoritySign() external { bytes32 messageHash = bytes("Hello world").toEthSignedMessageHash(); - uint exactSupermajoritySize = exactSupermajority(validatorSize); + uint256 exactSupermajoritySize = exactSupermajority(validatorSize); Vm.Wallet[] memory exactSupermajorityValidators = getValidatorSubset( validatorsWallets, exactSupermajoritySize @@ -119,7 +119,7 @@ contract SignatureValidatorTests is SignatureValidatorFixture { function testRevert_lessThanSupermajoritySign() external { bytes32 messageHash = bytes("Hello world").toEthSignedMessageHash(); - uint exactSupermajoritySize = exactSupermajority(validatorSize) - 1; + uint256 exactSupermajoritySize = exactSupermajority(validatorSize) - 1; Vm.Wallet[] memory exactSupermajorityValidators = getValidatorSubset( validatorsWallets, exactSupermajoritySize @@ -221,7 +221,7 @@ contract SignatureValidatorTests is SignatureValidatorFixture { } function test_largeValidatorSet() external { - uint _validatorSize = 25_000; + uint256 _validatorSize = 25_000; ( Vm.Wallet[] memory _validatorWallet, @@ -242,10 +242,10 @@ contract SignatureValidatorTests is SignatureValidatorFixture { } /// forge-config: default.fuzz.runs = 100 - function testFuzz_signatureCount(uint input) external { - uint size = 200; - uint exactSupermajoritySize = exactSupermajority(size); - uint signaturesCount = exactSupermajoritySize + + function testFuzz_signatureCount(uint256 input) external { + uint256 size = 200; + uint256 exactSupermajoritySize = exactSupermajority(size); + uint256 signaturesCount = exactSupermajoritySize + (input % (size - exactSupermajoritySize)); ( diff --git a/zilliqa/src/contracts/tests/uccb/ValidatorManager.t.sol b/zilliqa/src/contracts/tests/uccb/ValidatorManager.t.sol index 2af014431b..8b4c920f3a 100644 --- a/zilliqa/src/contracts/tests/uccb/ValidatorManager.t.sol +++ b/zilliqa/src/contracts/tests/uccb/ValidatorManager.t.sol @@ -18,12 +18,16 @@ contract ValidatorManagerTests is Tester { vm.prank(owner); address implementation = address(new ValidatorManager()); - address proxy = address(new ERC1967Proxy( - implementation, - abi.encodeWithSelector( - ValidatorManager.initialize.selector, - address(owner), - validators))); + address proxy = address( + new ERC1967Proxy( + implementation, + abi.encodeWithSelector( + ValidatorManager.initialize.selector, + address(owner), + validators + ) + ) + ); validatorManager = ValidatorManager(proxy); } diff --git a/zilliqa/src/contracts/uccb/ChainDispatcher.sol b/zilliqa/src/contracts/uccb/ChainDispatcher.sol index b04a5919b2..acb0fb4cef 100644 --- a/zilliqa/src/contracts/uccb/ChainDispatcher.sol +++ b/zilliqa/src/contracts/uccb/ChainDispatcher.sol @@ -13,11 +13,11 @@ interface IChainDispatcherEvents { * @dev Triggered when an event enters this chain */ event Dispatched( - uint indexed sourceChainId, + uint256 indexed sourceChainId, address indexed target, bool success, bytes response, - uint indexed nonce + uint256 indexed nonce ); } @@ -38,11 +38,11 @@ interface IChainDispatcher is function setValidatorManager(address validatorManager) external; function dispatch( - uint sourceChainId, + uint256 sourceChainId, address target, bytes calldata call, - uint gasLimit, - uint nonce, + uint256 gasLimit, + uint256 nonce, bytes[] calldata signatures ) external; } @@ -155,11 +155,11 @@ abstract contract ChainDispatcher is * @param signatures the signatures of the messages of the validator */ function dispatch( - uint sourceChainId, + uint256 sourceChainId, address target, bytes calldata call, - uint gasLimit, - uint nonce, + uint256 gasLimit, + uint256 nonce, bytes[] calldata signatures ) external replayDispatchGuard(sourceChainId, nonce) { ChainDispatcherStorage storage $ = _getChainDispatcherStorage(); diff --git a/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol b/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol index b29f3bd5ee..911ba578cb 100644 --- a/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol +++ b/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol @@ -10,8 +10,8 @@ interface IDispatchReplayCheckerErrors { interface IDispatchReplayChecker is IDispatchReplayCheckerErrors { function dispatched( - uint sourceChainId, - uint nonce + uint256 sourceChainId, + uint256 nonce ) external view returns (bool); } @@ -33,7 +33,7 @@ abstract contract DispatchReplayChecker is IDispatchReplayChecker { */ struct DispatchReplayCheckerStorage { // sourceChainId => nonce => isDispatched - mapping(uint => mapping(uint => bool)) dispatched; + mapping(uint256 => mapping(uint256 => bool)) dispatched; } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.DispatchReplayChecker")) - 1)) & ~bytes32(uint256(0xff)) @@ -57,8 +57,8 @@ abstract contract DispatchReplayChecker is IDispatchReplayChecker { * @dev view function to verify if a message has been dispatched */ function dispatched( - uint sourceChainId, - uint nonce + uint256 sourceChainId, + uint256 nonce ) external view returns (bool) { DispatchReplayCheckerStorage storage $ = _getDispatchReplayCheckerStorage(); @@ -68,7 +68,7 @@ abstract contract DispatchReplayChecker is IDispatchReplayChecker { /** * @dev Internal function handling the replay check and reverts if the message has been dispatched */ - function _replayDispatchCheck(uint sourceChainId, uint nonce) internal { + function _replayDispatchCheck(uint256 sourceChainId, uint256 nonce) internal { DispatchReplayCheckerStorage storage $ = _getDispatchReplayCheckerStorage(); @@ -81,7 +81,7 @@ abstract contract DispatchReplayChecker is IDispatchReplayChecker { /** * @dev Modifier to protect functions from replay attacks and used by child contracts */ - modifier replayDispatchGuard(uint sourceShardId, uint nonce) { + modifier replayDispatchGuard(uint256 sourceShardId, uint256 nonce) { _replayDispatchCheck(sourceShardId, nonce); _; } diff --git a/zilliqa/src/contracts/uccb/Relayer.sol b/zilliqa/src/contracts/uccb/Relayer.sol index c8973c1aa5..32779eb349 100644 --- a/zilliqa/src/contracts/uccb/Relayer.sol +++ b/zilliqa/src/contracts/uccb/Relayer.sol @@ -6,22 +6,21 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini import {Registry, IRegistry} from "./Registry.sol"; - interface IRelayerEvents { /** * @dev Triggered when a outgoing message is relayed to another chain */ event Relayed( - uint indexed targetChainId, + uint256 indexed targetChainId, address target, bytes call, - uint gasLimit, - uint nonce + uint256 gasLimit, + uint256 nonce ); } struct CallMetadata { - uint sourceChainId; + uint256 sourceChainId; address sender; } @@ -30,26 +29,26 @@ interface IRelayer is IRelayerEvents, IRegistry { * @dev Incorporates the extra metadata to add on relay */ struct CallMetadata { - uint sourceChainId; + uint256 sourceChainId; address sender; } - function nonce(uint chainId) external view returns (uint); + function nonce(uint256 chainId) external view returns (uint256); function relayWithMetadata( - uint targetChainId, + uint256 targetChainId, address target, bytes4 callSelector, bytes calldata callData, - uint gasLimit - ) external returns (uint); + uint256 gasLimit + ) external returns (uint256); function relay( - uint targetChainId, + uint256 targetChainId, address target, bytes calldata call, - uint gasLimit - ) external returns (uint); + uint256 gasLimit + ) external returns (uint256); } /** @@ -75,7 +74,7 @@ abstract contract Relayer is */ struct RelayerStorage { // TargetChainId => Nonce - mapping(uint => uint) nonce; + mapping(uint256 => uint256) nonce; } // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.Relayer")) - 1)) & ~bytes32(uint256(0xff)) @@ -111,7 +110,7 @@ abstract contract Relayer is /** * @dev Returns the nonce for a given chain */ - function nonce(uint chainId) external view returns (uint) { + function nonce(uint256 chainId) external view returns (uint256) { RelayerStorage storage $ = _getRelayerStorage(); return $.nonce[chainId]; } @@ -126,13 +125,13 @@ abstract contract Relayer is */ function _relay( - uint targetChainId, + uint256 targetChainId, address target, bytes memory call, - uint gasLimit - ) internal isRegistered(_msgSender()) returns (uint) { + uint256 gasLimit + ) internal isRegistered(_msgSender()) returns (uint256) { RelayerStorage storage $ = _getRelayerStorage(); - uint _nonce = ++$.nonce[targetChainId]; + uint256 _nonce = ++$.nonce[targetChainId]; emit Relayed(targetChainId, target, call, gasLimit, _nonce); return _nonce; @@ -148,11 +147,11 @@ abstract contract Relayer is * @param gasLimit the gas limit for the call executed on the target chain */ function relay( - uint targetChainId, + uint256 targetChainId, address target, bytes calldata call, - uint gasLimit - ) external returns (uint) { + uint256 gasLimit + ) external returns (uint256) { return _relay(targetChainId, target, call, gasLimit); } @@ -170,12 +169,12 @@ abstract contract Relayer is * @param gasLimit the gas limit for the call executed on the target chain */ function relayWithMetadata( - uint targetChainId, + uint256 targetChainId, address target, bytes4 callSelector, bytes calldata callData, - uint gasLimit - ) external returns (uint) { + uint256 gasLimit + ) external returns (uint256) { return _relay( targetChainId, diff --git a/zilliqa/src/contracts/uccb/SignatureValidator.sol b/zilliqa/src/contracts/uccb/SignatureValidator.sol index a7f40ea4e9..7d1856272c 100644 --- a/zilliqa/src/contracts/uccb/SignatureValidator.sol +++ b/zilliqa/src/contracts/uccb/SignatureValidator.sol @@ -34,7 +34,7 @@ library SignatureValidator { */ function isSupermajority( EnumerableSet.AddressSet storage self, - uint count + uint256 count ) internal view returns (bool) { return count * 3 > self.length() * 2; } @@ -51,9 +51,9 @@ library SignatureValidator { bytes[] calldata signatures ) internal view { address lastSigner = address(0); - uint signaturesLength = signatures.length; + uint256 signaturesLength = signatures.length; - for (uint i = 0; i < signaturesLength; ) { + for (uint256 i = 0; i < signaturesLength; ) { address signer = ethSignedMessageHash.recover(signatures[i]); if (signer <= lastSigner) { revert ISignatureValidatorErrors diff --git a/zilliqa/src/contracts/uccb/ValidatorManager.sol b/zilliqa/src/contracts/uccb/ValidatorManager.sol index c0a8825e77..8e823e0389 100644 --- a/zilliqa/src/contracts/uccb/ValidatorManager.sol +++ b/zilliqa/src/contracts/uccb/ValidatorManager.sol @@ -17,7 +17,7 @@ interface IValidatorManager is ISignatureValidatorErrors { function isValidator(address user) external view returns (bool); - function validatorsSize() external view returns (uint); + function validatorsSize() external view returns (uint256); function validateMessageWithSupermajority( bytes32 ethSignedMessageHash, @@ -83,8 +83,8 @@ contract ValidatorManager is ) external initializer { __Ownable_init(_owner); - uint validatorsLength = validators.length; - for (uint i = 0; i < validatorsLength; ++i) { + uint256 validatorsLength = validators.length; + for (uint256 i = 0; i < validatorsLength; ++i) { _addValidator(validators[i]); } } @@ -146,7 +146,7 @@ contract ValidatorManager is /** * @dev getter to get the size of the validator set */ - function validatorsSize() external view returns (uint) { + function validatorsSize() external view returns (uint256) { return _validators().length(); } From d1d6bc0991d6d3e794a50dc86b34c7c85275a5c4 Mon Sep 17 00:00:00 2001 From: rrw-zilliqa <108257153+rrw-zilliqa@users.noreply.github.com> Date: Tue, 4 Mar 2025 12:25:01 +0000 Subject: [PATCH 9/9] Apply formatting changes to solidity files --- zilliqa/src/contracts/test/Tester.sol | 5 ++++- zilliqa/src/contracts/uccb/DispatchReplayChecker.sol | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/zilliqa/src/contracts/test/Tester.sol b/zilliqa/src/contracts/test/Tester.sol index 395af8f2c2..c7c2702677 100644 --- a/zilliqa/src/contracts/test/Tester.sol +++ b/zilliqa/src/contracts/test/Tester.sol @@ -22,7 +22,10 @@ abstract contract Tester is Test { while (arr[uint(i)].addr < pivot.addr) i++; while (pivot.addr < arr[uint(j)].addr) j--; if (i <= j) { - (arr[uint256(i)], arr[uint256uint256uint256(j)]) = (arr[uint(j)], arr[uint(i)]); + (arr[uint256(i)], arr[uint256uint256uint256(j)]) = ( + arr[uint256(j)], + arr[uint256(i)] + ); i++; j--; } diff --git a/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol b/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol index 911ba578cb..4412a56deb 100644 --- a/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol +++ b/zilliqa/src/contracts/uccb/DispatchReplayChecker.sol @@ -68,7 +68,10 @@ abstract contract DispatchReplayChecker is IDispatchReplayChecker { /** * @dev Internal function handling the replay check and reverts if the message has been dispatched */ - function _replayDispatchCheck(uint256 sourceChainId, uint256 nonce) internal { + function _replayDispatchCheck( + uint256 sourceChainId, + uint256 nonce + ) internal { DispatchReplayCheckerStorage storage $ = _getDispatchReplayCheckerStorage();