@@ -125,7 +125,7 @@ abstract contract SpokePool is
125125
126126 bytes32 public constant UPDATE_V3_DEPOSIT_DETAILS_HASH =
127127 keccak256 (
128- "UpdateDepositDetails(uint32 depositId,uint256 originChainId,uint256 updatedOutputAmount,address updatedRecipient,bytes updatedMessage) "
128+ "UpdateDepositDetails(uint256 depositId,uint256 originChainId,uint256 updatedOutputAmount,address updatedRecipient,bytes updatedMessage) "
129129 );
130130
131131 // Default chain Id used to signify that no repayment is requested, for example when executing a slow fill.
@@ -549,59 +549,92 @@ abstract contract SpokePool is
549549 uint32 exclusivityPeriod ,
550550 bytes calldata message
551551 ) public payable override nonReentrant unpausedDeposits {
552- // Check that deposit route is enabled for the input token. There are no checks required for the output token
553- // which is pulled from the relayer at fill time and passed through this contract atomically to the recipient.
554- if (! enabledDepositRoutes[inputToken][destinationChainId]) revert DisabledRoute ();
555-
556- // Require that quoteTimestamp has a maximum age so that depositors pay an LP fee based on recent HubPool usage.
557- // It is assumed that cross-chain timestamps are normally loosely in-sync, but clock drift can occur. If the
558- // SpokePool time stalls or lags significantly, it is still possible to make deposits by setting quoteTimestamp
559- // within the configured buffer. The owner should pause deposits/fills if this is undesirable.
560- // This will underflow if quoteTimestamp is more than depositQuoteTimeBuffer;
561- // this is safe but will throw an unintuitive error.
562-
563- // slither-disable-next-line timestamp
564- uint256 currentTime = getCurrentTime ();
565- if (currentTime - quoteTimestamp > depositQuoteTimeBuffer) revert InvalidQuoteTimestamp ();
566-
567- // fillDeadline is relative to the destination chain.
568- // Don’t allow fillDeadline to be more than several bundles into the future.
569- // This limits the maximum required lookback for dataworker and relayer instances.
570- // Also, don't allow fillDeadline to be in the past. This poses a potential UX issue if the destination
571- // chain time keeping and this chain's time keeping are out of sync but is not really a practical hurdle
572- // unless they are significantly out of sync or the depositor is setting very short fill deadlines. This latter
573- // situation won't be a problem for honest users.
574- if (fillDeadline < currentTime || fillDeadline > currentTime + fillDeadlineBuffer) revert InvalidFillDeadline ();
575-
576- // If the address of the origin token is a wrappedNativeToken contract and there is a msg.value with the
577- // transaction then the user is sending the native token. In this case, the native token should be
578- // wrapped.
579- if (inputToken == address (wrappedNativeToken) && msg .value > 0 ) {
580- if (msg .value != inputAmount) revert MsgValueDoesNotMatchInputAmount ();
581- wrappedNativeToken.deposit { value: msg .value }();
582- // Else, it is a normal ERC20. In this case pull the token from the caller as per normal.
583- // Note: this includes the case where the L2 caller has WETH (already wrapped ETH) and wants to bridge them.
584- // In this case the msg.value will be set to 0, indicating a "normal" ERC20 bridging action.
585- } else {
586- // msg.value should be 0 if input token isn't the wrapped native token.
587- if (msg .value != 0 ) revert MsgValueDoesNotMatchInputAmount ();
588- IERC20Upgradeable (inputToken).safeTransferFrom (msg .sender , address (this ), inputAmount);
589- }
590-
591- emit V3FundsDeposited (
552+ _depositV3 (
553+ depositor,
554+ recipient,
592555 inputToken,
593556 outputToken,
594557 inputAmount,
595558 outputAmount,
596559 destinationChainId,
560+ exclusiveRelayer,
597561 // Increment count of deposits so that deposit ID for this spoke pool is unique.
562+ // @dev Implicitly casts from uint32 to uint256 by padding the left-most bytes with zeros. Guarantees
563+ // that the 24 most significant bytes are 0.
598564 numberOfDeposits++ ,
599565 quoteTimestamp,
600566 fillDeadline,
601- uint32 (currentTime) + exclusivityPeriod,
567+ uint32 (getCurrentTime ()) + exclusivityPeriod,
568+ message
569+ );
570+ }
571+
572+ /**
573+ * @notice See depositV3 for details. This function is identical to depositV3 except that it does not use the
574+ * global deposit ID counter as a deposit nonce, instead allowing the caller to pass in a deposit nonce. This
575+ * function is designed to be used by anyone who wants to pre-compute their resultant relay data hash, which
576+ * could be useful for filling a deposit faster and avoiding any risk of a relay hash unexpectedly changing
577+ * due to another deposit front-running this one and incrementing the global deposit ID counter.
578+ * @dev This is labeled "unsafe" because there is no guarantee that the depositId emitted in the resultant
579+ * V3FundsDeposited event is unique which means that the
580+ * corresponding fill might collide with an existing relay hash on the destination chain SpokePool,
581+ * which would make this deposit unfillable. In this case, the depositor would subsequently receive a refund
582+ * of `inputAmount` of `inputToken` on the origin chain after the fill deadline.
583+ * @dev On the destination chain, the hash of the deposit data will be used to uniquely identify this deposit, so
584+ * modifying any params in it will result in a different hash and a different deposit. The hash will comprise
585+ * all parameters to this function along with this chain's chainId(). Relayers are only refunded for filling
586+ * deposits with deposit hashes that map exactly to the one emitted by this contract.
587+ * @param depositNonce The nonce that uniquely identifies this deposit. This function will combine this parameter
588+ * with the msg.sender address to create a unique uint256 depositNonce and ensure that the msg.sender cannot
589+ * use this function to front-run another depositor's unsafe deposit. This function guarantees that the resultant
590+ * deposit nonce will not collide with a safe uint256 deposit nonce whose 24 most significant bytes are always 0.
591+ * @param depositor See identically named parameter in depositV3() comments.
592+ * @param recipient See identically named parameter in depositV3() comments.
593+ * @param inputToken See identically named parameter in depositV3() comments.
594+ * @param outputToken See identically named parameter in depositV3() comments.
595+ * @param inputAmount See identically named parameter in depositV3() comments.
596+ * @param outputAmount See identically named parameter in depositV3() comments.
597+ * @param destinationChainId See identically named parameter in depositV3() comments.
598+ * @param exclusiveRelayer See identically named parameter in depositV3() comments.
599+ * @param quoteTimestamp See identically named parameter in depositV3() comments.
600+ * @param fillDeadline See identically named parameter in depositV3() comments.
601+ * @param exclusivityPeriod See identically named parameter in depositV3() comments.
602+ * @param message See identically named parameter in depositV3() comments.
603+ */
604+ function unsafeDepositV3 (
605+ address depositor ,
606+ address recipient ,
607+ address inputToken ,
608+ address outputToken ,
609+ uint256 inputAmount ,
610+ uint256 outputAmount ,
611+ uint256 destinationChainId ,
612+ address exclusiveRelayer ,
613+ uint256 depositNonce ,
614+ uint32 quoteTimestamp ,
615+ uint32 fillDeadline ,
616+ uint32 exclusivityPeriod ,
617+ bytes calldata message
618+ ) public payable nonReentrant unpausedDeposits {
619+ // @dev Create the uint256 deposit ID by concatenating the msg.sender and depositor address with the inputted
620+ // depositNonce parameter. The resultant 32 byte string will be hashed and then casted to an "unsafe"
621+ // uint256 deposit ID. The probability that the resultant ID collides with a "safe" deposit ID is
622+ // equal to the chance that the first 28 bytes of the hash are 0, which is too small for us to consider.
623+
624+ uint256 depositId = getUnsafeDepositId (msg .sender , depositor, depositNonce);
625+ _depositV3 (
602626 depositor,
603627 recipient,
628+ inputToken,
629+ outputToken,
630+ inputAmount,
631+ outputAmount,
632+ destinationChainId,
604633 exclusiveRelayer,
634+ depositId,
635+ quoteTimestamp,
636+ fillDeadline,
637+ uint32 (getCurrentTime ()) + exclusivityPeriod,
605638 message
606639 );
607640 }
@@ -755,7 +788,7 @@ abstract contract SpokePool is
755788 */
756789 function speedUpV3Deposit (
757790 address depositor ,
758- uint32 depositId ,
791+ uint256 depositId ,
759792 uint256 updatedOutputAmount ,
760793 address updatedRecipient ,
761794 bytes calldata updatedMessage ,
@@ -1084,10 +1117,99 @@ abstract contract SpokePool is
10841117 return block .timestamp ; // solhint-disable-line not-rely-on-time
10851118 }
10861119
1120+ /**
1121+ * @notice Returns the deposit ID for an unsafe deposit. This function is used to compute the deposit ID
1122+ * in unsafeDepositV3 and is provided as a convenience.
1123+ * @dev msgSenderand depositor are both used as inputs to allow passthrough depositors to create unique
1124+ * deposit hash spaces for unique depositors.
1125+ * @param msgSender The caller of the transaction used as input to produce the deposit ID.
1126+ * @param depositor The depositor address used as input to produce the deposit ID.
1127+ * @param depositNonce The nonce used as input to produce the deposit ID.
1128+ * @return The deposit ID for the unsafe deposit.
1129+ */
1130+ function getUnsafeDepositId (
1131+ address msgSender ,
1132+ address depositor ,
1133+ uint256 depositNonce
1134+ ) public pure returns (uint256 ) {
1135+ return uint256 (keccak256 (abi.encodePacked (msgSender, depositor, depositNonce)));
1136+ }
1137+
10871138 /**************************************
10881139 * INTERNAL FUNCTIONS *
10891140 **************************************/
10901141
1142+ function _depositV3 (
1143+ address depositor ,
1144+ address recipient ,
1145+ address inputToken ,
1146+ address outputToken ,
1147+ uint256 inputAmount ,
1148+ uint256 outputAmount ,
1149+ uint256 destinationChainId ,
1150+ address exclusiveRelayer ,
1151+ uint256 depositId ,
1152+ uint32 quoteTimestamp ,
1153+ uint32 fillDeadline ,
1154+ uint32 exclusivityDeadline ,
1155+ bytes calldata message
1156+ ) internal {
1157+ // Check that deposit route is enabled for the input token. There are no checks required for the output token
1158+ // which is pulled from the relayer at fill time and passed through this contract atomically to the recipient.
1159+ if (! enabledDepositRoutes[inputToken][destinationChainId]) revert DisabledRoute ();
1160+
1161+ // Require that quoteTimestamp has a maximum age so that depositors pay an LP fee based on recent HubPool usage.
1162+ // It is assumed that cross-chain timestamps are normally loosely in-sync, but clock drift can occur. If the
1163+ // SpokePool time stalls or lags significantly, it is still possible to make deposits by setting quoteTimestamp
1164+ // within the configured buffer. The owner should pause deposits/fills if this is undesirable.
1165+ // This will underflow if quoteTimestamp is more than depositQuoteTimeBuffer;
1166+ // this is safe but will throw an unintuitive error.
1167+
1168+ // slither-disable-next-line timestamp
1169+ uint256 currentTime = getCurrentTime ();
1170+ if (currentTime - quoteTimestamp > depositQuoteTimeBuffer) revert InvalidQuoteTimestamp ();
1171+
1172+ // fillDeadline is relative to the destination chain.
1173+ // Don’t allow fillDeadline to be more than several bundles into the future.
1174+ // This limits the maximum required lookback for dataworker and relayer instances.
1175+ // Also, don't allow fillDeadline to be in the past. This poses a potential UX issue if the destination
1176+ // chain time keeping and this chain's time keeping are out of sync but is not really a practical hurdle
1177+ // unless they are significantly out of sync or the depositor is setting very short fill deadlines. This latter
1178+ // situation won't be a problem for honest users.
1179+ if (fillDeadline < currentTime || fillDeadline > currentTime + fillDeadlineBuffer) revert InvalidFillDeadline ();
1180+
1181+ // If the address of the origin token is a wrappedNativeToken contract and there is a msg.value with the
1182+ // transaction then the user is sending the native token. In this case, the native token should be
1183+ // wrapped.
1184+ if (inputToken == address (wrappedNativeToken) && msg .value > 0 ) {
1185+ if (msg .value != inputAmount) revert MsgValueDoesNotMatchInputAmount ();
1186+ wrappedNativeToken.deposit { value: msg .value }();
1187+ // Else, it is a normal ERC20. In this case pull the token from the caller as per normal.
1188+ // Note: this includes the case where the L2 caller has WETH (already wrapped ETH) and wants to bridge them.
1189+ // In this case the msg.value will be set to 0, indicating a "normal" ERC20 bridging action.
1190+ } else {
1191+ // msg.value should be 0 if input token isn't the wrapped native token.
1192+ if (msg .value != 0 ) revert MsgValueDoesNotMatchInputAmount ();
1193+ IERC20Upgradeable (inputToken).safeTransferFrom (msg .sender , address (this ), inputAmount);
1194+ }
1195+
1196+ emit V3FundsDeposited (
1197+ inputToken,
1198+ outputToken,
1199+ inputAmount,
1200+ outputAmount,
1201+ destinationChainId,
1202+ depositId,
1203+ quoteTimestamp,
1204+ fillDeadline,
1205+ exclusivityDeadline,
1206+ depositor,
1207+ recipient,
1208+ exclusiveRelayer,
1209+ message
1210+ );
1211+ }
1212+
10911213 function _deposit (
10921214 address depositor ,
10931215 address recipient ,
@@ -1214,7 +1336,7 @@ abstract contract SpokePool is
12141336
12151337 function _verifyUpdateV3DepositMessage (
12161338 address depositor ,
1217- uint32 depositId ,
1339+ uint256 depositId ,
12181340 uint256 originChainId ,
12191341 uint256 updatedOutputAmount ,
12201342 address updatedRecipient ,
0 commit comments