Skip to content
18 changes: 14 additions & 4 deletions contracts/HubPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.0;

import "./MerkleLib.sol";
import "./HubPoolInterface.sol";
import "./interfaces/AdapterInterface.sol";
import "./interfaces/LpTokenFactoryInterface.sol";
import "./interfaces/WETH9.sol";
Expand All @@ -22,7 +23,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";

contract HubPool is Testable, Lockable, MultiCaller, Ownable {
contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable {
using SafeERC20 for IERC20;
using Address for address;

Expand All @@ -31,6 +32,7 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {
uint64 unclaimedPoolRebalanceLeafCount;
bytes32 poolRebalanceRoot;
bytes32 destinationDistributionRoot;
bytes32 slowRelayFulfillmentRoot;
uint256 claimedBitMap; // This is a 1D bitmap, with max size of 256 elements, limiting us to 256 chainsIds.
address proposer;
bool proposerBondRepaid;
Expand Down Expand Up @@ -115,6 +117,7 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {
uint256[] bundleEvaluationBlockNumbers,
bytes32 indexed poolRebalanceRoot,
bytes32 indexed destinationDistributionRoot,
bytes32 slowRelayFulfillmentRoot,
address indexed proposer
);
event RelayerRefundExecuted(
Expand Down Expand Up @@ -276,7 +279,8 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {
uint256[] memory bundleEvaluationBlockNumbers,
uint64 poolRebalanceLeafCount,
bytes32 poolRebalanceRoot,
bytes32 destinationDistributionRoot
bytes32 destinationDistributionRoot,
bytes32 slowRelayFulfillmentRoot
) public noActiveRequests {
require(poolRebalanceLeafCount > 0, "Bundle must have at least 1 leaf");

Expand All @@ -288,6 +292,7 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {
refundRequest.unclaimedPoolRebalanceLeafCount = poolRebalanceLeafCount;
refundRequest.poolRebalanceRoot = poolRebalanceRoot;
refundRequest.destinationDistributionRoot = destinationDistributionRoot;
refundRequest.slowRelayFulfillmentRoot = slowRelayFulfillmentRoot;
refundRequest.proposer = msg.sender;

// Pull bondAmount of bondToken from the caller.
Expand All @@ -299,11 +304,12 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {
bundleEvaluationBlockNumbers,
poolRebalanceRoot,
destinationDistributionRoot,
slowRelayFulfillmentRoot,
msg.sender
);
}

function executeRelayerRefund(MerkleLib.PoolRebalance memory poolRebalanceLeaf, bytes32[] memory proof) public {
function executeRelayerRefund(PoolRebalanceLeaf memory poolRebalanceLeaf, bytes32[] memory proof) public {
require(getCurrentTime() >= refundRequest.requestExpirationTimestamp, "Not passed liveness");

// Verify the leafId in the poolRebalanceLeaf has not yet been claimed.
Expand Down Expand Up @@ -508,7 +514,11 @@ contract HubPool is Testable, Lockable, MultiCaller, Ownable {
AdapterInterface adapter = crossChainContracts[chainId].adapter;
adapter.relayMessage(
crossChainContracts[chainId].spokePool, // target. This should be the spokePool on the L2.
abi.encodeWithSignature("initializeRelayerRefund(bytes32)", refundRequest.destinationDistributionRoot) // message
abi.encodeWithSignature(
"initializeRelayerRefund(bytes32,bytes32)",
refundRequest.destinationDistributionRoot,
refundRequest.slowRelayFulfillmentRoot
) // message
);
}

Expand Down
72 changes: 72 additions & 0 deletions contracts/HubPoolInterface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/AdapterInterface.sol";

interface HubPoolInterface {
struct PoolRebalanceLeaf {
// Used as the index in the bitmap to track whether this leaf has been executed or not.
uint256 leafId;
// This is used to know which chain to send cross-chain transactions to (and which SpokePool to sent to).
uint256 chainId;
// The following arrays are required to be the same length. They are parallel arrays for the given chainId and should be ordered by the `l1Tokens` field.
// All whitelisted tokens with nonzero relays on this chain in this bundle in the order of whitelisting.
address[] l1Tokens;
uint256[] bundleLpFees; // Total LP fee amount per token in this bundle, encompassing all associated bundled relays.
// This array is grouped with the two above, and it represents the amount to send or request back from the
// SpokePool. If positive, the pool will pay the SpokePool. If negative the SpokePool will pay the HubPool.
// There can be arbitrarily complex rebalancing rules defined offchain. This number is only nonzero
// when the rules indicate that a rebalancing action should occur. When a rebalance does not occur,
// runningBalances for this token should change by the total relays - deposits in this bundle. When a rebalance
// does occur, runningBalances should be set to zero for this token and the netSendAmounts should be set to the
// previous runningBalances + relays - deposits in this bundle.
int256[] netSendAmounts;
// This is only here to be emitted in an event to track a running unpaid balance between the L2 pool and the L1 pool.
// A positive number indicates that the HubPool owes the SpokePool funds. A negative number indicates that the
// SpokePool owes the HubPool funds. See the comment above for the dynamics of this and netSendAmounts
int256[] runningBalances;
}

function setBond(IERC20 newBondToken, uint256 newBondAmount) external;

function setCrossChainContracts(
uint256 l2ChainId,
address adapter,
address spokePool
) external;

function whitelistRoute(
uint256 destinationChainId,
address originToken,
address destinationToken
) external;

function enableL1TokenForLiquidityProvision(address l1Token, bool isWeth) external;

function disableL1TokenForLiquidityProvision(address l1Token) external;

function addLiquidity(address l1Token, uint256 l1TokenAmount) external payable;

function removeLiquidity(
address l1Token,
uint256 lpTokenAmount,
bool sendEth
) external;

function exchangeRateCurrent(address l1Token) external returns (uint256);

function liquidityUtilizationPostRelay(address token, uint256 relayedAmount) external returns (uint256);

function initiateRelayerRefund(
uint256[] memory bundleEvaluationBlockNumbers,
uint64 poolRebalanceLeafCount,
bytes32 poolRebalanceRoot,
bytes32 destinationDistributionRoot,
bytes32 slowRelayFulfillmentRoot
) external;

function executeRelayerRefund(PoolRebalanceLeaf memory poolRebalanceLeaf, bytes32[] memory proof) external;

function disputeRelayerRefund() external;
}
65 changes: 18 additions & 47 deletions contracts/MerkleLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,13 @@
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "./SpokePoolInterface.sol";
import "./HubPoolInterface.sol";

/**
* @notice Library to help with merkle roots, proofs, and claims.
*/
library MerkleLib {
// TODO: some of these data structures can be moved out if convenient.
Copy link
Member

Choose a reason for hiding this comment

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

+1 moving these to the spoke/hub pool interface i think that makes a lot of sense

// This data structure is used in the settlement process on L1.
// Each PoolRebalance structure is responsible for balancing a single chain's SpokePool across all tokens.
struct PoolRebalance {
// Used as the index in the bitmap to track whether this leaf has been executed or not.
uint256 leafId;
// This is used to know which chain to send cross-chain transactions to (and which SpokePool to sent to).
uint256 chainId;
// The following arrays are required to be the same length. They are parallel arrays for the given chainId and should be ordered by the `l1Tokens` field.
// All whitelisted tokens with nonzero relays on this chain in this bundle in the order of whitelisting.
address[] l1Tokens;
uint256[] bundleLpFees; // Total LP fee amount per token in this bundle, encompassing all associated bundled relays.
// This array is grouped with the two above, and it represents the amount to send or request back from the
// SpokePool. If positive, the pool will pay the SpokePool. If negative the SpokePool will pay the HubPool.
// There can be arbitrarily complex rebalancing rules defined offchain. This number is only nonzero
// when the rules indicate that a rebalancing action should occur. When a rebalance does not occur,
// runningBalances for this token should change by the total relays - deposits in this bundle. When a rebalance
// does occur, runningBalances should be set to zero for this token and the netSendAmounts should be set to the
// previous runningBalances + relays - deposits in this bundle.
int256[] netSendAmounts;
// This is only here to be emitted in an event to track a running unpaid balance between the L2 pool and the L1 pool.
// A positive number indicates that the HubPool owes the SpokePool funds. A negative number indicates that the

// SpokePool owes the HubPool funds. See the comment above for the dynamics of this and netSendAmounts
int256[] runningBalances;
}

// This leaf is meant to be decoded in the SpokePool in order to pay out individual relayers for this bundle.
struct DestinationDistribution {
// Used as the index in the bitmap to track whether this leaf has been executed or not.
uint256 leafId;
// Used to verify that this is being decoded on the correct chainId.
uint256 chainId;
// This is the amount to return to the HubPool. This occurs when there is a PoolRebalance netSendAmount that is
// negative. This is just that value inverted.
uint256 amountToReturn;
// The associated L2TokenAddress that these claims apply to.
address l2TokenAddress;
// These two arrays must be the same length and are parallel arrays. They should be order by refundAddresses.
// This array designates each address that must be refunded.
address[] refundAddresses;
// This array designates how much each of those addresses should be refunded.
uint256[] refundAmounts;
}

/**
* @notice Verifies that a repayment is contained within a merkle root.
* @param root the merkle root.
Expand All @@ -60,7 +17,7 @@ library MerkleLib {
*/
function verifyPoolRebalance(
bytes32 root,
PoolRebalance memory rebalance,
HubPoolInterface.PoolRebalanceLeaf memory rebalance,
bytes32[] memory proof
) public pure returns (bool) {
return MerkleProof.verify(proof, root, keccak256(abi.encode(rebalance)));
Expand All @@ -74,12 +31,26 @@ library MerkleLib {
*/
function verifyRelayerDistribution(
bytes32 root,
DestinationDistribution memory distribution,
SpokePoolInterface.DestinationDistributionLeaf memory distribution,
bytes32[] memory proof
) public pure returns (bool) {
return MerkleProof.verify(proof, root, keccak256(abi.encode(distribution)));
}

/**
* @notice Verifies that a distribution is contained within a merkle root.
* @param root the merkle root.
* @param slowRelayFulfillment the relayData fulfullment struct.
* @param proof the merkle proof.
*/
function verifySlowRelayFulfillment(
bytes32 root,
SpokePoolInterface.RelayData memory slowRelayFulfillment,
bytes32[] memory proof
) public pure returns (bool) {
return MerkleProof.verify(proof, root, keccak256(abi.encode(slowRelayFulfillment)));
}

// The following functions are primarily copied from
// https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol with minor changes.

Expand Down
6 changes: 3 additions & 3 deletions contracts/Optimism_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,16 @@ contract Optimism_SpokePool is CrossDomainEnabled, SpokePoolInterface, SpokePool
_setDepositQuoteTimeBuffer(buffer);
}

function initializeRelayerRefund(bytes32 relayerRepaymentDistributionProof)
function initializeRelayerRefund(bytes32 relayerRepaymentDistributionRoot, bytes32 slowRelayRoot)
public
override
onlyFromCrossDomainAccount(crossDomainAdmin)
nonReentrant
{
_initializeRelayerRefund(relayerRepaymentDistributionProof);
_initializeRelayerRefund(relayerRepaymentDistributionRoot, slowRelayRoot);
}

function _bridgeTokensToHubPool(MerkleLib.DestinationDistribution memory distributionLeaf) internal override {
function _bridgeTokensToHubPool(DestinationDistributionLeaf memory distributionLeaf) internal override {
// TODO: Handle WETH token unwrapping
IL2ERC20Bridge(Lib_PredeployAddresses.L2_STANDARD_BRIDGE).withdrawTo(
distributionLeaf.l2TokenAddress, // _l2Token. Address of the L2 token to bridge over.
Expand Down
Loading