Skip to content

Commit

Permalink
feat: init for vIBC escrow based on Universal escrows
Browse files Browse the repository at this point in the history
  • Loading branch information
reednaa committed Mar 7, 2024
1 parent 4abd57c commit b129bd8
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 77 deletions.
6 changes: 3 additions & 3 deletions script/Deploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { BaseMultiChainDeployer} from "./BaseMultiChainDeployer.s.sol";
// Import all the Apps for deployment here.
import { IncentivizedMockEscrow } from "../src/apps/mock/IncentivizedMockEscrow.sol";
import { IncentivizedWormholeEscrow } from "../src/apps/wormhole/IncentivizedWormholeEscrow.sol";
import { IncentivizedPolymerEscrow } from "../src/apps/polymer/IncentivizedPolymerEscrow.sol";
import { UniversalPolymerEscrow } from "../src/apps/polymer/UniversalPolymerEscrow.sol";

contract DeployGeneralisedIncentives is BaseMultiChainDeployer {
using stdJson for string;
Expand Down Expand Up @@ -81,7 +81,7 @@ contract DeployGeneralisedIncentives is BaseMultiChainDeployer {

address expectedAddress = _getAddress(
abi.encodePacked(
type(IncentivizedPolymerEscrow).creationCode,
type(UniversalPolymerEscrow).creationCode,
abi.encode(vm.envAddress("SEND_LOST_GAS_TO"), polymerBridgeContract)
),
salt
Expand All @@ -90,7 +90,7 @@ contract DeployGeneralisedIncentives is BaseMultiChainDeployer {
// Check if it is already deployed. If it is, we skip.
if (expectedAddress.codehash != bytes32(0)) return expectedAddress;

IncentivizedPolymerEscrow polymerEscrow = new IncentivizedPolymerEscrow{salt: salt}(vm.envAddress("SEND_LOST_GAS_TO"), polymerBridgeContract);
UniversalPolymerEscrow polymerEscrow = new UniversalPolymerEscrow{salt: salt}(vm.envAddress("SEND_LOST_GAS_TO"), polymerBridgeContract);

incentive = address(polymerEscrow);
} else {
Expand Down
80 changes: 80 additions & 0 deletions src/apps/polymer/APolymerEscrow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {IncentivizedMessageEscrow} from "../../IncentivizedMessageEscrow.sol";
import "../../MessagePayload.sol";

/// @notice Scaffolding for Polymer Escrows
abstract contract APolymerEscrow is IncentivizedMessageEscrow {
error NotEnoughGasProvidedForVerification();
error NonVerifiableMessage();
error NotImplemented();

struct VerifiedMessageHashContext {
bytes32 chainIdentifier;
bytes implementationIdentifier;
}

mapping(bytes32 => VerifiedMessageHashContext) public isVerifiedMessageHash;

constructor(address sendLostGasTo)
IncentivizedMessageEscrow(sendLostGasTo)
{}

function estimateAdditionalCost() external pure returns (address asset, uint256 amount) {
asset = address(0);
amount = 0;
}

function _uniqueSourceIdentifier() internal view override returns (bytes32 sourceIdentifier) {
return sourceIdentifier = bytes32(block.chainid);
}

function _proofValidPeriod(bytes32 /* destinationIdentifier */ ) internal pure override returns (uint64) {
return 0;
}

/** @dev Disable processPacket */
function processPacket(
bytes calldata, /* messagingProtocolContext */
bytes calldata, /* rawMessage */
bytes32 /* feeRecipitent */
) external payable override {
revert NotImplemented();
}

/** @dev Disable reemitAckMessage. Polymer manages the entire flow, so we don't need to worry about expired proofs. */
function reemitAckMessage(
bytes32 /* sourceIdentifier */,
bytes calldata /* implementationIdentifier */,
bytes calldata /* receiveAckWithContext */
) external payable override {
revert NotImplemented();
}

/** @dev Disable timeoutMessage */
function timeoutMessage(
bytes32 /* sourceIdentifier */,
bytes calldata /* implementationIdentifier */,
uint256 /* originBlockNumber */,
bytes calldata /* message */
) external payable override {
revert NotImplemented();
}

/// @notice This function is used to allow acks to be executed twice (if the first one ran out of gas)
/// This is not intended to allow processPacket to work.
function _verifyPacket(bytes calldata, /* messagingProtocolContext */ bytes calldata _message)
internal
view
override
returns (bytes32 sourceIdentifier, bytes memory implementationIdentifier, bytes calldata message_)
{
sourceIdentifier = isVerifiedMessageHash[keccak256(_message)].chainIdentifier;
implementationIdentifier = isVerifiedMessageHash[keccak256(_message)].implementationIdentifier;

if (sourceIdentifier == bytes32(0)) revert NonVerifiableMessage();

message_ = _message;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {IncentivizedMessageEscrow} from "../../IncentivizedMessageEscrow.sol";
import {APolymerEscrow} from "./APolymerEscrow.sol";
import "../../MessagePayload.sol";

import {AckPacket} from "vibc-core-smart-contracts/libs/Ibc.sol";
Expand All @@ -13,83 +13,14 @@ import {
} from "vibc-core-smart-contracts/interfaces/IbcMiddleware.sol";

/// @notice Polymer implementation of the Generalised Incentives based on vIBC.
contract IncentivizedPolymerEscrow is IncentivizedMessageEscrow, IbcMwUser, IbcUniversalPacketReceiver {
error NotEnoughGasProvidedForVerification();
error NonVerifiableMessage();
error NotImplemented();

struct VerifiedMessageHashContext {
bytes32 chainIdentifier;
bytes implementationIdentifier;
}

mapping(bytes32 => VerifiedMessageHashContext) public isVerifiedMessageHash;
// packet will timeout if it's delivered on the destination chain after (this block time + _TIMEOUT_AFTER_BLOCK).
uint64 constant _TIMEOUT_AFTER_BLOCK = 1 days;
contract UniversalPolymerEscrow is APolymerEscrow, IbcMwUser, IbcUniversalPacketReceiver {

constructor(address sendLostGasTo, address messagingProtocol)
IncentivizedMessageEscrow(sendLostGasTo)
APolymerEscrow(sendLostGasTo)
IbcMwUser(messagingProtocol)
{}

function estimateAdditionalCost() external pure returns (address asset, uint256 amount) {
asset = address(0);
amount = 0;
}

function _uniqueSourceIdentifier() internal view override returns (bytes32 sourceIdentifier) {
return sourceIdentifier = bytes32(block.chainid);
}

function _proofValidPeriod(bytes32 /* destinationIdentifier */ ) internal pure override returns (uint64) {
return 0;
}

/** @dev Disable processPacket */
function processPacket(
bytes calldata, /* messagingProtocolContext */
bytes calldata, /* rawMessage */
bytes32 /* feeRecipitent */
) external payable override {
revert NotImplemented();
}

/** @dev Disable reemitAckMessage. Polymer manages the entire flow, so we don't need to worry about expired proofs. */
function reemitAckMessage(
bytes32 /* sourceIdentifier */,
bytes calldata /* implementationIdentifier */,
bytes calldata /* receiveAckWithContext */
) external payable override {
revert NotImplemented();
}

/** @dev Disable timeoutMessage */
function timeoutMessage(
bytes32 /* sourceIdentifier */,
bytes calldata /* implementationIdentifier */,
uint256 /* originBlockNumber */,
bytes calldata /* message */
) external payable override {
revert NotImplemented();
}

/// @notice This function is used to allow acks to be executed twice (if the first one ran out of gas)
/// This is not intended to allow processPacket to work.
function _verifyPacket(bytes calldata, /* messagingProtocolContext */ bytes calldata _message)
internal
view
override
returns (bytes32 sourceIdentifier, bytes memory implementationIdentifier, bytes calldata message_)
{
sourceIdentifier = isVerifiedMessageHash[keccak256(_message)].chainIdentifier;
implementationIdentifier = isVerifiedMessageHash[keccak256(_message)].implementationIdentifier;

if (sourceIdentifier == bytes32(0)) revert NonVerifiableMessage();

message_ = _message;
}

// packet.srcPortAddr is the IncentivizedPolymerEscrow address on the source chain.
// packet.srcPortAddr is the UniversalPolymerEscrow 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.
function onRecvUniversalPacket(bytes32 channelId, UniversalPacket calldata packet)
Expand Down Expand Up @@ -145,7 +76,7 @@ contract IncentivizedPolymerEscrow is IncentivizedMessageEscrow, IbcMwUser, IbcU
* 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 destinationImplementation IncentivizedPolymerEscrow address on the counterparty chain.
* @param destinationImplementation UniversalPolymerEscrow address on the counterparty chain.
* @param message packet payload
* @param deadline Packet will timeout after the dest chain's block time in nanoseconds since the epoch passes timeoutTimestamp.
*/
Expand Down
155 changes: 155 additions & 0 deletions src/apps/polymer/vIBCEscrow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

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.
contract IncentivizedPolymerEscrow is APolymerEscrow, IbcReceiverBase, IbcReceiver {

bytes32[] public connectedChannels;
string constant VERSION = '1.0';

constructor(address sendLostGasTo, address dispatcher)
APolymerEscrow(sendLostGasTo)
IbcReceiverBase(IbcDispatcher(dispatcher))
{}

// IBC callback functions

function onOpenIbcChannel(
string calldata version,
ChannelOrder /* */,
bool,
string[] calldata,
CounterParty calldata counterparty
) external view onlyIbcDispatcher returns (string memory selectedVersion) {
if (counterparty.channelId == bytes32(0)) {
// ChanOpenInit
require(
keccak256(abi.encodePacked(version)) == keccak256(abi.encodePacked(VERSION)),
'Unsupported version'
);
} else {
// ChanOpenTry
require(
keccak256(abi.encodePacked(counterparty.version)) == keccak256(abi.encodePacked(VERSION)),
'Unsupported version'
);
}
return VERSION;
}

function onConnectIbcChannel(
bytes32 channelId,
bytes32,
string calldata counterpartyVersion
) external onlyIbcDispatcher {
require(
keccak256(abi.encodePacked(counterpartyVersion)) == keccak256(abi.encodePacked(VERSION)),
'Unsupported version'
);
connectedChannels.push(channelId);
}

function onCloseIbcChannel(bytes32 channelId, string calldata, bytes32) external onlyIbcDispatcher {
// logic to determin if the channel should be closed
bool channelFound = false;
for (uint256 i = 0; i < connectedChannels.length; i++) {
if (connectedChannels[i] == channelId) {
delete connectedChannels[i];
channelFound = true;
break;
}
}
require(channelFound, 'Channel not found');
}

// 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.
function onRecvPacket(IbcPacket calldata packet)
external override
onlyIbcDispatcher
returns (AckPacket memory)
{
uint256 gasLimit = gasleft();
bytes32 feeRecipitent = bytes32(uint256(uint160(tx.origin)));

bytes memory sourceImplementationIdentifier = bytes.concat(hex""); // TODO packet.src.portId decode from a mapping

bytes memory receiveAck =
_handleMessage(packet.src.channelId, sourceImplementationIdentifier, packet.data, feeRecipitent, gasLimit);

// Send ack:
return AckPacket({success: true, data: receiveAck});
}

// The escrow manages acks, so any message can be directly provided to _handleAck.
function onAcknowledgementPacket(
IbcPacket calldata packet,
AckPacket calldata ack
)
external override
onlyIbcDispatcher
{
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.

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;
bytes32 messageIdentifier = bytes32(rawMessage[MESSAGE_IDENTIFIER_START:MESSAGE_IDENTIFIER_END]);
address fromApplication = address(uint160(bytes20(rawMessage[FROM_APPLICATION_START_EVM:FROM_APPLICATION_END])));
_handleTimeout(
packet.src.channelId, messageIdentifier, fromApplication, rawMessage[CTX0_MESSAGE_START:], feeRecipitent, gasLimit
);
}

/**
* @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.
*/
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.
uint64 timeoutTimestamp = deadline > 0 ? deadline : type(uint64).max;

dispatcher.sendPacket(destinationChainIdentifier, message, timeoutTimestamp);
return 0;
}
}

0 comments on commit b129bd8

Please sign in to comment.