Skip to content

Commit

Permalink
feat: store own address inside vIBC packet to do address checks easier
Browse files Browse the repository at this point in the history
  • Loading branch information
reednaa committed Mar 8, 2024
1 parent b129bd8 commit 9d62152
Showing 1 changed file with 75 additions and 49 deletions.
124 changes: 75 additions & 49 deletions src/apps/polymer/vIBCEscrow.sol
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -108,22 +127,26 @@ 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
});
_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(
Expand All @@ -132,24 +155,27 @@ 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,
bytes memory /* destinationImplementation */,
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;
}
}

0 comments on commit 9d62152

Please sign in to comment.