Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for sending validators data #3

Draft
wants to merge 4 commits into
base: perm-tanssi-relayer-v1.0.30
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ tsconfig.tsbuildinfo
lcov.info
tenderly.yaml
docs/

.history/
6 changes: 6 additions & 0 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {MerkleProof} from "openzeppelin/utils/cryptography/MerkleProof.sol";
import {Verification} from "./Verification.sol";

import {Assets} from "./Assets.sol";
import {Operators} from "./Operators.sol";
import {AgentExecutor} from "./AgentExecutor.sol";
import {Agent} from "./Agent.sol";
import {
Expand Down Expand Up @@ -473,6 +474,11 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
_submitOutbound(ticket);
}

function sendOperatorsData(bytes calldata data, ParaID destinationChain) external payable {
Ticket memory ticket = Operators.encodeOperatorsData(data, destinationChain);
_submitOutbound(ticket);
}

// @dev Get token address by tokenID
function tokenAddressOf(bytes32 tokenID) external view returns (address) {
return Assets.tokenAddressOf(tokenID);
Expand Down
51 changes: 51 additions & 0 deletions contracts/src/Operators.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//SPDX-License-Identifier: GPL-3.0-or-later

// Copyright (C) Moondance Labs Ltd.
// This file is part of Tanssi.
// Tanssi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Tanssi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Tanssi. If not, see <http://www.gnu.org/licenses/>
pragma solidity 0.8.25;

import {BeefyClient} from "./BeefyClient.sol";
import {ScaleCodec} from "./utils/ScaleCodec.sol";
import {SubstrateTypes} from "./SubstrateTypes.sol";
import {MultiAddress, Ticket, Costs, ParaID} from "./Types.sol";
import {IGateway} from "./interfaces/IGateway.sol";

library Operators {
error Operators__UnsupportedOperatorsLength();
error Operators__OperatorsLengthTooLong();
error Operators__OperatorsKeysCannotBeEmpty();

uint8 private constant VALIDATOR_KEY_HEX_LENGTH = 32;
uint16 private constant MAX_OPERATORS = 1000;

function encodeOperatorsData(bytes calldata operatorsKeys, ParaID dest) internal returns (Ticket memory ticket) {
if (operatorsKeys.length == 0) {
revert Operators__OperatorsKeysCannotBeEmpty();
}

if (operatorsKeys.length % VALIDATOR_KEY_HEX_LENGTH != 0) {
revert Operators__UnsupportedOperatorsLength();
}
uint256 validatorsKeysLength = operatorsKeys.length / VALIDATOR_KEY_HEX_LENGTH;
if (validatorsKeysLength > MAX_OPERATORS) {
revert Operators__OperatorsLengthTooLong();
}

ticket.dest = dest;
//TODO For now mock it to 0
ticket.costs = Costs(0, 0);

ticket.payload = SubstrateTypes.EncodedOperatorsData(operatorsKeys, uint32(validatorsKeysLength));
emit IGateway.OperatorsDataCreated(validatorsKeysLength, ticket.payload);
}
}
24 changes: 23 additions & 1 deletion contracts/src/SubstrateTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {ParaID} from "./Types.sol";
* @title SCALE encoders for common Substrate types
*/
library SubstrateTypes {
error UnsupportedCompactEncoding();
error SubstrateTypes__UnsupportedCompactEncoding();

/**
* @dev Encodes `MultiAddress::Id`: https://crates.parity.io/sp_runtime/enum.MultiAddress.html#variant.Id
Expand Down Expand Up @@ -196,4 +196,26 @@ library SubstrateTypes {
ScaleCodec.encodeU128(xcmFee)
);
}

enum Message {
V0
}

enum OutboundCommandV1 {
ReceiveValidators
}

function EncodedOperatorsData(bytes calldata operatorsKeys, uint32 operatorsCount)
internal
pure
returns (bytes memory)
{
return bytes.concat(
bytes4(0x70150038),
bytes1(uint8(Message.V0)),
bytes1(uint8(OutboundCommandV1.ReceiveValidators)),
ScaleCodec.encodeCompactU32(operatorsCount),
operatorsKeys
);
}
}
4 changes: 4 additions & 0 deletions contracts/src/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,8 @@ interface IGateway {
uint128 destinationFee,
uint128 amount
) external payable;

event OperatorsDataCreated(uint256 indexed validatorsCount, bytes payload);

function sendOperatorsData(bytes calldata data, ParaID destinationChain) external payable;
}
71 changes: 71 additions & 0 deletions contracts/test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {AgentExecutor} from "../src/AgentExecutor.sol";
import {Agent} from "../src/Agent.sol";
import {Verification} from "../src/Verification.sol";
import {Assets} from "../src/Assets.sol";
import {Operators} from "../src/Operators.sol";
import {SubstrateTypes} from "./../src/SubstrateTypes.sol";
import {MultiAddress} from "../src/MultiAddress.sol";
import {Channel, InboundMessage, OperatingMode, ParaID, Command, ChannelID, MultiAddress} from "../src/Types.sol";
Expand Down Expand Up @@ -1014,4 +1015,74 @@ contract GatewayTest is Test {
bytes memory encodedParams = abi.encode(params);
MockGateway(address(gateway)).agentExecutePublic(encodedParams);
}

bytes private constant FINAL_VALIDATORS_PAYLOAD =
hex"7015003800000cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe228eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48";
bytes private constant VALIDATORS_DATA =
hex"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe228eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48";

bytes private constant WRONG_LENGTH_VALIDATORS_DATA =
hex"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe228eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a";

function createLongOperatorsData() public pure returns (bytes memory) {
bytes memory result = new bytes(VALIDATORS_DATA.length * 1000);

for (uint256 i = 0; i < 33; i++) {
for (uint256 j = 0; j < VALIDATORS_DATA.length; j++) {
result[i * VALIDATORS_DATA.length + j] = VALIDATORS_DATA[j];
}
}

return result;
}

function testSendOperatorsData() public {
// Create mock agent and paraID
ParaID paraID = ParaID.wrap(1);
bytes32 agentID = keccak256("1");

MockGateway(address(gateway)).createAgentPublic(abi.encode(CreateAgentParams({agentID: agentID})));

CreateChannelParams memory params =
CreateChannelParams({channelID: paraID.into(), agentID: agentID, mode: OperatingMode.Normal});

MockGateway(address(gateway)).createChannelPublic(abi.encode(params));

vm.expectEmit(true, false, false, true);
emit IGateway.OutboundMessageAccepted(paraID.into(), 1, messageID, FINAL_VALIDATORS_PAYLOAD);

IGateway(address(gateway)).sendOperatorsData{value: 1 ether}(VALIDATORS_DATA, paraID);
}

function testShouldNotSendOperatorsDataBecauseOperatorsNotMultipleOf32() public {
// Create mock agent and paraID
ParaID paraID = ParaID.wrap(1);
bytes32 agentID = keccak256("1");

MockGateway(address(gateway)).createAgentPublic(abi.encode(CreateAgentParams({agentID: agentID})));

CreateChannelParams memory params =
CreateChannelParams({channelID: paraID.into(), agentID: agentID, mode: OperatingMode.Normal});

MockGateway(address(gateway)).createChannelPublic(abi.encode(params));
vm.expectRevert(Operators.Operators__UnsupportedOperatorsLength.selector);
IGateway(address(gateway)).sendOperatorsData{value: 1 ether}(WRONG_LENGTH_VALIDATORS_DATA, paraID);
}

function testShouldNotSendOperatorsDataBecauseOperatorsTooLong() public {
// Create mock agent and paraID
ParaID paraID = ParaID.wrap(1);
bytes32 agentID = keccak256("1");

MockGateway(address(gateway)).createAgentPublic(abi.encode(CreateAgentParams({agentID: agentID})));

CreateChannelParams memory params =
CreateChannelParams({channelID: paraID.into(), agentID: agentID, mode: OperatingMode.Normal});

MockGateway(address(gateway)).createChannelPublic(abi.encode(params));
bytes memory longOperatorsData = createLongOperatorsData();

vm.expectRevert(Operators.Operators__OperatorsLengthTooLong.selector);
IGateway(address(gateway)).sendOperatorsData{value: 1 ether}(longOperatorsData, paraID);
}
}