Skip to content

Commit

Permalink
Merge pull request #11 from moondance-labs/feat/add-rewards-messaging
Browse files Browse the repository at this point in the history
Added rewards message processing
  • Loading branch information
NindoK authored Jan 28, 2025
2 parents 4e02380 + c9c981c commit 03d5052
Show file tree
Hide file tree
Showing 6 changed files with 452 additions and 154 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
build/
.vscode/
.env

.history/*
43 changes: 43 additions & 0 deletions overridden_contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -492,6 +501,40 @@ 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();
}

// Probably need to send me the token to be minted?
(
uint256 epoch,
uint256 eraIndex,
uint256 totalPointsToken,
uint256 totalTokensInflated,
bytes32 rewardsRoot,
bytes32 foreignTokenId
) = abi.decode(data, (uint256, uint256, uint256, uint256, bytes32, bytes32));

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, tokenAddress, totalPointsToken, totalTokensInflated, rewardsRoot, err
);
} catch (bytes memory err) {
emit UnableToProcessRewardsB(
epoch, eraIndex, tokenAddress, totalPointsToken, totalTokensInflated, rewardsRoot, err
);
}
}

function isTokenRegistered(address token) external view returns (bool) {
return Assets.isTokenRegistered(token);
}
Expand Down
20 changes: 12 additions & 8 deletions overridden_contracts/src/interfaces/IMiddlewareBasic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,25 @@ 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
* @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 tokensInflatedToken,
bytes32 rewardsRoot
uint256 amount,
bytes32 root,
address tokenAddress
) external;

/**
* @notice Slashes an operator's stake
* @dev Only the owner can call this function
Expand Down
32 changes: 32 additions & 0 deletions overridden_contracts/src/interfaces/IOGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,34 @@ 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,
address indexed tokenAddress,
uint256 totalPointsToken,
uint256 totalTokensInflated,
bytes32 rewardsRoot,
bytes error
);

// Emitted when the middleware fails to process rewards
event UnableToProcessRewardsS(
uint256 indexed epoch,
uint256 indexed eraIndex,
address indexed tokenAddress,
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
Expand All @@ -60,6 +88,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;
Expand Down
125 changes: 1 addition & 124 deletions overridden_contracts/test/Gateway.t.sol
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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";

Expand Down Expand Up @@ -163,13 +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 makeMockProof() public pure returns (Verification.Proof memory) {
return Verification.Proof({
leafPartial: Verification.MMRLeafPartial({
Expand Down Expand Up @@ -1017,119 +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);
}
}
}
Loading

0 comments on commit 03d5052

Please sign in to comment.