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