diff --git a/src/ICS02Client.sol b/src/ICS02Client.sol index 51649ce..bb83be5 100644 --- a/src/ICS02Client.sol +++ b/src/ICS02Client.sol @@ -36,14 +36,27 @@ contract ICS02Client is IICS02Client, IICS02ClientErrors { return clients[clientId]; } - function addClient(string calldata clientType, CounterpartyInfo calldata counterpartyInfo, address client) external returns (string memory) { + function addClient( + string calldata clientType, + CounterpartyInfo calldata counterpartyInfo, + address client + ) + external + returns (string memory) + { string memory clientId = getNextClientId(clientType); clients[clientId] = ILightClient(client); counterpartyInfos[clientId] = counterpartyInfo; return clientId; } - function updateClient(string calldata clientId, bytes calldata updateMsg) external returns (ILightClient.UpdateResult) { + function updateClient( + string calldata clientId, + bytes calldata updateMsg + ) + external + returns (ILightClient.UpdateResult) + { return clients[clientId].updateClient(updateMsg); } diff --git a/src/ICS26Router.sol b/src/ICS26Router.sol index 4240727..0b4e2d5 100644 --- a/src/ICS26Router.sol +++ b/src/ICS26Router.sol @@ -11,10 +11,12 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { IBCIdentifiers } from "./utils/IBCIdentifiers.sol"; import { IIBCAppCallbacks } from "./msgs/IIBCAppCallbacks.sol"; import { ICS24Host } from "./utils/ICS24Host.sol"; +import { ILightClientMsgs } from "./msgs/ILightClientMsgs.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; /// @title IBC Eureka Router /// @notice ICS26Router is the router for the IBC Eureka protocol -contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors { +contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, ReentrancyGuard { mapping(string => IIBCApp) private apps; IICS02Client private ics02Client; @@ -55,14 +57,12 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors { /// @notice Sends a packet /// @param msg_ The message for sending packets /// @return The sequence number of the packet - function sendPacket(MsgSendPacket calldata msg_) external returns (uint32) { - IIBCApp app = apps[msg_.sourcePort]; - + function sendPacket(MsgSendPacket calldata msg_) external nonReentrant returns (uint32) { string memory counterpartyId = ics02Client.getCounterparty(msg_.sourcePort).clientId; // TODO: validate all identifiers if (msg_.timeoutTimestamp <= block.timestamp) { - revert IBCInvalidTimeoutTimestamp(msg_.timeoutTimestamp); + revert IBCInvalidTimeoutTimestamp(msg_.timeoutTimestamp, block.timestamp); } uint32 sequence = IBCStore.nextSequenceSend(msg_.sourcePort, msg_.sourceChannel); @@ -81,7 +81,7 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors { IIBCAppCallbacks.OnSendPacketCallback memory sendPacketCallback = IIBCAppCallbacks.OnSendPacketCallback({ packet: packet, sender: msg.sender }); - app.onSendPacket(sendPacketCallback); // TODO: do not allow reentrancy + apps[msg_.sourcePort].onSendPacket(sendPacketCallback); IBCStore.commitPacket(packet); @@ -91,7 +91,7 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors { /// @notice Receives a packet /// @param msg_ The message for receiving packets - function recvPacket(MsgRecvPacket calldata msg_) external { + function recvPacket(MsgRecvPacket calldata msg_) external nonReentrant { // TODO: implement IIBCApp app = apps[msg_.packet.destPort]; @@ -101,24 +101,52 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors { } bytes memory commitmentPath = ICS24Host.packetCommitmentPathCalldata( - msg_.packet.sourcePort, - msg_.packet.sourceChannel, - msg_.packet.sequence + msg_.packet.sourcePort, msg_.packet.sourceChannel, msg_.packet.sequence ); bytes32 commitmentBz = ICS24Host.packetCommitmentBytes32(msg_.packet); + + ILightClientMsgs.MsgMembership memory membershipMsg = ILightClientMsgs.MsgMembership({ + proof: msg_.proofCommitment, + proofHeight: msg_.proofHeight, + kvPair: ILightClientMsgs.KVPair({ path: commitmentPath, value: abi.encodePacked(commitmentBz) }) + }); + + uint32 proofTimestamp = ics02Client.getClient(msg_.packet.destChannel).verifyMembership(membershipMsg); + if (msg_.packet.timeoutTimestamp <= proofTimestamp) { + revert IBCInvalidTimeoutTimestamp(proofTimestamp, msg_.packet.timeoutTimestamp); + } + if (msg_.packet.timeoutTimestamp <= block.timestamp) { + revert IBCInvalidTimeoutTimestamp(msg_.packet.timeoutTimestamp, block.timestamp); + } + + bytes memory ack = + app.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: msg_.packet, relayer: msg.sender })); + if (ack.length == 0) { + revert IBCAsyncAcknowledgementNotSupported(); + } + + writeAcknowledgement(msg_.packet, ack); + + emit RecvPacket(msg_.packet); } /// @notice Acknowledges a packet /// @param msg_ The message for acknowledging packets - function ackPacket(MsgAckPacket calldata msg_) external { + function ackPacket(MsgAckPacket calldata msg_) external nonReentrant { // TODO: implement // IIBCApp app = IIBCApp(apps[msg.packet.sourcePort]); } /// @notice Timeouts a packet /// @param msg_ The message for timing out packets - function timeoutPacket(MsgTimeoutPacket calldata msg_) external { + function timeoutPacket(MsgTimeoutPacket calldata msg_) external nonReentrant { // TODO: implement // IIBCApp app = IIBCApp(apps[msg.packet.sourcePort]); } + + /// @notice Writes a packet acknowledgement and emits an event + function writeAcknowledgement(Packet calldata packet, bytes memory ack) private { + IBCStore.commitPacketAcknowledgement(packet, ack); + emit WriteAcknowledgement(packet, ack); + } } diff --git a/src/errors/IICS26RouterErrors.sol b/src/errors/IICS26RouterErrors.sol index b4b1658..8881565 100644 --- a/src/errors/IICS26RouterErrors.sol +++ b/src/errors/IICS26RouterErrors.sol @@ -9,9 +9,11 @@ interface IICS26RouterErrors { error IBCInvalidPortIdentifier(string portId); /// @param timeoutTimestamp timeout timestamp in seconds - error IBCInvalidTimeoutTimestamp(uint32 timeoutTimestamp); + error IBCInvalidTimeoutTimestamp(uint256 timeoutTimestamp, uint256 comparedTimestamp); /// @param expected expected counterparty identifier /// @param actual actual counterparty identifier error IBCInvalidCounterparty(string expected, string actual); + + error IBCAsyncAcknowledgementNotSupported(); } diff --git a/src/interfaces/IICS02Client.sol b/src/interfaces/IICS02Client.sol index d099c33..1e921a8 100644 --- a/src/interfaces/IICS02Client.sol +++ b/src/interfaces/IICS02Client.sol @@ -22,7 +22,13 @@ interface IICS02Client is IICS02ClientMsgs { /// @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); + function addClient( + string calldata clientType, + CounterpartyInfo calldata counterpartyInfo, + address client + ) + external + returns (string memory); /// @notice Updates the client given the client identifier. /// @param clientId The client identifier diff --git a/test/IbcIdentifiers.t.sol b/test/IbcIdentifiers.t.sol index 442fd53..cce3637 100644 --- a/test/IbcIdentifiers.t.sol +++ b/test/IbcIdentifiers.t.sol @@ -15,68 +15,29 @@ contract IBCIdentifiersTest is Test { // The following test cases are based on the test cases of ibc-go: // https://github.com/cosmos/ibc-go/blob/e443a88e0f2c84c131c5a1de47945a5733ff9c91/modules/core/24-host/validate_test.go#L57 ValidatePortIdentifierTestCase[] memory testCases = new ValidatePortIdentifierTestCase[](12); - testCases[0] = ValidatePortIdentifierTestCase({ - m: "valid lowercase", - id: "transfer", - expPass: true - }); - testCases[1] = ValidatePortIdentifierTestCase({ - m: "valid id special chars", - id: "._+-#[]<>._+-#[]<>", - expPass: true - }); + testCases[0] = ValidatePortIdentifierTestCase({ m: "valid lowercase", id: "transfer", expPass: true }); + testCases[1] = + ValidatePortIdentifierTestCase({ m: "valid id special chars", id: "._+-#[]<>._+-#[]<>", expPass: true }); testCases[2] = ValidatePortIdentifierTestCase({ m: "valid id lower and special chars", id: "lower._+-#[]<>", expPass: true }); - testCases[3] = ValidatePortIdentifierTestCase({ - m: "numeric id", - id: "1234567890", - expPass: true - }); - testCases[4] = ValidatePortIdentifierTestCase({ - m: "uppercase id", - id: "NOTLOWERCASE", - expPass: true - }); - testCases[5] = ValidatePortIdentifierTestCase({ - m: "numeric id", - id: "1234567890", - expPass: true - }); - testCases[6] = ValidatePortIdentifierTestCase({ - m: "blank id", - id: " ", - expPass: false - }); - testCases[7] = ValidatePortIdentifierTestCase({ - m: "id length out of range", - id: "1", - expPass: false - }); + testCases[3] = ValidatePortIdentifierTestCase({ m: "numeric id", id: "1234567890", expPass: true }); + testCases[4] = ValidatePortIdentifierTestCase({ m: "uppercase id", id: "NOTLOWERCASE", expPass: true }); + testCases[5] = ValidatePortIdentifierTestCase({ m: "numeric id", id: "1234567890", expPass: true }); + testCases[6] = ValidatePortIdentifierTestCase({ m: "blank id", id: " ", expPass: false }); + testCases[7] = ValidatePortIdentifierTestCase({ m: "id length out of range", id: "1", expPass: false }); testCases[8] = ValidatePortIdentifierTestCase({ m: "id is too long", id: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis eros neque, ultricies vel ligula ac, convallis porttitor elit. Maecenas tincidunt turpis elit, vel faucibus nisl pellentesque sodales", expPass: false }); - testCases[9] = ValidatePortIdentifierTestCase({ - m: "path-like id", - id: "lower/case/id", - expPass: false - }); - testCases[10] = ValidatePortIdentifierTestCase({ - m: "invalid id", - id: "(clientid)", - expPass: false - }); - testCases[11] = ValidatePortIdentifierTestCase({ - m: "empty string", - id: "", - expPass: false - }); + testCases[9] = ValidatePortIdentifierTestCase({ m: "path-like id", id: "lower/case/id", expPass: false }); + testCases[10] = ValidatePortIdentifierTestCase({ m: "invalid id", id: "(clientid)", expPass: false }); + testCases[11] = ValidatePortIdentifierTestCase({ m: "empty string", id: "", expPass: false }); - for (uint i = 0; i < testCases.length; i++) { + for (uint256 i = 0; i < testCases.length; i++) { ValidatePortIdentifierTestCase memory tc = testCases[i]; bool res = IBCIdentifiers.validatePortIdentifier(bytes(tc.id)); if (tc.expPass) {