diff --git a/contracts/test/MerkleLibTest.sol b/contracts/test/MerkleLibTest.sol index e0652b1d3..f302dd9d4 100644 --- a/contracts/test/MerkleLibTest.sol +++ b/contracts/test/MerkleLibTest.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.0; import "../MerkleLib.sol"; import "../interfaces/HubPoolInterface.sol"; import "../interfaces/SpokePoolInterface.sol"; +import "./interfaces/MockV2SpokePoolInterface.sol"; +import "./V2MerkleLib.sol"; /** * @notice Contract to test the MerkleLib. @@ -29,6 +31,14 @@ contract MerkleLibTest { return MerkleLib.verifyRelayerRefund(root, refund, proof); } + function verifySlowRelayFulfillment( + bytes32 root, + MockV2SpokePoolInterface.SlowFill memory slowFill, + bytes32[] memory proof + ) public pure returns (bool) { + return V2MerkleLib.verifySlowRelayFulfillment(root, slowFill, proof); + } + function verifyV3SlowRelayFulfillment( bytes32 root, V3SpokePoolInterface.V3SlowFill memory slowFill, diff --git a/contracts/test/MockSpokePool.sol b/contracts/test/MockSpokePool.sol index f00d50507..6c7ddbe90 100644 --- a/contracts/test/MockSpokePool.sol +++ b/contracts/test/MockSpokePool.sol @@ -1,17 +1,28 @@ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; -import "../SpokePool.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "../SpokePool.sol"; +import "./interfaces/MockV2SpokePoolInterface.sol"; +import "./V2MerkleLib.sol"; /** * @title MockSpokePool * @notice Implements abstract contract for testing. */ -contract MockSpokePool is SpokePool, OwnableUpgradeable { +contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + uint256 private chainId_; uint256 private currentTime; - using SafeERC20Upgradeable for IERC20Upgradeable; + mapping(bytes32 => uint256) private relayFills; + + uint256 public constant SLOW_FILL_MAX_TOKENS_TO_SEND = 1e40; + + bytes32 public constant UPDATE_DEPOSIT_DETAILS_HASH = + keccak256( + "UpdateDepositDetails(uint32 depositId,uint256 originChainId,int64 updatedRelayerFeePct,address updatedRecipient,bytes updatedMessage)" + ); event BridgedToHubPool(uint256 amount, address token); event PreLeafExecuteHook(address token); @@ -44,6 +55,33 @@ contract MockSpokePool is SpokePool, OwnableUpgradeable { _distributeRelayerRefunds(_chainId, amountToReturn, refundAmounts, leafId, l2TokenAddress, refundAddresses); } + function _verifyUpdateDepositMessage( + address depositor, + uint32 depositId, + uint256 originChainId, + int64 updatedRelayerFeePct, + address updatedRecipient, + bytes memory updatedMessage, + bytes memory depositorSignature + ) internal view { + bytes32 expectedTypedDataV4Hash = _hashTypedDataV4( + // EIP-712 compliant hash struct: https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct + keccak256( + abi.encode( + UPDATE_DEPOSIT_DETAILS_HASH, + depositId, + originChainId, + updatedRelayerFeePct, + updatedRecipient, + keccak256(updatedMessage) + ) + ), + // By passing in the origin chain id, we enable the verification of the signature on a different chain + originChainId + ); + _verifyDepositorSignature(depositor, expectedTypedDataV4Hash, depositorSignature); + } + function verifyUpdateV3DepositMessage( address depositor, uint32 depositId, @@ -106,4 +144,359 @@ contract MockSpokePool is SpokePool, OwnableUpgradeable { function setChainId(uint256 _chainId) public { chainId_ = _chainId; } + + function depositV2( + address recipient, + address originToken, + uint256 amount, + uint256 destinationChainId, + int64 relayerFeePct, + uint32 quoteTimestamp, + bytes memory message, + uint256 // maxCount + ) public payable virtual nonReentrant unpausedDeposits { + // Increment count of deposits so that deposit ID for this spoke pool is unique. + uint32 newDepositId = numberOfDeposits++; + + if (originToken == address(wrappedNativeToken) && msg.value > 0) { + require(msg.value == amount); + wrappedNativeToken.deposit{ value: msg.value }(); + } else IERC20Upgradeable(originToken).safeTransferFrom(msg.sender, address(this), amount); + + emit FundsDeposited( + amount, + chainId(), + destinationChainId, + relayerFeePct, + newDepositId, + quoteTimestamp, + originToken, + recipient, + msg.sender, + message + ); + } + + function speedUpDeposit( + address depositor, + int64 updatedRelayerFeePct, + uint32 depositId, + address updatedRecipient, + bytes memory updatedMessage, + bytes memory depositorSignature + ) public nonReentrant { + require(SignedMath.abs(updatedRelayerFeePct) < 0.5e18, "Invalid relayer fee"); + + _verifyUpdateDepositMessage( + depositor, + depositId, + chainId(), + updatedRelayerFeePct, + updatedRecipient, + updatedMessage, + depositorSignature + ); + + // Assuming the above checks passed, a relayer can take the signature and the updated relayer fee information + // from the following event to submit a fill with an updated fee %. + emit RequestedSpeedUpDeposit( + updatedRelayerFeePct, + depositId, + depositor, + updatedRecipient, + updatedMessage, + depositorSignature + ); + } + + function fillRelay( + address depositor, + address recipient, + address destinationToken, + uint256 amount, + uint256 maxTokensToSend, + uint256 repaymentChainId, + uint256 originChainId, + int64 realizedLpFeePct, + int64 relayerFeePct, + uint32 depositId, + bytes memory message, + uint256 maxCount + ) public nonReentrant unpausedFills { + RelayExecution memory relayExecution = RelayExecution({ + relay: MockV2SpokePoolInterface.RelayData({ + depositor: depositor, + recipient: recipient, + destinationToken: destinationToken, + amount: amount, + realizedLpFeePct: realizedLpFeePct, + relayerFeePct: relayerFeePct, + depositId: depositId, + originChainId: originChainId, + destinationChainId: chainId(), + message: message + }), + relayHash: bytes32(0), + updatedRelayerFeePct: relayerFeePct, + updatedRecipient: recipient, + updatedMessage: message, + repaymentChainId: repaymentChainId, + maxTokensToSend: maxTokensToSend, + slowFill: false, + payoutAdjustmentPct: 0, + maxCount: maxCount + }); + relayExecution.relayHash = _getRelayHash(relayExecution.relay); + + uint256 fillAmountPreFees = _fillRelay(relayExecution); + _emitFillRelay(relayExecution, fillAmountPreFees); + } + + function executeSlowRelayLeaf( + address depositor, + address recipient, + address destinationToken, + uint256 amount, + uint256 originChainId, + int64 realizedLpFeePct, + int64 relayerFeePct, + uint32 depositId, + uint32 rootBundleId, + bytes memory message, + int256 payoutAdjustment, + bytes32[] memory proof + ) public nonReentrant { + _executeSlowRelayLeaf( + depositor, + recipient, + destinationToken, + amount, + originChainId, + chainId(), + realizedLpFeePct, + relayerFeePct, + depositId, + rootBundleId, + message, + payoutAdjustment, + proof + ); + } + + function fillRelayWithUpdatedDeposit( + address depositor, + address recipient, + address updatedRecipient, + address destinationToken, + uint256 amount, + uint256 maxTokensToSend, + uint256 repaymentChainId, + uint256 originChainId, + int64 realizedLpFeePct, + int64 relayerFeePct, + int64 updatedRelayerFeePct, + uint32 depositId, + bytes memory message, + bytes memory updatedMessage, + bytes memory depositorSignature, + uint256 maxCount + ) public nonReentrant unpausedFills { + RelayExecution memory relayExecution = RelayExecution({ + relay: MockV2SpokePoolInterface.RelayData({ + depositor: depositor, + recipient: recipient, + destinationToken: destinationToken, + amount: amount, + realizedLpFeePct: realizedLpFeePct, + relayerFeePct: relayerFeePct, + depositId: depositId, + originChainId: originChainId, + destinationChainId: chainId(), + message: message + }), + relayHash: bytes32(0), + updatedRelayerFeePct: updatedRelayerFeePct, + updatedRecipient: updatedRecipient, + updatedMessage: updatedMessage, + repaymentChainId: repaymentChainId, + maxTokensToSend: maxTokensToSend, + slowFill: false, + payoutAdjustmentPct: 0, + maxCount: maxCount + }); + relayExecution.relayHash = _getRelayHash(relayExecution.relay); + + _verifyUpdateDepositMessage( + depositor, + depositId, + originChainId, + updatedRelayerFeePct, + updatedRecipient, + updatedMessage, + depositorSignature + ); + uint256 fillAmountPreFees = _fillRelay(relayExecution); + _emitFillRelay(relayExecution, fillAmountPreFees); + } + + function _executeSlowRelayLeaf( + address depositor, + address recipient, + address destinationToken, + uint256 amount, + uint256 originChainId, + uint256 destinationChainId, + int64 realizedLpFeePct, + int64 relayerFeePct, + uint32 depositId, + uint32 rootBundleId, + bytes memory message, + int256 payoutAdjustmentPct, + bytes32[] memory proof + ) internal { + RelayExecution memory relayExecution = RelayExecution({ + relay: MockV2SpokePoolInterface.RelayData({ + depositor: depositor, + recipient: recipient, + destinationToken: destinationToken, + amount: amount, + realizedLpFeePct: realizedLpFeePct, + relayerFeePct: relayerFeePct, + depositId: depositId, + originChainId: originChainId, + destinationChainId: destinationChainId, + message: message + }), + relayHash: bytes32(0), + updatedRelayerFeePct: 0, + updatedRecipient: recipient, + updatedMessage: message, + repaymentChainId: 0, + maxTokensToSend: SLOW_FILL_MAX_TOKENS_TO_SEND, + slowFill: true, + payoutAdjustmentPct: payoutAdjustmentPct, + maxCount: type(uint256).max + }); + relayExecution.relayHash = _getRelayHash(relayExecution.relay); + + _verifySlowFill(relayExecution, rootBundleId, proof); + + uint256 fillAmountPreFees = _fillRelay(relayExecution); + + _emitFillRelay(relayExecution, fillAmountPreFees); + } + + function _computeAmountPreFees(uint256 amount, int64 feesPct) private pure returns (uint256) { + return (1e18 * amount) / uint256((int256(1e18) - feesPct)); + } + + function __computeAmountPostFees(uint256 amount, int256 feesPct) private pure returns (uint256) { + return (amount * uint256(int256(1e18) - feesPct)) / 1e18; + } + + function _getRelayHash(MockV2SpokePoolInterface.RelayData memory relayData) private pure returns (bytes32) { + return keccak256(abi.encode(relayData)); + } + + function _fillRelay(RelayExecution memory relayExecution) internal returns (uint256 fillAmountPreFees) { + MockV2SpokePoolInterface.RelayData memory relayData = relayExecution.relay; + + require(relayFills[relayExecution.relayHash] < relayData.amount, "relay filled"); + + fillAmountPreFees = _computeAmountPreFees( + relayExecution.maxTokensToSend, + (relayData.realizedLpFeePct + relayExecution.updatedRelayerFeePct) + ); + require(fillAmountPreFees > 0, "fill amount pre fees is 0"); + + uint256 amountRemainingInRelay = relayData.amount - relayFills[relayExecution.relayHash]; + if (amountRemainingInRelay < fillAmountPreFees) { + fillAmountPreFees = amountRemainingInRelay; + } + + uint256 amountToSend = __computeAmountPostFees( + fillAmountPreFees, + relayData.realizedLpFeePct + relayExecution.updatedRelayerFeePct + ); + + if (relayExecution.payoutAdjustmentPct != 0) { + require(relayExecution.payoutAdjustmentPct >= -1e18, "payoutAdjustmentPct too small"); + require(relayExecution.payoutAdjustmentPct <= 100e18, "payoutAdjustmentPct too large"); + + amountToSend = __computeAmountPostFees(amountToSend, -relayExecution.payoutAdjustmentPct); + require(amountToSend <= relayExecution.maxTokensToSend, "Somehow hit maxTokensToSend!"); + } + + bool localRepayment = relayExecution.repaymentChainId == relayExecution.relay.destinationChainId; + require( + localRepayment || relayExecution.relay.amount == fillAmountPreFees || relayExecution.slowFill, + "invalid repayment chain" + ); + + relayFills[relayExecution.relayHash] += fillAmountPreFees; + + if (msg.sender == relayExecution.updatedRecipient && !relayExecution.slowFill) return fillAmountPreFees; + + if (relayData.destinationToken == address(wrappedNativeToken)) { + if (!relayExecution.slowFill) + IERC20Upgradeable(relayData.destinationToken).safeTransferFrom(msg.sender, address(this), amountToSend); + _unwrapwrappedNativeTokenTo(payable(relayExecution.updatedRecipient), amountToSend); + } else { + if (!relayExecution.slowFill) + IERC20Upgradeable(relayData.destinationToken).safeTransferFrom( + msg.sender, + relayExecution.updatedRecipient, + amountToSend + ); + else + IERC20Upgradeable(relayData.destinationToken).safeTransfer( + relayExecution.updatedRecipient, + amountToSend + ); + } + } + + function _verifySlowFill( + RelayExecution memory relayExecution, + uint32 rootBundleId, + bytes32[] memory proof + ) internal view { + SlowFill memory slowFill = SlowFill({ + relayData: relayExecution.relay, + payoutAdjustmentPct: relayExecution.payoutAdjustmentPct + }); + + require( + V2MerkleLib.verifySlowRelayFulfillment(rootBundles[rootBundleId].slowRelayRoot, slowFill, proof), + "Invalid slow relay proof" + ); + } + + function _emitFillRelay(RelayExecution memory relayExecution, uint256 fillAmountPreFees) internal { + RelayExecutionInfo memory relayExecutionInfo = RelayExecutionInfo({ + relayerFeePct: relayExecution.updatedRelayerFeePct, + recipient: relayExecution.updatedRecipient, + message: relayExecution.updatedMessage, + isSlowRelay: relayExecution.slowFill, + payoutAdjustmentPct: relayExecution.payoutAdjustmentPct + }); + + emit FilledRelay( + relayExecution.relay.amount, + relayFills[relayExecution.relayHash], + fillAmountPreFees, + relayExecution.repaymentChainId, + relayExecution.relay.originChainId, + relayExecution.relay.destinationChainId, + relayExecution.relay.relayerFeePct, + relayExecution.relay.realizedLpFeePct, + relayExecution.relay.depositId, + relayExecution.relay.destinationToken, + msg.sender, + relayExecution.relay.depositor, + relayExecution.relay.recipient, + relayExecution.relay.message, + relayExecutionInfo + ); + } } diff --git a/contracts/test/V2MerkleLib.sol b/contracts/test/V2MerkleLib.sol new file mode 100644 index 000000000..b18af96cb --- /dev/null +++ b/contracts/test/V2MerkleLib.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import "./interfaces/MockV2SpokePoolInterface.sol"; + +/** + * @notice Library to help with merkle roots, proofs, and claims. + */ +library V2MerkleLib { + function verifySlowRelayFulfillment( + bytes32 root, + MockV2SpokePoolInterface.SlowFill memory slowRelayFulfillment, + bytes32[] memory proof + ) internal pure returns (bool) { + return MerkleProof.verify(proof, root, keccak256(abi.encode(slowRelayFulfillment))); + } +} diff --git a/contracts/test/interfaces/MockV2SpokePoolInterface.sol b/contracts/test/interfaces/MockV2SpokePoolInterface.sol new file mode 100644 index 000000000..8f2f8acf2 --- /dev/null +++ b/contracts/test/interfaces/MockV2SpokePoolInterface.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +/** + * @notice Contains common data structures and functions used by all SpokePool implementations. + */ +interface MockV2SpokePoolInterface { + struct RelayData { + address depositor; + address recipient; + address destinationToken; + uint256 amount; + uint256 originChainId; + uint256 destinationChainId; + int64 realizedLpFeePct; + int64 relayerFeePct; + uint32 depositId; + bytes message; + } + + struct RelayExecution { + RelayData relay; + bytes32 relayHash; + int64 updatedRelayerFeePct; + address updatedRecipient; + bytes updatedMessage; + uint256 repaymentChainId; + uint256 maxTokensToSend; + uint256 maxCount; + bool slowFill; + int256 payoutAdjustmentPct; + } + + struct SlowFill { + RelayData relayData; + int256 payoutAdjustmentPct; + } +} diff --git a/package.json b/package.json index 945b344c6..95fbdca3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@across-protocol/contracts-v2", - "version": "2.5.2", + "version": "2.5.3", "author": "UMA Team", "license": "AGPL-3.0-only", "repository": { diff --git a/test/MerkleLib.utils.ts b/test/MerkleLib.utils.ts index 97a31f5dd..cd2dddcc6 100644 --- a/test/MerkleLib.utils.ts +++ b/test/MerkleLib.utils.ts @@ -10,7 +10,7 @@ import { } from "../utils/utils"; import { amountToReturn, repaymentChainId } from "./constants"; import { MerkleTree } from "../utils/MerkleTree"; -import { V3SlowFill } from "./fixtures/SpokePool.Fixture"; +import { SlowFill, V3SlowFill } from "./fixtures/SpokePool.Fixture"; export interface PoolRebalanceLeaf { chainId: BigNumber; groupIndex: BigNumber; @@ -135,6 +135,14 @@ export async function constructSingleChainTree(token: string, scalingSize = 1, r return { tokensSendToL2, realizedLpFees, leaves, tree }; } +export async function buildSlowRelayTree(slowFills: SlowFill[]) { + const paramType = await getParamType("MerkleLibTest", "verifySlowRelayFulfillment", "slowFill"); + const hashFn = (input: SlowFill) => { + return keccak256(defaultAbiCoder.encode([paramType!], [input])); + }; + return new MerkleTree(slowFills, hashFn); +} + export async function buildV3SlowRelayTree(slowFills: V3SlowFill[]) { const paramType = await getParamType("MerkleLibTest", "verifyV3SlowRelayFulfillment", "slowFill"); const hashFn = (input: V3SlowFill) => { diff --git a/test/fixtures/SpokePool.Fixture.ts b/test/fixtures/SpokePool.Fixture.ts index 72bd74248..97ba0bdfd 100644 --- a/test/fixtures/SpokePool.Fixture.ts +++ b/test/fixtures/SpokePool.Fixture.ts @@ -80,6 +80,107 @@ export async function enableRoutes(spokePool: Contract, routes: DepositRoute[]) } } +export async function depositV2( + spokePool: Contract, + token: Contract, + recipient: SignerWithAddress, + depositor: SignerWithAddress, + destinationChainId: number = consts.destinationChainId, + amount = consts.amountToDeposit, + relayerFeePct = consts.depositRelayerFeePct, + quoteTimestamp?: number, + message?: string +): Promise | null> { + await spokePool.connect(depositor).depositV2( + ...getDepositParams({ + recipient: recipient.address, + originToken: token.address, + amount, + destinationChainId, + relayerFeePct, + quoteTimestamp: quoteTimestamp ?? (await spokePool.getCurrentTime()).toNumber(), + message, + }) + ); + const [events, originChainId] = await Promise.all([ + spokePool.queryFilter(spokePool.filters.FundsDeposited()), + spokePool.chainId(), + ]); + + const lastEvent = events[events.length - 1]; + return lastEvent.args === undefined + ? null + : { + amount: lastEvent.args.amount, + originChainId: Number(originChainId), + destinationChainId: Number(lastEvent.args.destinationChainId), + relayerFeePct: lastEvent.args.relayerFeePct, + depositId: lastEvent.args.depositId, + quoteTimestamp: lastEvent.args.quoteTimestamp, + originToken: lastEvent.args.originToken, + recipient: lastEvent.args.recipient, + depositor: lastEvent.args.depositor, + message: lastEvent.args.message, + }; +} +export async function fillRelay( + spokePool: Contract, + destErc20: Contract | string, + recipient: SignerWithAddress, + depositor: SignerWithAddress, + relayer: SignerWithAddress, + depositId = consts.firstDepositId, + originChainId = consts.originChainId, + depositAmount = consts.amountToDeposit, + amountToRelay = consts.amountToRelay, + realizedLpFeePct = consts.realizedLpFeePct, + relayerFeePct = consts.depositRelayerFeePct +) { + await spokePool + .connect(relayer) + .fillRelay( + ...getFillRelayParams( + getRelayHash( + depositor.address, + recipient.address, + depositId, + originChainId, + consts.destinationChainId, + (destErc20 as Contract).address ?? (destErc20 as string), + depositAmount, + realizedLpFeePct, + relayerFeePct + ).relayData, + amountToRelay, + consts.repaymentChainId + ) + ); + const [events, destinationChainId] = await Promise.all([ + spokePool.queryFilter(spokePool.filters.FilledRelay()), + spokePool.chainId(), + ]); + const lastEvent = events[events.length - 1]; + if (lastEvent.args) + return { + amount: lastEvent.args.amount, + totalFilledAmount: lastEvent.args.totalFilledAmount, + fillAmount: lastEvent.args.fillAmount, + repaymentChainId: Number(lastEvent.args.repaymentChainId), + originChainId: Number(lastEvent.args.originChainId), + relayerFeePct: lastEvent.args.relayerFeePct, + appliedRelayerFeePct: lastEvent.args.appliedRelayerFeePct, + realizedLpFeePct: lastEvent.args.realizedLpFeePct, + depositId: lastEvent.args.depositId, + destinationToken: lastEvent.args.destinationToken, + relayer: lastEvent.args.relayer, + depositor: lastEvent.args.depositor, + recipient: lastEvent.args.recipient, + isSlowRelay: lastEvent.args.isSlowRelay, + destinationChainId: Number(destinationChainId), + }; + else return null; +} + export interface RelayData { depositor: string; recipient: string; @@ -129,12 +230,53 @@ export const enum FillStatus { Filled, } +export interface SlowFill { + relayData: RelayData; + payoutAdjustmentPct: BigNumber; +} + export interface V3SlowFill { relayData: V3RelayData; chainId: number; updatedOutputAmount: BigNumber; } +export function getRelayHash( + _depositor: string, + _recipient: string, + _depositId: number, + _originChainId: number, + _destinationChainId: number, + _destinationToken: string, + _amount?: BigNumber, + _realizedLpFeePct?: BigNumber, + _relayerFeePct?: BigNumber, + _message?: string +): { relayHash: string; relayData: RelayData } { + const relayData = { + depositor: _depositor, + recipient: _recipient, + destinationToken: _destinationToken, + amount: _amount || consts.amountToDeposit, + originChainId: _originChainId.toString(), + destinationChainId: _destinationChainId.toString(), + realizedLpFeePct: _realizedLpFeePct || consts.realizedLpFeePct, + relayerFeePct: _relayerFeePct || consts.depositRelayerFeePct, + depositId: _depositId.toString(), + message: _message || "0x", + }; + + const relayHash = ethers.utils.keccak256( + defaultAbiCoder.encode( + [ + "tuple(address depositor, address recipient, address destinationToken, uint256 amount, uint256 originChainId, uint256 destinationChainId, int64 realizedLpFeePct, int64 relayerFeePct, uint32 depositId, bytes message)", + ], + [relayData] + ) + ); + return { relayHash, relayData }; +} + export function getV3RelayHash(relayData: V3RelayData, destinationChainId: number): string { return ethers.utils.keccak256( defaultAbiCoder.encode( @@ -169,6 +311,130 @@ export function getDepositParams(args: { ]; } +export function getFillRelayParams( + _relayData: RelayData, + _maxTokensToSend: BigNumber, + _repaymentChain?: number, + _maxCount?: BigNumber +): string[] { + return [ + _relayData.depositor, + _relayData.recipient, + _relayData.destinationToken, + _relayData.amount.toString(), + _maxTokensToSend.toString(), + _repaymentChain ? _repaymentChain.toString() : consts.repaymentChainId.toString(), + _relayData.originChainId, + _relayData.realizedLpFeePct.toString(), + _relayData.relayerFeePct.toString(), + _relayData.depositId, + _relayData.message || "0x", + _maxCount ? _maxCount.toString() : consts.maxUint256.toString(), + ]; +} + +export function getFillRelayUpdatedFeeParams( + _relayData: RelayData, + _maxTokensToSend: BigNumber, + _updatedFee: BigNumber, + _signature: string, + _repaymentChain?: number, + _updatedRecipient?: string, + _updatedMessage?: string, + _maxCount?: BigNumber +): string[] { + return [ + _relayData.depositor, + _relayData.recipient, + _updatedRecipient || _relayData.recipient, + _relayData.destinationToken, + _relayData.amount.toString(), + _maxTokensToSend.toString(), + _repaymentChain ? _repaymentChain.toString() : consts.repaymentChainId.toString(), + _relayData.originChainId, + _relayData.realizedLpFeePct.toString(), + _relayData.relayerFeePct.toString(), + _updatedFee.toString(), + _relayData.depositId, + _relayData.message, + _updatedMessage || _relayData.message, + _signature, + _maxCount ? _maxCount.toString() : consts.maxUint256.toString(), + ]; +} + +export function getExecuteSlowRelayParams( + _depositor: string, + _recipient: string, + _destToken: string, + _amount: BigNumber, + _originChainId: number, + _realizedLpFeePct: BigNumber, + _relayerFeePct: BigNumber, + _depositId: number, + _relayerRefundId: number, + _message: string, + _payoutAdjustment: BigNumber, + _proof: string[] +): (string | string[])[] { + return [ + _depositor, + _recipient, + _destToken, + _amount.toString(), + _originChainId.toString(), + _realizedLpFeePct.toString(), + _relayerFeePct.toString(), + _depositId.toString(), + _relayerRefundId.toString(), + _message, + _payoutAdjustment.toString(), + _proof, + ]; +} + +export interface UpdatedRelayerFeeData { + newRelayerFeePct: string; + depositorMessageHash: string; + depositorSignature: string; +} +export async function modifyRelayHelper( + modifiedRelayerFeePct: BigNumber, + depositId: string, + originChainId: string, + depositor: SignerWithAddress, + updatedRecipient: string, + updatedMessage: string +): Promise<{ signature: string }> { + const typedData = { + types: { + UpdateDepositDetails: [ + { name: "depositId", type: "uint32" }, + { name: "originChainId", type: "uint256" }, + { name: "updatedRelayerFeePct", type: "int64" }, + { name: "updatedRecipient", type: "address" }, + { name: "updatedMessage", type: "bytes" }, + ], + }, + domain: { + name: "ACROSS-V2", + version: "1.0.0", + chainId: Number(originChainId), + }, + message: { + depositId, + originChainId, + updatedRelayerFeePct: modifiedRelayerFeePct, + updatedRecipient, + updatedMessage, + }, + }; + const signature = await depositor._signTypedData(typedData.domain, typedData.types, typedData.message); + return { + signature, + }; +} + export async function getUpdatedV3DepositSignature( depositor: SignerWithAddress, depositId: number,