From 7ff866f9d37930966b89bbd2e8ce05aef799a047 Mon Sep 17 00:00:00 2001 From: NindoK Date: Fri, 24 Jan 2025 23:49:53 +0000 Subject: [PATCH 1/5] Added rewards message processing --- overridden_contracts/src/Gateway.sol | 29 ++++ .../src/interfaces/IMiddlewareBasic.sol | 24 ++- .../src/interfaces/IOGateway.sol | 30 ++++ overridden_contracts/test/Gateway.t.sol | 147 ++++++++++++++++++ .../test/override_test/Gateway.t.sol | 26 ++-- 5 files changed, 229 insertions(+), 27 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 40274a1..002f9e9 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -267,6 +267,15 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { emit UnableToProcessSlashMessageB(err); success = false; } + } else if (message.command == Command.ReportRewards) { + try Gateway(this).sendRewards{gas: maxDispatchGas}(message.params) {} + catch Error(string memory err) { + emit UnableToProcessRewardsMessageS(err); + success = false; + } catch (bytes memory err) { + emit UnableToProcessRewardsMessageB(err); + success = false; + } } // Calculate a gas refund, capped to protect against huge spikes in `tx.gasprice` @@ -492,6 +501,26 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { } } + function sendRewards(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 (middlewareAddress == address(0)) { + revert MiddlewareNotSet(); + } + + (uint256 epoch, uint256 eraIndex, uint256 totalPointsToken, uint256 totalTokensInflated, bytes32 rewardsRoot) = + abi.decode(data, (uint256, uint256, uint256, uint256, bytes32)); + + try IMiddlewareBasic(middlewareAddress).distributeRewards( + epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot + ) {} catch Error(string memory err) { + emit UnableToProcessRewardsS(epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot, err); + } catch (bytes memory err) { + emit UnableToProcessRewardsB(epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot, err); + } + } + function isTokenRegistered(address token) external view returns (bool) { return Assets.isTokenRegistered(token); } diff --git a/overridden_contracts/src/interfaces/IMiddlewareBasic.sol b/overridden_contracts/src/interfaces/IMiddlewareBasic.sol index 4fd2908..996f107 100644 --- a/overridden_contracts/src/interfaces/IMiddlewareBasic.sol +++ b/overridden_contracts/src/interfaces/IMiddlewareBasic.sol @@ -16,21 +16,17 @@ 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 - * @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 + * @notice Distribute rewards for a specific era contained in an epoch by providing a Merkle root, total points, and total amount of tokens. + * @param epoch network epoch of the middleware + * @param eraIndex era index of Starlight's rewards distribution + * @param totalPointsToken total amount of points for the reward distribution + * @param amount amount of tokens to distribute + * @param root Merkle root of the reward distribution + * @dev Emit DistributeRewards event. */ - function distributeRewards( - uint256 epoch, - uint256 eraIndex, - uint256 totalPointsToken, - uint256 tokensInflatedToken, - bytes32 rewardsRoot - ) external; + function distributeRewards(uint256 epoch, uint256 eraIndex, uint256 totalPointsToken, uint256 amount, bytes32 root) + external; + /** * @notice Slashes an operator's stake * @dev Only the owner can call this function diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index 9f21d17..8387bb7 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -43,6 +43,32 @@ interface IOGateway is IGateway { // Emitted when the middleware fails to apply the slash message event UnableToProcessSlashMessageS(string error); + // Emitted when the middleware fails to process rewards + event UnableToProcessRewardsB( + uint256 indexed epoch, + uint256 indexed eraIndex, + uint256 totalPointsToken, + uint256 totalTokensInflated, + bytes32 rewardsRoot, + bytes error + ); + + // Emitted when the middleware fails to process rewards + event UnableToProcessRewardsS( + uint256 indexed epoch, + uint256 indexed eraIndex, + uint256 totalPointsToken, + uint256 totalTokensInflated, + bytes32 rewardsRoot, + string error + ); + + // Emitted when the middleware fails to apply the slash message + event UnableToProcessRewardsMessageB(bytes error); + + // Emitted when the middleware fails to apply the slash message + event UnableToProcessRewardsMessageS(string error); + // Slash struct, used to decode slashes, which are identified by // operatorKey to be slashed // slashFraction to be applied as parts per billion @@ -60,6 +86,10 @@ interface IOGateway is IGateway { function s_middleware() external view returns (address); + function reportSlashes(bytes calldata data) external; + + function sendRewards(bytes calldata data) external; + function sendOperatorsData(bytes32[] calldata data, uint48 epoch) external; function setMiddleware(address middleware) external; diff --git a/overridden_contracts/test/Gateway.t.sol b/overridden_contracts/test/Gateway.t.sol index 3d823f7..abc36c5 100644 --- a/overridden_contracts/test/Gateway.t.sol +++ b/overridden_contracts/test/Gateway.t.sol @@ -170,6 +170,16 @@ contract GatewayTest is Test { return (Command.ReportSlashes, abi.encode(IOGateway.SlashParams({eraIndex: eraIndex, slashes: slashes}))); } + function _makeReportRewardsCommand() public pure returns (Command, bytes memory) { + uint256 epoch = 0; + uint256 eraIndex = 1; + uint256 totalPointsToken = 1 ether; + uint256 tokensInflatedToken = 1 ether; + bytes32 rewardsRoot = bytes32(uint256(1)); + + return (Command.ReportRewards, abi.encode(epoch, eraIndex, totalPointsToken, tokensInflatedToken, rewardsRoot)); + } + function makeMockProof() public pure returns (Verification.Proof memory) { return Verification.Proof({ leafPartial: Verification.MMRLeafPartial({ @@ -1132,4 +1142,141 @@ contract GatewayTest is Test { assertNotEq(entries[i].topics[0], IOGateway.UnableToProcessIndividualSlashS.selector); } } + + function testSubmitRewards() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + // We mock the call so that it does not revert + vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); + + IOGateway(address(gateway)).setMiddleware(address(1)); + + // Expect the gateway to emit `InboundMessageDispatched` + vm.expectEmit(true, true, true, true); + 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() + ); + } + + function testSubmitRewardsWithoutMiddleware() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + vm.expectEmit(true, true, true, true); + emit IOGateway.UnableToProcessRewardsMessageB(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); + + 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 rewards + function testSubmitRewardsWithMiddlewareNotComplyingInterface() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + 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.UnableToProcessRewardsMessageB(empty); + vm.expectEmit(true, true, true, true); + emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, false); + + 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 rewards reverts + function testSubmitRewardsWithMiddlewareComplyingInterfaceAndRewardsRevert() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + bytes memory expectedError = bytes("can't process rewards"); //This should actually come from IODefaultOperatorRewards + + // We mock the call so that it reverts + vm.mockCallRevert( + address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), "can't process rewards" + ); + + IOGateway(address(gateway)).setMiddleware(address(1)); + + uint256 expectedEpoch = 0; + uint256 expectedEraIndex = 1; + uint256 expectedTotalPointsToken = 1 ether; + uint256 expectedTotalTokensInflated = 1 ether; + bytes32 expectedRewardsRoot = bytes32(uint256(1)); + + vm.expectEmit(true, true, true, true); + emit IOGateway.UnableToProcessRewardsB( + expectedEpoch, + expectedEraIndex, + expectedTotalPointsToken, + expectedTotalTokensInflated, + expectedRewardsRoot, + expectedError + ); + vm.expectEmit(true, true, true, true); + 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 rewards processed + function testSubmitRewardsWithMiddlewareComplyingInterfaceAndRewardsProcessed() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + // We mock the call so that it does not revert + vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); + + IOGateway(address(gateway)).setMiddleware(address(1)); + + 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 rewards error events has been emitted + for (uint256 i = 0; i < entries.length; i++) { + assertNotEq(entries[i].topics[0], IOGateway.UnableToProcessRewardsMessageB.selector); + assertNotEq(entries[i].topics[0], IOGateway.UnableToProcessRewardsMessageS.selector); + } + } } diff --git a/overridden_contracts/test/override_test/Gateway.t.sol b/overridden_contracts/test/override_test/Gateway.t.sol index 9f98492..9fb889e 100644 --- a/overridden_contracts/test/override_test/Gateway.t.sol +++ b/overridden_contracts/test/override_test/Gateway.t.sol @@ -99,6 +99,19 @@ contract GatewayTest is Test { ChannelID internal constant PRIMARY_GOVERNANCE_CHANNEL_ID = ChannelID.wrap(bytes32(uint256(1))); ChannelID internal constant SECONDARY_GOVERNANCE_CHANNEL_ID = ChannelID.wrap(bytes32(uint256(2))); + bytes private constant FINAL_VALIDATORS_PAYLOAD = + hex"7015003800000cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe228eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000"; + + bytes32[] private VALIDATORS_DATA = [ + bytes32(0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d), + bytes32(0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22), + 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 setUp() public { AgentExecutor executor = new AgentExecutor(); gatewayLogic = new MockOGateway( @@ -152,19 +165,6 @@ contract GatewayTest is Test { IOGateway(address(gateway)).setMiddleware(middleware); } - bytes private constant FINAL_VALIDATORS_PAYLOAD = - hex"7015003800000cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe228eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000"; - - bytes32[] private VALIDATORS_DATA = [ - bytes32(0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d), - bytes32(0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22), - 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); From 26fce6bd7570fec1043018fa19a12169f004c36e Mon Sep 17 00:00:00 2001 From: NindoK Date: Sun, 26 Jan 2025 11:30:17 +0000 Subject: [PATCH 2/5] Added token minting comments --- overridden_contracts/src/Gateway.sol | 5 + overridden_contracts/test/Gateway.t.sol | 272 +--------------- .../test/override_test/Gateway.t.sol | 291 +++++++++++++++++- 3 files changed, 293 insertions(+), 275 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 002f9e9..42e5743 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -509,9 +509,14 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { revert MiddlewareNotSet(); } + // Probably need to send me the token to be minted? (uint256 epoch, uint256 eraIndex, uint256 totalPointsToken, uint256 totalTokensInflated, bytes32 rewardsRoot) = abi.decode(data, (uint256, uint256, uint256, uint256, bytes32)); + // ! We need a foreignTokenID to mint the token. I don't want to save it in the Assets storage otherwise it's another change to be made. Can we send directly the address of the token everytime? + // bytes32 foreignTokenID = bytes32(0); + // Assets.mintForeignToken(foreignTokenID, middlewareAddress, totalTokensInflated); + try IMiddlewareBasic(middlewareAddress).distributeRewards( epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot ) {} catch Error(string memory err) { diff --git a/overridden_contracts/test/Gateway.t.sol b/overridden_contracts/test/Gateway.t.sol index abc36c5..f6aa41f 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, Vm} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {Strings} from "openzeppelin/utils/Strings.sol"; import {console} from "forge-std/console.sol"; @@ -11,7 +11,6 @@ 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"; @@ -163,23 +162,6 @@ 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 _makeReportRewardsCommand() public pure returns (Command, bytes memory) { - uint256 epoch = 0; - uint256 eraIndex = 1; - uint256 totalPointsToken = 1 ether; - uint256 tokensInflatedToken = 1 ether; - bytes32 rewardsRoot = bytes32(uint256(1)); - - return (Command.ReportRewards, abi.encode(epoch, eraIndex, totalPointsToken, tokensInflatedToken, rewardsRoot)); - } - function makeMockProof() public pure returns (Verification.Proof memory) { return Verification.Proof({ leafPartial: Verification.MMRLeafPartial({ @@ -1027,256 +1009,4 @@ 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 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); - - 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); - - 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.UnableToProcessSlashMessageB(empty); - vm.expectEmit(true, true, true, true); - emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, false); - - 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(); - - 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"); - - // 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}); - - vm.expectEmit(true, true, true, true); - emit IOGateway.UnableToProcessIndividualSlashB( - expectedSlash.operatorKey, expectedSlash.slashFraction, expectedSlash.timestamp, expectedError - ); - vm.expectEmit(true, true, true, true); - 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(); - - // 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(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 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); - } - } - - function testSubmitRewards() public { - deal(assetHubAgent, 50 ether); - - (Command command, bytes memory params) = _makeReportRewardsCommand(); - - // We mock the call so that it does not revert - vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); - - IOGateway(address(gateway)).setMiddleware(address(1)); - - // Expect the gateway to emit `InboundMessageDispatched` - vm.expectEmit(true, true, true, true); - 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() - ); - } - - function testSubmitRewardsWithoutMiddleware() public { - deal(assetHubAgent, 50 ether); - - (Command command, bytes memory params) = _makeReportRewardsCommand(); - - vm.expectEmit(true, true, true, true); - emit IOGateway.UnableToProcessRewardsMessageB(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); - - 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 rewards - function testSubmitRewardsWithMiddlewareNotComplyingInterface() public { - deal(assetHubAgent, 50 ether); - - (Command command, bytes memory params) = _makeReportRewardsCommand(); - - 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.UnableToProcessRewardsMessageB(empty); - vm.expectEmit(true, true, true, true); - emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, false); - - 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 rewards reverts - function testSubmitRewardsWithMiddlewareComplyingInterfaceAndRewardsRevert() public { - deal(assetHubAgent, 50 ether); - - (Command command, bytes memory params) = _makeReportRewardsCommand(); - - bytes memory expectedError = bytes("can't process rewards"); //This should actually come from IODefaultOperatorRewards - - // We mock the call so that it reverts - vm.mockCallRevert( - address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), "can't process rewards" - ); - - IOGateway(address(gateway)).setMiddleware(address(1)); - - uint256 expectedEpoch = 0; - uint256 expectedEraIndex = 1; - uint256 expectedTotalPointsToken = 1 ether; - uint256 expectedTotalTokensInflated = 1 ether; - bytes32 expectedRewardsRoot = bytes32(uint256(1)); - - vm.expectEmit(true, true, true, true); - emit IOGateway.UnableToProcessRewardsB( - expectedEpoch, - expectedEraIndex, - expectedTotalPointsToken, - expectedTotalTokensInflated, - expectedRewardsRoot, - expectedError - ); - vm.expectEmit(true, true, true, true); - 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 rewards processed - function testSubmitRewardsWithMiddlewareComplyingInterfaceAndRewardsProcessed() public { - deal(assetHubAgent, 50 ether); - - (Command command, bytes memory params) = _makeReportRewardsCommand(); - - // We mock the call so that it does not revert - vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); - - IOGateway(address(gateway)).setMiddleware(address(1)); - - 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 rewards error events has been emitted - for (uint256 i = 0; i < entries.length; i++) { - assertNotEq(entries[i].topics[0], IOGateway.UnableToProcessRewardsMessageB.selector); - assertNotEq(entries[i].topics[0], IOGateway.UnableToProcessRewardsMessageS.selector); - } - } } diff --git a/overridden_contracts/test/override_test/Gateway.t.sol b/overridden_contracts/test/override_test/Gateway.t.sol index 9fb889e..64fbab5 100644 --- a/overridden_contracts/test/override_test/Gateway.t.sol +++ b/overridden_contracts/test/override_test/Gateway.t.sol @@ -14,7 +14,7 @@ // along with Tanssi. If not, see pragma solidity 0.8.25; -import {Test, console2} from "forge-std/Test.sol"; +import {Test, console2, Vm} from "forge-std/Test.sol"; import { AgentExecuteCommand, InboundMessage, @@ -26,6 +26,7 @@ import { multiAddressFromBytes20 } from "../../src/Types.sol"; import {IGateway} from "../../src/interfaces/IGateway.sol"; +import {IMiddlewareBasic} from "../../src/interfaces/IMiddlewareBasic.sol"; import {MockGateway} from "../mocks/MockGateway.sol"; import {CreateAgentParams, CreateChannelParams} from "../../src/Params.sol"; import {OperatingMode, ParaID, Command} from "../../src/Types.sol"; @@ -33,6 +34,7 @@ import {GatewayProxy} from "../../src/GatewayProxy.sol"; import {MultiAddress} from "../../src/MultiAddress.sol"; import {AgentExecutor} from "../../src/AgentExecutor.sol"; import {SetOperatingModeParams} from "../../src/Params.sol"; +import {Verification} from "../../src/Verification.sol"; import {Strings} from "openzeppelin/utils/Strings.sol"; @@ -147,7 +149,6 @@ contract GatewayTest is Test { account1 = makeAddr("account1"); account2 = makeAddr("account2"); - middleware = makeAddr("middleware"); // create tokens for account 1 hoax(account1); @@ -160,9 +161,39 @@ contract GatewayTest is Test { recipientAddress20 = multiAddressFromBytes20(bytes20(keccak256("recipient"))); dotTokenID = bytes32(uint256(1)); + } + + 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}))); + } - // set middleware - IOGateway(address(gateway)).setMiddleware(middleware); + function _makeReportRewardsCommand() public pure returns (Command, bytes memory) { + uint256 epoch = 0; + uint256 eraIndex = 1; + uint256 totalPointsToken = 1 ether; + uint256 tokensInflatedToken = 1 ether; + bytes32 rewardsRoot = bytes32(uint256(1)); + + return (Command.ReportRewards, abi.encode(epoch, eraIndex, totalPointsToken, tokensInflatedToken, rewardsRoot)); + } + + function makeMockProof() public pure returns (Verification.Proof memory) { + return Verification.Proof({ + leafPartial: Verification.MMRLeafPartial({ + version: 0, + parentNumber: 0, + parentHash: bytes32(0), + nextAuthoritySetID: 0, + nextAuthoritySetLen: 0, + nextAuthoritySetRoot: 0 + }), + leafProof: new bytes32[](0), + leafProofOrder: 0, + parachainHeadsRoot: bytes32(0) + }); } function createLongOperatorsData() public view returns (bytes32[] memory) { @@ -296,4 +327,256 @@ contract GatewayTest is Test { assertEq(abi.encode(IOGateway.SlashParams({eraIndex: eraIndex, slashes: slashes})), TEST_VECTOR_SLASH_DATA); } + + // 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 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); + + 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); + + 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.UnableToProcessSlashMessageB(empty); + vm.expectEmit(true, true, true, true); + emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, false); + + 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(); + + 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"); + + // 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}); + + vm.expectEmit(true, true, true, true); + emit IOGateway.UnableToProcessIndividualSlashB( + expectedSlash.operatorKey, expectedSlash.slashFraction, expectedSlash.timestamp, expectedError + ); + vm.expectEmit(true, true, true, true); + 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(); + + // 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(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 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); + } + } + + function testSubmitRewards() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + // We mock the call so that it does not revert + vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); + + IOGateway(address(gateway)).setMiddleware(address(1)); + + // Expect the gateway to emit `InboundMessageDispatched` + vm.expectEmit(true, true, true, true); + 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() + ); + } + + function testSubmitRewardsWithoutMiddleware() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + vm.expectEmit(true, true, true, true); + emit IOGateway.UnableToProcessRewardsMessageB(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); + + 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 rewards + function testSubmitRewardsWithMiddlewareNotComplyingInterface() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + 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.UnableToProcessRewardsMessageB(empty); + vm.expectEmit(true, true, true, true); + emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, false); + + 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 rewards reverts + function testSubmitRewardsWithMiddlewareComplyingInterfaceAndRewardsRevert() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + bytes memory expectedError = bytes("can't process rewards"); //This should actually come from IODefaultOperatorRewards + + // We mock the call so that it reverts + vm.mockCallRevert( + address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), "can't process rewards" + ); + + IOGateway(address(gateway)).setMiddleware(address(1)); + + uint256 expectedEpoch = 0; + uint256 expectedEraIndex = 1; + uint256 expectedTotalPointsToken = 1 ether; + uint256 expectedTotalTokensInflated = 1 ether; + bytes32 expectedRewardsRoot = bytes32(uint256(1)); + + vm.expectEmit(true, true, true, true); + emit IOGateway.UnableToProcessRewardsB( + expectedEpoch, + expectedEraIndex, + expectedTotalPointsToken, + expectedTotalTokensInflated, + expectedRewardsRoot, + expectedError + ); + vm.expectEmit(true, true, true, true); + 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 rewards processed + function testSubmitRewardsWithMiddlewareComplyingInterfaceAndRewardsProcessed() public { + deal(assetHubAgent, 50 ether); + + (Command command, bytes memory params) = _makeReportRewardsCommand(); + + // We mock the call so that it does not revert + vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); + + IOGateway(address(gateway)).setMiddleware(address(1)); + + 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 rewards error events has been emitted + for (uint256 i = 0; i < entries.length; i++) { + assertNotEq(entries[i].topics[0], IOGateway.UnableToProcessRewardsMessageB.selector); + assertNotEq(entries[i].topics[0], IOGateway.UnableToProcessRewardsMessageS.selector); + } + } } From ba08e659561867140fc095c18949e3b48bf51de4 Mon Sep 17 00:00:00 2001 From: NindoK Date: Mon, 27 Jan 2025 23:49:16 +0000 Subject: [PATCH 3/5] Added token address to the flow --- .gitignore | 2 + overridden_contracts/src/Gateway.sol | 16 +++++--- .../src/interfaces/IMiddlewareBasic.sol | 14 +++++-- .../test/override_test/Gateway.t.sol | 40 ++++++++++++++----- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 0285a63..c44e2aa 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ build/ .vscode/ .env + +.history/* \ No newline at end of file diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index 42e5743..ce83a7a 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -510,15 +510,21 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { } // Probably need to send me the token to be minted? - (uint256 epoch, uint256 eraIndex, uint256 totalPointsToken, uint256 totalTokensInflated, bytes32 rewardsRoot) = - abi.decode(data, (uint256, uint256, uint256, uint256, bytes32)); + ( + uint256 epoch, + uint256 eraIndex, + uint256 totalPointsToken, + uint256 totalTokensInflated, + bytes32 rewardsRoot, + bytes32 foreignTokenId + ) = abi.decode(data, (uint256, uint256, uint256, uint256, bytes32, bytes32)); // ! We need a foreignTokenID to mint the token. I don't want to save it in the Assets storage otherwise it's another change to be made. Can we send directly the address of the token everytime? - // bytes32 foreignTokenID = bytes32(0); - // Assets.mintForeignToken(foreignTokenID, middlewareAddress, totalTokensInflated); + Assets.mintForeignToken(foreignTokenId, middlewareAddress, totalTokensInflated); + address tokenAddress = Assets.tokenAddressOf(foreignTokenId); try IMiddlewareBasic(middlewareAddress).distributeRewards( - epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot + epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot, tokenAddress ) {} catch Error(string memory err) { emit UnableToProcessRewardsS(epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot, err); } catch (bytes memory err) { diff --git a/overridden_contracts/src/interfaces/IMiddlewareBasic.sol b/overridden_contracts/src/interfaces/IMiddlewareBasic.sol index 996f107..46f418a 100644 --- a/overridden_contracts/src/interfaces/IMiddlewareBasic.sol +++ b/overridden_contracts/src/interfaces/IMiddlewareBasic.sol @@ -16,16 +16,24 @@ pragma solidity ^0.8.0; interface IMiddlewareBasic { /** - * @notice Distribute rewards for a specific era contained in an epoch by providing a Merkle root, total points, and total amount of tokens. + * @notice Distribute rewards for a specific era contained in an epoch by providing a Merkle root, total points, total amount of tokens and the token address of the rewards. * @param epoch network epoch of the middleware * @param eraIndex era index of Starlight's rewards distribution * @param totalPointsToken total amount of points for the reward distribution * @param amount amount of tokens to distribute * @param root Merkle root of the reward distribution + * @param tokenAddress The token address of the rewards + * @dev This function is called by the gateway only * @dev Emit DistributeRewards event. */ - function distributeRewards(uint256 epoch, uint256 eraIndex, uint256 totalPointsToken, uint256 amount, bytes32 root) - external; + function distributeRewards( + uint256 epoch, + uint256 eraIndex, + uint256 totalPointsToken, + uint256 amount, + bytes32 root, + address tokenAddress + ) external; /** * @notice Slashes an operator's stake diff --git a/overridden_contracts/test/override_test/Gateway.t.sol b/overridden_contracts/test/override_test/Gateway.t.sol index 64fbab5..6931271 100644 --- a/overridden_contracts/test/override_test/Gateway.t.sol +++ b/overridden_contracts/test/override_test/Gateway.t.sol @@ -28,12 +28,16 @@ import { import {IGateway} from "../../src/interfaces/IGateway.sol"; import {IMiddlewareBasic} from "../../src/interfaces/IMiddlewareBasic.sol"; import {MockGateway} from "../mocks/MockGateway.sol"; -import {CreateAgentParams, CreateChannelParams} from "../../src/Params.sol"; +import { + CreateAgentParams, + CreateChannelParams, + SetOperatingModeParams, + RegisterForeignTokenParams +} from "../../src/Params.sol"; import {OperatingMode, ParaID, Command} from "../../src/Types.sol"; import {GatewayProxy} from "../../src/GatewayProxy.sol"; import {MultiAddress} from "../../src/MultiAddress.sol"; import {AgentExecutor} from "../../src/AgentExecutor.sol"; -import {SetOperatingModeParams} from "../../src/Params.sol"; import {Verification} from "../../src/Verification.sol"; import {Strings} from "openzeppelin/utils/Strings.sol"; @@ -41,6 +45,7 @@ import {Strings} from "openzeppelin/utils/Strings.sol"; import {Gateway} from "../../src/Gateway.sol"; import {IOGateway} from "../../src/interfaces/IOGateway.sol"; import {Operators} from "../../src/Operators.sol"; +import {Assets} from "../../src/Assets.sol"; import {MockOGateway} from "../mocks/MockOGateway.sol"; //NEW @@ -170,14 +175,29 @@ contract GatewayTest is Test { return (Command.ReportSlashes, abi.encode(IOGateway.SlashParams({eraIndex: eraIndex, slashes: slashes}))); } - function _makeReportRewardsCommand() public pure returns (Command, bytes memory) { + function _makeReportRewardsCommand() public returns (Command, bytes memory, address) { uint256 epoch = 0; uint256 eraIndex = 1; uint256 totalPointsToken = 1 ether; uint256 tokensInflatedToken = 1 ether; bytes32 rewardsRoot = bytes32(uint256(1)); + bytes32 foreignTokenId = bytes32(uint256(1)); + + RegisterForeignTokenParams memory params = + RegisterForeignTokenParams({foreignTokenID: dotTokenID, name: "Test", symbol: "TST", decimals: 10}); + + vm.expectEmit(true, true, false, false); + emit IGateway.ForeignTokenRegistered(foreignTokenId, address(0)); - return (Command.ReportRewards, abi.encode(epoch, eraIndex, totalPointsToken, tokensInflatedToken, rewardsRoot)); + MockGateway(address(gateway)).registerForeignTokenPublic(abi.encode(params)); + + address tokenAddress = Assets.tokenAddressOf(foreignTokenId); + + return ( + Command.ReportRewards, + abi.encode(epoch, eraIndex, totalPointsToken, tokensInflatedToken, rewardsRoot, foreignTokenId), + tokenAddress + ); } function makeMockProof() public pure returns (Verification.Proof memory) { @@ -443,10 +463,10 @@ contract GatewayTest is Test { } } - function testSubmitRewards() public { + function testSubmitRewardsx() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = _makeReportRewardsCommand(); + (Command command, bytes memory params, address tokenAddress) = _makeReportRewardsCommand(); // We mock the call so that it does not revert vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); @@ -468,7 +488,7 @@ contract GatewayTest is Test { function testSubmitRewardsWithoutMiddleware() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = _makeReportRewardsCommand(); + (Command command, bytes memory params, address tokenAddress) = _makeReportRewardsCommand(); vm.expectEmit(true, true, true, true); emit IOGateway.UnableToProcessRewardsMessageB(abi.encodeWithSelector(Gateway.MiddlewareNotSet.selector)); @@ -488,7 +508,7 @@ contract GatewayTest is Test { function testSubmitRewardsWithMiddlewareNotComplyingInterface() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = _makeReportRewardsCommand(); + (Command command, bytes memory params, address tokenAddress) = _makeReportRewardsCommand(); IOGateway(address(gateway)).setMiddleware(0x0123456789012345678901234567890123456789); @@ -513,7 +533,7 @@ contract GatewayTest is Test { function testSubmitRewardsWithMiddlewareComplyingInterfaceAndRewardsRevert() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = _makeReportRewardsCommand(); + (Command command, bytes memory params, address tokenAddress) = _makeReportRewardsCommand(); bytes memory expectedError = bytes("can't process rewards"); //This should actually come from IODefaultOperatorRewards @@ -554,7 +574,7 @@ contract GatewayTest is Test { function testSubmitRewardsWithMiddlewareComplyingInterfaceAndRewardsProcessed() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = _makeReportRewardsCommand(); + (Command command, bytes memory params, address tokenAddress) = _makeReportRewardsCommand(); // We mock the call so that it does not revert vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); From 0a6a43ab4c2482f930be0e91123172442a37b220 Mon Sep 17 00:00:00 2001 From: NindoK Date: Mon, 27 Jan 2025 23:54:41 +0000 Subject: [PATCH 4/5] Fix comments --- .../test/override_test/Gateway.t.sol | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/overridden_contracts/test/override_test/Gateway.t.sol b/overridden_contracts/test/override_test/Gateway.t.sol index 6931271..bee7df8 100644 --- a/overridden_contracts/test/override_test/Gateway.t.sol +++ b/overridden_contracts/test/override_test/Gateway.t.sol @@ -103,9 +103,12 @@ contract GatewayTest is Test { // tokenID for DOT bytes32 public dotTokenID; + uint256 public constant SLASH_FRACTION = 500_000; + ChannelID internal constant PRIMARY_GOVERNANCE_CHANNEL_ID = ChannelID.wrap(bytes32(uint256(1))); ChannelID internal constant SECONDARY_GOVERNANCE_CHANNEL_ID = ChannelID.wrap(bytes32(uint256(2))); + // It's generated using VALIDATORS_DATA using scaleCodec. See OSubstrateTypes.EncodedOperatorsData in OSubstrateTypes.sol bytes private constant FINAL_VALIDATORS_PAYLOAD = hex"7015003800000cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe228eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000"; @@ -168,9 +171,9 @@ contract GatewayTest is Test { dotTokenID = bytes32(uint256(1)); } - function _makeReportSlashesCommand() public pure returns (Command, bytes memory) { + function _makeReportSlashesCommand(uint256 slashFraction) 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}); + slashes[0] = IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: slashFraction, timestamp: 1}); uint256 eraIndex = 1; return (Command.ReportSlashes, abi.encode(IOGateway.SlashParams({eraIndex: eraIndex, slashes: slashes}))); } @@ -239,7 +242,7 @@ contract GatewayTest is Test { return paraID; } - function testSendOperatorsDataX() public { + function testSendOperatorsData() public { // FINAL_VALIDATORS_PAYLOAD has been encoded with epoch 1. uint48 epoch = 1; @@ -352,7 +355,7 @@ contract GatewayTest is Test { function testSubmitSlashesWithoutMiddleware() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = _makeReportSlashesCommand(); + (Command command, bytes memory params) = _makeReportSlashesCommand(SLASH_FRACTION); vm.expectEmit(true, true, true, true); emit IOGateway.UnableToProcessSlashMessageB(abi.encodeWithSelector(Gateway.MiddlewareNotSet.selector)); @@ -372,7 +375,7 @@ contract GatewayTest is Test { function testSubmitSlashesWithMiddlewareNotComplyingInterface() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = _makeReportSlashesCommand(); + (Command command, bytes memory params) = _makeReportSlashesCommand(SLASH_FRACTION); IOGateway(address(gateway)).setMiddleware(0x0123456789012345678901234567890123456789); @@ -397,7 +400,7 @@ contract GatewayTest is Test { function testSubmitSlashesWithMiddlewareComplyingInterfaceAndSlashRevert() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = _makeReportSlashesCommand(); + (Command command, bytes memory params) = _makeReportSlashesCommand(SLASH_FRACTION); bytes memory expectedError = bytes("no process slash"); @@ -410,7 +413,7 @@ contract GatewayTest is Test { IOGateway(address(gateway)).setMiddleware(address(1)); IOGateway.Slash memory expectedSlash = - IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: 500_000, timestamp: 1}); + IOGateway.Slash({operatorKey: bytes32(uint256(1)), slashFraction: SLASH_FRACTION, timestamp: 1}); vm.expectEmit(true, true, true, true); emit IOGateway.UnableToProcessIndividualSlashB( @@ -431,7 +434,7 @@ contract GatewayTest is Test { function testSubmitSlashesWithMiddlewareComplyingInterfaceAndSlashProcessed() public { deal(assetHubAgent, 50 ether); - (Command command, bytes memory params) = _makeReportSlashesCommand(); + (Command command, bytes memory params) = _makeReportSlashesCommand(SLASH_FRACTION); // We mock the call so that it does not revert vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.slash.selector), abi.encode(10)); From c9c981ced94de83c4359845f8e59ca54cf8afb97 Mon Sep 17 00:00:00 2001 From: NindoK Date: Tue, 28 Jan 2025 11:48:31 +0000 Subject: [PATCH 5/5] Fix comments and added more tests --- overridden_contracts/src/Gateway.sol | 9 ++- .../src/interfaces/IOGateway.sol | 2 + .../test/override_test/Gateway.t.sol | 64 ++++++++++++++----- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/overridden_contracts/src/Gateway.sol b/overridden_contracts/src/Gateway.sol index ce83a7a..7c914f4 100644 --- a/overridden_contracts/src/Gateway.sol +++ b/overridden_contracts/src/Gateway.sol @@ -519,16 +519,19 @@ contract Gateway is IOGateway, IInitializable, IUpgradable { bytes32 foreignTokenId ) = abi.decode(data, (uint256, uint256, uint256, uint256, bytes32, bytes32)); - // ! We need a foreignTokenID to mint the token. I don't want to save it in the Assets storage otherwise it's another change to be made. Can we send directly the address of the token everytime? Assets.mintForeignToken(foreignTokenId, middlewareAddress, totalTokensInflated); address tokenAddress = Assets.tokenAddressOf(foreignTokenId); try IMiddlewareBasic(middlewareAddress).distributeRewards( epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot, tokenAddress ) {} catch Error(string memory err) { - emit UnableToProcessRewardsS(epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot, err); + emit UnableToProcessRewardsS( + epoch, eraIndex, tokenAddress, totalPointsToken, totalTokensInflated, rewardsRoot, err + ); } catch (bytes memory err) { - emit UnableToProcessRewardsB(epoch, eraIndex, totalPointsToken, totalTokensInflated, rewardsRoot, err); + emit UnableToProcessRewardsB( + epoch, eraIndex, tokenAddress, totalPointsToken, totalTokensInflated, rewardsRoot, err + ); } } diff --git a/overridden_contracts/src/interfaces/IOGateway.sol b/overridden_contracts/src/interfaces/IOGateway.sol index 8387bb7..055a2ed 100644 --- a/overridden_contracts/src/interfaces/IOGateway.sol +++ b/overridden_contracts/src/interfaces/IOGateway.sol @@ -47,6 +47,7 @@ interface IOGateway is IGateway { event UnableToProcessRewardsB( uint256 indexed epoch, uint256 indexed eraIndex, + address indexed tokenAddress, uint256 totalPointsToken, uint256 totalTokensInflated, bytes32 rewardsRoot, @@ -57,6 +58,7 @@ interface IOGateway is IGateway { event UnableToProcessRewardsS( uint256 indexed epoch, uint256 indexed eraIndex, + address indexed tokenAddress, uint256 totalPointsToken, uint256 totalTokensInflated, bytes32 rewardsRoot, diff --git a/overridden_contracts/test/override_test/Gateway.t.sol b/overridden_contracts/test/override_test/Gateway.t.sol index bee7df8..61b6e97 100644 --- a/overridden_contracts/test/override_test/Gateway.t.sol +++ b/overridden_contracts/test/override_test/Gateway.t.sol @@ -47,7 +47,7 @@ import {IOGateway} from "../../src/interfaces/IOGateway.sol"; import {Operators} from "../../src/Operators.sol"; import {Assets} from "../../src/Assets.sol"; import {MockOGateway} from "../mocks/MockOGateway.sol"; - +import {Token} from "../../src/Token.sol"; //NEW import {WETH9} from "canonical-weth/WETH9.sol"; import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol"; @@ -76,7 +76,7 @@ contract GatewayTest is Test { address public account1; address public account2; - address public middleware; + address public middleware = makeAddr("middleware"); uint64 public maxDispatchGas = 500_000; uint256 public maxRefund = 1 ether; @@ -122,6 +122,9 @@ contract GatewayTest is Test { bytes private constant TEST_VECTOR_SLASH_DATA = hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002A000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030404040404040404040404040404040404040404040404040404040404040404000000000000000000000000000000000000000000000000000000000000138800000000000000000000000000000000000000000000000000000000000001F405050505050505050505050505050505050505050505050505050505050505050000000000000000000000000000000000000000000000000000000000000FA0000000000000000000000000000000000000000000000000000000000000019006060606060606060606060606060606060606060606060606060606060606060000000000000000000000000000000000000000000000000000000000000BB8000000000000000000000000000000000000000000000000000000000000012C"; + bytes private constant TEST_VECTOR_REWARDS_DATA = + hex"00000000000000000000000000000000000000000000000000000000075BCD15000000000000000000000000000000000000000000000000000000000000002A00000000000000000000000000000000000000000000000000007048860DDF79000000000000000000000000000000000000000000000000000000E5F4C8F3CAb6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e40101010101010101010101010101010101010101010101010101010101010101"; + function setUp() public { AgentExecutor executor = new AgentExecutor(); gatewayLogic = new MockOGateway( @@ -187,14 +190,13 @@ contract GatewayTest is Test { bytes32 foreignTokenId = bytes32(uint256(1)); RegisterForeignTokenParams memory params = - RegisterForeignTokenParams({foreignTokenID: dotTokenID, name: "Test", symbol: "TST", decimals: 10}); + RegisterForeignTokenParams({foreignTokenID: foreignTokenId, name: "Test", symbol: "TST", decimals: 10}); vm.expectEmit(true, true, false, false); emit IGateway.ForeignTokenRegistered(foreignTokenId, address(0)); - MockGateway(address(gateway)).registerForeignTokenPublic(abi.encode(params)); - address tokenAddress = Assets.tokenAddressOf(foreignTokenId); + address tokenAddress = MockGateway(address(gateway)).tokenAddressOf(foreignTokenId); return ( Command.ReportRewards, @@ -245,7 +247,7 @@ contract GatewayTest is Test { function testSendOperatorsData() public { // FINAL_VALIDATORS_PAYLOAD has been encoded with epoch 1. uint48 epoch = 1; - + IOGateway(address(gateway)).setMiddleware(middleware); // Create mock agent and paraID vm.prank(middleware); vm.expectEmit(true, false, false, true); @@ -257,7 +259,7 @@ contract GatewayTest is Test { function testShouldNotSendOperatorsDataBecauseOperatorsTooLong() public { bytes32[] memory longOperatorsData = createLongOperatorsData(); uint48 epoch = 42; - + IOGateway(address(gateway)).setMiddleware(middleware); vm.prank(middleware); vm.expectRevert(Operators.Operators__OperatorsLengthTooLong.selector); IOGateway(address(gateway)).sendOperatorsData(longOperatorsData, epoch); @@ -276,6 +278,8 @@ contract GatewayTest is Test { uint48 epoch = abi.decode(vm.parseJson(json, "$.epoch"), (uint48)); + IOGateway(address(gateway)).setMiddleware(middleware); + vm.prank(middleware); vm.expectEmit(true, false, false, true); emit IGateway.OutboundMessageAccepted(PRIMARY_GOVERNANCE_CHANNEL_ID, 1, messageID, final_payload); @@ -295,6 +299,8 @@ contract GatewayTest is Test { bytes32[] memory accounts = abi.decode(vm.parseJson(json, "$.accounts"), (bytes32[])); uint48 epoch = abi.decode(vm.parseJson(json, "$.epoch"), (uint48)); + IOGateway(address(gateway)).setMiddleware(middleware); + vm.prank(middleware); vm.expectEmit(true, false, false, true); emit IGateway.OutboundMessageAccepted(PRIMARY_GOVERNANCE_CHANNEL_ID, 1, messageID, final_payload); @@ -314,6 +320,8 @@ contract GatewayTest is Test { bytes32[] memory accounts = abi.decode(vm.parseJson(json, "$.accounts"), (bytes32[])); uint48 epoch = abi.decode(vm.parseJson(json, "$.epoch"), (uint48)); + IOGateway(address(gateway)).setMiddleware(middleware); + vm.prank(middleware); vm.expectEmit(true, false, false, true); emit IGateway.OutboundMessageAccepted(PRIMARY_GOVERNANCE_CHANNEL_ID, 1, messageID, final_payload); @@ -323,7 +331,7 @@ contract GatewayTest is Test { function testOwnerCanChangeMiddleware() public { vm.expectEmit(true, true, false, false); - emit IOGateway.MiddlewareChanged(address(middleware), 0x0123456789012345678901234567890123456789); + emit IOGateway.MiddlewareChanged(address(0), 0x0123456789012345678901234567890123456789); IOGateway(address(gateway)).setMiddleware(0x0123456789012345678901234567890123456789); @@ -466,15 +474,31 @@ contract GatewayTest is Test { } } - function testSubmitRewardsx() public { + function testDecodeRewards() public { + uint256 epoch = 123_456_789; + uint256 eraIndex = 42; + uint256 totalPointsToken = 123_456_789_012_345; + uint256 tokensInflatedToken = 987_654_321_098; + bytes32 rewardsRoot = 0xb6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4; + bytes32 foreignTokenId = 0x0101010101010101010101010101010101010101010101010101010101010101; + + assertEq( + abi.encode(epoch, eraIndex, totalPointsToken, tokensInflatedToken, rewardsRoot, foreignTokenId), + TEST_VECTOR_REWARDS_DATA + ); + } + + function testSubmitRewards() public { deal(assetHubAgent, 50 ether); (Command command, bytes memory params, address tokenAddress) = _makeReportRewardsCommand(); // We mock the call so that it does not revert - vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); + vm.mockCall( + address(middleware), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true) + ); - IOGateway(address(gateway)).setMiddleware(address(1)); + IOGateway(address(gateway)).setMiddleware(address(middleware)); // Expect the gateway to emit `InboundMessageDispatched` vm.expectEmit(true, true, true, true); @@ -486,6 +510,8 @@ contract GatewayTest is Test { proof, makeMockProof() ); + + assert(Token(tokenAddress).balanceOf(address(middleware)) > 0); } function testSubmitRewardsWithoutMiddleware() public { @@ -542,21 +568,27 @@ contract GatewayTest is Test { // We mock the call so that it reverts vm.mockCallRevert( - address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), "can't process rewards" + address(middleware), + abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), + "can't process rewards" ); - IOGateway(address(gateway)).setMiddleware(address(1)); + IOGateway(address(gateway)).setMiddleware(address(middleware)); uint256 expectedEpoch = 0; uint256 expectedEraIndex = 1; uint256 expectedTotalPointsToken = 1 ether; uint256 expectedTotalTokensInflated = 1 ether; bytes32 expectedRewardsRoot = bytes32(uint256(1)); + bytes32 expectedForeignTokenId = bytes32(uint256(1)); + + address expectedTokenAddress = MockGateway(address(gateway)).tokenAddressOf(expectedForeignTokenId); vm.expectEmit(true, true, true, true); emit IOGateway.UnableToProcessRewardsB( expectedEpoch, expectedEraIndex, + expectedTokenAddress, expectedTotalPointsToken, expectedTotalTokensInflated, expectedRewardsRoot, @@ -580,9 +612,11 @@ contract GatewayTest is Test { (Command command, bytes memory params, address tokenAddress) = _makeReportRewardsCommand(); // We mock the call so that it does not revert - vm.mockCall(address(1), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true)); + vm.mockCall( + address(middleware), abi.encodeWithSelector(IMiddlewareBasic.distributeRewards.selector), abi.encode(true) + ); - IOGateway(address(gateway)).setMiddleware(address(1)); + IOGateway(address(gateway)).setMiddleware(address(middleware)); vm.expectEmit(true, true, true, true); emit IGateway.InboundMessageDispatched(assetHubParaID.into(), 1, messageID, true);