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