Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions contracts/Arbitrum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,27 +77,27 @@ contract Arbitrum_SpokePool is SpokePoolInterface, SpokePool {
_setDepositQuoteTimeBuffer(buffer);
}

function initializeRelayerRefund(bytes32 relayerRepaymentDistributionRoot, bytes32 slowRelayRoot)
function relayRootBundle(bytes32 relayerRefundRoot, bytes32 slowRelayFulfillmentRoot)
public
override
onlyFromCrossDomainAdmin
nonReentrant
{
_initializeRelayerRefund(relayerRepaymentDistributionRoot, slowRelayRoot);
_relayRootBundle(relayerRefundRoot, slowRelayFulfillmentRoot);
}

/**************************************
* INTERNAL FUNCTIONS *
**************************************/

function _bridgeTokensToHubPool(DestinationDistributionLeaf memory distributionLeaf) internal override {
function _bridgeTokensToHubPool(RelayerRefundLeaf memory relayerRefundLeaf) internal override {
StandardBridgeLike(l2GatewayRouter).outboundTransfer(
whitelistedTokens[distributionLeaf.l2TokenAddress], // _l1Token. Address of the L1 token to bridge over.
whitelistedTokens[relayerRefundLeaf.l2TokenAddress], // _l1Token. Address of the L1 token to bridge over.
hubPool, // _to. Withdraw, over the bridge, to the l1 hub pool contract.
distributionLeaf.amountToReturn, // _amount.
relayerRefundLeaf.amountToReturn, // _amount.
"" // _data. We don't need to send any data for the bridging action.
);
emit ArbitrumTokensBridged(address(0), hubPool, distributionLeaf.amountToReturn);
emit ArbitrumTokensBridged(address(0), hubPool, relayerRefundLeaf.amountToReturn);
}

function _setL2GatewayRouter(address _l2GatewayRouter) internal {
Expand Down
10 changes: 5 additions & 5 deletions contracts/Ethereum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ contract Ethereum_SpokePool is SpokePoolInterface, SpokePool, Ownable {

function setEnableRoute(
address originToken,
uint32 destinationChainId,
uint256 destinationChainId,
Copy link
Member Author

Choose a reason for hiding this comment

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

this should match the types of the other spoke pool interfaces

bool enable
) public override onlyOwner nonReentrant {
_setEnableRoute(originToken, destinationChainId, enable);
Expand All @@ -48,16 +48,16 @@ contract Ethereum_SpokePool is SpokePoolInterface, SpokePool, Ownable {
_setDepositQuoteTimeBuffer(buffer);
}

function initializeRelayerRefund(bytes32 relayerRepaymentDistributionRoot, bytes32 slowRelayRoot)
function relayRootBundle(bytes32 relayerRefundRoot, bytes32 slowRelayFulfillmentRoot)
public
override
onlyOwner
nonReentrant
{
_initializeRelayerRefund(relayerRepaymentDistributionRoot, slowRelayRoot);
_relayRootBundle(relayerRefundRoot, slowRelayFulfillmentRoot);
}

function _bridgeTokensToHubPool(DestinationDistributionLeaf memory distributionLeaf) internal override {
IERC20(distributionLeaf.l2TokenAddress).transfer(hubPool, distributionLeaf.amountToReturn);
function _bridgeTokensToHubPool(RelayerRefundLeaf memory relayerRefundLeaf) internal override {
IERC20(relayerRefundLeaf.l2TokenAddress).transfer(hubPool, relayerRefundLeaf.amountToReturn);
}
}
224 changes: 124 additions & 100 deletions contracts/HubPool.sol

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions contracts/HubPoolInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ interface HubPoolInterface {

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

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

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

function disputeRelayerRefund() external;
function disputeRootBundle() external;
}
10 changes: 5 additions & 5 deletions contracts/MerkleLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ library MerkleLib {
}

/**
* @notice Verifies that a distribution is contained within a merkle root.
* @notice Verifies that a relayer refund is contained within a merkle root.
* @param root the merkle root.
* @param distribution the distribution struct.
* @param refund the refund struct.
* @param proof the merkle proof.
*/
function verifyRelayerDistribution(
function verifyRelayerRefund(
bytes32 root,
SpokePoolInterface.DestinationDistributionLeaf memory distribution,
SpokePoolInterface.RelayerRefundLeaf memory refund,
bytes32[] memory proof
) public pure returns (bool) {
return MerkleProof.verify(proof, root, keccak256(abi.encode(distribution)));
return MerkleProof.verify(proof, root, keccak256(abi.encode(refund)));
}

/**
Expand Down
18 changes: 9 additions & 9 deletions contracts/Optimism_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ contract Optimism_SpokePool is CrossDomainEnabled, SpokePoolInterface, SpokePool
_setDepositQuoteTimeBuffer(buffer);
}

function initializeRelayerRefund(bytes32 relayerRepaymentDistributionRoot, bytes32 slowRelayRoot)
function relayRootBundle(bytes32 relayerRefundRoot, bytes32 slowRelayFulfillmentRoot)
public
override
onlyFromCrossDomainAccount(crossDomainAdmin)
nonReentrant
{
_initializeRelayerRefund(relayerRepaymentDistributionRoot, slowRelayRoot);
_relayRootBundle(relayerRefundRoot, slowRelayFulfillmentRoot);
}

/**************************************
Expand All @@ -93,21 +93,21 @@ contract Optimism_SpokePool is CrossDomainEnabled, SpokePoolInterface, SpokePool
emit SetL1Gas(l1Gas);
}

function _bridgeTokensToHubPool(DestinationDistributionLeaf memory distributionLeaf) internal override {
function _bridgeTokensToHubPool(RelayerRefundLeaf memory relayerRefundLeaf) internal override {
// If the token being bridged is WETH then we need to first unwrap it to ETH and then send ETH over the
// canonical bridge. On Optimism, this is address 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000.
if (distributionLeaf.l2TokenAddress == address(weth)) {
WETH9(distributionLeaf.l2TokenAddress).withdraw(distributionLeaf.amountToReturn); // Unwrap ETH.
distributionLeaf.l2TokenAddress = l2Eth; // Set the l2TokenAddress to ETH.
if (relayerRefundLeaf.l2TokenAddress == address(weth)) {
WETH9(relayerRefundLeaf.l2TokenAddress).withdraw(relayerRefundLeaf.amountToReturn); // Unwrap ETH.
relayerRefundLeaf.l2TokenAddress = l2Eth; // Set the l2TokenAddress to ETH.
}
IL2ERC20Bridge(Lib_PredeployAddresses.L2_STANDARD_BRIDGE).withdrawTo(
distributionLeaf.l2TokenAddress, // _l2Token. Address of the L2 token to bridge over.
relayerRefundLeaf.l2TokenAddress, // _l2Token. Address of the L2 token to bridge over.
hubPool, // _to. Withdraw, over the bridge, to the l1 pool contract.
distributionLeaf.amountToReturn, // _amount.
relayerRefundLeaf.amountToReturn, // _amount.
l1Gas, // _l1Gas. Unused, but included for potential forward compatibility considerations
"" // _data. We don't need to send any data for the bridging action.
);

emit OptimismTokensBridged(distributionLeaf.l2TokenAddress, hubPool, distributionLeaf.amountToReturn, l1Gas);
emit OptimismTokensBridged(relayerRefundLeaf.l2TokenAddress, hubPool, relayerRefundLeaf.amountToReturn, l1Gas);
}
}
128 changes: 56 additions & 72 deletions contracts/SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,16 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
// Origin token to destination token routings can be turned on or off.
mapping(address => mapping(uint256 => bool)) public enabledDepositRoutes;

struct RelayerRefund {
struct RootBundle {
// Merkle root of slow relays that were not fully filled and whose recipient is still owed funds from the LP pool.
bytes32 slowRelayFulfillmentRoot;
// Merkle root of relayer refunds.
bytes32 distributionRoot;
bytes32 relayerRefundRoot;
// This is a 2D bitmap tracking which leafs in the relayer refund root have been claimed, with max size of
// 256x256 leaves per root.
mapping(uint256 => uint256) claimedBitmap;
}
RelayerRefund[] public relayerRefunds;
RootBundle[] public rootBundles;

// Each relay is associated with the hash of parameters that uniquely identify the original deposit and a relay
// attempt for that deposit. The relay itself is just represented as the amount filled so far. The total amount to
Expand Down Expand Up @@ -100,7 +100,7 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
address depositor,
address recipient
);
event DistributeRelaySlow(
event ExecutedSlowRelayFulfillmentRoot(
bytes32 indexed relayHash,
uint256 totalRelayAmount,
uint256 totalFilledAmount,
Expand All @@ -114,16 +114,12 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
address depositor,
address recipient
);
event InitializedRelayerRefund(
uint32 indexed relayerRefundId,
bytes32 relayerRepaymentDistributionRoot,
bytes32 slowRelayFulfillmentRoot
);
event DistributedRelayerRefund(
event RelayedRootBundle(uint32 indexed rootBundleId, bytes32 relayerRefundRoot, bytes32 slowRelayFulfillmentRoot);
event ExecutedRelayerRefundRoot(
uint256 amountToReturn,
uint256 chainId,
uint256[] refundAmounts,
uint32 indexed relayerRefundId,
uint32 indexed rootBundleId,
uint32 indexed leafId,
address l2TokenAddress,
address[] refundAddresses,
Expand Down Expand Up @@ -337,7 +333,10 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
_emitFillRelay(relayHash, fillAmountPreFees, repaymentChain, newRelayerFeePct, relayData);
}

function distributeRelaySlow(
/**************************************
* DATA WORKER FUNCTIONS *
**************************************/
function executeSlowRelayFulfillmentRoot(
address depositor,
address recipient,
address destinationToken,
Expand All @@ -346,7 +345,7 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
uint64 realizedLpFeePct,
uint64 relayerFeePct,
uint32 depositId,
uint32 relayerRefundId,
uint32 rootBundleId,
bytes32[] memory proof
) public nonReentrant {
RelayData memory relayData = RelayData({
Expand All @@ -361,11 +360,7 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
});

require(
MerkleLib.verifySlowRelayFulfillment(
relayerRefunds[relayerRefundId].slowRelayFulfillmentRoot,
relayData,
proof
),
MerkleLib.verifySlowRelayFulfillment(rootBundles[rootBundleId].slowRelayFulfillmentRoot, relayData, proof),
"Invalid proof"
);

Expand All @@ -375,67 +370,60 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
// funds in all cases.
uint256 fillAmountPreFees = _fillRelay(relayHash, relayData, relayData.relayAmount, relayerFeePct, true);

_emitDistributeRelaySlow(relayHash, fillAmountPreFees, relayData);
_emitExecutedSlowRelayFulfillmentRoot(relayHash, fillAmountPreFees, relayData);
}

/**************************************
* DATA WORKER FUNCTIONS *
**************************************/
// Call this method to execute a leaf within the `distributionRoot` stored on this contract. Caller must include a
// valid `inclusionProof` to verify that the leaf is contained within the root. The `relayerRefundId` is the index
// of the specific distribution root containing the passed in leaf.
function distributeRelayerRefund(
uint32 relayerRefundId,
SpokePoolInterface.DestinationDistributionLeaf memory distributionLeaf,
function executeRelayerRefundRoot(
uint32 rootBundleId,
SpokePoolInterface.RelayerRefundLeaf memory relayerRefundLeaf,
bytes32[] memory proof
) public override nonReentrant {
) public nonReentrant {
// Check integrity of leaf structure:
require(distributionLeaf.chainId == chainId(), "Invalid chainId");
require(distributionLeaf.refundAddresses.length == distributionLeaf.refundAmounts.length, "invalid leaf");
require(relayerRefundLeaf.chainId == chainId(), "Invalid chainId");
require(relayerRefundLeaf.refundAddresses.length == relayerRefundLeaf.refundAmounts.length, "invalid leaf");

// Grab distribution root stored at `relayerRefundId`.
RelayerRefund storage refund = relayerRefunds[relayerRefundId];
RootBundle storage rootBundle = rootBundles[rootBundleId];

// Check that `inclusionProof` proves that `distributionLeaf` is contained within the distribution root.
// Note: This should revert if the `distributionRoot` is uninitialized.
require(MerkleLib.verifyRelayerDistribution(refund.distributionRoot, distributionLeaf, proof), "Bad Proof");
// Check that `inclusionProof` proves that `relayerRefundLeaf` is contained within the relayer refund root.
// Note: This should revert if the `relayerRefundRoot` is uninitialized.
require(MerkleLib.verifyRelayerRefund(rootBundle.relayerRefundRoot, relayerRefundLeaf, proof), "Bad Proof");

// Verify the leafId in the leaf has not yet been claimed.
require(!MerkleLib.isClaimed(refund.claimedBitmap, distributionLeaf.leafId), "Already claimed");
require(!MerkleLib.isClaimed(rootBundle.claimedBitmap, relayerRefundLeaf.leafId), "Already claimed");

// Set leaf as claimed in bitmap.
MerkleLib.setClaimed(refund.claimedBitmap, distributionLeaf.leafId);
MerkleLib.setClaimed(rootBundle.claimedBitmap, relayerRefundLeaf.leafId);

// For each relayerRefundAddress in relayerRefundAddresses, send the associated refundAmount for the L2 token address.
// Send each relayer refund address the associated refundAmount for the L2 token address.
// Note: Even if the L2 token is not enabled on this spoke pool, we should still refund relayers.
for (uint32 i = 0; i < distributionLeaf.refundAmounts.length; i++) {
uint256 amount = distributionLeaf.refundAmounts[i];
for (uint32 i = 0; i < relayerRefundLeaf.refundAmounts.length; i++) {
uint256 amount = relayerRefundLeaf.refundAmounts[i];
if (amount > 0)
IERC20(distributionLeaf.l2TokenAddress).safeTransfer(distributionLeaf.refundAddresses[i], amount);
IERC20(relayerRefundLeaf.l2TokenAddress).safeTransfer(relayerRefundLeaf.refundAddresses[i], amount);
}

// If `distributionLeaf.amountToReturn` is positive, then send L2 --> L1 message to bridge tokens back via
// If leaf's `amountToReturn` is positive, then send L2 --> L1 message to bridge tokens back via
// chain-specific bridging method.
if (distributionLeaf.amountToReturn > 0) {
_bridgeTokensToHubPool(distributionLeaf);
if (relayerRefundLeaf.amountToReturn > 0) {
_bridgeTokensToHubPool(relayerRefundLeaf);

emit TokensBridged(
distributionLeaf.amountToReturn,
distributionLeaf.chainId,
distributionLeaf.leafId,
distributionLeaf.l2TokenAddress,
relayerRefundLeaf.amountToReturn,
relayerRefundLeaf.chainId,
relayerRefundLeaf.leafId,
relayerRefundLeaf.l2TokenAddress,
msg.sender
);
}

emit DistributedRelayerRefund(
distributionLeaf.amountToReturn,
distributionLeaf.chainId,
distributionLeaf.refundAmounts,
relayerRefundId,
distributionLeaf.leafId,
distributionLeaf.l2TokenAddress,
distributionLeaf.refundAddresses,
emit ExecutedRelayerRefundRoot(
relayerRefundLeaf.amountToReturn,
relayerRefundLeaf.chainId,
relayerRefundLeaf.refundAmounts,
rootBundleId,
relayerRefundLeaf.leafId,
relayerRefundLeaf.l2TokenAddress,
relayerRefundLeaf.refundAddresses,
msg.sender
);
}
Expand All @@ -452,9 +440,7 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
* INTERNAL FUNCTIONS *
**************************************/

function _bridgeTokensToHubPool(SpokePoolInterface.DestinationDistributionLeaf memory distributionLeaf)
internal
virtual;
function _bridgeTokensToHubPool(SpokePoolInterface.RelayerRefundLeaf memory relayerRefundLeaf) internal virtual;

function _computeAmountPreFees(uint256 amount, uint64 feesPct) private pure returns (uint256) {
return (1e18 * amount) / (1e18 - feesPct);
Expand All @@ -479,19 +465,17 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
}
}

// This internal method should be called by an external "initializeRelayerRefund" function that validates the
// This internal method should be called by an external "relayRootBundle" function that validates the
// cross domain sender is the HubPool. This validation step differs for each L2, which is why the implementation
// specifics are left to the implementor of this abstract contract.
// Once this method is executed and a distribution root is stored in this contract, then `distributeRelayerRefund`
// Once this method is executed and a distribution root is stored in this contract, then `distributeRootBundle`
// can be called to execute each leaf in the root.
function _initializeRelayerRefund(bytes32 relayerRepaymentDistributionRoot, bytes32 slowRelayFulfillmentRoot)
internal
{
uint32 relayerRefundId = uint32(relayerRefunds.length);
RelayerRefund storage relayerRefund = relayerRefunds.push();
relayerRefund.distributionRoot = relayerRepaymentDistributionRoot;
relayerRefund.slowRelayFulfillmentRoot = slowRelayFulfillmentRoot;
emit InitializedRelayerRefund(relayerRefundId, relayerRepaymentDistributionRoot, slowRelayFulfillmentRoot);
function _relayRootBundle(bytes32 relayerRefundRoot, bytes32 slowRelayFulfillmentRoot) internal {
uint32 rootBundleId = uint32(rootBundles.length);
RootBundle storage rootBundle = rootBundles.push();
rootBundle.relayerRefundRoot = relayerRefundRoot;
rootBundle.slowRelayFulfillmentRoot = slowRelayFulfillmentRoot;
emit RelayedRootBundle(rootBundleId, relayerRefundRoot, slowRelayFulfillmentRoot);
}

function _fillRelay(
Expand Down Expand Up @@ -570,12 +554,12 @@ abstract contract SpokePool is SpokePoolInterface, Testable, Lockable, MultiCall
);
}

function _emitDistributeRelaySlow(
function _emitExecutedSlowRelayFulfillmentRoot(
bytes32 relayHash,
uint256 fillAmount,
RelayData memory relayData
) internal {
emit DistributeRelaySlow(
emit ExecutedSlowRelayFulfillmentRoot(
relayHash,
relayData.relayAmount,
relayFills[relayHash],
Expand Down
Loading