From a0e1f9f5f38e464ebe6f003f9a85c16d829e1302 Mon Sep 17 00:00:00 2001 From: girazoki Date: Tue, 21 Jan 2025 18:39:36 +0100 Subject: [PATCH 01/21] add slashing processing --- overridden_contracts/src/Gateway.sol | 20 +++++++- overridden_contracts/src/Types.sol | 3 +- .../src/interfaces/IMiddlewareBasic.sol | 51 +++++++++++++++++++ .../src/interfaces/IOGateway.sol | 6 +++ 4 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 overridden_contracts/src/interfaces/IMiddlewareBasic.sol diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index fcca8a1..6501721 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -54,7 +54,8 @@ import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol"; import {Operators} from "./Operators.sol"; -import {IOGateway} from "./interfaces/IOGateway.sol"; +import {IOGateway, Slash} from "./interfaces/IOGateway.sol"; +import {IMiddlewareBasic} from "./interfaces/IMiddlewareBasic.sol"; contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { using Address for address; @@ -240,6 +241,21 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { success = false; } } + else if (message.command == Command.ReportSlashes) { + if (s_middleware != address(0)) { + (Slash[] memory slashes) = abi.decode(message.params, (Slash[])); + // At most it will be 10, defined by + // https://github.com/moondance-labs/tanssi/blob/88e59e6e5afb198947690487f286b9ad7cd4cde6/chains/orchestrator-relays/runtime/dancelight/src/lib.rs#L1446 + for(uint i=0; i +pragma solidity 0.8.25; + +interface IMiddlewareBasic { + /** + * @notice Distributes rewards + * @param epoch The epoch of the rewards distribution + * @param eraIndex The era index of the rewards distribution + * @param totalPointsToken The total points token + * @param tokensInflatedToken The total tokens inflated token + * @param rewardsRoot The rewards root + * @dev This function is called by the gateway only + */ + function distributeRewards( + uint256 epoch, + uint256 eraIndex, + uint256 totalPointsToken, + uint256 tokensInflatedToken, + bytes32 rewardsRoot + ) external; + /** + * @notice Slashes an operator's stake + * @dev Only the owner can call this function + * @dev This function first updates the stake cache for the target epoch + * @param epoch The epoch number + * @param operatorKey The operator key to slash + * @param percentage Percentage to slash, represented as parts per billion. + */ + function slash(uint48 epoch, bytes32 operatorKey, uint256 percentage) external; + /** + * @notice Determines which epoch a timestamp belongs to + * @param timestamp The timestamp to check + * @return epoch The corresponding epoch number + */ + function getEpochAtTs( + uint48 timestamp + ) external view returns (uint48 epoch); +} \ No newline at end of file diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index d296a62..c06fe2e 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -28,3 +28,9 @@ interface IOGateway is IGateway { bytes32[] calldata data ) external; } + +struct Slash { + bytes32 operatorKey; + uint256 slashFraction; + uint256 timestamp; +} \ No newline at end of file From 9a52c2a45a82ff2cb8e7d6ef2661538fe37f0118 Mon Sep 17 00:00:00 2001 From: girazoki Date: Wed, 22 Jan 2025 10:25:03 +0100 Subject: [PATCH 02/21] apply some PR comments --- overridden_contracts/src/Gateway.sol | 6 +++--- .../src/interfaces/IMiddlewareBasic.sol | 2 +- .../src/interfaces/IOGateway.sol | 18 +++++++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 6501721..b04ed88 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -54,7 +54,7 @@ import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol"; import {Operators} from "./Operators.sol"; -import {IOGateway, Slash} from "./interfaces/IOGateway.sol"; +import {IOGateway} from "./interfaces/IOGateway.sol"; import {IMiddlewareBasic} from "./interfaces/IMiddlewareBasic.sol"; contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { @@ -243,7 +243,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } else if (message.command == Command.ReportSlashes) { if (s_middleware != address(0)) { - (Slash[] memory slashes) = abi.decode(message.params, (Slash[])); + (IOGateway.Slash[] memory slashes) = abi.decode(message.params, (IOGateway.Slash[])); // At most it will be 10, defined by // https://github.com/moondance-labs/tanssi/blob/88e59e6e5afb198947690487f286b9ad7cd4cde6/chains/orchestrator-relays/runtime/dancelight/src/lib.rs#L1446 for(uint i=0; i -pragma solidity 0.8.25; +pragma solidity ^0.8.0; interface IMiddlewareBasic { /** diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index c06fe2e..fe5c11f 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -12,7 +12,7 @@ // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Tanssi. If not, see -pragma solidity 0.8.25; +pragma solidity ^0.8.0; import {ParaID} from "../Types.sol"; import {IGateway} from "./IGateway.sol"; @@ -24,13 +24,17 @@ interface IOGateway is IGateway { // Emitted when the middleware contract address is changed by the owner. event MiddlewareChanged(address indexed previousMiddleware, address indexed newMiddleware); - function sendOperatorsData( - bytes32[] calldata data - ) external; -} - -struct Slash { + // Slash struct, used to decode slashes, which are identified by + // operatorKey to be slashed + // slashFraction to be applied as parts per billion + // timestamp identifying when the slash happened + struct Slash { bytes32 operatorKey; uint256 slashFraction; uint256 timestamp; + } + + function sendOperatorsData( + bytes32[] calldata data + ) external; } \ No newline at end of file From 7d98e07655b1c9c09da42aa6998395c9ca7bd1af Mon Sep 17 00:00:00 2001 From: girazoki Date: Wed, 22 Jan 2025 11:01:18 +0100 Subject: [PATCH 03/21] add one more generic event --- overridden_contracts/src/Gateway.sol | 47 +++++++++++++++++++++------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index b04ed88..c9ee518 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -61,6 +61,9 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { using Address for address; using SafeNativeTransfer for address payable; + event UnableToProcessIndividualSlash(IOGateway.Slash slash); + event UnableToProcessSlashMessage(); + address public immutable AGENT_EXECUTOR; // Verification state @@ -108,6 +111,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { error TokenNotRegistered(); error CantSetMiddlewareToZeroAddress(); error CantSetMiddlewareToSameAddress(); + error MiddlewareNotSet(); // Message handlers can only be dispatched by the gateway itself modifier onlySelf() { @@ -242,19 +246,12 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } } else if (message.command == Command.ReportSlashes) { - if (s_middleware != address(0)) { - (IOGateway.Slash[] memory slashes) = abi.decode(message.params, (IOGateway.Slash[])); - // At most it will be 10, defined by - // https://github.com/moondance-labs/tanssi/blob/88e59e6e5afb198947690487f286b9ad7cd4cde6/chains/orchestrator-relays/runtime/dancelight/src/lib.rs#L1446 - for(uint i=0; i Date: Wed, 22 Jan 2025 14:55:46 +0100 Subject: [PATCH 04/21] add basic testing --- .github/workflows/cancel.yml | 14 ++++++++ .github/workflows/test.yml | 70 ++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 .github/workflows/cancel.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml new file mode 100644 index 0000000..e30e7ca --- /dev/null +++ b/.github/workflows/cancel.yml @@ -0,0 +1,14 @@ +name: Cancel +on: [push] +jobs: + cancel: + name: "Cancel Previous Build" + if: github.ref != 'refs/heads/master' + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - uses: styfle/cancel-workflow-action@0.9.1 + with: + workflow_id: ".github/workflows/test.yml" + all_but_latest: true + access_token: ${{ github.token }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a2a9c7f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + set-tags: + runs-on: ubuntu-latest + outputs: + git_ref: ${{ steps.check-git-ref.outputs.git_ref }} + git_branch: ${{ steps.check-git-ref.outputs.git_branch }} + git_target_branch: ${{ steps.check-git-ref.outputs.git_target_branch }} + steps: + - name: Check git ref + id: check-git-ref + # if PR + # else if manual PR + # else (push) + run: | + if [[ -n "${{ github.event.pull_request.head.sha }}" ]]; then + echo "git_branch=$(echo ${GITHUB_HEAD_REF})" >> $GITHUB_OUTPUT + echo "git_target_branch=$(echo ${GITHUB_BASE_REF})" >> $GITHUB_OUTPUT + echo "git_ref=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT + else + echo "git_branch=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT + echo "git_target_branch=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT + echo "git_ref=$GITHUB_REF" >> $GITHUB_OUTPUT + fi + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + + needs: ["set-tags"] + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: stable + + - name: Show Forge version + run: | + forge --version + + - name: Override contracts and Build + run: | + ./add_overridden_contracts.sh + + - name: Run Forge fmt + run: | + cd snowbridge/contracts + forge fmt --check + id: fmt + + - name: Run Forge tests + run: | + cd snowbridge/contracts + forge test -vvv + id: test \ No newline at end of file From 84e37218021f3138a237251735ee696b37ea771e Mon Sep 17 00:00:00 2001 From: girazoki Date: Wed, 22 Jan 2025 15:00:27 +0100 Subject: [PATCH 05/21] fmt --- overridden_contracts/src/Gateway.sol | 165 ++++++------------ overridden_contracts/src/Operators.sol | 4 +- overridden_contracts/src/Verification.sol | 17 +- .../src/interfaces/IMiddlewareBasic.sol | 8 +- .../src/interfaces/IOGateway.sol | 6 +- .../src/libraries/OSubstrateTypes.sol | 9 +- overridden_contracts/test/BeefyClient.t.sol | 3 +- .../test/mocks/MockOGateway.sol | 64 +++---- 8 files changed, 99 insertions(+), 177 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index c9ee518..18e8866 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -244,14 +244,13 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { catch { success = false; } - } - else if (message.command == Command.ReportSlashes) { - // We need to put all this inside a generic try-catch, since we dont want to revert decoding nor anything - try Gateway(this).reportSlashes{gas: maxDispatchGas}(message.params){} - catch { - emit UnableToProcessSlashMessage(); - success = false; - } + } else if (message.command == Command.ReportSlashes) { + // We need to put all this inside a generic try-catch, since we dont want to revert decoding nor anything + try Gateway(this).reportSlashes{gas: maxDispatchGas}(message.params) {} + catch { + emit UnableToProcessSlashMessage(); + success = false; + } } // Calculate a gas refund, capped to protect against huge spikes in `tx.gasprice` @@ -278,23 +277,17 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { return CoreStorage.layout().mode; } - function channelOperatingModeOf( - ChannelID channelID - ) external view returns (OperatingMode) { + function channelOperatingModeOf(ChannelID channelID) external view returns (OperatingMode) { Channel storage ch = _ensureChannel(channelID); return ch.mode; } - function channelNoncesOf( - ChannelID channelID - ) external view returns (uint64, uint64) { + function channelNoncesOf(ChannelID channelID) external view returns (uint64, uint64) { Channel storage ch = _ensureChannel(channelID); return (ch.inboundNonce, ch.outboundNonce); } - function agentOf( - bytes32 agentID - ) external view returns (address) { + function agentOf(bytes32 agentID) external view returns (address) { return _ensureAgent(agentID); } @@ -312,9 +305,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { */ // Execute code within an agent - function agentExecute( - bytes calldata data - ) external onlySelf { + function agentExecute(bytes calldata data) external onlySelf { AgentExecuteParams memory params = abi.decode(data, (AgentExecuteParams)); address agent = _ensureAgent(params.agentID); @@ -332,9 +323,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } /// @dev Create an agent for a consensus system on Polkadot - function createAgent( - bytes calldata data - ) external onlySelf { + function createAgent(bytes calldata data) external onlySelf { CoreStorage.Layout storage $ = CoreStorage.layout(); CreateAgentParams memory params = abi.decode(data, (CreateAgentParams)); @@ -351,9 +340,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } /// @dev Create a messaging channel for a Polkadot parachain - function createChannel( - bytes calldata data - ) external onlySelf { + function createChannel(bytes calldata data) external onlySelf { CoreStorage.Layout storage $ = CoreStorage.layout(); CreateChannelParams memory params = abi.decode(data, (CreateChannelParams)); @@ -376,9 +363,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } /// @dev Update the configuration for a channel - function updateChannel( - bytes calldata data - ) external onlySelf { + function updateChannel(bytes calldata data) external onlySelf { UpdateChannelParams memory params = abi.decode(data, (UpdateChannelParams)); Channel storage ch = _ensureChannel(params.channelID); @@ -393,17 +378,13 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } /// @dev Perform an upgrade of the gateway - function upgrade( - bytes calldata data - ) external onlySelf { + function upgrade(bytes calldata data) external onlySelf { UpgradeParams memory params = abi.decode(data, (UpgradeParams)); Upgrade.upgrade(params.impl, params.implCodeHash, params.initParams); } // @dev Set the operating mode of the gateway - function setOperatingMode( - bytes calldata data - ) external onlySelf { + function setOperatingMode(bytes calldata data) external onlySelf { CoreStorage.Layout storage $ = CoreStorage.layout(); SetOperatingModeParams memory params = abi.decode(data, (SetOperatingModeParams)); $.mode = params.mode; @@ -411,9 +392,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } // @dev Transfer funds from an agent to a recipient account - function transferNativeFromAgent( - bytes calldata data - ) external onlySelf { + function transferNativeFromAgent(bytes calldata data) external onlySelf { TransferNativeFromAgentParams memory params = abi.decode(data, (TransferNativeFromAgentParams)); address agent = _ensureAgent(params.agentID); @@ -423,9 +402,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } // @dev Set token fees of the gateway - function setTokenTransferFees( - bytes calldata data - ) external onlySelf { + function setTokenTransferFees(bytes calldata data) external onlySelf { AssetsStorage.Layout storage $ = AssetsStorage.layout(); SetTokenTransferFeesParams memory params = abi.decode(data, (SetTokenTransferFeesParams)); $.assetHubCreateAssetFee = params.assetHubCreateAssetFee; @@ -435,9 +412,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } // @dev Set pricing params of the gateway - function setPricingParameters( - bytes calldata data - ) external onlySelf { + function setPricingParameters(bytes calldata data) external onlySelf { PricingStorage.Layout storage pricing = PricingStorage.layout(); SetPricingParametersParams memory params = abi.decode(data, (SetPricingParametersParams)); pricing.exchangeRate = params.exchangeRate; @@ -450,34 +425,26 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { * Assets */ // @dev Register a new fungible Polkadot token for an agent - function registerForeignToken( - bytes calldata data - ) external onlySelf { + function registerForeignToken(bytes calldata data) external onlySelf { RegisterForeignTokenParams memory params = abi.decode(data, (RegisterForeignTokenParams)); Assets.registerForeignToken(params.foreignTokenID, params.name, params.symbol, params.decimals); } // @dev Mint foreign token from polkadot - function mintForeignToken( - bytes calldata data - ) external onlySelf { + function mintForeignToken(bytes calldata data) external onlySelf { MintForeignTokenParams memory params = abi.decode(data, (MintForeignTokenParams)); Assets.mintForeignToken(params.foreignTokenID, params.recipient, params.amount); } // @dev Transfer Ethereum native token back from polkadot - function transferNativeToken( - bytes calldata data - ) external onlySelf { + function transferNativeToken(bytes calldata data) external onlySelf { TransferNativeTokenParams memory params = abi.decode(data, (TransferNativeTokenParams)); address agent = _ensureAgent(params.agentID); Assets.transferNativeToken(AGENT_EXECUTOR, agent, params.token, params.recipient, params.amount); } // @dev Mint foreign token from polkadot - function reportSlashes( - bytes calldata data - ) external onlySelf { + function reportSlashes(bytes calldata data) external onlySelf { // Dont process message if we dont have a middleware set if (s_middleware != address(0)) { revert MiddlewareNotSet(); @@ -489,7 +456,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { // At most it will be 10, defined by // https://github.com/moondance-labs/tanssi/blob/88e59e6e5afb198947690487f286b9ad7cd4cde6/chains/orchestrator-relays/runtime/dancelight/src/lib.rs#L1446 - for(uint i=0; i ETH) - function _convertToNative( - UD60x18 exchangeRate, - UD60x18 multiplier, - UD60x18 amount - ) internal view returns (uint256) { + function _convertToNative(UD60x18 exchangeRate, UD60x18 multiplier, UD60x18 amount) + internal + view + returns (uint256) + { UD60x18 ethDecimals = convert(1e18); UD60x18 foreignDecimals = convert(10).pow(convert(uint256(FOREIGN_TOKEN_DECIMALS))); UD60x18 nativeAmount = multiplier.mul(amount).mul(exchangeRate).div(foreignDecimals).mul(ethDecimals); @@ -600,18 +561,14 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } // Calculate the fee for accepting an outbound message - function _calculateFee( - Costs memory costs - ) internal view returns (uint256) { + function _calculateFee(Costs memory costs) internal view returns (uint256) { PricingStorage.Layout storage pricing = PricingStorage.layout(); UD60x18 amount = convert(pricing.deliveryCost + costs.foreign); return costs.native + _convertToNative(pricing.exchangeRate, pricing.multiplier, amount); } // Submit an outbound message to Polkadot, after taking fees - function _submitOutbound( - Ticket memory ticket - ) internal { + function _submitOutbound(Ticket memory ticket) internal { ChannelID channelID = ticket.dest.into(); Channel storage channel = _ensureChannel(channelID); @@ -642,7 +599,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { emit OutboundMessageAccepted(channelID, channel.outboundNonce, messageID, ticket.payload); } - // Submit an outbound message to a specific channel. + // Submit an outbound message to a specific channel. // Doesn't handle fees. function _submitOutboundToChannel(ChannelID channelID, bytes memory payload) internal { Channel storage channel = _ensureChannel(channelID); @@ -661,9 +618,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } /// @dev Outbound message can be disabled globally or on a per-channel basis. - function _ensureOutboundMessagingEnabled( - Channel storage ch - ) internal view { + function _ensureOutboundMessagingEnabled(Channel storage ch) internal view { CoreStorage.Layout storage $ = CoreStorage.layout(); if ($.mode != OperatingMode.Normal || ch.mode != OperatingMode.Normal) { revert Disabled(); @@ -671,9 +626,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } /// @dev Ensure that the specified parachain has a channel allocated - function _ensureChannel( - ChannelID channelID - ) internal view returns (Channel storage ch) { + function _ensureChannel(ChannelID channelID) internal view returns (Channel storage ch) { ch = CoreStorage.layout().channels[channelID]; // A channel always has an agent specified. if (ch.agent == address(0)) { @@ -682,9 +635,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } /// @dev Ensure that the specified agentID has a corresponding contract - function _ensureAgent( - bytes32 agentID - ) internal view returns (address agent) { + function _ensureAgent(bytes32 agentID) internal view returns (address agent) { agent = CoreStorage.layout().agents[agentID]; if (agent == address(0)) { revert AgentDoesNotExist(); @@ -733,7 +684,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { address rescueOperator; } - /// Initialize storage within the `GatewayProxy` contract using this initializer. + /// Initialize storage within the `GatewayProxy` contract using this initializer. /// /// This initializer cannot be called externally via the proxy as the function selector /// is overshadowed in the proxy. @@ -758,9 +709,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { /// } /// ``` /// - function initialize( - bytes calldata data - ) external virtual { + function initialize(bytes calldata data) external virtual { // Ensure that arbitrary users cannot initialize storage in this logic contract. if (ERC1967.load() == address(0)) { revert Unauthorized(); @@ -815,19 +764,17 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } /// Changes the middleware address. - function setMiddleware( - address middleware - ) external onlyOwner { + function setMiddleware(address middleware) external onlyOwner { address oldMiddleware = s_middleware; - if(middleware == address(0)) { + if (middleware == address(0)) { revert CantSetMiddlewareToZeroAddress(); } - if(middleware == oldMiddleware) { + if (middleware == oldMiddleware) { revert CantSetMiddlewareToSameAddress(); } - + s_middleware = middleware; emit MiddlewareChanged(oldMiddleware, middleware); } diff --git a/overridden_contracts/src/Operators.sol b/overridden_contracts/src/Operators.sol index 99e7906..ba424d9 100644 --- a/overridden_contracts/src/Operators.sol +++ b/overridden_contracts/src/Operators.sol @@ -28,9 +28,7 @@ library Operators { uint16 private constant MAX_OPERATORS = 1000; - function encodeOperatorsData( - bytes32[] calldata operatorsKeys - ) internal returns (Ticket memory ticket) { + function encodeOperatorsData(bytes32[] calldata operatorsKeys) internal returns (Ticket memory ticket) { if (operatorsKeys.length == 0) { revert Operators__OperatorsKeysCannotBeEmpty(); } diff --git a/overridden_contracts/src/Verification.sol b/overridden_contracts/src/Verification.sol index eaaba4b..443001b 100644 --- a/overridden_contracts/src/Verification.sol +++ b/overridden_contracts/src/Verification.sol @@ -98,11 +98,12 @@ library Verification { /// @param messageCommitment The message commitment root expected to be contained within the /// digest of BridgeHub parachain header. /// @param proof The chain of proofs described above - function verifyCommitment(address beefyClient, bytes4 encodedParaID, bytes32 messageCommitment, Proof calldata proof) - external - view - returns (bool) - { + function verifyCommitment( + address beefyClient, + bytes4 encodedParaID, + bytes32 messageCommitment, + Proof calldata proof + ) external view returns (bool) { bytes32 leafHash = createMMRLeaf(proof.leafPartial, proof.parachainHeadsRoot, messageCommitment); // Verify that the MMR leaf is part of the MMR maintained by the BEEFY light client @@ -202,7 +203,11 @@ library Verification { // SCALE-encode: MMRLeaf // Reference: https://github.com/paritytech/substrate/blob/14e0a0b628f9154c5a2c870062c3aac7df8983ed/primitives/consensus/beefy/src/mmr.rs#L52 - function createMMRLeaf(MMRLeafPartial memory leaf, bytes32 parachainHeadsRoot, bytes32 messageCommitment) internal pure returns (bytes32) { + function createMMRLeaf(MMRLeafPartial memory leaf, bytes32 parachainHeadsRoot, bytes32 messageCommitment) + internal + pure + returns (bytes32) + { bytes memory encodedLeaf = bytes.concat( ScaleCodec.encodeU8(leaf.version), ScaleCodec.encodeU32(leaf.parentNumber), diff --git a/overridden_contracts/src/interfaces/IMiddlewareBasic.sol b/overridden_contracts/src/interfaces/IMiddlewareBasic.sol index fd3ca20..4fd2908 100644 --- a/overridden_contracts/src/interfaces/IMiddlewareBasic.sol +++ b/overridden_contracts/src/interfaces/IMiddlewareBasic.sol @@ -15,7 +15,7 @@ pragma solidity ^0.8.0; interface IMiddlewareBasic { - /** + /** * @notice Distributes rewards * @param epoch The epoch of the rewards distribution * @param eraIndex The era index of the rewards distribution @@ -45,7 +45,5 @@ interface IMiddlewareBasic { * @param timestamp The timestamp to check * @return epoch The corresponding epoch number */ - function getEpochAtTs( - uint48 timestamp - ) external view returns (uint48 epoch); -} \ No newline at end of file + function getEpochAtTs(uint48 timestamp) external view returns (uint48 epoch); +} diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index fe5c11f..a5c05b4 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -34,7 +34,5 @@ interface IOGateway is IGateway { uint256 timestamp; } - function sendOperatorsData( - bytes32[] calldata data - ) external; -} \ No newline at end of file + function sendOperatorsData(bytes32[] calldata data) external; +} diff --git a/overridden_contracts/src/libraries/OSubstrateTypes.sol b/overridden_contracts/src/libraries/OSubstrateTypes.sol index 61cd84e..af28c69 100644 --- a/overridden_contracts/src/libraries/OSubstrateTypes.sol +++ b/overridden_contracts/src/libraries/OSubstrateTypes.sol @@ -26,10 +26,11 @@ library OSubstrateTypes { ReceiveValidators } - function EncodedOperatorsData( - bytes32[] calldata operatorsKeys, - uint32 operatorsCount - ) internal view returns (bytes memory) { + function EncodedOperatorsData(bytes32[] calldata operatorsKeys, uint32 operatorsCount) + internal + view + returns (bytes memory) + { bytes memory operatorsFlattened = new bytes(operatorsCount * 32); for (uint32 i = 0; i < operatorsCount; i++) { for (uint32 j = 0; j < 32; j++) { diff --git a/overridden_contracts/test/BeefyClient.t.sol b/overridden_contracts/test/BeefyClient.t.sol index 895744a..8731e4f 100644 --- a/overridden_contracts/test/BeefyClient.t.sol +++ b/overridden_contracts/test/BeefyClient.t.sol @@ -155,7 +155,7 @@ contract BeefyClientTest is Test { uint32 nextAuthoritySetLen = uint32(beefyCommitmentRaw.readUint(".params.leaf.nextAuthoritySetLen")); bytes32 nextAuthoritySetRoot = beefyCommitmentRaw.readBytes32(".params.leaf.nextAuthoritySetRoot"); bytes32 parachainHeadsRoot = beefyCommitmentRaw.readBytes32(".params.leaf.parachainHeadsRoot"); - bytes32 messageCommitment = beefyCommitmentRaw.readBytes32(".params.leaf.messageCommitment"); + bytes32 messageCommitment = beefyCommitmentRaw.readBytes32(".params.leaf.messageCommitment"); mmrLeaf = BeefyClient.MMRLeaf( version, parentNumber, @@ -165,7 +165,6 @@ contract BeefyClientTest is Test { nextAuthoritySetRoot, parachainHeadsRoot, messageCommitment - ); } diff --git a/overridden_contracts/test/mocks/MockOGateway.sol b/overridden_contracts/test/mocks/MockOGateway.sol index 4e2a881..e4299c6 100644 --- a/overridden_contracts/test/mocks/MockOGateway.sol +++ b/overridden_contracts/test/mocks/MockOGateway.sol @@ -23,58 +23,44 @@ contract MockOGateway is Gateway { Gateway(beefyClient, agentExecutor, bridgeHubParaID, bridgeHubHubAgentID, foreignTokenDecimals, maxDestinationFee) {} - function agentExecutePublic( - bytes calldata params - ) external { + function agentExecutePublic(bytes calldata params) external { this.agentExecute(params); } - function createAgentPublic( - bytes calldata params - ) external { + function createAgentPublic(bytes calldata params) external { this.createAgent(params); } - function upgradePublic( - bytes calldata params - ) external { + function upgradePublic(bytes calldata params) external { this.upgrade(params); } - function createChannelPublic( - bytes calldata params - ) external { + function createChannelPublic(bytes calldata params) external { this.createChannel(params); } - function updateChannelPublic( - bytes calldata params - ) external { + function updateChannelPublic(bytes calldata params) external { this.updateChannel(params); } - function setOperatingModePublic( - bytes calldata params - ) external { + function setOperatingModePublic(bytes calldata params) external { this.setOperatingMode(params); } - function transferNativeFromAgentPublic( - bytes calldata params - ) external { + function transferNativeFromAgentPublic(bytes calldata params) external { this.transferNativeFromAgent(params); } - function setCommitmentsAreVerified( - bool value - ) external { + function setCommitmentsAreVerified(bool value) external { commitmentsAreVerified = value; } - function _verifyCommitment( - bytes32 commitment, - Verification.Proof calldata proof - ) internal view override returns (bool) { + function _verifyCommitment(bytes32 commitment, Verification.Proof calldata proof) + internal + view + override + returns (bool) + { if (BEEFY_CLIENT != address(0)) { return super._verifyCommitment(commitment, proof); } else { @@ -83,33 +69,23 @@ contract MockOGateway is Gateway { } } - function setTokenTransferFeesPublic( - bytes calldata params - ) external { + function setTokenTransferFeesPublic(bytes calldata params) external { this.setTokenTransferFees(params); } - function setPricingParametersPublic( - bytes calldata params - ) external { + function setPricingParametersPublic(bytes calldata params) external { this.setPricingParameters(params); } - function registerForeignTokenPublic( - bytes calldata params - ) external { + function registerForeignTokenPublic(bytes calldata params) external { this.registerForeignToken(params); } - function mintForeignTokenPublic( - bytes calldata params - ) external { + function mintForeignTokenPublic(bytes calldata params) external { this.mintForeignToken(params); } - function transferNativeTokenPublic( - bytes calldata params - ) external { + function transferNativeTokenPublic(bytes calldata params) external { this.transferNativeToken(params); } -} \ No newline at end of file +} From 86059c83f0e1a883aba1637d101a53a74d993c15 Mon Sep 17 00:00:00 2001 From: girazoki Date: Wed, 22 Jan 2025 15:00:43 +0100 Subject: [PATCH 06/21] test --- .github/workflows/test.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2a9c7f..80dad1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,16 +53,15 @@ jobs: run: | forge --version - - name: Override contracts and Build - run: | - ./add_overridden_contracts.sh - - name: Run Forge fmt run: | - cd snowbridge/contracts - forge fmt --check + forge fmt overridden_contracts/ --check id: fmt + - name: Override contracts and Build + run: | + ./add_overridden_contracts.sh + - name: Run Forge tests run: | cd snowbridge/contracts From 49b878bc0f479686b30bfa423f051094fe756195 Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 11:38:05 +0100 Subject: [PATCH 07/21] modify job --- .github/workflows/relayer.yml | 62 ------------------- .github/workflows/test.yml | 112 ++++++++++++++++++---------------- 2 files changed, 58 insertions(+), 116 deletions(-) delete mode 100644 .github/workflows/relayer.yml diff --git a/.github/workflows/relayer.yml b/.github/workflows/relayer.yml deleted file mode 100644 index 8ffa3b2..0000000 --- a/.github/workflows/relayer.yml +++ /dev/null @@ -1,62 +0,0 @@ -# This workflow will test relayer - -name: Relayer - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Check jq - run: jq --version - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.23' - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: stable - - - name: Show Forge version - run: forge --version - - - name: Install abigen - run: go install github.com/ethereum/go-ethereum/cmd/abigen@v1.14.11 - - - name: Check abigen - run: abigen --version - - - name: Install sszgen - run: go install github.com/ferranbt/fastssz/sszgen@v0.1.4 - - - name: Check sszgen - run: sszgen --help - - - name: Install Mage - run: go install github.com/magefile/mage@v1.15.0 - - - name: Check mage - run: mage --version - - - name: Build - run: mage build - - - name: Check if go contract bindings are up-to-date - run: git diff --exit-code ./relays/contracts || (echo "The contract bindings are not up-to-date against contracts." && exit 1) - - - name: Test - run: mage test - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80dad1d..cfaf5f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,69 +1,73 @@ -name: CI +# This workflow will test relayer + +name: Test on: push: + branches: [ "main" ] pull_request: - workflow_dispatch: - -env: - FOUNDRY_PROFILE: ci + branches: [ "main" ] jobs: - set-tags: - runs-on: ubuntu-latest - outputs: - git_ref: ${{ steps.check-git-ref.outputs.git_ref }} - git_branch: ${{ steps.check-git-ref.outputs.git_branch }} - git_target_branch: ${{ steps.check-git-ref.outputs.git_target_branch }} - steps: - - name: Check git ref - id: check-git-ref - # if PR - # else if manual PR - # else (push) - run: | - if [[ -n "${{ github.event.pull_request.head.sha }}" ]]; then - echo "git_branch=$(echo ${GITHUB_HEAD_REF})" >> $GITHUB_OUTPUT - echo "git_target_branch=$(echo ${GITHUB_BASE_REF})" >> $GITHUB_OUTPUT - echo "git_ref=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT - else - echo "git_branch=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT - echo "git_target_branch=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT - echo "git_ref=$GITHUB_REF" >> $GITHUB_OUTPUT - fi - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - needs: ["set-tags"] + test: + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: + - uses: actions/checkout@v4 + with: submodules: recursive - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: + - name: Check jq + run: jq --version + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.23' + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: version: stable - - name: Show Forge version - run: | - forge --version + - name: Show Forge version + run: forge --version + + - name: Install abigen + run: go install github.com/ethereum/go-ethereum/cmd/abigen@v1.14.11 + + - name: Check abigen + run: abigen --version + + - name: Install sszgen + run: go install github.com/ferranbt/fastssz/sszgen@v0.1.4 + + - name: Check sszgen + run: sszgen --help + + - name: Install Mage + run: go install github.com/magefile/mage@v1.15.0 + + - name: Check mage + run: mage --version + + - name: Run Forge fmt + run: | + forge fmt overridden_contracts/ --check + id: fmt + + - name: Build + run: mage build + + - name: Check if go contract bindings are up-to-date + run: git diff --exit-code ./relays/contracts || (echo "The contract bindings are not up-to-date against contracts." && exit 1) - - name: Run Forge fmt - run: | - forge fmt overridden_contracts/ --check - id: fmt + - name: Test Relayer + run: mage test - - name: Override contracts and Build - run: | - ./add_overridden_contracts.sh + - name: Run Forge tests + run: | + cd snowbridge/contracts + forge test -vvv + id: test - - name: Run Forge tests - run: | - cd snowbridge/contracts - forge test -vvv - id: test \ No newline at end of file From 307c042104d93bd4dfad1bb32dd2f06ec0b71289 Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 11:38:47 +0100 Subject: [PATCH 08/21] fmt --- overridden_contracts/src/interfaces/IOGateway.sol | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index 793eb22..343d90a 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -33,14 +33,10 @@ interface IOGateway is IGateway { uint256 slashFraction; uint256 timestamp; } - - function s_middleware() external view returns(address); - function sendOperatorsData( - bytes32[] calldata data - ) external; + function s_middleware() external view returns (address); - function setMiddleware( - address middleware - ) external; + function sendOperatorsData(bytes32[] calldata data) external; + + function setMiddleware(address middleware) external; } From ec674405dfe642c49d1f77fcf2e350c8f23e4d9e Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 14:16:24 +0100 Subject: [PATCH 09/21] take into account era index --- overridden_contracts/src/Gateway.sol | 10 +++++----- overridden_contracts/src/interfaces/IOGateway.sol | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index c21f574..2624df4 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -451,17 +451,17 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { } // Decode - (IOGateway.Slash[] memory slashes) = abi.decode(data, (IOGateway.Slash[])); + (IOGateway.SlashParams memory slashes) = abi.decode(data, (IOGateway.SlashParams)); IMiddlewareBasic middleware = IMiddlewareBasic(s_middleware); // At most it will be 10, defined by // https://github.com/moondance-labs/tanssi/blob/88e59e6e5afb198947690487f286b9ad7cd4cde6/chains/orchestrator-relays/runtime/dancelight/src/lib.rs#L1446 - for (uint256 i = 0; i < slashes.length; ++i) { - uint48 epoch = middleware.getEpochAtTs(uint48(slashes[i].timestamp)); + for (uint256 i = 0; i < slashes.slashes.length; ++i) { + uint48 epoch = middleware.getEpochAtTs(uint48(slashes.slashes[i].timestamp)); //TODO maxDispatchGas should be probably be defined for all slashes, not only for one - try middleware.slash(epoch, slashes[i].operatorKey, slashes[i].slashFraction) {} + try middleware.slash(epoch, slashes[i].operatorKey, slashes.slashes[i].slashFraction) {} catch { - emit UnableToProcessIndividualSlash(slashes[i]); + emit UnableToProcessIndividualSlash(slashes.slashes[i]); continue; } } diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index 343d90a..06696f1 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -34,6 +34,11 @@ interface IOGateway is IGateway { uint256 timestamp; } + struct SlashParams { + uint256 eraIndex; + Slash[] slashes; + } + function s_middleware() external view returns (address); function sendOperatorsData(bytes32[] calldata data) external; From 837ba217864e1d30491f7386ba396bb37240949c Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 14:17:11 +0100 Subject: [PATCH 10/21] fmt --- overridden_contracts/src/Gateway.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 2624df4..183ed64 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -459,7 +459,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { for (uint256 i = 0; i < slashes.slashes.length; ++i) { uint48 epoch = middleware.getEpochAtTs(uint48(slashes.slashes[i].timestamp)); //TODO maxDispatchGas should be probably be defined for all slashes, not only for one - try middleware.slash(epoch, slashes[i].operatorKey, slashes.slashes[i].slashFraction) {} + try middleware.slash(epoch, slashes.slashes[i].operatorKey, slashes.slashes[i].slashFraction) {} catch { emit UnableToProcessIndividualSlash(slashes.slashes[i]); continue; From f0462c6de5c7feeef0e3a961d17c04a254552ae5 Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 16:17:15 +0100 Subject: [PATCH 11/21] tests --- overridden_contracts/src/Gateway.sol | 2 +- overridden_contracts/test/Gateway.t.sol | 103 ++++++++++++++++++ .../test/mocks/MockOMiddlewareProcessor.sol | 27 +++++ .../test/mocks/MockOMiddlewareReverter.sol | 24 ++++ 4 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 overridden_contracts/test/mocks/MockOMiddlewareProcessor.sol create mode 100644 overridden_contracts/test/mocks/MockOMiddlewareReverter.sol diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 183ed64..1887d1b 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -446,7 +446,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable, Ownable { // @dev Mint foreign token from polkadot function reportSlashes(bytes calldata data) external onlySelf { // Dont process message if we dont have a middleware set - if (s_middleware != address(0)) { + if (s_middleware == address(0)) { revert MiddlewareNotSet(); } diff --git a/overridden_contracts/test/Gateway.t.sol b/overridden_contracts/test/Gateway.t.sol index cafe474..b7371f2 100644 --- a/overridden_contracts/test/Gateway.t.sol +++ b/overridden_contracts/test/Gateway.t.sol @@ -8,10 +8,15 @@ import {console} from "forge-std/console.sol"; import {BeefyClient} from "../src/BeefyClient.sol"; import {IGateway} from "../src/interfaces/IGateway.sol"; +import {IOGateway} from "../src/interfaces/IOGateway.sol"; import {IInitializable} from "../src/interfaces/IInitializable.sol"; import {IUpgradable} from "../src/interfaces/IUpgradable.sol"; +import {IMiddlewareBasic} from "../src/interfaces/IMiddlewareBasic.sol"; import {Gateway} from "../src/Gateway.sol"; import {MockGateway} from "./mocks/MockGateway.sol"; +import {MockOMiddlewareReverter} from "./mocks/MockOMiddlewareReverter.sol"; +import {MockOMiddlewareProcessor} from "./mocks/MockOMiddlewareProcessor.sol"; + import {MockGatewayV2} from "./mocks/MockGatewayV2.sol"; import {GatewayProxy} from "../src/GatewayProxy.sol"; @@ -160,6 +165,13 @@ contract GatewayTest is Test { return (Command.CreateAgent, abi.encode((keccak256("6666")))); } + function makeReportSlashesCommand() public pure returns (Command, bytes memory) { + IOGateway.Slash[] memory slashes = new IOGateway.Slash[](1); + slashes[0] = IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: 500_000, timestamp: 1}); + uint256 eraIndex = 1; + return (Command.ReportSlashes, abi.encode(IOGateway.SlashParams({eraIndex: eraIndex, slashes: slashes}))); + } + function makeMockProof() public pure returns (Verification.Proof memory) { return Verification.Proof({ leafPartial: Verification.MMRLeafPartial({ @@ -1007,4 +1019,95 @@ contract GatewayTest is Test { bytes memory encodedParams = abi.encode(params); MockGateway(address(gateway)).agentExecutePublic(encodedParams); } + + // middleware not set, should not be able to process slash + function testSubmitSlashesWithoutMiddleware() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = makeReportSlashesCommand(); + + vm.expectEmit(true, true, true, true); + emit Gateway.UnableToProcessSlashMessage(); + // Expect the gateway to emit `InboundMessageDispatched` + vm.expectEmit(true, false, false, false); + emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); + + hoax(relayer, 1 ether); + IGateway(address(gateway)).submitV1( + InboundMessage(assetHubParaID.into(), 1, command, params, maxDispatchGas, maxRefund, reward, messageID), + proof, + makeMockProof() + ); + } + + // middleware set, but not complying with the interface, should not process slash + function testSubmitSlashesWithMiddlewareNotComplyingInterface() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = makeReportSlashesCommand(); + + IOGateway(address(gateway)).setMiddleware(0x0123456789012345678901234567890123456789); + + // Expect the gateway to emit `InboundMessageDispatched` + vm.expectEmit(true, true, true, true); + emit Gateway.UnableToProcessSlashMessage(); + vm.expectEmit(true, false, false, false); + emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); + + hoax(relayer, 1 ether); + IGateway(address(gateway)).submitV1( + InboundMessage(assetHubParaID.into(), 1, command, params, maxDispatchGas, maxRefund, reward, messageID), + proof, + makeMockProof() + ); + } + + // middleware set, complying interface but slash reverts + function testSubmitSlashesWithMiddlewareComplyingInterfaceAndSlashRevert() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = makeReportSlashesCommand(); + + IMiddlewareBasic middleware = new MockOMiddlewareReverter(); + + IOGateway(address(gateway)).setMiddleware(address(middleware)); + + IOGateway.Slash memory expectedSlash = + IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: 500_000, timestamp: 1}); + + vm.expectEmit(true, true, true, true); + emit Gateway.UnableToProcessIndividualSlash(expectedSlash); + vm.expectEmit(true, false, false, false); + emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); + + hoax(relayer, 1 ether); + IGateway(address(gateway)).submitV1( + InboundMessage(assetHubParaID.into(), 1, command, params, maxDispatchGas, maxRefund, reward, messageID), + proof, + makeMockProof() + ); + } + + // middleware set, complying interface and slash processed + function testSubmitSlashesWithMiddlewareComplyingInterfaceAndSlashProcessed() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = makeReportSlashesCommand(); + + IMiddlewareBasic middleware = new MockOMiddlewareProcessor(); + + IOGateway(address(gateway)).setMiddleware(address(middleware)); + + vm.expectEmit(true, true, true, true); + emit MockOMiddlewareProcessor.SlashProcessed(); + vm.expectEmit(true, false, false, false); + emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); + + hoax(relayer, 1 ether); + IGateway(address(gateway)).submitV1( + InboundMessage(assetHubParaID.into(), 1, command, params, maxDispatchGas, maxRefund, reward, messageID), + proof, + makeMockProof() + ); + } } diff --git a/overridden_contracts/test/mocks/MockOMiddlewareProcessor.sol b/overridden_contracts/test/mocks/MockOMiddlewareProcessor.sol new file mode 100644 index 0000000..caa9b7d --- /dev/null +++ b/overridden_contracts/test/mocks/MockOMiddlewareProcessor.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.25; + +import {IMiddlewareBasic} from "../../src/interfaces/IMiddlewareBasic.sol"; + +contract MockOMiddlewareProcessor is IMiddlewareBasic { + event RewardProcessed(); + event SlashProcessed(); + + function distributeRewards( + uint256 epoch, + uint256 eraIndex, + uint256 totalPointsToken, + uint256 tokensInflatedToken, + bytes32 rewardsRoot + ) external { + emit RewardProcessed(); + } + + function slash(uint48 epoch, bytes32 operatorKey, uint256 percentage) external { + emit SlashProcessed(); + } + + function getEpochAtTs(uint48 timestamp) external view returns (uint48 epoch) { + return timestamp; + } +} diff --git a/overridden_contracts/test/mocks/MockOMiddlewareReverter.sol b/overridden_contracts/test/mocks/MockOMiddlewareReverter.sol new file mode 100644 index 0000000..eb21adb --- /dev/null +++ b/overridden_contracts/test/mocks/MockOMiddlewareReverter.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.25; + +import {IMiddlewareBasic} from "../../src/interfaces/IMiddlewareBasic.sol"; + +contract MockOMiddlewareReverter is IMiddlewareBasic { + function distributeRewards( + uint256 epoch, + uint256 eraIndex, + uint256 totalPointsToken, + uint256 tokensInflatedToken, + bytes32 rewardsRoot + ) external { + revert("no distribute rewards"); + } + + function slash(uint48 epoch, bytes32 operatorKey, uint256 percentage) external { + revert("no process slash"); + } + + function getEpochAtTs(uint48 timestamp) external view returns (uint48 epoch) { + return timestamp; + } +} From 247bc96365c5f8a9cdbcf3fae09a8ede79ac4c91 Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 16:23:03 +0100 Subject: [PATCH 12/21] fmt --- overridden_contracts/src/Gateway.sol | 21 ++++++------------- overridden_contracts/src/Operators.sol | 8 +++---- .../src/interfaces/IOGateway.sol | 7 ++----- .../src/libraries/OSubstrateTypes.sol | 10 ++++----- .../src/storage/GatewayCoreStorage.sol | 2 +- .../test/override_test/Gateway.t.sol | 2 +- 6 files changed, 19 insertions(+), 31 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 907a87c..5294e1c 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -530,10 +530,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { return Assets.tokenAddressOf(tokenID); } - function sendOperatorsData( - bytes32[] calldata data, - uint48 epoch - ) external onlyMiddleware { + function sendOperatorsData(bytes32[] calldata data, uint48 epoch) external onlyMiddleware { Ticket memory ticket = Operators.encodeOperatorsData(data, epoch); _submitOutboundToChannel(PRIMARY_GOVERNANCE_CHANNEL_ID, ticket.payload); } @@ -784,9 +781,7 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { operatorStorage.operator = config.rescueOperator; } - function _transferOwnership( - address newOwner - ) internal { + function _transferOwnership(address newOwner) internal { GatewayCoreStorage.Layout storage layout = GatewayCoreStorage.layout(); address oldOwner = layout.owner; @@ -795,16 +790,12 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { emit OwnershipTransferred(oldOwner, newOwner); } - function transferOwnership( - address newOwner - ) external onlyOwner { + function transferOwnership(address newOwner) external onlyOwner { _transferOwnership(newOwner); } /// Changes the middleware address. - function setMiddleware( - address middleware - ) external onlyOwner { + function setMiddleware(address middleware) external onlyOwner { GatewayCoreStorage.Layout storage layout = GatewayCoreStorage.layout(); address oldMiddleware = layout.middleware; @@ -815,12 +806,12 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { if (middleware == oldMiddleware) { revert CantSetMiddlewareToSameAddress(); } - + layout.middleware = middleware; emit MiddlewareChanged(oldMiddleware, middleware); } - function s_middleware() external view returns(address) { + function s_middleware() external view returns (address) { GatewayCoreStorage.Layout storage layout = GatewayCoreStorage.layout(); return layout.middleware; } diff --git a/overridden_contracts/src/Operators.sol b/overridden_contracts/src/Operators.sol index 8c68674..c665013 100644 --- a/overridden_contracts/src/Operators.sol +++ b/overridden_contracts/src/Operators.sol @@ -28,10 +28,10 @@ library Operators { uint16 private constant MAX_OPERATORS = 1000; - function encodeOperatorsData( - bytes32[] calldata operatorsKeys, - uint48 epoch - ) internal returns (Ticket memory ticket) { + function encodeOperatorsData(bytes32[] calldata operatorsKeys, uint48 epoch) + internal + returns (Ticket memory ticket) + { if (operatorsKeys.length == 0) { revert Operators__OperatorsKeysCannotBeEmpty(); } diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index d63f022..ca529ec 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -41,11 +41,8 @@ interface IOGateway is IGateway { uint256 eraIndex; Slash[] slashes; } - - function sendOperatorsData( - bytes32[] calldata data, - uint48 epoch - ) external; + + function sendOperatorsData(bytes32[] calldata data, uint48 epoch) external; function s_middleware() external view returns (address); diff --git a/overridden_contracts/src/libraries/OSubstrateTypes.sol b/overridden_contracts/src/libraries/OSubstrateTypes.sol index f5f5d2d..980f032 100644 --- a/overridden_contracts/src/libraries/OSubstrateTypes.sol +++ b/overridden_contracts/src/libraries/OSubstrateTypes.sol @@ -26,11 +26,11 @@ library OSubstrateTypes { ReceiveValidators } - function EncodedOperatorsData( - bytes32[] calldata operatorsKeys, - uint32 operatorsCount, - uint48 epoch - ) internal view returns (bytes memory) { + function EncodedOperatorsData(bytes32[] calldata operatorsKeys, uint32 operatorsCount, uint48 epoch) + internal + view + returns (bytes memory) + { bytes memory operatorsFlattened = new bytes(operatorsCount * 32); for (uint32 i = 0; i < operatorsCount; i++) { for (uint32 j = 0; j < 32; j++) { diff --git a/overridden_contracts/src/storage/GatewayCoreStorage.sol b/overridden_contracts/src/storage/GatewayCoreStorage.sol index f00ca88..d161c96 100644 --- a/overridden_contracts/src/storage/GatewayCoreStorage.sol +++ b/overridden_contracts/src/storage/GatewayCoreStorage.sol @@ -30,4 +30,4 @@ library GatewayCoreStorage { ptr.slot := slot } } -} \ No newline at end of file +} diff --git a/overridden_contracts/test/override_test/Gateway.t.sol b/overridden_contracts/test/override_test/Gateway.t.sol index 0622532..bd3d462 100644 --- a/overridden_contracts/test/override_test/Gateway.t.sol +++ b/overridden_contracts/test/override_test/Gateway.t.sol @@ -187,7 +187,7 @@ contract GatewayTest is Test { function testSendOperatorsDataX() public { // FINAL_VALIDATORS_PAYLOAD has been encoded with epoch 1. uint48 epoch = 1; - + // Create mock agent and paraID vm.prank(middleware); vm.expectEmit(true, false, false, true); From 51cff6e58e44aa9a14f984026cd0dbe60385994e Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 16:51:41 +0100 Subject: [PATCH 13/21] adapt --- overridden_contracts/src/Gateway.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 5294e1c..9d72e94 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -461,14 +461,16 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { // @dev Mint foreign token from polkadot function reportSlashes(bytes calldata data) external onlySelf { + GatewayCoreStorage.Layout storage layout = GatewayCoreStorage.layout(); + address middlewareAddress = layout.middleware; // Dont process message if we dont have a middleware set - if (s_middleware == address(0)) { + if (middlewareAddress == address(0)) { revert MiddlewareNotSet(); } // Decode (IOGateway.SlashParams memory slashes) = abi.decode(data, (IOGateway.SlashParams)); - IMiddlewareBasic middleware = IMiddlewareBasic(s_middleware); + IMiddlewareBasic middleware = IMiddlewareBasic(middlewareAddress); // At most it will be 10, defined by // https://github.com/moondance-labs/tanssi/blob/88e59e6e5afb198947690487f286b9ad7cd4cde6/chains/orchestrator-relays/runtime/dancelight/src/lib.rs#L1446 From b30eb75a75065a14eb9509430939df07d250988c Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 16:53:44 +0100 Subject: [PATCH 14/21] fix --- overridden_contracts/src/interfaces/IOGateway.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index ca529ec..0b2791b 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -42,11 +42,9 @@ interface IOGateway is IGateway { Slash[] slashes; } - function sendOperatorsData(bytes32[] calldata data, uint48 epoch) external; - function s_middleware() external view returns (address); - function sendOperatorsData(bytes32[] calldata data) external; + function sendOperatorsData(bytes32[] calldata data, uint48 epoch) external; function setMiddleware(address middleware) external; } From afd9679dd87155aee766a24ae00aecd5166509cf Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 18:02:12 +0100 Subject: [PATCH 15/21] put events in the interface --- overridden_contracts/src/Gateway.sol | 3 --- overridden_contracts/src/interfaces/IOGateway.sol | 6 ++++++ overridden_contracts/test/Gateway.t.sol | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 9d72e94..d1fc871 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -62,9 +62,6 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { using Address for address; using SafeNativeTransfer for address payable; - event UnableToProcessIndividualSlash(IOGateway.Slash slash); - event UnableToProcessSlashMessage(); - address public immutable AGENT_EXECUTOR; // Verification state diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index 0b2791b..a203e35 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -27,6 +27,12 @@ interface IOGateway is IGateway { // Emitted when the middleware contract address is changed by the owner. event MiddlewareChanged(address indexed previousMiddleware, address indexed newMiddleware); + // Emitted when the middleware fails to apply an individual slash + event UnableToProcessIndividualSlash(IOGateway.Slash slash); + + // Emitted when the middleware fails to apply the slash message + event UnableToProcessSlashMessage(); + // Slash struct, used to decode slashes, which are identified by // operatorKey to be slashed // slashFraction to be applied as parts per billion diff --git a/overridden_contracts/test/Gateway.t.sol b/overridden_contracts/test/Gateway.t.sol index b7371f2..9b287c2 100644 --- a/overridden_contracts/test/Gateway.t.sol +++ b/overridden_contracts/test/Gateway.t.sol @@ -1027,7 +1027,7 @@ contract GatewayTest is Test { (Command command, bytes memory params) = makeReportSlashesCommand(); vm.expectEmit(true, true, true, true); - emit Gateway.UnableToProcessSlashMessage(); + emit IOGateway.UnableToProcessSlashMessage(); // Expect the gateway to emit `InboundMessageDispatched` vm.expectEmit(true, false, false, false); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); @@ -1050,7 +1050,7 @@ contract GatewayTest is Test { // Expect the gateway to emit `InboundMessageDispatched` vm.expectEmit(true, true, true, true); - emit Gateway.UnableToProcessSlashMessage(); + emit IOGateway.UnableToProcessSlashMessage(); vm.expectEmit(true, false, false, false); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); @@ -1076,7 +1076,7 @@ contract GatewayTest is Test { IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: 500_000, timestamp: 1}); vm.expectEmit(true, true, true, true); - emit Gateway.UnableToProcessIndividualSlash(expectedSlash); + emit IOGateway.UnableToProcessIndividualSlash(expectedSlash); vm.expectEmit(true, false, false, false); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); From 9e3a4960020dd885639f4eee2aef6ac5df3f26b6 Mon Sep 17 00:00:00 2001 From: girazoki Date: Thu, 23 Jan 2025 18:35:47 +0100 Subject: [PATCH 16/21] add tests with respect to errors --- overridden_contracts/src/Gateway.sol | 14 ++++++++++---- overridden_contracts/src/interfaces/IOGateway.sol | 10 ++++++++-- overridden_contracts/test/Gateway.t.sol | 10 +++++++--- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index d1fc871..16dd277 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -260,8 +260,11 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { } else if (message.command == Command.ReportSlashes) { // We need to put all this inside a generic try-catch, since we dont want to revert decoding nor anything try Gateway(this).reportSlashes{gas: maxDispatchGas}(message.params) {} - catch { - emit UnableToProcessSlashMessage(); + catch Error(string memory err) { + emit UnableToProcessSlashMessage(err); + success = false; + } catch (bytes memory err) { + emit UnableToProcessSlashMessage(err); success = false; } } @@ -475,8 +478,11 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { uint48 epoch = middleware.getEpochAtTs(uint48(slashes.slashes[i].timestamp)); //TODO maxDispatchGas should be probably be defined for all slashes, not only for one try middleware.slash(epoch, slashes.slashes[i].operatorKey, slashes.slashes[i].slashFraction) {} - catch { - emit UnableToProcessIndividualSlash(slashes.slashes[i]); + catch Error(string memory err) { + emit UnableToProcessIndividualSlash(slashes.slashes[i], err); + continue; + } catch (bytes memory err) { + emit UnableToProcessIndividualSlash(slashes.slashes[i], err); continue; } } diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index a203e35..5443f33 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -28,10 +28,16 @@ interface IOGateway is IGateway { event MiddlewareChanged(address indexed previousMiddleware, address indexed newMiddleware); // Emitted when the middleware fails to apply an individual slash - event UnableToProcessIndividualSlash(IOGateway.Slash slash); + event UnableToProcessIndividualSlash(IOGateway.Slash slash, bytes error); + + // Emitted when the middleware fails to apply an individual slash + event UnableToProcessIndividualSlash(IOGateway.Slash slash, string error); + + // Emitted when the middleware fails to apply the slash message + event UnableToProcessSlashMessage(bytes error); // Emitted when the middleware fails to apply the slash message - event UnableToProcessSlashMessage(); + event UnableToProcessSlashMessage(string error); // Slash struct, used to decode slashes, which are identified by // operatorKey to be slashed diff --git a/overridden_contracts/test/Gateway.t.sol b/overridden_contracts/test/Gateway.t.sol index 9b287c2..0bec41d 100644 --- a/overridden_contracts/test/Gateway.t.sol +++ b/overridden_contracts/test/Gateway.t.sol @@ -1027,7 +1027,7 @@ contract GatewayTest is Test { (Command command, bytes memory params) = makeReportSlashesCommand(); vm.expectEmit(true, true, true, true); - emit IOGateway.UnableToProcessSlashMessage(); + emit IOGateway.UnableToProcessSlashMessage(abi.encodeWithSelector(Gateway.MiddlewareNotSet.selector)); // Expect the gateway to emit `InboundMessageDispatched` vm.expectEmit(true, false, false, false); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); @@ -1048,9 +1048,12 @@ contract GatewayTest is Test { IOGateway(address(gateway)).setMiddleware(0x0123456789012345678901234567890123456789); + bytes memory empty; // Expect the gateway to emit `InboundMessageDispatched` + // For some reason when you are loading an address not complying an interface, you get an empty message + // It still serves us to know that this is the reason vm.expectEmit(true, true, true, true); - emit IOGateway.UnableToProcessSlashMessage(); + emit IOGateway.UnableToProcessSlashMessage(empty); vm.expectEmit(true, false, false, false); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); @@ -1075,8 +1078,9 @@ contract GatewayTest is Test { IOGateway.Slash memory expectedSlash = IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: 500_000, timestamp: 1}); + string memory expectedError = "no process slash"; vm.expectEmit(true, true, true, true); - emit IOGateway.UnableToProcessIndividualSlash(expectedSlash); + emit IOGateway.UnableToProcessIndividualSlash(expectedSlash, expectedError); vm.expectEmit(true, false, false, false); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); From 05446e40bf073e2560f84b37d3a9f32875dd666d Mon Sep 17 00:00:00 2001 From: girazoki Date: Fri, 24 Jan 2025 10:34:53 +0100 Subject: [PATCH 17/21] remove mockers --- overridden_contracts/test/Gateway.t.sol | 33 ++++++++++++------- .../test/mocks/MockOMiddlewareProcessor.sol | 27 --------------- .../test/mocks/MockOMiddlewareReverter.sol | 24 -------------- 3 files changed, 21 insertions(+), 63 deletions(-) delete mode 100644 overridden_contracts/test/mocks/MockOMiddlewareProcessor.sol delete mode 100644 overridden_contracts/test/mocks/MockOMiddlewareReverter.sol diff --git a/overridden_contracts/test/Gateway.t.sol b/overridden_contracts/test/Gateway.t.sol index 0bec41d..a96c86b 100644 --- a/overridden_contracts/test/Gateway.t.sol +++ b/overridden_contracts/test/Gateway.t.sol @@ -1029,8 +1029,8 @@ contract GatewayTest is Test { vm.expectEmit(true, true, true, true); emit IOGateway.UnableToProcessSlashMessage(abi.encodeWithSelector(Gateway.MiddlewareNotSet.selector)); // Expect the gateway to emit `InboundMessageDispatched` - vm.expectEmit(true, false, false, false); - emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); + vm.expectEmit(true, true, true, true); + emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, false); hoax(relayer, 1 ether); IGateway(address(gateway)).submitV1( @@ -1054,8 +1054,8 @@ contract GatewayTest is Test { // It still serves us to know that this is the reason vm.expectEmit(true, true, true, true); emit IOGateway.UnableToProcessSlashMessage(empty); - vm.expectEmit(true, false, false, false); - emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); + vm.expectEmit(true, true, true, true); + emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, false); hoax(relayer, 1 ether); IGateway(address(gateway)).submitV1( @@ -1071,17 +1071,22 @@ contract GatewayTest is Test { (Command command, bytes memory params) = makeReportSlashesCommand(); - IMiddlewareBasic middleware = new MockOMiddlewareReverter(); + bytes memory expectedError = bytes("no process slash"); + + // We mock the call so that it reverts + vm.mockCallRevert(address(1), abi.encodeWithSelector(IMiddlewareBasic.slash.selector), "no process slash"); - IOGateway(address(gateway)).setMiddleware(address(middleware)); + // We mock the call so that it does not revert, but it will revert in the previous one + vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.getEpochAtTs.selector), abi.encode(10)); + + IOGateway(address(gateway)).setMiddleware(address(1)); IOGateway.Slash memory expectedSlash = IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: 500_000, timestamp: 1}); - string memory expectedError = "no process slash"; vm.expectEmit(true, true, true, true); emit IOGateway.UnableToProcessIndividualSlash(expectedSlash, expectedError); - vm.expectEmit(true, false, false, false); + vm.expectEmit(true, true, true, true); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); hoax(relayer, 1 ether); @@ -1098,13 +1103,17 @@ contract GatewayTest is Test { (Command command, bytes memory params) = makeReportSlashesCommand(); - IMiddlewareBasic middleware = new MockOMiddlewareProcessor(); + // We mock the call so that it does not revert + vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.slash.selector), abi.encode(10)); + + // We mock the call so that it does not revert + vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.getEpochAtTs.selector), abi.encode(10)); - IOGateway(address(gateway)).setMiddleware(address(middleware)); + IOGateway(address(gateway)).setMiddleware(address(1)); + // Since we are asserting all fields, the last one is a true, therefore meaning + // that the dispatch went through correctly vm.expectEmit(true, true, true, true); - emit MockOMiddlewareProcessor.SlashProcessed(); - vm.expectEmit(true, false, false, false); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); hoax(relayer, 1 ether); diff --git a/overridden_contracts/test/mocks/MockOMiddlewareProcessor.sol b/overridden_contracts/test/mocks/MockOMiddlewareProcessor.sol deleted file mode 100644 index caa9b7d..0000000 --- a/overridden_contracts/test/mocks/MockOMiddlewareProcessor.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.25; - -import {IMiddlewareBasic} from "../../src/interfaces/IMiddlewareBasic.sol"; - -contract MockOMiddlewareProcessor is IMiddlewareBasic { - event RewardProcessed(); - event SlashProcessed(); - - function distributeRewards( - uint256 epoch, - uint256 eraIndex, - uint256 totalPointsToken, - uint256 tokensInflatedToken, - bytes32 rewardsRoot - ) external { - emit RewardProcessed(); - } - - function slash(uint48 epoch, bytes32 operatorKey, uint256 percentage) external { - emit SlashProcessed(); - } - - function getEpochAtTs(uint48 timestamp) external view returns (uint48 epoch) { - return timestamp; - } -} diff --git a/overridden_contracts/test/mocks/MockOMiddlewareReverter.sol b/overridden_contracts/test/mocks/MockOMiddlewareReverter.sol deleted file mode 100644 index eb21adb..0000000 --- a/overridden_contracts/test/mocks/MockOMiddlewareReverter.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.25; - -import {IMiddlewareBasic} from "../../src/interfaces/IMiddlewareBasic.sol"; - -contract MockOMiddlewareReverter is IMiddlewareBasic { - function distributeRewards( - uint256 epoch, - uint256 eraIndex, - uint256 totalPointsToken, - uint256 tokensInflatedToken, - bytes32 rewardsRoot - ) external { - revert("no distribute rewards"); - } - - function slash(uint48 epoch, bytes32 operatorKey, uint256 percentage) external { - revert("no process slash"); - } - - function getEpochAtTs(uint48 timestamp) external view returns (uint48 epoch) { - return timestamp; - } -} From 99fe5893b90b4c372911a9d17082bbc42a053924 Mon Sep 17 00:00:00 2001 From: girazoki Date: Fri, 24 Jan 2025 11:35:04 +0100 Subject: [PATCH 18/21] change a bunch of things for tests --- overridden_contracts/src/Gateway.sol | 8 ++++++-- overridden_contracts/src/interfaces/IOGateway.sol | 8 ++++++-- overridden_contracts/test/Gateway.t.sol | 15 +++++++++++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 16dd277..54ba44a 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -479,10 +479,14 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { //TODO maxDispatchGas should be probably be defined for all slashes, not only for one try middleware.slash(epoch, slashes.slashes[i].operatorKey, slashes.slashes[i].slashFraction) {} catch Error(string memory err) { - emit UnableToProcessIndividualSlash(slashes.slashes[i], err); + emit UnableToProcessIndividualSlashS( + slashes.slashes[i].operatorKey, slashes.slashes[i].slashFraction, slashes.slashes[i].timestamp, err + ); continue; } catch (bytes memory err) { - emit UnableToProcessIndividualSlash(slashes.slashes[i], err); + emit UnableToProcessIndividualSlashB( + slashes.slashes[i].operatorKey, slashes.slashes[i].slashFraction, slashes.slashes[i].timestamp, err + ); continue; } } diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index 5443f33..90732cd 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -28,10 +28,14 @@ interface IOGateway is IGateway { event MiddlewareChanged(address indexed previousMiddleware, address indexed newMiddleware); // Emitted when the middleware fails to apply an individual slash - event UnableToProcessIndividualSlash(IOGateway.Slash slash, bytes error); + event UnableToProcessIndividualSlashB( + bytes32 indexed operatorKey, uint256 slashFranction, uint256 indexed timestamp, bytes error + ); // Emitted when the middleware fails to apply an individual slash - event UnableToProcessIndividualSlash(IOGateway.Slash slash, string error); + event UnableToProcessIndividualSlashS( + bytes32 indexed operatorKey, uint256 slashFranction, uint256 indexed timestamp, string error + ); // Emitted when the middleware fails to apply the slash message event UnableToProcessSlashMessage(bytes error); diff --git a/overridden_contracts/test/Gateway.t.sol b/overridden_contracts/test/Gateway.t.sol index a96c86b..acf5d81 100644 --- a/overridden_contracts/test/Gateway.t.sol +++ b/overridden_contracts/test/Gateway.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.25; -import {Test} from "forge-std/Test.sol"; +import {Test, Vm} from "forge-std/Test.sol"; import {Strings} from "openzeppelin/utils/Strings.sol"; import {console} from "forge-std/console.sol"; @@ -1085,7 +1085,9 @@ contract GatewayTest is Test { IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: 500_000, timestamp: 1}); vm.expectEmit(true, true, true, true); - emit IOGateway.UnableToProcessIndividualSlash(expectedSlash, expectedError); + emit IOGateway.UnableToProcessIndividualSlashB( + expectedSlash.operatorKey, expectedSlash.slashFraction, expectedSlash.timestamp, expectedError + ); vm.expectEmit(true, true, true, true); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); @@ -1113,14 +1115,23 @@ contract GatewayTest is Test { // Since we are asserting all fields, the last one is a true, therefore meaning // that the dispatch went through correctly + vm.expectEmit(true, true, true, true); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true); hoax(relayer, 1 ether); + vm.recordLogs(); IGateway(address(gateway)).submitV1( InboundMessage(assetHubParaID.into(), 1, command, params, maxDispatchGas, maxRefund, reward, messageID), proof, makeMockProof() ); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + // We assert none of the slash error events has been emitted + for (uint256 i = 0; i < entries.length; i++) { + assertNotEq(entries[i].topics[0], IOGateway.UnableToProcessIndividualSlashB.selector); + assertNotEq(entries[i].topics[0], IOGateway.UnableToProcessIndividualSlashS.selector); + } } } From 5a7f2277cff56bb12075b8d7ad77620ad79b9e77 Mon Sep 17 00:00:00 2001 From: girazoki Date: Fri, 24 Jan 2025 14:05:32 +0100 Subject: [PATCH 19/21] add test vector slashes --- .../test/override_test/Gateway.t.sol | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/overridden_contracts/test/override_test/Gateway.t.sol b/overridden_contracts/test/override_test/Gateway.t.sol index bd3d462..9f98492 100644 --- a/overridden_contracts/test/override_test/Gateway.t.sol +++ b/overridden_contracts/test/override_test/Gateway.t.sol @@ -161,6 +161,10 @@ contract GatewayTest is Test { bytes32(0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48) ]; + // Test vector generated by: https://github.com/moondance-labs/tanssi/blob/242196324a37ac0020a7c7955bffe09670f63751/primitives/bridge/src/tests.rs#L84 + bytes private constant TEST_VECTOR_SLASH_DATA = + hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002A000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030404040404040404040404040404040404040404040404040404040404040404000000000000000000000000000000000000000000000000000000000000138800000000000000000000000000000000000000000000000000000000000001F405050505050505050505050505050505050505050505050505050505050505050000000000000000000000000000000000000000000000000000000000000FA0000000000000000000000000000000000000000000000000000000000000019006060606060606060606060606060606060606060606060606060606060606060000000000000000000000000000000000000000000000000000000000000BB8000000000000000000000000000000000000000000000000000000000000012C"; + function createLongOperatorsData() public view returns (bytes32[] memory) { bytes32[] memory result = new bytes32[](1001); @@ -278,4 +282,18 @@ contract GatewayTest is Test { vm.expectRevert(abi.encodeWithSelector(Gateway.Unauthorized.selector)); IOGateway(address(gateway)).setMiddleware(0x9876543210987654321098765432109876543210); } + + function testDecodeSlashes() public { + uint256 eraIndex = 42; + IOGateway.Slash[] memory slashes = new IOGateway.Slash[](3); + bytes32 alice = 0x0404040404040404040404040404040404040404040404040404040404040404; + bytes32 bob = 0x0505050505050505050505050505050505050505050505050505050505050505; + bytes32 charlie = 0x0606060606060606060606060606060606060606060606060606060606060606; + + slashes[0] = IOGateway.Slash({operatorKey: alice, slashFraction: 5_000, timestamp: 500}); + slashes[1] = IOGateway.Slash({operatorKey: bob, slashFraction: 4_000, timestamp: 400}); + slashes[2] = IOGateway.Slash({operatorKey: charlie, slashFraction: 3_000, timestamp: 300}); + + assertEq(abi.encode(IOGateway.SlashParams({eraIndex: eraIndex, slashes: slashes})), TEST_VECTOR_SLASH_DATA); + } } From 1368bb1e201187be01d1c13a2d9c5418b48936be Mon Sep 17 00:00:00 2001 From: girazoki Date: Fri, 24 Jan 2025 14:40:06 +0100 Subject: [PATCH 20/21] fix comp --- overridden_contracts/test/Gateway.t.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/overridden_contracts/test/Gateway.t.sol b/overridden_contracts/test/Gateway.t.sol index acf5d81..26567a4 100644 --- a/overridden_contracts/test/Gateway.t.sol +++ b/overridden_contracts/test/Gateway.t.sol @@ -14,8 +14,6 @@ import {IUpgradable} from "../src/interfaces/IUpgradable.sol"; import {IMiddlewareBasic} from "../src/interfaces/IMiddlewareBasic.sol"; import {Gateway} from "../src/Gateway.sol"; import {MockGateway} from "./mocks/MockGateway.sol"; -import {MockOMiddlewareReverter} from "./mocks/MockOMiddlewareReverter.sol"; -import {MockOMiddlewareProcessor} from "./mocks/MockOMiddlewareProcessor.sol"; import {MockGatewayV2} from "./mocks/MockGatewayV2.sol"; import {GatewayProxy} from "../src/GatewayProxy.sol"; From d8b10ed5c4f1af272d8e75caff9f4a91234966c7 Mon Sep 17 00:00:00 2001 From: girazoki Date: Fri, 24 Jan 2025 16:34:22 +0100 Subject: [PATCH 21/21] pr feedback --- overridden_contracts/src/Gateway.sol | 4 ++-- overridden_contracts/src/interfaces/IOGateway.sol | 4 ++-- overridden_contracts/test/Gateway.t.sol | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 54ba44a..40274a1 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -261,10 +261,10 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { // We need to put all this inside a generic try-catch, since we dont want to revert decoding nor anything try Gateway(this).reportSlashes{gas: maxDispatchGas}(message.params) {} catch Error(string memory err) { - emit UnableToProcessSlashMessage(err); + emit UnableToProcessSlashMessageS(err); success = false; } catch (bytes memory err) { - emit UnableToProcessSlashMessage(err); + emit UnableToProcessSlashMessageB(err); success = false; } } diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index 90732cd..9f21d17 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -38,10 +38,10 @@ interface IOGateway is IGateway { ); // Emitted when the middleware fails to apply the slash message - event UnableToProcessSlashMessage(bytes error); + event UnableToProcessSlashMessageB(bytes error); // Emitted when the middleware fails to apply the slash message - event UnableToProcessSlashMessage(string error); + event UnableToProcessSlashMessageS(string error); // Slash struct, used to decode slashes, which are identified by // operatorKey to be slashed diff --git a/overridden_contracts/test/Gateway.t.sol b/overridden_contracts/test/Gateway.t.sol index 26567a4..3d823f7 100644 --- a/overridden_contracts/test/Gateway.t.sol +++ b/overridden_contracts/test/Gateway.t.sol @@ -163,7 +163,7 @@ contract GatewayTest is Test { return (Command.CreateAgent, abi.encode((keccak256("6666")))); } - function makeReportSlashesCommand() public pure returns (Command, bytes memory) { + function _makeReportSlashesCommand() public pure returns (Command, bytes memory) { IOGateway.Slash[] memory slashes = new IOGateway.Slash[](1); slashes[0] = IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: 500_000, timestamp: 1}); uint256 eraIndex = 1; @@ -1022,10 +1022,10 @@ contract GatewayTest is Test { function testSubmitSlashesWithoutMiddleware() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = makeReportSlashesCommand(); + (Command command, bytes memory params) = _makeReportSlashesCommand(); vm.expectEmit(true, true, true, true); - emit IOGateway.UnableToProcessSlashMessage(abi.encodeWithSelector(Gateway.MiddlewareNotSet.selector)); + emit IOGateway.UnableToProcessSlashMessageB(abi.encodeWithSelector(Gateway.MiddlewareNotSet.selector)); // Expect the gateway to emit `InboundMessageDispatched` vm.expectEmit(true, true, true, true); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, false); @@ -1042,7 +1042,7 @@ contract GatewayTest is Test { function testSubmitSlashesWithMiddlewareNotComplyingInterface() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = makeReportSlashesCommand(); + (Command command, bytes memory params) = _makeReportSlashesCommand(); IOGateway(address(gateway)).setMiddleware(0x0123456789012345678901234567890123456789); @@ -1051,7 +1051,7 @@ contract GatewayTest is Test { // For some reason when you are loading an address not complying an interface, you get an empty message // It still serves us to know that this is the reason vm.expectEmit(true, true, true, true); - emit IOGateway.UnableToProcessSlashMessage(empty); + emit IOGateway.UnableToProcessSlashMessageB(empty); vm.expectEmit(true, true, true, true); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, false); @@ -1067,7 +1067,7 @@ contract GatewayTest is Test { function testSubmitSlashesWithMiddlewareComplyingInterfaceAndSlashRevert() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = makeReportSlashesCommand(); + (Command command, bytes memory params) = _makeReportSlashesCommand(); bytes memory expectedError = bytes("no process slash"); @@ -1101,7 +1101,7 @@ contract GatewayTest is Test { function testSubmitSlashesWithMiddlewareComplyingInterfaceAndSlashProcessed() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = makeReportSlashesCommand(); + (Command command, bytes memory params) = _makeReportSlashesCommand(); // We mock the call so that it does not revert vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.slash.selector), abi.encode(10));