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

Change counterpartyInfo to channel #100

Merged
merged 17 commits into from
Dec 2, 2024
Merged
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ test-e2e testname:

# Install the sp1-ics07-tendermint operator for use in the e2e tests
install-operator:
cargo install --git https://github.com/cosmos/sp1-ics07-tendermint --rev {{sp1_operator_rev}} sp1-ics07-tendermint-operator --bin operator --locked
cargo +nightly install --git https://github.com/cosmos/sp1-ics07-tendermint --rev {{sp1_operator_rev}} sp1-ics07-tendermint-operator --bin operator --locked
srdtrk marked this conversation as resolved.
Show resolved Hide resolved

# Generate the fixtures for the Solidity tests using the e2e tests
generate-fixtures:
Expand Down
37 changes: 20 additions & 17 deletions src/ICS02Client.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
pragma solidity ^0.8.28;

import { IICS02Client } from "./interfaces/IICS02Client.sol";
import { IICS04Channel } from "./interfaces/IICS04Channel.sol";
import { Strings } from "@openzeppelin/utils/Strings.sol";
import { IBCIdentifiers } from "./utils/IBCIdentifiers.sol";
import { ILightClient } from "./interfaces/ILightClient.sol";
import { IICS02ClientErrors } from "./errors/IICS02ClientErrors.sol";
import { Ownable } from "@openzeppelin/access/Ownable.sol";

contract ICS02Client is IICS02Client, IICS02ClientErrors, Ownable {
/// @dev clientId => counterpartyInfo
mapping(string clientId => CounterpartyInfo info) private counterpartyInfos;
contract ICS02Client is IICS02Client, IICS04Channel, IICS02ClientErrors, Ownable {
/// @dev channelId => counterpartyInfo
mapping(string channelId => Channel channel) private channels;
/// @dev clientId => light client contract
mapping(string clientId => ILightClient client) private clients;
/// @dev clientType => nextClientSeq
Expand All @@ -32,14 +33,14 @@ contract ICS02Client is IICS02Client, IICS02ClientErrors, Ownable {
return string.concat(clientType, "-", Strings.toString(seq));
}

/// @inheritdoc IICS02Client
function getCounterparty(string calldata clientId) public view returns (CounterpartyInfo memory) {
CounterpartyInfo memory counterpartyInfo = counterpartyInfos[clientId];
if (bytes(counterpartyInfo.clientId).length == 0) {
revert IBCCounterpartyClientNotFound(clientId);
/// @inheritdoc IICS04Channel
function getChannel(string calldata channelId) public view returns (Channel memory) {
Channel memory channel = channels[channelId];
if (bytes(channel.counterpartyId).length == 0) {
revert IBCCounterpartyClientNotFound(channelId);
}

return counterpartyInfo;
return channel;
}

/// @inheritdoc IICS02Client
Expand All @@ -52,20 +53,22 @@ contract ICS02Client is IICS02Client, IICS02ClientErrors, Ownable {
return client;
}

/// @inheritdoc IICS02Client
function addClient(
/// @inheritdoc IICS04Channel
function addChannel(
string calldata clientType,
CounterpartyInfo calldata counterpartyInfo,
Channel calldata channel,
address client
)
external
returns (string memory)
{
string memory clientId = getNextClientId(clientType);
clients[clientId] = ILightClient(client);
counterpartyInfos[clientId] = counterpartyInfo;
// use the same identifier for channelId and clientId
// for Solidity implementation
channels[clientId] = channel;

emit ICS02ClientAdded(clientId, counterpartyInfo);
emit ICS04ChannelAdded(clientId, channel);

return clientId;
}
Expand All @@ -75,10 +78,10 @@ contract ICS02Client is IICS02Client, IICS02ClientErrors, Ownable {
getClient(subjectClientId); // Ensure subject client exists
ILightClient substituteClient = getClient(substituteClientId);

getCounterparty(subjectClientId); // Ensure subject client's counterparty exists
CounterpartyInfo memory substituteCounterpartyInfo = getCounterparty(substituteClientId);
getChannel(subjectClientId); // Ensure channel exists for this clientId
Channel memory substituteChannel = getChannel(substituteClientId);

counterpartyInfos[subjectClientId] = substituteCounterpartyInfo;
channels[subjectClientId] = substituteChannel;
clients[subjectClientId] = substituteClient;
}

Expand Down
31 changes: 18 additions & 13 deletions src/ICS26Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.28;
import { IIBCApp } from "./interfaces/IIBCApp.sol";
import { IICS26Router } from "./interfaces/IICS26Router.sol";
import { IICS02Client } from "./interfaces/IICS02Client.sol";
import { IICS04Channel } from "./interfaces/IICS04Channel.sol";
import { IBCStore } from "./utils/IBCStore.sol";
import { IICS26RouterErrors } from "./errors/IICS26RouterErrors.sol";
import { Ownable } from "@openzeppelin/access/Ownable.sol";
Expand All @@ -13,6 +14,7 @@ import { IIBCAppCallbacks } from "./msgs/IIBCAppCallbacks.sol";
import { ICS24Host } from "./utils/ICS24Host.sol";
import { ILightClientMsgs } from "./msgs/ILightClientMsgs.sol";
import { IICS02ClientMsgs } from "./msgs/IICS02ClientMsgs.sol";
import { IICS04ChannelMsgs } from "./msgs/IICS04ChannelMsgs.sol";
import { ReentrancyGuard } from "@openzeppelin/utils/ReentrancyGuard.sol";

/// @title IBC Eureka Router
Expand All @@ -22,9 +24,12 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree
mapping(string portId => IIBCApp app) private apps;
/// @dev ICS02Client contract
IICS02Client private ics02Client;
/// @dev ICS04Channel contract
IICS04Channel private ics04Channel;

constructor(address ics02Client_, address owner) Ownable(owner) {
ics02Client = IICS02Client(ics02Client_);
ics04Channel = IICS04Channel(ics02Client_);
}

/// @notice Returns the address of the IBC application given the port identifier
Expand Down Expand Up @@ -70,7 +75,7 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree
/// @return The sequence number of the packet
/// @inheritdoc IICS26Router
function sendPacket(MsgSendPacket calldata msg_) external nonReentrant returns (uint32) {
string memory counterpartyId = ics02Client.getCounterparty(msg_.sourceChannel).clientId;
string memory counterpartyId = ics04Channel.getChannel(msg_.sourceChannel).counterpartyId;

// TODO: validate all identifiers
if (msg_.timeoutTimestamp <= block.timestamp) {
Expand Down Expand Up @@ -104,9 +109,9 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree
/// @param msg_ The message for receiving packets
/// @inheritdoc IICS26Router
function recvPacket(MsgRecvPacket calldata msg_) external nonReentrant {
IICS02ClientMsgs.CounterpartyInfo memory cInfo = ics02Client.getCounterparty(msg_.packet.destChannel);
if (keccak256(bytes(cInfo.clientId)) != keccak256(bytes(msg_.packet.sourceChannel))) {
revert IBCInvalidCounterparty(cInfo.clientId, msg_.packet.sourceChannel);
IICS04ChannelMsgs.Channel memory channel = ics04Channel.getChannel(msg_.packet.destChannel);
if (keccak256(bytes(channel.counterpartyId)) != keccak256(bytes(msg_.packet.sourceChannel))) {
revert IBCInvalidCounterparty(channel.counterpartyId, msg_.packet.sourceChannel);
}

if (msg_.packet.timeoutTimestamp <= block.timestamp) {
Expand All @@ -121,7 +126,7 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree
ILightClientMsgs.MsgMembership memory membershipMsg = ILightClientMsgs.MsgMembership({
proof: msg_.proofCommitment,
proofHeight: msg_.proofHeight,
path: ICS24Host.prefixedPath(cInfo.merklePrefix, commitmentPath),
path: ICS24Host.prefixedPath(channel.merklePrefix, commitmentPath),
value: abi.encodePacked(commitmentBz)
});

Expand All @@ -145,9 +150,9 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree
/// @param msg_ The message for acknowledging packets
/// @inheritdoc IICS26Router
function ackPacket(MsgAckPacket calldata msg_) external nonReentrant {
IICS02ClientMsgs.CounterpartyInfo memory cInfo = ics02Client.getCounterparty(msg_.packet.sourceChannel);
if (keccak256(bytes(cInfo.clientId)) != keccak256(bytes(msg_.packet.destChannel))) {
revert IBCInvalidCounterparty(cInfo.clientId, msg_.packet.destChannel);
IICS04ChannelMsgs.Channel memory channel = ics04Channel.getChannel(msg_.packet.sourceChannel);
if (keccak256(bytes(channel.counterpartyId)) != keccak256(bytes(msg_.packet.destChannel))) {
revert IBCInvalidCounterparty(channel.counterpartyId, msg_.packet.destChannel);
}

// this will revert if the packet commitment does not exist
Expand All @@ -165,7 +170,7 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree
ILightClientMsgs.MsgMembership memory membershipMsg = ILightClientMsgs.MsgMembership({
proof: msg_.proofAcked,
proofHeight: msg_.proofHeight,
path: ICS24Host.prefixedPath(cInfo.merklePrefix, commitmentPath),
path: ICS24Host.prefixedPath(channel.merklePrefix, commitmentPath),
value: abi.encodePacked(commitmentBz)
});

Expand All @@ -186,9 +191,9 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree
/// @param msg_ The message for timing out packets
/// @inheritdoc IICS26Router
function timeoutPacket(MsgTimeoutPacket calldata msg_) external nonReentrant {
IICS02ClientMsgs.CounterpartyInfo memory cInfo = ics02Client.getCounterparty(msg_.packet.sourceChannel);
if (keccak256(bytes(cInfo.clientId)) != keccak256(bytes(msg_.packet.destChannel))) {
revert IBCInvalidCounterparty(cInfo.clientId, msg_.packet.destChannel);
IICS04ChannelMsgs.Channel memory channel = ics04Channel.getChannel(msg_.packet.sourceChannel);
if (keccak256(bytes(channel.counterpartyId)) != keccak256(bytes(msg_.packet.destChannel))) {
revert IBCInvalidCounterparty(channel.counterpartyId, msg_.packet.destChannel);
}

// this will revert if the packet commitment does not exist
Expand All @@ -203,7 +208,7 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree
ILightClientMsgs.MsgMembership memory nonMembershipMsg = ILightClientMsgs.MsgMembership({
proof: msg_.proofTimeout,
proofHeight: msg_.proofHeight,
path: ICS24Host.prefixedPath(cInfo.merklePrefix, receiptPath),
path: ICS24Host.prefixedPath(channel.merklePrefix, receiptPath),
value: bytes("")
});

Expand Down
23 changes: 0 additions & 23 deletions src/interfaces/IICS02Client.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,11 @@ import { ILightClient } from "./ILightClient.sol";
/// @title ICS02 Light Client Router Interface
/// @notice IICS02Client is an interface for the IBC Eureka client router
interface IICS02Client is IICS02ClientMsgs {
/// @notice Emitted when a new client is added to the client router.
/// @param clientId The newly created client identifier
/// @param counterpartyInfo The counterparty client information, if provided
event ICS02ClientAdded(string clientId, CounterpartyInfo counterpartyInfo);

/// @notice Returns the counterparty client information given the client identifier.
/// @param clientId The client identifier
/// @return The counterparty client information
function getCounterparty(string calldata clientId) external view returns (CounterpartyInfo memory);

/// @notice Returns the address of the client contract given the client identifier.
/// @param clientId The client identifier
/// @return The address of the client contract
function getClient(string calldata clientId) external view returns (ILightClient);

/// @notice Adds a client to the client router.
/// @param clientType The client type, e.g., "07-tendermint".
/// @param counterpartyInfo The counterparty client information
/// @param client The address of the client contract
/// @return The client identifier
function addClient(
string calldata clientType,
CounterpartyInfo calldata counterpartyInfo,
address client
)
external
returns (string memory);

/// @notice Migrate the underlying client of the subject client to the substitute client.
/// @dev This is a privilaged operation, only the owner of ICS02Client can call this function.
/// @param subjectClientId The client identifier of the subject client
Expand Down
31 changes: 31 additions & 0 deletions src/interfaces/IICS04Channel.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { IICS04ChannelMsgs } from "../msgs/IICS04ChannelMsgs.sol";

/// @title ICS04 Channel Interface
/// @notice IICS04CHANNEL is an interface for the IBC Eureka channel router
interface IICS04Channel is IICS04ChannelMsgs {
/// @notice Emitted when a new channel is added to the channel router.
/// @param channelId The newly created channel identifier. NOTE: In this implementation, the channelId is the client identifier.
/// @param channel The counterparty client information, if provided
event ICS04ChannelAdded(string channelId, Channel channel);

/// @notice Returns the channel given the channel identifier.
/// @param channelId The channel identifier
/// @return channel
function getChannel(string calldata channelId) external view returns (Channel memory);

/// @notice Adds a channel to the channel router.
/// @param clientType The client type, e.g., "07-tendermint".
/// @param channel The channel information
/// @param client The address of the client contract
/// @return The channel identifier NOTE: This is the same as the client identifier
function addChannel(
string calldata clientType,
Channel calldata channel,
address client
)
external
returns (string memory);
}
10 changes: 0 additions & 10 deletions src/msgs/IICS02ClientMsgs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@
pragma solidity ^0.8.28;

interface IICS02ClientMsgs {
/// @notice Counterparty client information.
/// @custom:spec
/// https://github.com/cosmos/ibc/blob/67fe813f7e4ec603a7c5dec35bc654f3b012afda/spec/micro/README.md?plain=1#L91
/// @param clientId The client identifier from the counterparty chain.
/// @param merklePrefix The counterparty chain's merkle prefix.
struct CounterpartyInfo {
string clientId;
bytes[] merklePrefix;
}

/// @notice Height of the counterparty chain
/// @param revisionNumber The revision number of the counterparty chain
/// @param revisionHeight The height of the counterparty chain
Expand Down
14 changes: 14 additions & 0 deletions src/msgs/IICS04ChannelMsgs.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

interface IICS04ChannelMsgs {
/// @notice Channel information.
/// @custom:spec
/// https://github.com/cosmos/ibc/blob/67fe813f7e4ec603a7c5dec35bc654f3b012afda/spec/micro/README.md?plain=1#L91
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link should be updated?

/// @param counterpartyId The counterparty channel identifier from the counterparty chain.
/// @param merklePrefix The counterparty chain's merkle prefix.
struct Channel {
string counterpartyId;
bytes[] merklePrefix;
}
}
18 changes: 9 additions & 9 deletions test/ICS02ClientTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ pragma solidity ^0.8.28;
// solhint-disable custom-errors,max-line-length

import { Test } from "forge-std/Test.sol";
import { IICS02Client } from "../src/interfaces/IICS02Client.sol";
import { ICS02Client } from "../src/ICS02Client.sol";
import { IICS02ClientMsgs } from "../src/msgs/IICS02ClientMsgs.sol";
import { IICS04Channel } from "../src/interfaces/IICS04Channel.sol";
import { IICS04ChannelMsgs } from "../src/msgs/IICS04ChannelMsgs.sol";
import { ILightClient } from "../src/interfaces/ILightClient.sol";
import { ILightClientMsgs } from "../src/msgs/ILightClientMsgs.sol";
import { DummyLightClient } from "./mocks/DummyLightClient.sol";

contract ICS02ClientTest is Test {
IICS02Client public ics02Client;
ICS02Client public ics02Client;
DummyLightClient public lightClient;

bytes[] public merklePrefix = [bytes("ibc"), bytes("")];
Expand All @@ -23,17 +23,17 @@ contract ICS02ClientTest is Test {
}

function test_ICS02Client() public {
string memory counterpartyClient = "42-dummy-01";
IICS02ClientMsgs.CounterpartyInfo memory counterpartyInfo =
IICS02ClientMsgs.CounterpartyInfo(counterpartyClient, merklePrefix);
string memory counterpartyId = "42-dummy-01";
IICS04ChannelMsgs.Channel memory channel =
IICS04ChannelMsgs.Channel(counterpartyId, merklePrefix);
vm.expectEmit();
emit IICS02Client.ICS02ClientAdded("07-tendermint-0", counterpartyInfo);
string memory clientIdentifier = ics02Client.addClient("07-tendermint", counterpartyInfo, address(lightClient));
emit IICS04Channel.ICS04ChannelAdded("07-tendermint-0", channel);
string memory clientIdentifier = ics02Client.addChannel("07-tendermint", channel, address(lightClient));

ILightClient fetchedLightClient = ics02Client.getClient(clientIdentifier);
assertNotEq(address(fetchedLightClient), address(0), "client not found");

assertEq(counterpartyInfo.clientId, counterpartyClient, "counterpartyInfo not found");
assertEq(channel.counterpartyId, counterpartyId, "channel not set correctly");

bytes memory updateMsg = "testUpdateMsg";
ILightClient.UpdateResult updateResult = ics02Client.updateClient(clientIdentifier, updateMsg);
Expand Down
9 changes: 5 additions & 4 deletions test/ICS26RouterTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pragma solidity ^0.8.28;
import { Test } from "forge-std/Test.sol";
import { ICS02Client } from "../src/ICS02Client.sol";
import { IICS02ClientMsgs } from "../src/msgs/IICS02ClientMsgs.sol";
import { IICS04ChannelMsgs } from "../src/msgs/IICS04ChannelMsgs.sol";
import { ICS26Router } from "../src/ICS26Router.sol";
import { IICS26Router } from "../src/interfaces/IICS26Router.sol";
import { IICS26RouterMsgs } from "../src/msgs/IICS26RouterMsgs.sol";
Expand Down Expand Up @@ -47,10 +48,10 @@ contract ICS26RouterTest is Test {
}

function test_RecvPacketWithFailedMembershipVerification() public {
string memory counterpartyClientID = "42-dummy-01";
string memory counterpartyID = "42-dummy-01";
DummyLightClient lightClient = new DummyLightClient(ILightClientMsgs.UpdateResult.Update, 0, true);
string memory clientIdentifier = ics02Client.addClient(
"07-tendermint", IICS02ClientMsgs.CounterpartyInfo(counterpartyClientID, merklePrefix), address(lightClient)
string memory clientIdentifier = ics02Client.addChannel(
"07-tendermint", IICS04ChannelMsgs.Channel(counterpartyID, merklePrefix), address(lightClient)
);

ICS20Transfer ics20Transfer = new ICS20Transfer(address(ics26Router));
Expand All @@ -60,7 +61,7 @@ contract ICS26RouterTest is Test {
sequence: 1,
timeoutTimestamp: uint64(block.timestamp + 1000),
sourcePort: "transfer",
sourceChannel: counterpartyClientID,
sourceChannel: counterpartyID,
destPort: "transfer",
destChannel: clientIdentifier,
version: "ics20-1",
Expand Down
Loading