Skip to content

Commit

Permalink
Added rewards message processing
Browse files Browse the repository at this point in the history
  • Loading branch information
NindoK committed Jan 24, 2025
1 parent 4e02380 commit 7ff866f
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 27 deletions.
29 changes: 29 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,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);
}
Expand Down
24 changes: 10 additions & 14 deletions overridden_contracts/src/interfaces/IMiddlewareBasic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions overridden_contracts/src/interfaces/IOGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down
147 changes: 147 additions & 0 deletions overridden_contracts/test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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);
}
}
}
26 changes: 13 additions & 13 deletions overridden_contracts/test/override_test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit 7ff866f

Please sign in to comment.