diff --git a/contracts/Arbitrum_SpokePool.sol b/contracts/Arbitrum_SpokePool.sol index ac9d096e0..2616437e0 100644 --- a/contracts/Arbitrum_SpokePool.sol +++ b/contracts/Arbitrum_SpokePool.sol @@ -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 { diff --git a/contracts/Ethereum_SpokePool.sol b/contracts/Ethereum_SpokePool.sol index 86bcdc317..359c32bf5 100644 --- a/contracts/Ethereum_SpokePool.sol +++ b/contracts/Ethereum_SpokePool.sol @@ -38,7 +38,7 @@ contract Ethereum_SpokePool is SpokePoolInterface, SpokePool, Ownable { function setEnableRoute( address originToken, - uint32 destinationChainId, + uint256 destinationChainId, bool enable ) public override onlyOwner nonReentrant { _setEnableRoute(originToken, destinationChainId, enable); @@ -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); } } diff --git a/contracts/HubPool.sol b/contracts/HubPool.sol index 95913e579..1163a73e1 100644 --- a/contracts/HubPool.sol +++ b/contracts/HubPool.sol @@ -28,35 +28,29 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { using SafeERC20 for IERC20; using Address for address; - struct RefundRequest { - uint256 claimedBitMap; // This is a 1D bitmap, with max size of 256 elements, limiting us to 256 chainsIds. - uint32 requestExpirationTimestamp; + // A data worker can optimistically store several merkle roots on this contract by staking a bond and calling + // proposeRootBundle. By staking a bond, the data worker is alleging that the merkle roots all + // contain valid leaves that can be executed later to: + // - Send funds from this contract to a SpokePool or vice versa + // - Send funds from a SpokePool to Relayer as a refund for a relayed deposit + // - Send funds from a SpokePool to a deposit recipient to fulfill a "slow" relay + // Anyone can dispute this struct if the merkle roots contain invalid leaves before the + // `requestExpirationTimestamp`. Once the expiration timestamp is passed, `executeRootBundle` to execute a leaf + // from the `poolRebalanceRoot` on this contract and it will simultaneously publish the `relayerRefundRoot` and + // `slowRelayFulfillmentRoot` to a SpokePool. The latter two roots, once published to the SpokePool, contain + // leaves that can be executed on the SpokePool to pay relayers or recipients. + struct RootBundle { + uint64 requestExpirationTimestamp; + uint64 unclaimedPoolRebalanceLeafCount; bytes32 poolRebalanceRoot; - bytes32 destinationDistributionRoot; + bytes32 relayerRefundRoot; bytes32 slowRelayFulfillmentRoot; + uint256 claimedBitMap; // This is a 1D bitmap, with max size of 256 elements, limiting us to 256 chainsIds. address proposer; bool proposerBondRepaid; - uint8 unclaimedPoolRebalanceLeafCount; } - RefundRequest public refundRequest; - - // When bundles are disputed a price request is enqueued with the DVM to resolve the resolution. - bytes32 public identifier = "IS_ACROSS_V2_BUNDLE_VALID"; - - // The computed bond amount as the UMA Store's final fee multiplied by the bondTokenFinalFeeMultiplier. - uint256 public bondAmount; - - // Each refund proposal must stay in liveness for this period of time before it can be considered finalized. It can - // be disputed only during this period of time. Defaults to 2 hours, like the rest of the UMA ecosystem. - uint32 public refundProposalLiveness = 7200; - - // Interest rate payment that scales the amount of pending fees per second paid to LPs. 0.0000015e18 will pay out - // the full amount of fees entitled to LPs in ~ 7.72 days, just over the standard L2 7 day liveness. - uint64 public lpFeeRatePerSecond = 1500000000000; - - // Percentage of lpFees that are captured by the protocol and claimable by the protocolFeeCaptureAddress. - uint64 public protocolFeeCapturePct; + RootBundle public rootBundleProposal; // Whitelist of origin token to destination token routings to be used by off-chain agents. The notion of a route // does not need to include L1; it can store L2->L2 routes i.e USDC on Arbitrum -> USDC on Optimism as a "route". @@ -64,12 +58,12 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { // Mapping of L1TokenAddress to the associated Pool information. struct PooledToken { + address lpToken; + bool isEnabled; + uint32 lastLpFeeUpdate; int256 utilizedReserves; uint256 liquidReserves; uint256 undistributedLpFees; - uint32 lastLpFeeUpdate; - address lpToken; - bool isEnabled; } mapping(address => PooledToken) public pooledTokens; @@ -81,27 +75,44 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { mapping(uint256 => CrossChainContract) public crossChainContracts; // Mapping of chainId to the associated adapter and spokePool contracts. - mapping(address => uint256) public unclaimedAccumulatedProtocolFees; - WETH9 public weth; LpTokenFactoryInterface public lpTokenFactory; FinderInterface public finder; + // When bundles are disputed a price request is enqueued with the DVM to resolve the resolution. + bytes32 public identifier = "IS_ACROSS_V2_BUNDLE_VALID"; + + // Interest rate payment that scales the amount of pending fees per second paid to LPs. 0.0000015e18 will pay out + // the full amount of fees entitled to LPs in ~ 7.72 days, just over the standard L2 7 day liveness. + uint256 public lpFeeRatePerSecond = 1500000000000; + + mapping(address => uint256) public unclaimedAccumulatedProtocolFees; + // Address that captures protocol fees. Accumulated protocol fees can be claimed by this address. address public protocolFeeCaptureAddress; + // Percentage of lpFees that are captured by the protocol and claimable by the protocolFeeCaptureAddress. + uint256 public protocolFeeCapturePct; + // Token used to bond the data worker for proposing relayer refund bundles. IERC20 public bondToken; - event ProtocolFeeCaptureSet(address indexed newProtocolFeeCaptureAddress, uint64 indexed newProtocolFeeCapturePct); + // The computed bond amount as the UMA Store's final fee multiplied by the bondTokenFinalFeeMultiplier. + uint256 public bondAmount; + + // Each root bundle proposal must stay in liveness for this period of time before it can be considered finalized. + // It can be disputed only during this period of time. Defaults to 2 hours, like the rest of the UMA ecosystem. + uint64 public rootBundleProposalLiveness = 7200; + + event ProtocolFeeCaptureSet(address indexed newProtocolFeeCaptureAddress, uint256 indexed newProtocolFeeCapturePct); event ProtocolFeesCapturedClaimed(address indexed l1Token, uint256 indexed accumulatedFees); event BondSet(address indexed newBondToken, uint256 newBondAmount); - event RefundProposalLivenessSet(uint32 newRefundProposalLiveness); + event RootBundleProposalLivenessSet(uint256 newProposalLiveness); event IdentifierSet(bytes32 newIdentifier); @@ -125,30 +136,30 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { ); event WhitelistRoute(uint256 destinationChainId, address originToken, address destinationToken); - event InitiateRefundRequested( + event ProposeRootBundle( + uint64 requestExpirationTimestamp, + uint64 unclaimedPoolRebalanceLeafCount, uint256[] bundleEvaluationBlockNumbers, - uint32 requestExpirationTimestamp, - uint8 unclaimedPoolRebalanceLeafCount, bytes32 indexed poolRebalanceRoot, - bytes32 indexed destinationDistributionRoot, + bytes32 indexed relayerRefundRoot, bytes32 slowRelayFulfillmentRoot, address indexed proposer ); - event RelayerRefundExecuted( + event RootBundleExecuted( + uint256 indexed leafId, uint256 indexed chainId, + address[] l1Token, uint256[] bundleLpFees, int256[] netSendAmount, int256[] runningBalance, - uint32 indexed leafId, - address[] l1Token, address indexed caller ); event SpokePoolAdminFunctionTriggered(uint256 indexed chainId, bytes message); - event RelayerRefundDisputed(address indexed disputer, uint32 requestTime, bytes disputedAncillaryData); + event RootBundleDisputed(address indexed disputer, uint256 requestTime, bytes disputedAncillaryData); modifier noActiveRequests() { - require(refundRequest.unclaimedPoolRebalanceLeafCount == 0, "Active request has unclaimed leafs"); + require(rootBundleProposal.unclaimedPoolRebalanceLeafCount == 0, "proposal has unclaimed leafs"); _; } @@ -174,7 +185,7 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { _relaySpokePoolAdminFunction(chainId, functionData); } - function setProtocolFeeCapture(address newProtocolFeeCaptureAddress, uint64 newProtocolFeeCapturePct) + function setProtocolFeeCapture(address newProtocolFeeCaptureAddress, uint256 newProtocolFeeCapturePct) public onlyOwner { @@ -190,9 +201,9 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { emit BondSet(address(newBondToken), newBondAmount); } - function setRefundProposalLiveness(uint32 newRefundProposalLiveness) public onlyOwner { - refundProposalLiveness = newRefundProposalLiveness; - emit RefundProposalLivenessSet(newRefundProposalLiveness); + function setRootBundleProposalLiveness(uint64 newProposalLiveness) public onlyOwner { + rootBundleProposalLiveness = newProposalLiveness; + emit RootBundleProposalLivenessSet(newProposalLiveness); } function setIdentifier(bytes32 newIdentifier) public onlyOwner { @@ -219,7 +230,7 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { address destinationToken ) public onlyOwner { whitelistedRoutes[originToken][destinationChainId] = destinationToken; - relaySpokePoolAdminFunction( + _relaySpokePoolAdminFunction( destinationChainId, abi.encodeWithSignature("setEnableRoute(address,uint256,bool)", originToken, destinationChainId, true) ); @@ -308,60 +319,63 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { * DATA WORKER FUNCTIONS * *************************************************/ - // After initiateRelayerRefund is called, if the any props are wrong then this proposal can be challenged. Once the - // challenge period passes, then the roots are no longer disputable, and only executeRelayerRefund can be called and - // initiateRelayerRefund can't be called again until all leafs are executed. - function initiateRelayerRefund( + // After proposeRootBundle is called, if the any props are wrong then this proposal can be challenged. Once the + // challenge period passes, then the roots are no longer disputable, and only executeRootBundle can be called and + // this can't be called again until all leafs are executed. + function proposeRootBundle( uint256[] memory bundleEvaluationBlockNumbers, uint8 poolRebalanceLeafCount, bytes32 poolRebalanceRoot, - bytes32 destinationDistributionRoot, + bytes32 relayerRefundRoot, bytes32 slowRelayFulfillmentRoot - ) public nonReentrant noActiveRequests { + ) public override nonReentrant noActiveRequests { require(poolRebalanceLeafCount > 0, "Bundle must have at least 1 leaf"); - uint32 requestExpirationTimestamp = uint32(getCurrentTime() + refundProposalLiveness); + uint64 requestExpirationTimestamp = uint64(getCurrentTime() + rootBundleProposalLiveness); - delete refundRequest; // Remove the existing information relating to the previous relayer refund request. + delete rootBundleProposal; // Only one bundle of roots can be executed at a time. - refundRequest.requestExpirationTimestamp = requestExpirationTimestamp; - refundRequest.unclaimedPoolRebalanceLeafCount = poolRebalanceLeafCount; - refundRequest.poolRebalanceRoot = poolRebalanceRoot; - refundRequest.destinationDistributionRoot = destinationDistributionRoot; - refundRequest.slowRelayFulfillmentRoot = slowRelayFulfillmentRoot; - refundRequest.proposer = msg.sender; + rootBundleProposal.requestExpirationTimestamp = requestExpirationTimestamp; + rootBundleProposal.unclaimedPoolRebalanceLeafCount = poolRebalanceLeafCount; + rootBundleProposal.poolRebalanceRoot = poolRebalanceRoot; + rootBundleProposal.relayerRefundRoot = relayerRefundRoot; + rootBundleProposal.slowRelayFulfillmentRoot = slowRelayFulfillmentRoot; + rootBundleProposal.proposer = msg.sender; // Pull bondAmount of bondToken from the caller. bondToken.safeTransferFrom(msg.sender, address(this), bondAmount); - emit InitiateRefundRequested( - bundleEvaluationBlockNumbers, + emit ProposeRootBundle( requestExpirationTimestamp, poolRebalanceLeafCount, + bundleEvaluationBlockNumbers, poolRebalanceRoot, - destinationDistributionRoot, + relayerRefundRoot, slowRelayFulfillmentRoot, msg.sender ); } - function executeRelayerRefund(PoolRebalanceLeaf memory poolRebalanceLeaf, bytes32[] memory proof) - public - nonReentrant - { - require(getCurrentTime() >= refundRequest.requestExpirationTimestamp, "Not passed liveness"); + function executeRootBundle(PoolRebalanceLeaf memory poolRebalanceLeaf, bytes32[] memory proof) public nonReentrant { + require(getCurrentTime() >= rootBundleProposal.requestExpirationTimestamp, "Not passed liveness"); // Verify the leafId in the poolRebalanceLeaf has not yet been claimed. - require(!MerkleLib.isClaimed1D(refundRequest.claimedBitMap, poolRebalanceLeaf.leafId), "Already claimed"); + require(!MerkleLib.isClaimed1D(rootBundleProposal.claimedBitMap, poolRebalanceLeaf.leafId), "Already claimed"); // Verify the props provided generate a leaf that, along with the proof, are included in the merkle root. - require(MerkleLib.verifyPoolRebalance(refundRequest.poolRebalanceRoot, poolRebalanceLeaf, proof), "Bad Proof"); + require( + MerkleLib.verifyPoolRebalance(rootBundleProposal.poolRebalanceRoot, poolRebalanceLeaf, proof), + "Bad Proof" + ); // Set the leafId in the claimed bitmap. - refundRequest.claimedBitMap = MerkleLib.setClaimed1D(refundRequest.claimedBitMap, poolRebalanceLeaf.leafId); + rootBundleProposal.claimedBitMap = MerkleLib.setClaimed1D( + rootBundleProposal.claimedBitMap, + poolRebalanceLeaf.leafId + ); // Decrement the unclaimedPoolRebalanceLeafCount. - refundRequest.unclaimedPoolRebalanceLeafCount--; + rootBundleProposal.unclaimedPoolRebalanceLeafCount--; _sendTokensToChainAndUpdatePooledTokenTrackers( poolRebalanceLeaf.chainId, @@ -369,30 +383,30 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { poolRebalanceLeaf.netSendAmounts, poolRebalanceLeaf.bundleLpFees ); - _executeRelayerRefundOnChain(poolRebalanceLeaf.chainId); + _relayRootBundleToSpokePool(poolRebalanceLeaf.chainId); // Transfer the bondAmount to back to the proposer, if this the last executed leaf. Only sending this once all // leafs have been executed acts to force the data worker to execute all bundles or they wont receive their bond. - if (refundRequest.unclaimedPoolRebalanceLeafCount == 0) - bondToken.safeTransfer(refundRequest.proposer, bondAmount); + if (rootBundleProposal.unclaimedPoolRebalanceLeafCount == 0) + bondToken.safeTransfer(rootBundleProposal.proposer, bondAmount); - emit RelayerRefundExecuted( + emit RootBundleExecuted( + poolRebalanceLeaf.leafId, poolRebalanceLeaf.chainId, + poolRebalanceLeaf.l1Tokens, poolRebalanceLeaf.bundleLpFees, poolRebalanceLeaf.netSendAmounts, poolRebalanceLeaf.runningBalances, - poolRebalanceLeaf.leafId, - poolRebalanceLeaf.l1Tokens, msg.sender ); } - function disputeRelayerRefund() public nonReentrant { - require(getCurrentTime() <= refundRequest.requestExpirationTimestamp, "Request passed liveness"); + function disputeRootBundle() public nonReentrant { + require(getCurrentTime() <= rootBundleProposal.requestExpirationTimestamp, "Request passed liveness"); // Request price from OO and dispute it. uint256 totalBond = _getBondTokenFinalFee() + bondAmount; - bytes memory requestAncillaryData = _getRefundProposalAncillaryData(); + bytes memory requestAncillaryData = getRootBundleProposalAncillaryData(); bondToken.safeTransferFrom(msg.sender, address(this), totalBond); // This contract needs to approve totalBond*2 against the OO contract. (for the price request and dispute). bondToken.safeApprove(address(_getOptimisticOracle()), totalBond * 2); @@ -401,31 +415,31 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { uint32(getCurrentTime()), requestAncillaryData, bondToken, - // Set reward to 0, since we'll settle proposer reward payouts directly from this contract after a relay + // Set reward to 0, since we'll settle proposer reward payouts directly from this contract after a root // proposal has passed the challenge period. 0, // Set the Optimistic oracle proposer bond for the price request. bondAmount, // Set the Optimistic oracle liveness for the price request. - refundProposalLiveness, - refundRequest.proposer, + rootBundleProposalLiveness, + rootBundleProposal.proposer, // Canonical value representing "True"; i.e. the proposed relay is valid. int256(1e18) ); // Dispute the request that we just sent. SkinnyOptimisticOracleInterface.Request memory ooPriceRequest = SkinnyOptimisticOracleInterface.Request({ - proposer: refundRequest.proposer, + proposer: rootBundleProposal.proposer, disputer: address(0), currency: bondToken, settled: false, proposedPrice: int256(1e18), resolvedPrice: 0, - expirationTime: getCurrentTime() + refundProposalLiveness, + expirationTime: getCurrentTime() + rootBundleProposalLiveness, reward: 0, finalFee: _getBondTokenFinalFee(), bond: bondAmount, - customLiveness: refundProposalLiveness + customLiveness: rootBundleProposalLiveness }); _getOptimisticOracle().disputePriceFor( @@ -437,10 +451,11 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { address(this) ); - emit RelayerRefundDisputed(msg.sender, uint32(getCurrentTime()), requestAncillaryData); + emit RootBundleDisputed(msg.sender, getCurrentTime(), requestAncillaryData); - // Finally, delete the state pertaining to the active refundRequest. - delete refundRequest; + // Finally, delete the state pertaining to the active proposal so that another proposer can submit a new + // bundle of roots. + delete rootBundleProposal; } function claimProtocolFeesCaptured(address l1Token) public nonReentrant { @@ -449,30 +464,39 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { unclaimedAccumulatedProtocolFees[l1Token] = 0; } - function _getRefundProposalAncillaryData() public view returns (bytes memory ancillaryData) { + function getRootBundleProposalAncillaryData() public view returns (bytes memory ancillaryData) { ancillaryData = AncillaryData.appendKeyValueUint( "", "requestExpirationTimestamp", - refundRequest.requestExpirationTimestamp + rootBundleProposal.requestExpirationTimestamp ); ancillaryData = AncillaryData.appendKeyValueUint( ancillaryData, "unclaimedPoolRebalanceLeafCount", - refundRequest.unclaimedPoolRebalanceLeafCount + rootBundleProposal.unclaimedPoolRebalanceLeafCount ); ancillaryData = AncillaryData.appendKeyValueBytes32( ancillaryData, "poolRebalanceRoot", - refundRequest.poolRebalanceRoot + rootBundleProposal.poolRebalanceRoot ); ancillaryData = AncillaryData.appendKeyValueBytes32( ancillaryData, - "destinationDistributionRoot", - refundRequest.destinationDistributionRoot + "relayerRefundRoot", + rootBundleProposal.relayerRefundRoot + ); + ancillaryData = AncillaryData.appendKeyValueBytes32( + ancillaryData, + "slowRelayFulfillmentRoot", + rootBundleProposal.slowRelayFulfillmentRoot + ); + ancillaryData = AncillaryData.appendKeyValueUint( + ancillaryData, + "claimedBitMap", + rootBundleProposal.claimedBitMap ); - ancillaryData = AncillaryData.appendKeyValueUint(ancillaryData, "claimedBitMap", refundRequest.claimedBitMap); - ancillaryData = AncillaryData.appendKeyValueAddress(ancillaryData, "proposer", refundRequest.proposer); + ancillaryData = AncillaryData.appendKeyValueAddress(ancillaryData, "proposer", rootBundleProposal.proposer); } /************************************************* @@ -545,14 +569,14 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable { } } - function _executeRelayerRefundOnChain(uint256 chainId) internal { + function _relayRootBundleToSpokePool(uint256 chainId) internal { AdapterInterface adapter = crossChainContracts[chainId].adapter; adapter.relayMessage( crossChainContracts[chainId].spokePool, // target. This should be the spokePool on the L2. abi.encodeWithSignature( - "initializeRelayerRefund(bytes32,bytes32)", - refundRequest.destinationDistributionRoot, - refundRequest.slowRelayFulfillmentRoot + "relayRootBundle(bytes32,bytes32)", + rootBundleProposal.relayerRefundRoot, + rootBundleProposal.slowRelayFulfillmentRoot ) // message ); } diff --git a/contracts/HubPoolInterface.sol b/contracts/HubPoolInterface.sol index 50d2133eb..81496aeb5 100644 --- a/contracts/HubPoolInterface.sol +++ b/contracts/HubPoolInterface.sol @@ -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; } diff --git a/contracts/MerkleLib.sol b/contracts/MerkleLib.sol index 423ad3535..3c281504d 100644 --- a/contracts/MerkleLib.sol +++ b/contracts/MerkleLib.sol @@ -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))); } /** diff --git a/contracts/Optimism_SpokePool.sol b/contracts/Optimism_SpokePool.sol index d3da56984..d2715de0b 100644 --- a/contracts/Optimism_SpokePool.sol +++ b/contracts/Optimism_SpokePool.sol @@ -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); } /************************************** @@ -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); } } diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index e1a3db8eb..3e0370505 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -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 @@ -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, @@ -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, @@ -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, @@ -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({ @@ -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" ); @@ -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 ); } @@ -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); @@ -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( @@ -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], diff --git a/contracts/SpokePoolInterface.sol b/contracts/SpokePoolInterface.sol index 6444b1c8f..f2cce3758 100644 --- a/contracts/SpokePoolInterface.sol +++ b/contracts/SpokePoolInterface.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; interface SpokePoolInterface { // This leaf is meant to be decoded in the SpokePool in order to pay out individual relayers for this bundle. - struct DestinationDistributionLeaf { + struct RelayerRefundLeaf { // This is the amount to return to the HubPool. This occurs when there is a PoolRebalanceLeaf netSendAmount that is // negative. This is just that value inverted. uint256 amountToReturn; @@ -55,11 +55,5 @@ interface SpokePoolInterface { function setDepositQuoteTimeBuffer(uint32 buffer) external; - function initializeRelayerRefund(bytes32 relayerRepaymentDistributionRoot, bytes32 slowRelayRoot) external; - - function distributeRelayerRefund( - uint32 relayerRefundId, - DestinationDistributionLeaf memory distributionLeaf, - bytes32[] memory inclusionProof - ) external; + function relayRootBundle(bytes32 relayerRefundRoot, bytes32 slowRelayRoot) external; } diff --git a/contracts/test/MerkleLibTest.sol b/contracts/test/MerkleLibTest.sol index 4c473771b..de8c7e3aa 100644 --- a/contracts/test/MerkleLibTest.sol +++ b/contracts/test/MerkleLibTest.sol @@ -21,12 +21,12 @@ contract MerkleLibTest { return MerkleLib.verifyPoolRebalance(root, rebalance, proof); } - function verifyRelayerDistribution( + function verifyRelayerRefund( bytes32 root, - SpokePoolInterface.DestinationDistributionLeaf memory distribution, + SpokePoolInterface.RelayerRefundLeaf memory refund, bytes32[] memory proof ) public pure returns (bool) { - return MerkleLib.verifyRelayerDistribution(root, distribution, proof); + return MerkleLib.verifyRelayerRefund(root, refund, proof); } function verifySlowRelayFulfillment( diff --git a/contracts/test/MockSpokePool.sol b/contracts/test/MockSpokePool.sol index 87b7cf00f..934e072ba 100644 --- a/contracts/test/MockSpokePool.sol +++ b/contracts/test/MockSpokePool.sol @@ -36,12 +36,9 @@ contract MockSpokePool is SpokePoolInterface, SpokePool { _setDepositQuoteTimeBuffer(buffer); } - function initializeRelayerRefund(bytes32 relayerRepaymentDistributionRoot, bytes32 slowRelayFulfillmentRoot) - public - override - { - _initializeRelayerRefund(relayerRepaymentDistributionRoot, slowRelayFulfillmentRoot); + function relayRootBundle(bytes32 relayerRefundRoot, bytes32 slowRelayFulfillmentRoot) public override { + _relayRootBundle(relayerRefundRoot, slowRelayFulfillmentRoot); } - function _bridgeTokensToHubPool(DestinationDistributionLeaf memory distributionLeaf) internal override {} + function _bridgeTokensToHubPool(RelayerRefundLeaf memory relayerRefundLeaf) internal override {} } diff --git a/test/HubPool.Admin.ts b/test/HubPool.Admin.ts index 980058f90..e3ae7857a 100644 --- a/test/HubPool.Admin.ts +++ b/test/HubPool.Admin.ts @@ -56,7 +56,7 @@ describe("HubPool Admin functions", function () { it("Can not change the bond token and amount during a pending refund", async function () { await seedWallet(owner, [], weth, bondAmount); await weth.approve(hubPool.address, bondAmount); - await hubPool.initiateRelayerRefund([1, 2, 3], 5, mockTreeRoot, mockTreeRoot, mockSlowRelayFulfillmentRoot); - await expect(hubPool.setBond(usdc.address, "1")).to.be.revertedWith("Active request has unclaimed leafs"); + await hubPool.proposeRootBundle([1, 2, 3], 5, mockTreeRoot, mockTreeRoot, mockSlowRelayFulfillmentRoot); + await expect(hubPool.setBond(usdc.address, "1")).to.be.revertedWith("proposal has unclaimed leafs"); }); }); diff --git a/test/HubPool.RelayerDispute.ts b/test/HubPool.DisputeRootBundle.ts similarity index 66% rename from test/HubPool.RelayerDispute.ts rename to test/HubPool.DisputeRootBundle.ts index cd2c83bd3..51dcb3af8 100644 --- a/test/HubPool.RelayerDispute.ts +++ b/test/HubPool.DisputeRootBundle.ts @@ -6,7 +6,7 @@ import { hubPoolFixture, enableTokensForLP } from "./HubPool.Fixture"; let hubPool: Contract, weth: Contract, optimisticOracle: Contract; let owner: SignerWithAddress, dataWorker: SignerWithAddress, liquidityProvider: SignerWithAddress; -describe("HubPool Relayer Refund Dispute", function () { +describe("HubPool Root Bundle Dispute", function () { beforeEach(async function () { [owner, dataWorker, liquidityProvider] = await ethers.getSigners(); ({ weth, hubPool, optimisticOracle } = await hubPoolFixture()); @@ -18,31 +18,32 @@ describe("HubPool Relayer Refund Dispute", function () { await hubPool.connect(liquidityProvider).addLiquidity(weth.address, consts.amountToLp); }); - it("Dispute relayer refund correctly deletes the active request and enqueues a price request with the OO", async function () { + it("Dispute root bundle correctly deletes the active proposal and enqueues a price request with the OO", async function () { await weth.connect(dataWorker).approve(hubPool.address, consts.bondAmount.mul(10)); await hubPool .connect(dataWorker) - .initiateRelayerRefund( + .proposeRootBundle( consts.mockBundleEvaluationBlockNumbers, consts.mockPoolRebalanceLeafCount, consts.mockPoolRebalanceRoot, - consts.mockDestinationDistributionRoot, + consts.mockRelayerRefundRoot, consts.mockSlowRelayFulfillmentRoot ); - const preCallAncillaryData = await hubPool._getRefundProposalAncillaryData(); + const preCallAncillaryData = await hubPool.getRootBundleProposalAncillaryData(); - await hubPool.connect(dataWorker).disputeRelayerRefund(); + await hubPool.connect(dataWorker).disputeRootBundle(); // Data should be deleted from the contracts refundRequest struct. - const refundRequest = await hubPool.refundRequest(); - expect(refundRequest.requestExpirationTimestamp).to.equal(0); - expect(refundRequest.unclaimedPoolRebalanceLeafCount).to.equal(0); - expect(refundRequest.poolRebalanceRoot).to.equal(consts.zeroBytes32); - expect(refundRequest.destinationDistributionRoot).to.equal(consts.zeroBytes32); - expect(refundRequest.claimedBitMap).to.equal(0); // no claims yet so everything should be marked at 0. - expect(refundRequest.proposer).to.equal(consts.zeroAddress); - expect(refundRequest.proposerBondRepaid).to.equal(false); + const rootBundle = await hubPool.rootBundleProposal(); + expect(rootBundle.requestExpirationTimestamp).to.equal(0); + expect(rootBundle.unclaimedPoolRebalanceLeafCount).to.equal(0); + expect(rootBundle.poolRebalanceRoot).to.equal(consts.zeroBytes32); + expect(rootBundle.relayerRefundRoot).to.equal(consts.zeroBytes32); + expect(rootBundle.slowRelayFulfillmentRoot).to.equal(consts.zeroBytes32); + expect(rootBundle.claimedBitMap).to.equal(0); // no claims yet so everything should be marked at 0. + expect(rootBundle.proposer).to.equal(consts.zeroAddress); + expect(rootBundle.proposerBondRepaid).to.equal(false); const priceProposalEvent = (await optimisticOracle.queryFilter(optimisticOracle.filters.ProposePrice()))[0].args; @@ -56,7 +57,8 @@ describe("HubPool Relayer Refund Dispute", function () { ); expect(parsedAncillaryData?.unclaimedPoolRebalanceLeafCount).to.equal(consts.mockPoolRebalanceLeafCount); expect("0x" + parsedAncillaryData?.poolRebalanceRoot).to.equal(consts.mockPoolRebalanceRoot); - expect("0x" + parsedAncillaryData?.destinationDistributionRoot).to.equal(consts.mockDestinationDistributionRoot); + expect("0x" + parsedAncillaryData?.relayerRefundRoot).to.equal(consts.mockRelayerRefundRoot); + expect("0x" + parsedAncillaryData?.slowRelayFulfillmentRoot).to.equal(consts.mockSlowRelayFulfillmentRoot); expect(parsedAncillaryData?.claimedBitMap).to.equal(0); expect(ethers.utils.getAddress("0x" + parsedAncillaryData?.proposer)).to.equal(dataWorker.address); }); @@ -64,16 +66,16 @@ describe("HubPool Relayer Refund Dispute", function () { await weth.connect(dataWorker).approve(hubPool.address, consts.bondAmount.mul(10)); await hubPool .connect(dataWorker) - .initiateRelayerRefund( + .proposeRootBundle( consts.mockBundleEvaluationBlockNumbers, consts.mockPoolRebalanceLeafCount, consts.mockPoolRebalanceRoot, - consts.mockDestinationDistributionRoot, + consts.mockRelayerRefundRoot, consts.mockSlowRelayFulfillmentRoot ); await hubPool.setCurrentTime(Number(await hubPool.getCurrentTime()) + consts.refundProposalLiveness + 1); - await expect(hubPool.connect(dataWorker).disputeRelayerRefund()).to.be.revertedWith("Request passed liveness"); + await expect(hubPool.connect(dataWorker).disputeRootBundle()).to.be.revertedWith("Request passed liveness"); }); }); diff --git a/test/HubPool.RefundExecution.ts b/test/HubPool.ExecuteRootBundle.ts similarity index 79% rename from test/HubPool.RefundExecution.ts rename to test/HubPool.ExecuteRootBundle.ts index 6fff414f0..178a27d45 100644 --- a/test/HubPool.RefundExecution.ts +++ b/test/HubPool.ExecuteRootBundle.ts @@ -24,7 +24,7 @@ async function constructSimpleTree() { return { wethToSendToL2, daiToSend, leafs, tree }; } -describe("HubPool Relayer Refund Execution", function () { +describe("HubPool Root Bundle Execution", function () { beforeEach(async function () { [owner, dataWorker, liquidityProvider] = await ethers.getSigners(); ({ weth, dai, hubPool, mockAdapter, mockSpoke, timer, l2Weth, l2Dai } = await hubPoolFixture()); @@ -40,20 +40,20 @@ describe("HubPool Relayer Refund Execution", function () { await weth.connect(dataWorker).approve(hubPool.address, consts.bondAmount.mul(10)); }); - it("Execute relayer refund correctly produces the refund bundle call and sends cross-chain repayment actions", async function () { + it("Executing root bundle correctly produces the relay bundle call and sends repayment actions", async function () { const { wethToSendToL2, daiToSend, leafs, tree } = await constructSimpleTree(); - await hubPool.connect(dataWorker).initiateRelayerRefund( + await hubPool.connect(dataWorker).proposeRootBundle( [3117], // bundleEvaluationBlockNumbers used by bots to construct bundles. Length must equal the number of leafs. 1, // poolRebalanceLeafCount. There is exactly one leaf in the bundle (just sending WETH to one address). tree.getHexRoot(), // poolRebalanceRoot. Generated from the merkle tree constructed before. - consts.mockDestinationDistributionRoot, // destinationDistributionRoot. Not relevant for this test. - consts.mockSlowRelayFulfillmentRoot // Mock root because this isn't relevant for this test. + consts.mockRelayerRefundRoot, // Not relevant for this test. + consts.mockSlowRelayFulfillmentRoot // Not relevant for this test. ); // Advance time so the request can be executed and execute the request. await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); // Balances should have updated as expected. expect(await weth.balanceOf(hubPool.address)).to.equal(consts.amountToLp.sub(wethToSendToL2)); @@ -67,8 +67,8 @@ describe("HubPool Relayer Refund Execution", function () { // and 1 for the initiateRelayerRefund. expect(relayMessageEvents[relayMessageEvents.length - 1].args?.target).to.equal(mockSpoke.address); expect(relayMessageEvents[relayMessageEvents.length - 1].args?.message).to.equal( - mockSpoke.interface.encodeFunctionData("initializeRelayerRefund", [ - consts.mockDestinationDistributionRoot, + mockSpoke.interface.encodeFunctionData("relayRootBundle", [ + consts.mockRelayerRefundRoot, consts.mockSlowRelayFulfillmentRoot, ]) ); @@ -85,52 +85,70 @@ describe("HubPool Relayer Refund Execution", function () { expect(relayTokensEvents[1].args?.to).to.equal(mockSpoke.address); // Check the leaf count was decremented correctly. - expect((await hubPool.refundRequest()).unclaimedPoolRebalanceLeafCount).to.equal(0); + expect((await hubPool.rootBundleProposal()).unclaimedPoolRebalanceLeafCount).to.equal(0); }); it("Execution rejects leaf claim before liveness passed", async function () { const { leafs, tree } = await constructSimpleTree(); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle( + [3117], + 1, + tree.getHexRoot(), + consts.mockRelayerRefundRoot, + consts.mockSlowRelayFulfillmentRoot + ); // Set time 10 seconds before expiration. Should revert. await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness - 10); await expect( - hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])) + hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])) ).to.be.revertedWith("Not passed liveness"); // Set time after expiration. Should no longer revert. await timer.setCurrentTime(Number(await timer.getCurrentTime()) + 10); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); }); it("Execution rejects invalid leafs", async function () { const { leafs, tree } = await constructSimpleTree(); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle( + [3117], + 1, + tree.getHexRoot(), + consts.mockRelayerRefundRoot, + consts.mockSlowRelayFulfillmentRoot + ); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); // Take the valid root but change some element within it, such as the chainId. This will change the hash of the leaf // and as such the contract should reject it for not being included within the merkle tree for the valid proof. const badLeaf = { ...leafs[0], chainId: 13371 }; - await expect( - hubPool.connect(dataWorker).executeRelayerRefund(badLeaf, tree.getHexProof(leafs[0])) - ).to.be.revertedWith("Bad Proof"); + await expect(hubPool.connect(dataWorker).executeRootBundle(badLeaf, tree.getHexProof(leafs[0]))).to.be.revertedWith( + "Bad Proof" + ); }); it("Execution rejects double claimed leafs", async function () { const { leafs, tree } = await constructSimpleTree(); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle( + [3117], + 1, + tree.getHexRoot(), + consts.mockRelayerRefundRoot, + consts.mockSlowRelayFulfillmentRoot + ); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); // First claim should be fine. Second claim should be reverted as you cant double claim a leaf. - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); await expect( - hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])) + hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])) ).to.be.revertedWith("Already claimed"); }); }); diff --git a/test/HubPool.Fixture.ts b/test/HubPool.Fixture.ts index d741752c6..6afb078e0 100644 --- a/test/HubPool.Fixture.ts +++ b/test/HubPool.Fixture.ts @@ -37,7 +37,7 @@ export const hubPoolFixture = hre.deployments.createFixture(async ({ ethers }) = await getContractFactory("HubPool", { signer: signer, libraries: { MerkleLib: merkleLib.address } }) ).deploy(lpTokenFactory.address, parentFixture.finder.address, weth.address, parentFixture.timer.address); await hubPool.setBond(weth.address, bondAmount); - await hubPool.setRefundProposalLiveness(refundProposalLiveness); + await hubPool.setRootBundleProposalLiveness(refundProposalLiveness); // Deploy a mock chain adapter and add it as the chainAdapter for the test chainId. Set the SpokePool to address 0. const mockAdapter = await (await getContractFactory("Mock_Adapter", signer)).deploy(hubPool.address); diff --git a/test/HubPool.LiquidityProvisionFees.ts b/test/HubPool.LiquidityProvisionFees.ts index 3ddaf2fe5..1ed040a87 100644 --- a/test/HubPool.LiquidityProvisionFees.ts +++ b/test/HubPool.LiquidityProvisionFees.ts @@ -6,7 +6,7 @@ import { constructSingleChainTree } from "./MerkleLib.utils"; let hubPool: Contract, weth: Contract, timer: Contract; let owner: SignerWithAddress, dataWorker: SignerWithAddress, liquidityProvider: SignerWithAddress; -describe("HubPool Liquidity Provision", function () { +describe("HubPool Liquidity Provision Fees", function () { beforeEach(async function () { [owner, dataWorker, liquidityProvider] = await ethers.getSigners(); ({ weth, hubPool, timer } = await hubPoolFixture()); @@ -31,9 +31,9 @@ describe("HubPool Liquidity Provision", function () { await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); // Validate the post execution values have updated as expected. Liquid reserves should be the original LPed amount // minus the amount sent to L2. Utilized reserves should be the amount sent to L2 plus the attribute to LPs. @@ -57,9 +57,9 @@ describe("HubPool Liquidity Provision", function () { await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); // Exchange rate current right after the refund execution should be the amount deposited, grown by the 100 second // liveness period. Of the 10 ETH attributed to LPs, a total of 10*0.0000015*100=0.0015 was attributed to LPs. diff --git a/test/HubPool.PooledTokenSynchronization.ts b/test/HubPool.PooledTokenSynchronization.ts index 2963c1d7b..6dcce2fbc 100644 --- a/test/HubPool.PooledTokenSynchronization.ts +++ b/test/HubPool.PooledTokenSynchronization.ts @@ -35,9 +35,9 @@ describe("HubPool Pooled Token Synchronization", function () { const { tokensSendToL2, realizedLpFees, leafs, tree } = await constructSingleChainTree(weth); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); expect((await hubPool.pooledTokens(weth.address)).liquidReserves).to.equal(consts.amountToLp.sub(tokensSendToL2)); expect((await hubPool.pooledTokens(weth.address)).utilizedReserves).to.equal(tokensSendToL2.add(realizedLpFees)); @@ -99,12 +99,12 @@ describe("HubPool Pooled Token Synchronization", function () { const { tokensSendToL2, realizedLpFees, leafs, tree } = await constructSingleChainTree(weth); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); // Liquidity is not used until the relayerRefund is executed(i.e "pending" reserves are not considered). expect(await hubPool.callStatic.liquidityUtilizationCurrent(weth.address)).to.equal(0); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); // Now that the liquidity is used (sent to L2) we should be able to find the utilization. This should simply be // the utilizedReserves / (liquidReserves + utilizedReserves) = 110 / (900 + 110) = 0.108910891089108910 @@ -170,12 +170,12 @@ describe("HubPool Pooled Token Synchronization", function () { const { leafs, tree } = await constructSingleChainTree(weth); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); // Liquidity is not used until the relayerRefund is executed(i.e "pending" reserves are not considered). expect(await hubPool.callStatic.liquidityUtilizationCurrent(weth.address)).to.equal(0); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); // Now that the liquidity is used (sent to L2) we should be able to find the utilization. This should simply be // the utilizedReserves / (liquidReserves + utilizedReserves) = 110 / (900 + 110) = 0.108910891089108910 @@ -186,9 +186,9 @@ describe("HubPool Pooled Token Synchronization", function () { const { leafs, tree } = await constructSingleChainTree(weth, 5); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + 10 * 24 * 60 * 60); // Move time to accumulate all fees. // Liquidity utilization should now be (550) / (500 + 550) = 0.523809523809523809. I.e utilization is over 50%. @@ -219,9 +219,9 @@ describe("HubPool Pooled Token Synchronization", function () { const { leafs, tree, tokensSendToL2, realizedLpFees } = await constructSingleChainTree(weth); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + 10 * 24 * 60 * 60); // Move time to accumulate all fees. // Send back to L1 the tokensSendToL2 + realizedLpFees, i.e to mimic the finalization of the relay. @@ -252,9 +252,9 @@ describe("HubPool Pooled Token Synchronization", function () { // Going through a full refund lifecycle does returns to where we were before, with no memory of previous fees. await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockTreeRoot, consts.mockSlowRelayFulfillmentRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + 10 * 24 * 60 * 60); // Move time to accumulate all fees. // Exchange rate should be 1.01, with 1% accumulated on the back of refunds with no memory of the previous fees. diff --git a/test/HubPool.RefundInitilization.ts b/test/HubPool.ProposeRootBundle.ts similarity index 62% rename from test/HubPool.RefundInitilization.ts rename to test/HubPool.ProposeRootBundle.ts index 99eb97bd0..d33e7c9b6 100644 --- a/test/HubPool.RefundInitilization.ts +++ b/test/HubPool.ProposeRootBundle.ts @@ -4,14 +4,14 @@ import { hubPoolFixture } from "./HubPool.Fixture"; let hubPool: Contract, weth: Contract, dataWorker: SignerWithAddress; -describe("HubPool Relayer Refund Initialization", function () { +describe("HubPool Root Bundle Proposal", function () { beforeEach(async function () { [dataWorker] = await ethers.getSigners(); ({ weth, hubPool } = await hubPoolFixture()); await seedWallet(dataWorker, [], weth, consts.bondAmount.add(consts.finalFee).mul(2)); }); - it("Initialization of a relay correctly stores data, emits events and pulls the bond", async function () { + it("Proposal of root bundle correctly stores data, emits events and pulls the bond", async function () { const expectedRequestExpirationTimestamp = Number(await hubPool.getCurrentTime()) + consts.refundProposalLiveness; await weth.connect(dataWorker).approve(hubPool.address, consts.bondAmount); const dataWorkerWethBalancerBefore = await weth.callStatic.balanceOf(dataWorker.address); @@ -19,21 +19,21 @@ describe("HubPool Relayer Refund Initialization", function () { await expect( hubPool .connect(dataWorker) - .initiateRelayerRefund( + .proposeRootBundle( consts.mockBundleEvaluationBlockNumbers, consts.mockPoolRebalanceLeafCount, consts.mockPoolRebalanceRoot, - consts.mockDestinationDistributionRoot, + consts.mockRelayerRefundRoot, consts.mockSlowRelayFulfillmentRoot ) ) - .to.emit(hubPool, "InitiateRefundRequested") + .to.emit(hubPool, "ProposeRootBundle") .withArgs( - consts.mockBundleEvaluationBlockNumbers, expectedRequestExpirationTimestamp, consts.mockPoolRebalanceLeafCount, + consts.mockBundleEvaluationBlockNumbers, consts.mockPoolRebalanceRoot, - consts.mockDestinationDistributionRoot, + consts.mockRelayerRefundRoot, consts.mockSlowRelayFulfillmentRoot, dataWorker.address ); @@ -41,26 +41,26 @@ describe("HubPool Relayer Refund Initialization", function () { expect(await weth.balanceOf(hubPool.address)).to.equal(consts.bondAmount); expect(await weth.balanceOf(dataWorker.address)).to.equal(dataWorkerWethBalancerBefore.sub(consts.bondAmount)); - const refundRequest = await hubPool.refundRequest(); - expect(refundRequest.requestExpirationTimestamp).to.equal(expectedRequestExpirationTimestamp); - expect(refundRequest.unclaimedPoolRebalanceLeafCount).to.equal(consts.mockPoolRebalanceLeafCount); - expect(refundRequest.poolRebalanceRoot).to.equal(consts.mockPoolRebalanceRoot); - expect(refundRequest.destinationDistributionRoot).to.equal(consts.mockDestinationDistributionRoot); - expect(refundRequest.claimedBitMap).to.equal(0); // no claims yet so everything should be marked at 0. - expect(refundRequest.proposer).to.equal(dataWorker.address); - expect(refundRequest.proposerBondRepaid).to.equal(false); + const rootBundle = await hubPool.rootBundleProposal(); + expect(rootBundle.requestExpirationTimestamp).to.equal(expectedRequestExpirationTimestamp); + expect(rootBundle.unclaimedPoolRebalanceLeafCount).to.equal(consts.mockPoolRebalanceLeafCount); + expect(rootBundle.poolRebalanceRoot).to.equal(consts.mockPoolRebalanceRoot); + expect(rootBundle.relayerRefundRoot).to.equal(consts.mockRelayerRefundRoot); + expect(rootBundle.claimedBitMap).to.equal(0); // no claims yet so everything should be marked at 0. + expect(rootBundle.proposer).to.equal(dataWorker.address); + expect(rootBundle.proposerBondRepaid).to.equal(false); // Can not re-initialize if the previous bundle has unclaimed leaves. await expect( hubPool .connect(dataWorker) - .initiateRelayerRefund( + .proposeRootBundle( consts.mockBundleEvaluationBlockNumbers, consts.mockPoolRebalanceLeafCount, consts.mockPoolRebalanceRoot, - consts.mockDestinationDistributionRoot, + consts.mockRelayerRefundRoot, consts.mockSlowRelayFulfillmentRoot ) - ).to.be.revertedWith("Active request has unclaimed leafs"); + ).to.be.revertedWith("proposal has unclaimed leafs"); }); }); diff --git a/test/HubPool.ProtocolFees.ts b/test/HubPool.ProtocolFees.ts index 0b87c4e65..c09fcc410 100644 --- a/test/HubPool.ProtocolFees.ts +++ b/test/HubPool.ProtocolFees.ts @@ -37,9 +37,9 @@ describe("HubPool Protocol fees", function () { }); it("When fee capture pct is not set to zero fees correctly attribute between LPs and the protocol", async function () { const { leafs, tree, realizedLpFees } = await constructSingleChainTree(weth); - await hubPool.connect(dataWorker).initiateRelayerRefund([3117], 1, tree.getHexRoot(), mockTreeRoot, mockTreeRoot); + await hubPool.connect(dataWorker).proposeRootBundle([3117], 1, tree.getHexRoot(), mockTreeRoot, mockTreeRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); // 90% of the fees should be attributed to the LPs. expect((await hubPool.pooledTokens(weth.address)).undistributedLpFees).to.equal( @@ -68,9 +68,9 @@ describe("HubPool Protocol fees", function () { it("When fee capture pct is set to zero all fees accumulate to the LPs", async function () { await hubPool.setProtocolFeeCapture(owner.address, "0"); const { leafs, tree, realizedLpFees } = await constructSingleChainTree(weth); - await hubPool.connect(dataWorker).initiateRelayerRefund([3117], 1, tree.getHexRoot(), mockTreeRoot, mockTreeRoot); + await hubPool.connect(dataWorker).proposeRootBundle([3117], 1, tree.getHexRoot(), mockTreeRoot, mockTreeRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); expect((await hubPool.pooledTokens(weth.address)).undistributedLpFees).to.equal(realizedLpFees); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + 10 * 24 * 60 * 60); // Move time to accumulate all fees. diff --git a/test/MerkleLib.Proofs.ts b/test/MerkleLib.Proofs.ts index 70b2491c4..d21be1fdc 100644 --- a/test/MerkleLib.Proofs.ts +++ b/test/MerkleLib.Proofs.ts @@ -1,4 +1,4 @@ -import { PoolRebalanceLeaf, DestinationDistributionLeaf } from "./MerkleLib.utils"; +import { PoolRebalanceLeaf, RelayerRefundLeaf } from "./MerkleLib.utils"; import { merkleLibFixture } from "./MerkleLib.Fixture"; import { MerkleTree } from "../utils/MerkleTree"; import { expect, randomBigNumber, randomAddress, getParamType, defaultAbiCoder } from "./utils"; @@ -51,8 +51,8 @@ describe("MerkleLib Proofs", async function () { expect(() => merkleTree.getHexProof(invalidPoolRebalanceLeaf)).to.throw(); expect(await merkleLibTest.verifyPoolRebalance(root, invalidPoolRebalanceLeaf, proof)).to.equal(false); }); - it("DestinationDistributionLeafProof", async function () { - const destinationDistributionLeafs: DestinationDistributionLeaf[] = []; + it("RelayerRefundLeafProof", async function () { + const relayerRefundLeafs: RelayerRefundLeaf[] = []; const numDistributions = 101; // Create 101 and remove the last to use as the "invalid" one. for (let i = 0; i < numDistributions; i++) { const numAddresses = 10; @@ -62,7 +62,7 @@ describe("MerkleLib Proofs", async function () { refundAddresses.push(randomAddress()); refundAmounts.push(randomBigNumber()); } - destinationDistributionLeafs.push({ + relayerRefundLeafs.push({ leafId: BigNumber.from(i), chainId: randomBigNumber(2), amountToReturn: randomBigNumber(), @@ -73,20 +73,18 @@ describe("MerkleLib Proofs", async function () { } // Remove the last element. - const invalidDestinationDistributionLeaf = destinationDistributionLeafs.pop()!; + const invalidRelayerRefundLeaf = relayerRefundLeafs.pop()!; - const paramType = await getParamType("MerkleLib", "verifyRelayerDistribution", "distribution"); - const hashFn = (input: DestinationDistributionLeaf) => keccak256(defaultAbiCoder.encode([paramType!], [input])); - const merkleTree = new MerkleTree(destinationDistributionLeafs, hashFn); + const paramType = await getParamType("MerkleLib", "verifyRelayerRefund", "refund"); + const hashFn = (input: RelayerRefundLeaf) => keccak256(defaultAbiCoder.encode([paramType!], [input])); + const merkleTree = new MerkleTree(relayerRefundLeafs, hashFn); const root = merkleTree.getHexRoot(); - const proof = merkleTree.getHexProof(destinationDistributionLeafs[14]); - expect(await merkleLibTest.verifyRelayerDistribution(root, destinationDistributionLeafs[14], proof)).to.equal(true); + const proof = merkleTree.getHexProof(relayerRefundLeafs[14]); + expect(await merkleLibTest.verifyRelayerRefund(root, relayerRefundLeafs[14], proof)).to.equal(true); // Verify that the excluded element fails to generate a proof and fails verification using the proof generated above. - expect(() => merkleTree.getHexProof(invalidDestinationDistributionLeaf)).to.throw(); - expect(await merkleLibTest.verifyRelayerDistribution(root, invalidDestinationDistributionLeaf, proof)).to.equal( - false - ); + expect(() => merkleTree.getHexProof(invalidRelayerRefundLeaf)).to.throw(); + expect(await merkleLibTest.verifyRelayerRefund(root, invalidRelayerRefundLeaf, proof)).to.equal(false); }); }); diff --git a/test/MerkleLib.utils.ts b/test/MerkleLib.utils.ts index 815b86904..ae5523e63 100644 --- a/test/MerkleLib.utils.ts +++ b/test/MerkleLib.utils.ts @@ -11,7 +11,7 @@ export interface PoolRebalanceLeaf { runningBalances: BigNumber[]; } -export interface DestinationDistributionLeaf { +export interface RelayerRefundLeaf { leafId: BigNumber; chainId: BigNumber; amountToReturn: BigNumber; @@ -20,28 +20,24 @@ export interface DestinationDistributionLeaf { refundAmounts: BigNumber[]; } -export async function buildDestinationDistributionLeafTree( - destinationDistributionLeafs: DestinationDistributionLeaf[] -) { - for (let i = 0; i < destinationDistributionLeafs.length; i++) { +export async function buildRelayerRefundTree(relayerRefundLeafs: RelayerRefundLeaf[]) { + for (let i = 0; i < relayerRefundLeafs.length; i++) { // The 2 provided parallel arrays must be of equal length. - expect(destinationDistributionLeafs[i].refundAddresses.length).to.equal( - destinationDistributionLeafs[i].refundAmounts.length - ); + expect(relayerRefundLeafs[i].refundAddresses.length).to.equal(relayerRefundLeafs[i].refundAmounts.length); } - const paramType = await getParamType("MerkleLib", "verifyRelayerDistribution", "distribution"); - const hashFn = (input: DestinationDistributionLeaf) => keccak256(defaultAbiCoder.encode([paramType!], [input])); - return new MerkleTree(destinationDistributionLeafs, hashFn); + const paramType = await getParamType("MerkleLib", "verifyRelayerRefund", "refund"); + const hashFn = (input: RelayerRefundLeaf) => keccak256(defaultAbiCoder.encode([paramType!], [input])); + return new MerkleTree(relayerRefundLeafs, hashFn); } -export function buildDestinationDistributionLeafs( +export function buildRelayerRefundLeafs( destinationChainIds: number[], amountsToReturn: BigNumber[], l2Tokens: Contract[] | string[], refundAddresses: string[][], refundAmounts: BigNumber[][] -): DestinationDistributionLeaf[] { +): RelayerRefundLeaf[] { return Array(destinationChainIds.length) .fill(0) .map((_, i) => { diff --git a/test/SpokePool.RefundExecution.ts b/test/SpokePool.ExecuteRootBundle.ts similarity index 75% rename from test/SpokePool.RefundExecution.ts rename to test/SpokePool.ExecuteRootBundle.ts index 3ae59cc9c..5a99447cb 100644 --- a/test/SpokePool.RefundExecution.ts +++ b/test/SpokePool.ExecuteRootBundle.ts @@ -5,7 +5,7 @@ import { ethers } from "hardhat"; import { SignerWithAddress, seedContract, toBN } from "./utils"; import * as consts from "./constants"; import { spokePoolFixture } from "./SpokePool.Fixture"; -import { buildDestinationDistributionLeafs, buildDestinationDistributionLeafTree } from "./MerkleLib.utils"; +import { buildRelayerRefundTree, buildRelayerRefundLeafs } from "./MerkleLib.utils"; let spokePool: Contract, destErc20: Contract, weth: Contract; let dataWorker: SignerWithAddress, relayer: SignerWithAddress, rando: SignerWithAddress; @@ -13,7 +13,7 @@ let dataWorker: SignerWithAddress, relayer: SignerWithAddress, rando: SignerWith let destinationChainId: number; async function constructSimpleTree(l2Token: Contract, destinationChainId: number) { - const leafs = buildDestinationDistributionLeafs( + const leafs = buildRelayerRefundLeafs( [destinationChainId, destinationChainId], // Destination chain ID. [consts.amountToReturn, toBN(0)], // amountToReturn. [l2Token, l2Token], // l2Token. @@ -23,11 +23,11 @@ async function constructSimpleTree(l2Token: Contract, destinationChainId: number const leafsRefundAmount = leafs .map((leaf) => leaf.refundAmounts.reduce((bn1, bn2) => bn1.add(bn2), toBN(0))) .reduce((bn1, bn2) => bn1.add(bn2), toBN(0)); - const tree = await buildDestinationDistributionLeafTree(leafs); + const tree = await buildRelayerRefundTree(leafs); return { leafs, leafsRefundAmount, tree }; } -describe("SpokePool Relayer Refund Execution", function () { +describe("SpokePool Root Bundle Execution", function () { beforeEach(async function () { [dataWorker, relayer, rando] = await ethers.getSigners(); ({ destErc20, spokePool, weth } = await spokePoolFixture()); @@ -37,17 +37,17 @@ describe("SpokePool Relayer Refund Execution", function () { await seedContract(spokePool, dataWorker, [destErc20], weth, consts.amountHeldByPool); }); - it("Execute relayer refund correctly sends tokens to recipients", async function () { + it("Execute relayer root correctly sends tokens to recipients", async function () { const { leafs, leafsRefundAmount, tree } = await constructSimpleTree(destErc20, destinationChainId); // Store new tree. - await spokePool.connect(dataWorker).initializeRelayerRefund( - tree.getHexRoot(), // distribution root. Generated from the merkle tree constructed before. + await spokePool.connect(dataWorker).relayRootBundle( + tree.getHexRoot(), // relayer refund root. Generated from the merkle tree constructed before. consts.mockSlowRelayFulfillmentRoot ); // Distribute the first leaf. - await spokePool.connect(dataWorker).distributeRelayerRefund(0, leafs[0], tree.getHexProof(leafs[0])); + await spokePool.connect(dataWorker).executeRelayerRefundRoot(0, leafs[0], tree.getHexProof(leafs[0])); // Relayers should be refunded expect(await destErc20.balanceOf(spokePool.address)).to.equal(consts.amountHeldByPool.sub(leafsRefundAmount)); @@ -57,7 +57,7 @@ describe("SpokePool Relayer Refund Execution", function () { // TODO: Test token bridging logic. // Check events. - let relayTokensEvents = await spokePool.queryFilter(spokePool.filters.DistributedRelayerRefund()); + let relayTokensEvents = await spokePool.queryFilter(spokePool.filters.ExecutedRelayerRefundRoot()); expect(relayTokensEvents[0].args?.l2TokenAddress).to.equal(destErc20.address); expect(relayTokensEvents[0].args?.leafId).to.equal(0); expect(relayTokensEvents[0].args?.chainId).to.equal(destinationChainId); @@ -71,16 +71,16 @@ describe("SpokePool Relayer Refund Execution", function () { expect(tokensBridgedEvents.length).to.equal(1); // Does not attempt to bridge tokens if amountToReturn is 0. Execute a leaf where amountToReturn is 0. - await spokePool.connect(dataWorker).distributeRelayerRefund(0, leafs[1], tree.getHexProof(leafs[1])); + await spokePool.connect(dataWorker).executeRelayerRefundRoot(0, leafs[1], tree.getHexProof(leafs[1])); // Show that a second DistributedRelayRefund event was emitted but not a second TokensBridged event. - relayTokensEvents = await spokePool.queryFilter(spokePool.filters.DistributedRelayerRefund()); + relayTokensEvents = await spokePool.queryFilter(spokePool.filters.ExecutedRelayerRefundRoot()); expect(relayTokensEvents.length).to.equal(2); tokensBridgedEvents = await spokePool.queryFilter(spokePool.filters.TokensBridged()); expect(tokensBridgedEvents.length).to.equal(1); }); it("Execution rejects invalid leaf, tree, proof combinations", async function () { const { leafs, tree } = await constructSimpleTree(destErc20, destinationChainId); - await spokePool.connect(dataWorker).initializeRelayerRefund( + await spokePool.connect(dataWorker).relayRootBundle( tree.getHexRoot(), // distribution root. Generated from the merkle tree constructed before. consts.mockSlowRelayFulfillmentRoot ); @@ -88,35 +88,35 @@ describe("SpokePool Relayer Refund Execution", function () { // Take the valid root but change some element within it. This will change the hash of the leaf // and as such the contract should reject it for not being included within the merkle tree for the valid proof. const badLeaf = { ...leafs[0], chainId: 13371 }; - await expect(spokePool.connect(dataWorker).distributeRelayerRefund(0, badLeaf, tree.getHexProof(leafs[0]))).to.be + await expect(spokePool.connect(dataWorker).executeRelayerRefundRoot(0, badLeaf, tree.getHexProof(leafs[0]))).to.be .reverted; // Reverts if the distribution root index is incorrect. - await expect(spokePool.connect(dataWorker).distributeRelayerRefund(1, leafs[0], tree.getHexProof(leafs[0]))).to.be + await expect(spokePool.connect(dataWorker).executeRelayerRefundRoot(1, leafs[0], tree.getHexProof(leafs[0]))).to.be .reverted; }); it("Cannot refund leaf with chain ID for another network", async function () { // Create tree for another chain ID const { leafs, tree } = await constructSimpleTree(destErc20, 13371); - await spokePool.connect(dataWorker).initializeRelayerRefund( + await spokePool.connect(dataWorker).relayRootBundle( tree.getHexRoot(), // distribution root. Generated from the merkle tree constructed before. consts.mockSlowRelayFulfillmentRoot ); // Root is valid and leaf is contained in tree, but chain ID doesn't match pool's chain ID. - await expect(spokePool.connect(dataWorker).distributeRelayerRefund(0, leafs[0], tree.getHexProof(leafs[0]))).to.be + await expect(spokePool.connect(dataWorker).executeRelayerRefundRoot(0, leafs[0], tree.getHexProof(leafs[0]))).to.be .reverted; }); it("Execution rejects double claimed leafs", async function () { const { leafs, tree } = await constructSimpleTree(destErc20, destinationChainId); - await spokePool.connect(dataWorker).initializeRelayerRefund( + await spokePool.connect(dataWorker).relayRootBundle( tree.getHexRoot(), // distribution root. Generated from the merkle tree constructed before. consts.mockSlowRelayFulfillmentRoot ); // First claim should be fine. Second claim should be reverted as you cant double claim a leaf. - await spokePool.connect(dataWorker).distributeRelayerRefund(0, leafs[0], tree.getHexProof(leafs[0])); - await expect(spokePool.connect(dataWorker).distributeRelayerRefund(0, leafs[0], tree.getHexProof(leafs[0]))).to.be + await spokePool.connect(dataWorker).executeRelayerRefundRoot(0, leafs[0], tree.getHexProof(leafs[0])); + await expect(spokePool.connect(dataWorker).executeRelayerRefundRoot(0, leafs[0], tree.getHexProof(leafs[0]))).to.be .reverted; }); }); diff --git a/test/SpokePool.Fixture.ts b/test/SpokePool.Fixture.ts index d03900208..c587c7a6d 100644 --- a/test/SpokePool.Fixture.ts +++ b/test/SpokePool.Fixture.ts @@ -165,7 +165,7 @@ export function getFillRelayUpdatedFeeParams( ]; } -export function getDistributeRelaySlowParams( +export function getExecuteSlowRelayParams( _depositor: string, _recipient: string, _destToken: string, diff --git a/test/SpokePool.RefundInitialization.ts b/test/SpokePool.RefundInitialization.ts deleted file mode 100644 index 93f9dddcc..000000000 --- a/test/SpokePool.RefundInitialization.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { expect, Contract, ethers, SignerWithAddress } from "./utils"; -import { spokePoolFixture } from "./SpokePool.Fixture"; -import { mockDestinationDistributionRoot, mockSlowRelayFulfillmentRoot } from "./constants"; - -let spokePool: Contract; -let dataWorker: SignerWithAddress; - -describe("SpokePool Initialize Relayer Refund Logic", async function () { - beforeEach(async function () { - [dataWorker] = await ethers.getSigners(); - ({ spokePool } = await spokePoolFixture()); - }); - it("Initializing root stores root and emits event", async function () { - await expect( - spokePool - .connect(dataWorker) - .initializeRelayerRefund(mockDestinationDistributionRoot, mockSlowRelayFulfillmentRoot) - ) - .to.emit(spokePool, "InitializedRelayerRefund") - .withArgs(0, mockDestinationDistributionRoot, mockSlowRelayFulfillmentRoot); - - expect(await spokePool.relayerRefunds(0)).has.property("slowRelayFulfillmentRoot", mockSlowRelayFulfillmentRoot); - expect(await spokePool.relayerRefunds(0)).has.property("distributionRoot", mockDestinationDistributionRoot); - }); -}); diff --git a/test/SpokePool.RelayRootBundle.ts b/test/SpokePool.RelayRootBundle.ts new file mode 100644 index 000000000..d195a9ff1 --- /dev/null +++ b/test/SpokePool.RelayRootBundle.ts @@ -0,0 +1,21 @@ +import { expect, Contract, ethers, SignerWithAddress } from "./utils"; +import { spokePoolFixture } from "./SpokePool.Fixture"; +import { mockRelayerRefundRoot, mockSlowRelayFulfillmentRoot } from "./constants"; + +let spokePool: Contract; +let dataWorker: SignerWithAddress; + +describe("SpokePool Relay Bundle Logic", async function () { + beforeEach(async function () { + [dataWorker] = await ethers.getSigners(); + ({ spokePool } = await spokePoolFixture()); + }); + it("Relaying root stores root and emits event", async function () { + await expect(spokePool.connect(dataWorker).relayRootBundle(mockRelayerRefundRoot, mockSlowRelayFulfillmentRoot)) + .to.emit(spokePool, "RelayedRootBundle") + .withArgs(0, mockRelayerRefundRoot, mockSlowRelayFulfillmentRoot); + + expect(await spokePool.rootBundles(0)).has.property("slowRelayFulfillmentRoot", mockSlowRelayFulfillmentRoot); + expect(await spokePool.rootBundles(0)).has.property("relayerRefundRoot", mockRelayerRefundRoot); + }); +}); diff --git a/test/SpokePool.SlowRelay.ts b/test/SpokePool.SlowRelay.ts index a21c862da..3860b0f41 100644 --- a/test/SpokePool.SlowRelay.ts +++ b/test/SpokePool.SlowRelay.ts @@ -15,7 +15,7 @@ import { spokePoolFixture, enableRoutes, RelayData, - getDistributeRelaySlowParams, + getExecuteSlowRelayParams, getFillRelayParams, getRelayHash, } from "./SpokePool.Fixture"; @@ -95,14 +95,14 @@ describe("SpokePool Slow Relay Logic", async function () { }; tree = new MerkleTree(relays, hashFn); - await spokePool.connect(depositor).initializeRelayerRefund(consts.mockTreeRoot, tree.getHexRoot()); + await spokePool.connect(depositor).relayRootBundle(consts.mockTreeRoot, tree.getHexRoot()); }); it("Simple SlowRelay ERC20 balances", async function () { await expect(() => spokePool .connect(relayer) - .distributeRelaySlow( - ...getDistributeRelaySlowParams( + .executeSlowRelayFulfillmentRoot( + ...getExecuteSlowRelayParams( depositor.address, recipient.address, destErc20.address, @@ -128,8 +128,8 @@ describe("SpokePool Slow Relay Logic", async function () { await expect( spokePool .connect(relayer) - .distributeRelaySlow( - ...getDistributeRelaySlowParams( + .executeSlowRelayFulfillmentRoot( + ...getExecuteSlowRelayParams( depositor.address, recipient.address, destErc20.address, @@ -143,7 +143,7 @@ describe("SpokePool Slow Relay Logic", async function () { ) ) ) - .to.emit(spokePool, "DistributeRelaySlow") + .to.emit(spokePool, "ExecutedSlowRelayFulfillmentRoot") .withArgs( tree.hashFn(relay), consts.amountToRelay, @@ -164,8 +164,8 @@ describe("SpokePool Slow Relay Logic", async function () { await expect(() => spokePool .connect(relayer) - .distributeRelaySlow( - ...getDistributeRelaySlowParams( + .executeSlowRelayFulfillmentRoot( + ...getExecuteSlowRelayParams( depositor.address, recipient.address, weth.address, @@ -185,8 +185,8 @@ describe("SpokePool Slow Relay Logic", async function () { await expect(() => spokePool .connect(relayer) - .distributeRelaySlow( - ...getDistributeRelaySlowParams( + .executeSlowRelayFulfillmentRoot( + ...getExecuteSlowRelayParams( depositor.address, recipient.address, weth.address, @@ -224,8 +224,8 @@ describe("SpokePool Slow Relay Logic", async function () { await expect(() => spokePool .connect(relayer) - .distributeRelaySlow( - ...getDistributeRelaySlowParams( + .executeSlowRelayFulfillmentRoot( + ...getExecuteSlowRelayParams( depositor.address, recipient.address, destErc20.address, @@ -264,8 +264,8 @@ describe("SpokePool Slow Relay Logic", async function () { await expect(() => spokePool .connect(relayer) - .distributeRelaySlow( - ...getDistributeRelaySlowParams( + .executeSlowRelayFulfillmentRoot( + ...getExecuteSlowRelayParams( depositor.address, recipient.address, weth.address, @@ -304,8 +304,8 @@ describe("SpokePool Slow Relay Logic", async function () { await expect(() => spokePool .connect(relayer) - .distributeRelaySlow( - ...getDistributeRelaySlowParams( + .executeSlowRelayFulfillmentRoot( + ...getExecuteSlowRelayParams( depositor.address, recipient.address, weth.address, @@ -323,8 +323,8 @@ describe("SpokePool Slow Relay Logic", async function () { it("Bad proof", async function () { await expect( - spokePool.connect(relayer).distributeRelaySlow( - ...getDistributeRelaySlowParams( + spokePool.connect(relayer).executeSlowRelayFulfillmentRoot( + ...getExecuteSlowRelayParams( depositor.address, recipient.address, weth.address, diff --git a/test/chain-adapters/Arbitrum_Adapter.ts b/test/chain-adapters/Arbitrum_Adapter.ts index 1a8a5a555..36afcff05 100644 --- a/test/chain-adapters/Arbitrum_Adapter.ts +++ b/test/chain-adapters/Arbitrum_Adapter.ts @@ -96,15 +96,15 @@ describe("Arbitrum Chain Adapter", function () { const { leafs, tree, tokensSendToL2 } = await constructSingleChainTree(dai, 1, arbitrumChainId); await hubPool .connect(dataWorker) - .initiateRelayerRefund( + .proposeRootBundle( [3117], 1, tree.getHexRoot(), - consts.mockDestinationDistributionRoot, + consts.mockRelayerRefundRoot, consts.mockSlowRelayFulfillmentRoot ); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); // The correct functions should have been called on the arbitrum contracts. expect(l1ERC20Gateway.outboundTransfer).to.have.been.calledOnce; // One token transfer over the canonical bridge. expect(l1ERC20Gateway.outboundTransfer).to.have.been.calledWith( @@ -125,8 +125,8 @@ describe("Arbitrum Chain Adapter", function () { owner.address, consts.sampleL2Gas, consts.sampleL2GasPrice, - mockSpoke.interface.encodeFunctionData("initializeRelayerRefund", [ - consts.mockDestinationDistributionRoot, + mockSpoke.interface.encodeFunctionData("relayRootBundle", [ + consts.mockRelayerRefundRoot, consts.mockSlowRelayFulfillmentRoot, ]) ); diff --git a/test/chain-adapters/L1_Adapter.ts b/test/chain-adapters/L1_Adapter.ts index d596ec8c4..24d02e2ad 100644 --- a/test/chain-adapters/L1_Adapter.ts +++ b/test/chain-adapters/L1_Adapter.ts @@ -50,15 +50,15 @@ describe("L1 Chain Adapter", function () { const { leafs, tree, tokensSendToL2 } = await constructSingleChainTree(dai, 1, l1ChainId); await hubPool .connect(dataWorker) - .initiateRelayerRefund( + .proposeRootBundle( [3117], 1, tree.getHexRoot(), - consts.mockDestinationDistributionRoot, + consts.mockRelayerRefundRoot, consts.mockSlowRelayFulfillmentRoot ); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); - expect(await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0]))) + expect(await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0]))) .to.emit(l1Adapter, "TokensRelayed") .withArgs(dai.address, dai.address, tokensSendToL2, mockSpoke.address); }); diff --git a/test/chain-adapters/Optimism_Adapter.ts b/test/chain-adapters/Optimism_Adapter.ts index fa73e5525..d2d0fa506 100644 --- a/test/chain-adapters/Optimism_Adapter.ts +++ b/test/chain-adapters/Optimism_Adapter.ts @@ -69,9 +69,9 @@ describe("Optimism Chain Adapter", function () { const { leafs, tree, tokensSendToL2 } = await constructSingleChainTree(dai, 1, optimismChainId); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), mockTreeRoot, mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), mockTreeRoot, mockSlowRelayFulfillmentRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); // The correct functions should have been called on the optimism contracts. expect(l1StandardBridge.depositERC20To).to.have.been.calledOnce; // One token transfer over the bridge. @@ -80,7 +80,7 @@ describe("Optimism Chain Adapter", function () { expect(l1StandardBridge.depositERC20To).to.have.been.calledWith(...expectedErc20L1ToL2BridgeParams); const expectedL2ToL1FunctionCallParams = [ mockSpoke.address, - mockSpoke.interface.encodeFunctionData("initializeRelayerRefund", [mockTreeRoot, mockSlowRelayFulfillmentRoot]), + mockSpoke.interface.encodeFunctionData("relayRootBundle", [mockTreeRoot, mockSlowRelayFulfillmentRoot]), sampleL2Gas, ]; expect(l1CrossDomainMessenger.sendMessage).to.have.been.calledWith(...expectedL2ToL1FunctionCallParams); @@ -90,9 +90,9 @@ describe("Optimism Chain Adapter", function () { const { leafs, tree } = await constructSingleChainTree(weth, 1, optimismChainId); await hubPool .connect(dataWorker) - .initiateRelayerRefund([3117], 1, tree.getHexRoot(), mockTreeRoot, mockSlowRelayFulfillmentRoot); + .proposeRootBundle([3117], 1, tree.getHexRoot(), mockTreeRoot, mockSlowRelayFulfillmentRoot); await timer.setCurrentTime(Number(await timer.getCurrentTime()) + refundProposalLiveness); - await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); + await hubPool.connect(dataWorker).executeRootBundle(leafs[0], tree.getHexProof(leafs[0])); // The correct functions should have been called on the optimism contracts. expect(l1StandardBridge.depositETHTo).to.have.been.calledOnce; // One eth transfer over the bridge. @@ -100,7 +100,7 @@ describe("Optimism Chain Adapter", function () { expect(l1StandardBridge.depositETHTo).to.have.been.calledWith(mockSpoke.address, sampleL2Gas, "0x"); const expectedL2ToL1FunctionCallParams = [ mockSpoke.address, - mockSpoke.interface.encodeFunctionData("initializeRelayerRefund", [mockTreeRoot, mockSlowRelayFulfillmentRoot]), + mockSpoke.interface.encodeFunctionData("relayRootBundle", [mockTreeRoot, mockSlowRelayFulfillmentRoot]), sampleL2Gas, ]; expect(l1CrossDomainMessenger.sendMessage).to.have.been.calledWith(...expectedL2ToL1FunctionCallParams); diff --git a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts index 4e41d93f8..36f92f208 100644 --- a/test/chain-specific-spokepools/Arbitrum_SpokePool.ts +++ b/test/chain-specific-spokepools/Arbitrum_SpokePool.ts @@ -1,8 +1,8 @@ -import { mockTreeRoot, amountToReturn, amountToRelay, amountHeldByPool } from "../constants"; +import { mockTreeRoot, amountToReturn, amountHeldByPool } from "../constants"; import { ethers, expect, Contract, FakeContract, SignerWithAddress, createFake, toWei } from "../utils"; import { getContractFactory, seedContract, avmL1ToL2Alias, hre, toBN, toBNWei } from "../utils"; -import { hubPoolFixture, enableTokensForLP } from "../HubPool.Fixture"; -import { buildDestinationDistributionLeafTree, buildDestinationDistributionLeafs } from "../MerkleLib.utils"; +import { hubPoolFixture } from "../HubPool.Fixture"; +import { buildRelayerRefundLeafs, buildRelayerRefundTree } from "../MerkleLib.utils"; let hubPool: Contract, arbitrumSpokePool: Contract, merkleLib: Contract, timer: Contract, dai: Contract, weth: Contract; let l2Weth: string, l2Dai: string, crossDomainAliasAddress; @@ -11,7 +11,7 @@ let owner: SignerWithAddress, relayer: SignerWithAddress, rando: SignerWithAddre let l2GatewayRouter: FakeContract; async function constructSimpleTree(l2Token: Contract | string, destinationChainId: number) { - const leafs = buildDestinationDistributionLeafs( + const leafs = buildRelayerRefundLeafs( [destinationChainId], // Destination chain ID. [amountToReturn], // amountToReturn. [l2Token as string], // l2Token. @@ -19,7 +19,7 @@ async function constructSimpleTree(l2Token: Contract | string, destinationChainI [[]] // refundAmounts. ); - const tree = await buildDestinationDistributionLeafTree(leafs); + const tree = await buildRelayerRefundTree(leafs); return { leafs, tree }; } @@ -75,16 +75,16 @@ describe("Arbitrum Spoke Pool", function () { }); it("Only cross domain owner can initialize a relayer refund", async function () { - await expect(arbitrumSpokePool.initializeRelayerRefund(mockTreeRoot, mockTreeRoot)).to.be.reverted; - await arbitrumSpokePool.connect(crossDomainAlias).initializeRelayerRefund(mockTreeRoot, mockTreeRoot); - expect((await arbitrumSpokePool.relayerRefunds(0)).slowRelayFulfillmentRoot).to.equal(mockTreeRoot); - expect((await arbitrumSpokePool.relayerRefunds(0)).distributionRoot).to.equal(mockTreeRoot); + await expect(arbitrumSpokePool.relayRootBundle(mockTreeRoot, mockTreeRoot)).to.be.reverted; + await arbitrumSpokePool.connect(crossDomainAlias).relayRootBundle(mockTreeRoot, mockTreeRoot); + expect((await arbitrumSpokePool.rootBundles(0)).slowRelayFulfillmentRoot).to.equal(mockTreeRoot); + expect((await arbitrumSpokePool.rootBundles(0)).relayerRefundRoot).to.equal(mockTreeRoot); }); it("Bridge tokens to hub pool correctly calls the Standard L2 Gateway router", async function () { const { leafs, tree } = await constructSimpleTree(l2Dai, await arbitrumSpokePool.callStatic.chainId()); - await arbitrumSpokePool.connect(crossDomainAlias).initializeRelayerRefund(tree.getHexRoot(), mockTreeRoot); - await arbitrumSpokePool.connect(relayer).distributeRelayerRefund(0, leafs[0], tree.getHexProof(leafs[0])); + await arbitrumSpokePool.connect(crossDomainAlias).relayRootBundle(tree.getHexRoot(), mockTreeRoot); + await arbitrumSpokePool.connect(relayer).executeRelayerRefundRoot(0, leafs[0], tree.getHexProof(leafs[0])); // This should have sent tokens back to L1. Check the correct methods on the gateway are correctly called. // outboundTransfer is overloaded in the arbitrum gateway. Define the interface to check the method is called. diff --git a/test/constants.ts b/test/constants.ts index 0aa4c6ba2..7a9219ee2 100644 --- a/test/constants.ts +++ b/test/constants.ts @@ -56,7 +56,7 @@ export const mockPoolRebalanceLeafCount = 5; export const mockPoolRebalanceRoot = createRandomBytes32(); -export const mockDestinationDistributionRoot = createRandomBytes32(); +export const mockRelayerRefundRoot = createRandomBytes32(); export const mockSlowRelayFulfillmentRoot = createRandomBytes32();