diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index cdaaec7f7..362ef30af 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -60,7 +60,12 @@ abstract contract SpokePool is WETH9Interface private DEPRECATED_wrappedNativeToken; uint32 private DEPRECATED_depositQuoteTimeBuffer; - // Count of deposits is used to construct a unique deposit identifier for this spoke pool. + // Count of deposits is used to construct a unique deposit identifier for this spoke pool. This value + // gets emitted and incremented on each depositV3 call. Because its a uint32, it will get implicitly cast to + // uint256 in the emitted V3FundsDeposited event by setting its most significant bits to 0. + // This variable name `numberOfDeposits` should ideally be re-named to + // depositNonceCounter or something similar because its not a true representation of the number of deposits + // because `unsafeDepositV3` can be called directly and bypass this increment. uint32 public numberOfDeposits; // Whether deposits and fills are disabled. @@ -135,12 +140,12 @@ abstract contract SpokePool is bytes32 public constant UPDATE_V3_DEPOSIT_DETAILS_HASH = keccak256( - "UpdateDepositDetails(uint32 depositId,uint256 originChainId,uint256 updatedOutputAmount,bytes32 updatedRecipient,bytes updatedMessage)" + "UpdateDepositDetails(uint256 depositId,uint256 originChainId,uint256 updatedOutputAmount,bytes32 updatedRecipient,bytes updatedMessage)" ); bytes32 public constant UPDATE_V3_DEPOSIT_ADDRESS_OVERLOAD_DETAILS_HASH = keccak256( - "UpdateDepositDetails(uint32 depositId,uint256 originChainId,uint256 updatedOutputAmount,address updatedRecipient,bytes updatedMessage)" + "UpdateDepositDetails(uint256 depositId,uint256 originChainId,uint256 updatedOutputAmount,address updatedRecipient,bytes updatedMessage)" ); // Default chain Id used to signify that no repayment is requested, for example when executing a slow fill. @@ -487,7 +492,7 @@ abstract contract SpokePool is * the fill will revert on the destination chain. Must be set between [currentTime, currentTime + fillDeadlineBuffer] * where currentTime is block.timestamp on this chain or this transaction will revert. * @param exclusivityParameter This value is used to set the exclusivity deadline timestamp in the emitted deposit - * event. Before this destinationchain timestamp, only the exclusiveRelayer (if set to a non-zero address), + * event. Before this destination chain timestamp, only the exclusiveRelayer (if set to a non-zero address), * can fill this deposit. There are three ways to use this parameter: * 1. NO EXCLUSIVITY: If this value is set to 0, then a timestamp of 0 will be emitted, * meaning that there is no exclusivity period. @@ -514,6 +519,13 @@ abstract contract SpokePool is uint32 exclusivityParameter, bytes calldata message ) public payable override nonReentrant unpausedDeposits { + // Increment deposit nonce variable `numberOfDeposits` so that deposit ID for this deposit on this + // spoke pool is unique. This variable `numberOfDeposits` should ideally be re-named to + // depositNonceCounter or something similar because its not a true representation of the number of deposits + // because `unsafeDepositV3` can be called directly and bypass this increment. + // The `numberOfDeposits` is a uint32 that will get implicitly cast to uint256 by setting the + // most significant bits to 0, which creates very little chance this an unsafe deposit ID collides + // with a safe deposit ID. DepositV3Params memory params = DepositV3Params({ depositor: depositor, recipient: recipient, @@ -523,7 +535,7 @@ abstract contract SpokePool is outputAmount: outputAmount, destinationChainId: destinationChainId, exclusiveRelayer: exclusiveRelayer, - depositId: numberOfDeposits++, // Increment count of deposits so that deposit ID for this spoke pool is unique. + depositId: numberOfDeposits++, quoteTimestamp: quoteTimestamp, fillDeadline: fillDeadline, exclusivityParameter: exclusivityParameter, @@ -563,8 +575,17 @@ abstract contract SpokePool is * @param fillDeadline The deadline for the relayer to fill the deposit. After this destination chain timestamp, the fill will * revert on the destination chain. Must be set between [currentTime, currentTime + fillDeadlineBuffer] where currentTime * is block.timestamp on this chain. - * @param exclusivityPeriod Added to the current time to set the exclusive relayer deadline. After this timestamp, - * anyone can fill the deposit. + * @param exclusivityParameter This value is used to set the exclusivity deadline timestamp in the emitted deposit + * event. Before this destination chain timestamp, only the exclusiveRelayer (if set to a non-zero address), + * can fill this deposit. There are three ways to use this parameter: + * 1. NO EXCLUSIVITY: If this value is set to 0, then a timestamp of 0 will be emitted, + * meaning that there is no exclusivity period. + * 2. OFFSET: If this value is less than MAX_EXCLUSIVITY_PERIOD_SECONDS, then add this value to + * the block.timestamp to derive the exclusive relayer deadline. Note that using the parameter in this way + * will expose the filler of the deposit to the risk that the block.timestamp of this event gets changed + * due to a chain-reorg, which would also change the exclusivity timestamp. + * 3. TIMESTAMP: Otherwise, set this value as the exclusivity deadline timestamp. + * which is the deadline for the exclusiveRelayer to fill the deposit. * @param message The message to send to the recipient on the destination chain if the recipient is a contract. If the * message is not empty, the recipient contract must implement `handleV3AcrossMessage()` or the fill will revert. */ @@ -579,7 +600,7 @@ abstract contract SpokePool is address exclusiveRelayer, uint32 quoteTimestamp, uint32 fillDeadline, - uint32 exclusivityPeriod, + uint32 exclusivityParameter, bytes calldata message ) public payable override { depositV3( @@ -593,11 +614,121 @@ abstract contract SpokePool is exclusiveRelayer.toBytes32(), quoteTimestamp, fillDeadline, - exclusivityPeriod, + exclusivityParameter, message ); } + /** + * @notice An overloaded version of `unsafeDepositV3` that accepts `address` types for backward compatibility. * + * @dev This version mirrors the original `unsafeDepositV3` function, but uses `address` types for `depositor`, `recipient`, + * `inputToken`, `outputToken`, and `exclusiveRelayer` for compatibility with contracts using the `address` type. + * + * The key functionality and logic remain identical, ensuring interoperability across both versions. + */ + function unsafeDepositV3( + address depositor, + address recipient, + address inputToken, + address outputToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 destinationChainId, + address exclusiveRelayer, + uint256 depositNonce, + uint32 quoteTimestamp, + uint32 fillDeadline, + uint32 exclusivityParameter, + bytes calldata message + ) public payable { + unsafeDepositV3( + depositor.toBytes32(), + recipient.toBytes32(), + inputToken.toBytes32(), + outputToken.toBytes32(), + inputAmount, + outputAmount, + destinationChainId, + exclusiveRelayer.toBytes32(), + depositNonce, + quoteTimestamp, + fillDeadline, + exclusivityParameter, + message + ); + } + + /** + * @notice See depositV3 for details. This function is identical to depositV3 except that it does not use the + * global deposit ID counter as a deposit nonce, instead allowing the caller to pass in a deposit nonce. This + * function is designed to be used by anyone who wants to pre-compute their resultant relay data hash, which + * could be useful for filling a deposit faster and avoiding any risk of a relay hash unexpectedly changing + * due to another deposit front-running this one and incrementing the global deposit ID counter. + * @dev This is labeled "unsafe" because there is no guarantee that the depositId emitted in the resultant + * V3FundsDeposited event is unique which means that the + * corresponding fill might collide with an existing relay hash on the destination chain SpokePool, + * which would make this deposit unfillable. In this case, the depositor would subsequently receive a refund + * of `inputAmount` of `inputToken` on the origin chain after the fill deadline. + * @dev On the destination chain, the hash of the deposit data will be used to uniquely identify this deposit, so + * modifying any params in it will result in a different hash and a different deposit. The hash will comprise + * all parameters to this function along with this chain's chainId(). Relayers are only refunded for filling + * deposits with deposit hashes that map exactly to the one emitted by this contract. + * @param depositNonce The nonce that uniquely identifies this deposit. This function will combine this parameter + * with the msg.sender address to create a unique uint256 depositNonce and ensure that the msg.sender cannot + * use this function to front-run another depositor's unsafe deposit. This function guarantees that the resultant + * deposit nonce will not collide with a safe uint256 deposit nonce whose 24 most significant bytes are always 0. + * @param depositor See identically named parameter in depositV3() comments. + * @param recipient See identically named parameter in depositV3() comments. + * @param inputToken See identically named parameter in depositV3() comments. + * @param outputToken See identically named parameter in depositV3() comments. + * @param inputAmount See identically named parameter in depositV3() comments. + * @param outputAmount See identically named parameter in depositV3() comments. + * @param destinationChainId See identically named parameter in depositV3() comments. + * @param exclusiveRelayer See identically named parameter in depositV3() comments. + * @param quoteTimestamp See identically named parameter in depositV3() comments. + * @param fillDeadline See identically named parameter in depositV3() comments. + * @param exclusivityParameter See identically named parameter in depositV3() comments. + * @param message See identically named parameter in depositV3() comments. + */ + function unsafeDepositV3( + bytes32 depositor, + bytes32 recipient, + bytes32 inputToken, + bytes32 outputToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 destinationChainId, + bytes32 exclusiveRelayer, + uint256 depositNonce, + uint32 quoteTimestamp, + uint32 fillDeadline, + uint32 exclusivityParameter, + bytes calldata message + ) public payable nonReentrant unpausedDeposits { + // @dev Create the uint256 deposit ID by concatenating the msg.sender and depositor address with the inputted + // depositNonce parameter. The resultant 32 byte string will be hashed and then casted to an "unsafe" + // uint256 deposit ID. The probability that the resultant ID collides with a "safe" deposit ID is + // equal to the chance that the first 28 bytes of the hash are 0, which is too small for us to consider. + + uint256 depositId = getUnsafeDepositId(msg.sender, depositor, depositNonce); + DepositV3Params memory params = DepositV3Params({ + depositor: depositor, + recipient: recipient, + inputToken: inputToken, + outputToken: outputToken, + inputAmount: inputAmount, + outputAmount: outputAmount, + destinationChainId: destinationChainId, + exclusiveRelayer: exclusiveRelayer, + depositId: depositId, + quoteTimestamp: quoteTimestamp, + fillDeadline: fillDeadline, + exclusivityParameter: exclusivityParameter, + message: message + }); + _depositV3(params); + } + /** * @notice Submits deposit and sets quoteTimestamp to current Time. Sets fill and exclusivity * deadlines as offsets added to the current time. This function is designed to be called by users @@ -740,7 +871,7 @@ abstract contract SpokePool is */ function speedUpV3Deposit( bytes32 depositor, - uint32 depositId, + uint256 depositId, uint256 updatedOutputAmount, bytes32 updatedRecipient, bytes calldata updatedMessage, @@ -794,7 +925,7 @@ abstract contract SpokePool is */ function speedUpV3Deposit( address depositor, - uint32 depositId, + uint256 depositId, uint256 updatedOutputAmount, address updatedRecipient, bytes calldata updatedMessage, @@ -1167,6 +1298,24 @@ abstract contract SpokePool is return block.timestamp; // solhint-disable-line not-rely-on-time } + /** + * @notice Returns the deposit ID for an unsafe deposit. This function is used to compute the deposit ID + * in unsafeDepositV3 and is provided as a convenience. + * @dev msgSenderand depositor are both used as inputs to allow passthrough depositors to create unique + * deposit hash spaces for unique depositors. + * @param msgSender The caller of the transaction used as input to produce the deposit ID. + * @param depositor The depositor address used as input to produce the deposit ID. + * @param depositNonce The nonce used as input to produce the deposit ID. + * @return The deposit ID for the unsafe deposit. + */ + function getUnsafeDepositId( + address msgSender, + bytes32 depositor, + uint256 depositNonce + ) public pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(msgSender, depositor, depositNonce))); + } + function getRelayerRefund(address l2TokenAddress, address refundAddress) public view returns (uint256) { return relayerRefund[l2TokenAddress][refundAddress]; } @@ -1429,7 +1578,7 @@ abstract contract SpokePool is function _verifyUpdateV3DepositMessage( address depositor, - uint32 depositId, + uint256 depositId, uint256 originChainId, uint256 updatedOutputAmount, bytes32 updatedRecipient, diff --git a/contracts/erc7683/ERC7683OrderDepositor.sol b/contracts/erc7683/ERC7683OrderDepositor.sol index 05e274ffe..a6bcd4dcd 100644 --- a/contracts/erc7683/ERC7683OrderDepositor.sol +++ b/contracts/erc7683/ERC7683OrderDepositor.sol @@ -73,6 +73,7 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { acrossOrderData.outputAmount, acrossOrderData.destinationChainId, acrossOriginFillerData.exclusiveRelayer, + acrossOrderData.depositNonce, // Note: simplifying assumption to avoid quote timestamps that cause orders to expire before the deadline. SafeCast.toUint32(order.openDeadline - QUOTE_BEFORE_DEADLINE), order.fillDeadline, @@ -103,6 +104,7 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { acrossOrderData.outputAmount, acrossOrderData.destinationChainId, acrossOrderData.exclusiveRelayer, + acrossOrderData.depositNonce, // Note: simplifying assumption to avoid the order type having to bake in the quote timestamp. SafeCast.toUint32(block.timestamp), order.fillDeadline, @@ -161,6 +163,17 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { return SafeCast.toUint32(block.timestamp); // solhint-disable-line not-rely-on-time } + /** + * @notice Convenience method to compute the Across depositId for orders sent through 7683. + * @dev if a 0 depositNonce is used, the depositId will not be deterministic (meaning it can change depending on + * when the open txn is mined), but you will be safe from collisions. See the unsafeDepositV3 method on SpokePool + * for more details on how to choose between deterministic and non-deterministic. + * @param depositNonce the depositNonce field in the order. + * @param depositor the sender or signer of the order. + * @return the resulting Across depositId. + */ + function computeDepositId(uint256 depositNonce, address depositor) public view virtual returns (uint256); + function _resolveFor(GaslessCrossChainOrder calldata order, bytes calldata fillerData) internal view @@ -223,7 +236,7 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { relayData.inputAmount = acrossOrderData.inputAmount; relayData.outputAmount = acrossOrderData.outputAmount; relayData.originChainId = block.chainid; - relayData.depositId = _currentDepositId(); + relayData.depositId = computeDepositId(acrossOrderData.depositNonce, order.user); relayData.fillDeadline = order.fillDeadline; relayData.exclusivityDeadline = acrossOrderData.exclusivityPeriod; relayData.message = acrossOrderData.message; @@ -287,7 +300,7 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { relayData.inputAmount = acrossOrderData.inputAmount; relayData.outputAmount = acrossOrderData.outputAmount; relayData.originChainId = block.chainid; - relayData.depositId = _currentDepositId(); + relayData.depositId = computeDepositId(acrossOrderData.depositNonce, msg.sender); relayData.fillDeadline = order.fillDeadline; relayData.exclusivityDeadline = acrossOrderData.exclusivityPeriod; relayData.message = acrossOrderData.message; @@ -357,13 +370,12 @@ abstract contract ERC7683OrderDepositor is IOriginSettler { uint256 outputAmount, uint256 destinationChainId, address exclusiveRelayer, + uint256 depositNonce, uint32 quoteTimestamp, uint32 fillDeadline, uint32 exclusivityPeriod, bytes memory message ) internal virtual; - function _currentDepositId() internal view virtual returns (uint32); - function _destinationSettler(uint256 chainId) internal view virtual returns (address); } diff --git a/contracts/erc7683/ERC7683OrderDepositorExternal.sol b/contracts/erc7683/ERC7683OrderDepositorExternal.sol index 9cefb19f1..82371f9e1 100644 --- a/contracts/erc7683/ERC7683OrderDepositorExternal.sol +++ b/contracts/erc7683/ERC7683OrderDepositorExternal.sol @@ -15,6 +15,7 @@ import "@uma/core/contracts/common/implementation/MultiCaller.sol"; */ contract ERC7683OrderDepositorExternal is ERC7683OrderDepositor, Ownable, MultiCaller { using SafeERC20 for IERC20; + using AddressToBytes32 for address; event SetDestinationSettler( uint256 indexed chainId, @@ -50,6 +51,7 @@ contract ERC7683OrderDepositorExternal is ERC7683OrderDepositor, Ownable, MultiC uint256 outputAmount, uint256 destinationChainId, address exclusiveRelayer, + uint256 depositNonce, uint32 quoteTimestamp, uint32 fillDeadline, uint32 exclusivityDeadline, @@ -57,24 +59,45 @@ contract ERC7683OrderDepositorExternal is ERC7683OrderDepositor, Ownable, MultiC ) internal override { IERC20(inputToken).forceApprove(address(SPOKE_POOL), inputAmount); - SPOKE_POOL.depositV3( - depositor, - recipient, - inputToken, - outputToken, - inputAmount, - outputAmount, - destinationChainId, - exclusiveRelayer, - quoteTimestamp, - fillDeadline, - exclusivityDeadline, - message - ); + if (depositNonce == 0) { + SPOKE_POOL.depositV3( + depositor, + recipient, + inputToken, + outputToken, + inputAmount, + outputAmount, + destinationChainId, + exclusiveRelayer, + quoteTimestamp, + fillDeadline, + exclusivityDeadline, + message + ); + } else { + SPOKE_POOL.unsafeDepositV3( + depositor, + recipient, + inputToken, + outputToken, + inputAmount, + outputAmount, + destinationChainId, + exclusiveRelayer, + depositNonce, + quoteTimestamp, + fillDeadline, + exclusivityDeadline, + message + ); + } } - function _currentDepositId() internal view override returns (uint32) { - return SPOKE_POOL.numberOfDeposits(); + function computeDepositId(uint256 depositNonce, address depositor) public view override returns (uint256) { + return + depositNonce == 0 + ? SPOKE_POOL.numberOfDeposits() + : SPOKE_POOL.getUnsafeDepositId(address(this), depositor.toBytes32(), depositNonce); } function _destinationSettler(uint256 chainId) internal view override returns (address) { diff --git a/contracts/erc7683/ERC7683Permit2Lib.sol b/contracts/erc7683/ERC7683Permit2Lib.sol index 6e1a47236..37c68946f 100644 --- a/contracts/erc7683/ERC7683Permit2Lib.sol +++ b/contracts/erc7683/ERC7683Permit2Lib.sol @@ -13,6 +13,7 @@ struct AcrossOrderData { uint256 destinationChainId; bytes32 recipient; address exclusiveRelayer; + uint256 depositNonce; uint32 exclusivityPeriod; bytes message; } @@ -34,6 +35,7 @@ bytes constant ACROSS_ORDER_DATA_TYPE = abi.encodePacked( "uint256 destinationChainId,", "bytes32 recipient,", "address exclusiveRelayer," + "uint256 depositNonce,", "uint32 exclusivityPeriod,", "bytes message)" ); diff --git a/contracts/interfaces/V3SpokePoolInterface.sol b/contracts/interfaces/V3SpokePoolInterface.sol index 15fa4fa8e..6f5a4141d 100644 --- a/contracts/interfaces/V3SpokePoolInterface.sol +++ b/contracts/interfaces/V3SpokePoolInterface.sol @@ -53,7 +53,7 @@ interface V3SpokePoolInterface { // Origin chain id. uint256 originChainId; // The id uniquely identifying this deposit on the origin chain. - uint32 depositId; + uint256 depositId; // The timestamp on the destination chain after which this deposit can no longer be filled. uint32 fillDeadline; // The timestamp on the destination chain after which any relayer can fill the deposit. @@ -102,7 +102,7 @@ interface V3SpokePoolInterface { uint256 outputAmount; uint256 destinationChainId; bytes32 exclusiveRelayer; - uint32 depositId; + uint256 depositId; uint32 quoteTimestamp; uint32 fillDeadline; uint32 exclusivityParameter; @@ -119,7 +119,7 @@ interface V3SpokePoolInterface { uint256 inputAmount, uint256 outputAmount, uint256 indexed destinationChainId, - uint32 indexed depositId, + uint256 indexed depositId, uint32 quoteTimestamp, uint32 fillDeadline, uint32 exclusivityDeadline, @@ -131,7 +131,7 @@ interface V3SpokePoolInterface { event RequestedSpeedUpV3Deposit( uint256 updatedOutputAmount, - uint32 indexed depositId, + uint256 indexed depositId, bytes32 indexed depositor, bytes32 updatedRecipient, bytes updatedMessage, @@ -145,7 +145,7 @@ interface V3SpokePoolInterface { uint256 outputAmount, uint256 repaymentChainId, uint256 indexed originChainId, - uint32 indexed depositId, + uint256 indexed depositId, uint32 fillDeadline, uint32 exclusivityDeadline, bytes32 exclusiveRelayer, @@ -162,7 +162,7 @@ interface V3SpokePoolInterface { uint256 inputAmount, uint256 outputAmount, uint256 indexed originChainId, - uint32 indexed depositId, + uint256 indexed depositId, uint32 fillDeadline, uint32 exclusivityDeadline, bytes32 exclusiveRelayer, @@ -228,7 +228,7 @@ interface V3SpokePoolInterface { function speedUpV3Deposit( bytes32 depositor, - uint32 depositId, + uint256 depositId, uint256 updatedOutputAmount, bytes32 updatedRecipient, bytes calldata updatedMessage, diff --git a/contracts/test/MockSpokePool.sol b/contracts/test/MockSpokePool.sol index b9efea1b2..42bae1e24 100644 --- a/contracts/test/MockSpokePool.sol +++ b/contracts/test/MockSpokePool.sol @@ -24,7 +24,7 @@ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeabl bytes32 public constant UPDATE_DEPOSIT_DETAILS_HASH = keccak256( - "UpdateDepositDetails(uint32 depositId,uint256 originChainId,int64 updatedRelayerFeePct,address updatedRecipient,bytes updatedMessage)" + "UpdateDepositDetails(uint256 depositId,uint256 originChainId,int64 updatedRelayerFeePct,address updatedRecipient,bytes updatedMessage)" ); event BridgedToHubPool(uint256 amount, address token); @@ -60,7 +60,7 @@ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeabl function _verifyUpdateDepositMessage( address depositor, - uint32 depositId, + uint256 depositId, uint256 originChainId, int64 updatedRelayerFeePct, bytes32 updatedRecipient, @@ -87,7 +87,7 @@ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeabl function verifyUpdateV3DepositMessage( bytes32 depositor, - uint32 depositId, + uint256 depositId, uint256 originChainId, uint256 updatedOutputAmount, bytes32 updatedRecipient, @@ -109,7 +109,7 @@ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeabl function verifyUpdateV3DepositMessage( address depositor, - uint32 depositId, + uint256 depositId, uint256 originChainId, uint256 updatedOutputAmount, address updatedRecipient, diff --git a/test/evm/hardhat/SpokePool.Deposit.ts b/test/evm/hardhat/SpokePool.Deposit.ts index 96fdc1c9a..cf7db5718 100644 --- a/test/evm/hardhat/SpokePool.Deposit.ts +++ b/test/evm/hardhat/SpokePool.Deposit.ts @@ -372,6 +372,28 @@ describe("SpokePool Depositor Logic", async function () { _relayData.message, ]; } + function getUnsafeDepositArgsFromRelayData( + _relayData: V3RelayData, + _depositId: string, + _destinationChainId = destinationChainId, + _quoteTimestamp = quoteTimestamp + ) { + return [ + addressToBytes(_relayData.depositor), + addressToBytes(_relayData.recipient), + addressToBytes(_relayData.inputToken), + addressToBytes(_relayData.outputToken), + _relayData.inputAmount, + _relayData.outputAmount, + _destinationChainId, + addressToBytes(_relayData.exclusiveRelayer), + _depositId, + _quoteTimestamp, + _relayData.fillDeadline, + _relayData.exclusivityDeadline, + _relayData.message, + ]; + } beforeEach(async function () { relayData = { depositor: addressToBytes(depositor.address), @@ -804,6 +826,44 @@ describe("SpokePool Depositor Logic", async function () { "ReentrancyGuard: reentrant call" ); }); + it("unsafe deposit ID", async function () { + // new deposit ID should be the uint256 equivalent of the keccak256 hash of packed {msg.sender, depositor, forcedDepositId}. + const forcedDepositId = "99"; + const expectedDepositId = BigNumber.from( + ethers.utils.solidityKeccak256( + ["address", "bytes32", "uint256"], + [depositor.address, addressToBytes(recipient.address), forcedDepositId] + ) + ); + expect( + await spokePool.getUnsafeDepositId(depositor.address, addressToBytes(recipient.address), forcedDepositId) + ).to.equal(expectedDepositId); + // Note: we deliberately set the depositor != msg.sender to test that the hashing algorithm correctly includes + // both addresses in the hash. + await expect( + spokePool + .connect(depositor) + [SpokePoolFuncs.unsafeDepositV3Bytes]( + ...getUnsafeDepositArgsFromRelayData({ ...relayData, depositor: recipient.address }, forcedDepositId) + ) + ) + .to.emit(spokePool, "V3FundsDeposited") + .withArgs( + relayData.inputToken, + relayData.outputToken, + relayData.inputAmount, + relayData.outputAmount, + destinationChainId, + expectedDepositId, + quoteTimestamp, + relayData.fillDeadline, + 0, + addressToBytes(recipient.address), + relayData.recipient, + relayData.exclusiveRelayer, + relayData.message + ); + }); }); describe("speed up V3 deposit", function () { const updatedOutputAmount = amountToDeposit.add(1); diff --git a/test/evm/hardhat/constants.ts b/test/evm/hardhat/constants.ts index c32b60979..df01742e0 100644 --- a/test/evm/hardhat/constants.ts +++ b/test/evm/hardhat/constants.ts @@ -111,6 +111,8 @@ export const sampleRateModel = { }; export const SpokePoolFuncs = { + unsafeDepositV3Bytes: + "unsafeDepositV3(bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256,bytes32,uint256,uint32,uint32,uint32,bytes)", depositV3Bytes: "depositV3(bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256,bytes32,uint32,uint32,uint32,bytes)", depositV3Address: @@ -119,9 +121,10 @@ export const SpokePoolFuncs = { "depositV3Now(bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256,bytes32,uint32,uint32,bytes)", depositV3NowAddress: "depositV3Now(address,address,address,address,uint256,uint256,uint256,address,uint32,uint32,bytes)", - speedUpV3DepositBytes: "speedUpV3Deposit(bytes32,uint32,uint256,bytes32,bytes,bytes)", - speedUpV3DepositAddress: "speedUpV3Deposit(address,uint32,uint256,address,bytes,bytes)", - verifyUpdateV3DepositMessageBytes: "verifyUpdateV3DepositMessage(bytes32,uint32,uint256,uint256,bytes32,bytes,bytes)", + speedUpV3DepositBytes: "speedUpV3Deposit(bytes32,uint256,uint256,bytes32,bytes,bytes)", + speedUpV3DepositAddress: "speedUpV3Deposit(address,uint256,uint256,address,bytes,bytes)", + verifyUpdateV3DepositMessageBytes: + "verifyUpdateV3DepositMessage(bytes32,uint256,uint256,uint256,bytes32,bytes,bytes)", verifyUpdateV3DepositMessageAddress: - "verifyUpdateV3DepositMessage(address,uint32,uint256,uint256,address,bytes,bytes)", + "verifyUpdateV3DepositMessage(address,uint256,uint256,uint256,address,bytes,bytes)", }; diff --git a/test/evm/hardhat/fixtures/SpokePool.Fixture.ts b/test/evm/hardhat/fixtures/SpokePool.Fixture.ts index 56f39a474..ec9d8e6db 100644 --- a/test/evm/hardhat/fixtures/SpokePool.Fixture.ts +++ b/test/evm/hardhat/fixtures/SpokePool.Fixture.ts @@ -346,7 +346,7 @@ export async function getUpdatedV3DepositSignature( const typedData = { types: { UpdateDepositDetails: [ - { name: "depositId", type: "uint32" }, + { name: "depositId", type: "uint256" }, { name: "originChainId", type: "uint256" }, { name: "updatedOutputAmount", type: "uint256" }, { name: "updatedRecipient", type: isAddressOverload ? "address" : "bytes32" },