From 9d6215296ed78529713a5b8b831bc85096b0eec4 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 8 Mar 2024 13:33:51 +0100 Subject: [PATCH] feat: store own address inside vIBC packet to do address checks easier --- src/apps/polymer/vIBCEscrow.sol | 124 +++++++++++++++++++------------- 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/src/apps/polymer/vIBCEscrow.sol b/src/apps/polymer/vIBCEscrow.sol index 9347f9b..c6be86d 100644 --- a/src/apps/polymer/vIBCEscrow.sol +++ b/src/apps/polymer/vIBCEscrow.sol @@ -1,33 +1,43 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import {APolymerEscrow} from "./APolymerEscrow.sol"; +import { APolymerEscrow } from "./APolymerEscrow.sol"; import "../../MessagePayload.sol"; -import {AckPacket} from "vibc-core-smart-contracts/libs/Ibc.sol"; -import { - IbcMwUser, - UniversalPacket, - IbcUniversalPacketSender, - IbcUniversalPacketReceiver -} from "vibc-core-smart-contracts/interfaces/IbcMiddleware.sol"; import "vibc-core-smart-contracts/interfaces/IbcDispatcher.sol"; -import { - IbcReceiverBase, IbcReceiver -} from "vibc-core-smart-contracts/interfaces/IbcReceiver.sol"; - -/// @notice Polymer implementation of the Generalised Incentives based on vIBC. +import { AckPacket } from "vibc-core-smart-contracts/libs/Ibc.sol"; +import { IbcReceiverBase, IbcReceiver } from "vibc-core-smart-contracts/interfaces/IbcReceiver.sol"; + +/** + * @notice Polymer implementation of the Generalised Incentives based on vIBC. + * @dev An implementation quirk of Polymer is that channels map 1:1 to BOTH chains + * and contracts. As a result, if we trust a channel, we also imply that we trust + * the contract on the other end of that channel. This is unlike "traditional" chain + * mappings where there may be may addresses on the other end. + * As a result, we are allowed to just append our address to the package and then trust that. + * Because if someone trust the channel (which is a requirement) then they must also + * trust the account AND the set value. + */ contract IncentivizedPolymerEscrow is APolymerEscrow, IbcReceiverBase, IbcReceiver { + error ChannelNotFound(); + error UnsupportedVersion(); + + uint constant POLYMER_SENDER_IDENTIFIER_START = 0; + uint constant POLYMER_SENDER_IDENTIFIER_END = 32; + uint constant POLYMER_PACKAGE_PAYLOAD_START = 32; bytes32[] public connectedChannels; - string constant VERSION = '1.0'; + string constant VERSION = 'vIBC Escrow 1.0'; + + // Make a shortcut to save a bit of gas. + bytes32 immutable ADDRESS_THIS = bytes32(uint256(uint160(address(this)))); constructor(address sendLostGasTo, address dispatcher) APolymerEscrow(sendLostGasTo) IbcReceiverBase(IbcDispatcher(dispatcher)) {} - // IBC callback functions + //--- IBC Channel Callbacks ---// function onOpenIbcChannel( string calldata version, @@ -38,16 +48,14 @@ contract IncentivizedPolymerEscrow is APolymerEscrow, IbcReceiverBase, IbcReceiv ) external view onlyIbcDispatcher returns (string memory selectedVersion) { if (counterparty.channelId == bytes32(0)) { // ChanOpenInit - require( - keccak256(abi.encodePacked(version)) == keccak256(abi.encodePacked(VERSION)), - 'Unsupported version' - ); + if ( + keccak256(abi.encodePacked(version)) != keccak256(abi.encodePacked(VERSION)) + ) revert UnsupportedVersion(); } else { // ChanOpenTry - require( - keccak256(abi.encodePacked(counterparty.version)) == keccak256(abi.encodePacked(VERSION)), - 'Unsupported version' - ); + if ( + keccak256(abi.encodePacked(counterparty.version)) != keccak256(abi.encodePacked(VERSION)) + ) revert UnsupportedVersion(); } return VERSION; } @@ -57,29 +65,34 @@ contract IncentivizedPolymerEscrow is APolymerEscrow, IbcReceiverBase, IbcReceiv bytes32, string calldata counterpartyVersion ) external onlyIbcDispatcher { - require( - keccak256(abi.encodePacked(counterpartyVersion)) == keccak256(abi.encodePacked(VERSION)), - 'Unsupported version' - ); + if ( + keccak256(abi.encodePacked(counterpartyVersion)) != keccak256(abi.encodePacked(VERSION)) + ) revert UnsupportedVersion(); connectedChannels.push(channelId); } function onCloseIbcChannel(bytes32 channelId, string calldata, bytes32) external onlyIbcDispatcher { + unchecked { // logic to determin if the channel should be closed bool channelFound = false; - for (uint256 i = 0; i < connectedChannels.length; i++) { + for (uint256 i = 0; i < connectedChannels.length; ++i) { if (connectedChannels[i] == channelId) { delete connectedChannels[i]; channelFound = true; - break; + return; + // We could also break but early return saves gas. } } - require(channelFound, 'Channel not found'); + if (!channelFound) revert ChannelNotFound(); + + } } + //--- IBC Packet Callbacks ---// + // packet.srcPortAddr is the IncentivizedPolymerEscrow address on the source chain. // packet.destPortAddr is the address of this contract. - // channelId: the universal channel id from the running chain's perspective, which can be used to identify the counterparty chain. + // channelId: the channel id from the running chain's perspective, which can be used to identify the counterparty chain. function onRecvPacket(IbcPacket calldata packet) external override onlyIbcDispatcher @@ -88,16 +101,22 @@ contract IncentivizedPolymerEscrow is APolymerEscrow, IbcReceiverBase, IbcReceiv uint256 gasLimit = gasleft(); bytes32 feeRecipitent = bytes32(uint256(uint160(tx.origin))); - bytes memory sourceImplementationIdentifier = bytes.concat(hex""); // TODO packet.src.portId decode from a mapping + // Collect the implementation identifier we added. Remember, this is trusted IFF packet.src.channelId is trusted. + // sourceImplementationIdentifier has already been defined by the channel on channel creation. + bytes memory sourceImplementationIdentifier = packet.data[POLYMER_SENDER_IDENTIFIER_START:POLYMER_SENDER_IDENTIFIER_END]; - bytes memory receiveAck = - _handleMessage(packet.src.channelId, sourceImplementationIdentifier, packet.data, feeRecipitent, gasLimit); + bytes memory receiveAck = _handleMessage( + packet.src.channelId, + sourceImplementationIdentifier, + packet.data[POLYMER_PACKAGE_PAYLOAD_START: ], + feeRecipitent, + gasLimit + ); // Send ack: - return AckPacket({success: true, data: receiveAck}); + return AckPacket({success: true, data: bytes.concat(ADDRESS_THIS, receiveAck)}); } - // The escrow manages acks, so any message can be directly provided to _handleAck. function onAcknowledgementPacket( IbcPacket calldata packet, AckPacket calldata ack @@ -108,9 +127,13 @@ contract IncentivizedPolymerEscrow is APolymerEscrow, IbcReceiverBase, IbcReceiv uint256 gasLimit = gasleft(); bytes32 feeRecipitent = bytes32(uint256(uint160(tx.origin))); - bytes calldata rawMessage = ack.data; - bytes memory destinationImplementationIdentifier = bytes.concat(hex""); // TODO packet.dest.portId , we need to map from channelId. + // Collect the implementation identifier we added. Remember, this is trusted IFF packet.src.channelId is trusted. + bytes memory destinationImplementationIdentifier = ack.data[POLYMER_SENDER_IDENTIFIER_START:POLYMER_SENDER_IDENTIFIER_END]; + // Get the payload by removing the implementation identifier. + bytes calldata rawMessage = ack.data[POLYMER_PACKAGE_PAYLOAD_START:]; + + // Set a verificaiton context so we can recover the ack. isVerifiedMessageHash[keccak256(rawMessage)] = VerifiedMessageHashContext({ chainIdentifier: packet.src.channelId, implementationIdentifier: destinationImplementationIdentifier @@ -118,12 +141,12 @@ contract IncentivizedPolymerEscrow is APolymerEscrow, IbcReceiverBase, IbcReceiv _handleAck(packet.src.channelId, destinationImplementationIdentifier, rawMessage, feeRecipitent, gasLimit); } - // For timeouts, we need to construct the message. function onTimeoutPacket(IbcPacket calldata packet) external override onlyIbcDispatcher{ uint256 gasLimit = gasleft(); bytes32 feeRecipitent = bytes32(uint256(uint160(tx.origin))); - bytes calldata rawMessage = packet.data; + // We added a bytes32 implementation identifier. Remove it. + bytes calldata rawMessage = packet.data[POLYMER_PACKAGE_PAYLOAD_START:]; bytes32 messageIdentifier = bytes32(rawMessage[MESSAGE_IDENTIFIER_START:MESSAGE_IDENTIFIER_END]); address fromApplication = address(uint160(bytes20(rawMessage[FROM_APPLICATION_START_EVM:FROM_APPLICATION_END]))); _handleTimeout( @@ -132,12 +155,12 @@ contract IncentivizedPolymerEscrow is APolymerEscrow, IbcReceiverBase, IbcReceiv } /** - * @param destinationChainIdentifier Universal Channel ID. It's always from the running chain's perspective. - * Each universal channel/channelId represents a directional path from the running chain to a destination chain. - * Universal ChannelIds should _destChainIdToChannelIdd from the Polymer registry. - * Although everyone is free to establish their own channels, they're not "officially" vetted until they're in the Polymer registry. - * @param message packet payload - * @param deadline Packet will timeout after the dest chain's block time in nanoseconds since the epoch passes timeoutTimestamp. + * @param destinationChainIdentifier Channel ID. It's always from the running chain's perspective. + * Each channel/channelId represents a directional path from the running chain to a destination chain + * AND destination implementation. If we trust a channelId, then it is also implied that we trust the + * implementation deployed there. + * @param message Packet payload. We will add our address to it. This is to standardize with other implementations where we return the destination. + * @param deadline Packet will timeout after the dest chain's block time in nanoseconds since the epoch passes timeoutTimestamp. If set to 0 we set it to type(uint64).max. */ function _sendPacket( bytes32 destinationChainIdentifier, @@ -145,11 +168,14 @@ contract IncentivizedPolymerEscrow is APolymerEscrow, IbcReceiverBase, IbcReceiv bytes memory message, uint64 deadline ) internal override returns (uint128 costOfsendPacketInNativeToken) { - // If timeoutTimestamp is set to 0, set it to maximum. This does not really apply to Polymer since it is an onRecv implementation - // but it should still conform to the general spec of Generalised Incentives. + // If timeoutTimestamp is set to 0, set it to maximum. uint64 timeoutTimestamp = deadline > 0 ? deadline : type(uint64).max; - dispatcher.sendPacket(destinationChainIdentifier, message, timeoutTimestamp); + dispatcher.sendPacket( + destinationChainIdentifier, + bytes.concat(ADDRESS_THIS, message), + timeoutTimestamp + ); return 0; } }