Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions contracts/Linea_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
*/
contract Linea_SpokePool is SpokePool {
using SafeERC20 for IERC20;
using AddressToBytes32 for address;

/**
* @notice Address of Linea's Canonical Message Service contract on L2.
Expand Down Expand Up @@ -123,8 +122,8 @@ contract Linea_SpokePool is SpokePool {
* @notice Wraps any ETH into WETH before executing base function. This is necessary because SpokePool receives
* ETH over the canonical token bridge instead of WETH.
*/
function _preExecuteLeafHook(bytes32 l2TokenAddress) internal override {
if (l2TokenAddress == address(wrappedNativeToken).toBytes32()) _depositEthToWeth();
function _preExecuteLeafHook(address l2TokenAddress) internal override {
if (l2TokenAddress == address(wrappedNativeToken)) _depositEthToWeth();
}

// Wrap any ETH owned by this contract so we can send expected L2 token to recipient. This is necessary because
Expand Down
6 changes: 3 additions & 3 deletions contracts/Ovm_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface IL2ERC20Bridge {
*/
contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
using SafeERC20 for IERC20;
using AddressToBytes32 for address;

// "l1Gas" parameter used in call to bridge tokens from this contract back to L1 via IL2ERC20Bridge. Currently
// unused by bridge but included for future compatibility.

Expand Down Expand Up @@ -135,8 +135,8 @@ contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
* @notice Wraps any ETH into WETH before executing leaves. This is necessary because SpokePool receives
* ETH over the canonical token bridge instead of WETH.
*/
function _preExecuteLeafHook(bytes32 l2TokenAddress) internal override {
if (l2TokenAddress == address(wrappedNativeToken).toBytes32()) _depositEthToWeth();
function _preExecuteLeafHook(address l2TokenAddress) internal override {
if (l2TokenAddress == address(wrappedNativeToken)) _depositEthToWeth();
}

// Wrap any ETH owned by this contract so we can send expected L2 token to recipient. This is necessary because
Expand Down
4 changes: 2 additions & 2 deletions contracts/PolygonZkEVM_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ contract PolygonZkEVM_SpokePool is SpokePool, IBridgeMessageReceiver {
* @notice Wraps any ETH into WETH before executing base function. This is necessary because SpokePool receives
* ETH over the canonical token bridge instead of WETH.
*/
function _preExecuteLeafHook(bytes32 l2TokenAddress) internal override {
if (l2TokenAddress == address(wrappedNativeToken).toBytes32()) _depositEthToWeth();
function _preExecuteLeafHook(address l2TokenAddress) internal override {
if (l2TokenAddress == address(wrappedNativeToken)) _depositEthToWeth();
}

// Wrap any ETH owned by this contract so we can send expected L2 token to recipient. This is necessary because
Expand Down
2 changes: 1 addition & 1 deletion contracts/Polygon_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool, CircleCCTPAdapter
emit SetPolygonTokenBridger(address(_polygonTokenBridger));
}

function _preExecuteLeafHook(bytes32) internal override {
function _preExecuteLeafHook(address) internal override {
// Wraps MATIC --> WMATIC before distributing tokens from this contract.
_wrap();
}
Expand Down
91 changes: 42 additions & 49 deletions contracts/SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ abstract contract SpokePool is
RootBundle[] public rootBundles;

// Origin token to destination token routings can be turned on or off, which can enable or disable deposits.
mapping(bytes32 => mapping(uint256 => bool)) public enabledDepositRoutes;
mapping(address => mapping(uint256 => bool)) public enabledDepositRoutes;

// Each relay is associated with the hash of parameters that uniquely identify the original deposit and a relay
// attempt for that deposit. The relay itself is just represented as the amount filled so far. The total amount to
Expand Down Expand Up @@ -105,7 +105,7 @@ abstract contract SpokePool is

// Mapping of L2TokenAddress to relayer to outstanding refund amount. Used when a relayer repayment fails for some
// reason (eg blacklist) to track their outstanding liability, thereby letting them claim it later.
mapping(bytes32 => mapping(bytes32 => uint256)) public relayerRefund;
mapping(address => mapping(address => uint256)) public relayerRefund;

/**************************************************************
* CONSTANT/IMMUTABLE VARIABLES *
Expand Down Expand Up @@ -173,8 +173,8 @@ abstract contract SpokePool is
uint256[] refundAmounts,
uint32 indexed rootBundleId,
uint32 indexed leafId,
bytes32 l2TokenAddress,
bytes32[] refundAddresses,
address l2TokenAddress,
address[] refundAddresses,
bool deferredRefunds,
address caller
);
Expand Down Expand Up @@ -318,7 +318,7 @@ abstract contract SpokePool is
uint256 destinationChainId,
bool enabled
) public override onlyAdmin nonReentrant {
enabledDepositRoutes[originToken.toBytes32()][destinationChainId] = enabled;
enabledDepositRoutes[originToken][destinationChainId] = enabled;
emit EnabledDepositRoute(originToken, destinationChainId, enabled);
}

Expand Down Expand Up @@ -390,9 +390,9 @@ abstract contract SpokePool is
uint256 // maxCount. Deprecated.
) public payable override nonReentrant unpausedDeposits {
_deposit(
msg.sender.toBytes32(),
recipient.toBytes32(),
originToken.toBytes32(),
msg.sender,
recipient,
originToken,
amount,
destinationChainId,
relayerFeePct,
Expand Down Expand Up @@ -432,16 +432,7 @@ abstract contract SpokePool is
bytes memory message,
uint256 // maxCount. Deprecated.
) public payable nonReentrant unpausedDeposits {
_deposit(
depositor.toBytes32(),
recipient.toBytes32(),
originToken.toBytes32(),
amount,
destinationChainId,
relayerFeePct,
quoteTimestamp,
message
);
_deposit(depositor, recipient, originToken, amount, destinationChainId, relayerFeePct, quoteTimestamp, message);
}

/********************************************
Expand Down Expand Up @@ -514,7 +505,7 @@ abstract contract SpokePool is
) public payable override nonReentrant unpausedDeposits {
// Check that deposit route is enabled for the input token. There are no checks required for the output token
// which is pulled from the relayer at fill time and passed through this contract atomically to the recipient.
if (!enabledDepositRoutes[inputToken][destinationChainId]) revert DisabledRoute();
if (!enabledDepositRoutes[inputToken.toAddress()][destinationChainId]) revert DisabledRoute();

// Require that quoteTimestamp has a maximum age so that depositors pay an LP fee based on recent HubPool usage.
// It is assumed that cross-chain timestamps are normally loosely in-sync, but clock drift can occur. If the
Expand Down Expand Up @@ -981,8 +972,11 @@ abstract contract SpokePool is
// Exclusivity deadline is inclusive and is the latest timestamp that the exclusive relayer has sole right
// to fill the relay.
if (
_fillIsExclusive(relayData.exclusiveRelayer, relayData.exclusivityDeadline, uint32(getCurrentTime())) &&
relayData.exclusiveRelayer.toAddress() != msg.sender
_fillIsExclusive(
relayData.exclusiveRelayer.toAddress(),
relayData.exclusivityDeadline,
uint32(getCurrentTime())
) && relayData.exclusiveRelayer.toAddress() != msg.sender
) {
revert NotExclusiveRelayer();
}
Expand Down Expand Up @@ -1028,8 +1022,11 @@ abstract contract SpokePool is
// Exclusivity deadline is inclusive and is the latest timestamp that the exclusive relayer has sole right
// to fill the relay.
if (
_fillIsExclusive(relayData.exclusiveRelayer, relayData.exclusivityDeadline, uint32(getCurrentTime())) &&
relayData.exclusiveRelayer.toAddress() != msg.sender
_fillIsExclusive(
relayData.exclusiveRelayer.toAddress(),
relayData.exclusivityDeadline,
uint32(getCurrentTime())
) && relayData.exclusiveRelayer.toAddress() != msg.sender
) {
revert NotExclusiveRelayer();
}
Expand Down Expand Up @@ -1078,7 +1075,7 @@ abstract contract SpokePool is
// fast fill within this deadline. Moreover, the depositor should expect to get *fast* filled within
// this deadline, not slow filled. As a simplifying assumption, we will not allow slow fills to be requested
// during this exclusivity period.
if (_fillIsExclusive(relayData.exclusiveRelayer, relayData.exclusivityDeadline, currentTime)) {
if (_fillIsExclusive(relayData.exclusiveRelayer.toAddress(), relayData.exclusivityDeadline, currentTime)) {
revert NoSlowFillsInExclusivityWindow();
}
if (relayData.fillDeadline < currentTime) revert ExpiredFillDeadline();
Expand Down Expand Up @@ -1166,7 +1163,7 @@ abstract contract SpokePool is
) public override nonReentrant {
V3RelayData memory relayData = slowFillLeaf.relayData;

_preExecuteLeafHook(relayData.outputToken);
_preExecuteLeafHook(relayData.outputToken.toAddress());

// @TODO In the future consider allowing way for slow fill leaf to be created with updated
// deposit params like outputAmount, message and recipient.
Expand Down Expand Up @@ -1242,9 +1239,9 @@ abstract contract SpokePool is
* @param refundAddress Address to send the refund to.
*/
function claimRelayerRefund(bytes32 l2TokenAddress, bytes32 refundAddress) public {
uint256 refund = relayerRefund[l2TokenAddress][msg.sender.toBytes32()];
uint256 refund = relayerRefund[l2TokenAddress.toAddress()][msg.sender];
if (refund == 0) revert NoRelayerRefundToClaim();
relayerRefund[l2TokenAddress][msg.sender.toBytes32()] = 0;
relayerRefund[l2TokenAddress.toAddress()][refundAddress.toAddress()] = 0;
IERC20Upgradeable(l2TokenAddress.toAddress()).safeTransfer(refundAddress.toAddress(), refund);

emit ClaimedRelayerRefund(l2TokenAddress, refundAddress, refund, msg.sender);
Expand All @@ -1270,17 +1267,17 @@ abstract contract SpokePool is
return block.timestamp; // solhint-disable-line not-rely-on-time
}

function getRelayerRefund(bytes32 l2TokenAddress, bytes32 refundAddress) public view returns (uint256) {
function getRelayerRefund(address l2TokenAddress, address refundAddress) public view returns (uint256) {
return relayerRefund[l2TokenAddress][refundAddress];
}

/**************************************
* INTERNAL FUNCTIONS *
**************************************/
function _deposit(
bytes32 depositor,
bytes32 recipient,
bytes32 originToken,
address depositor,
address recipient,
address originToken,
uint256 amount,
uint256 destinationChainId,
int64 relayerFeePct,
Expand Down Expand Up @@ -1308,18 +1305,18 @@ abstract contract SpokePool is

// If the address of the origin token is a wrappedNativeToken contract and there is a msg.value with the
// transaction then the user is sending ETH. In this case, the ETH should be deposited to wrappedNativeToken.
if (originToken == address(wrappedNativeToken).toBytes32() && msg.value > 0) {
if (originToken == address(wrappedNativeToken) && msg.value > 0) {
if (msg.value != amount) revert MsgValueDoesNotMatchInputAmount();
wrappedNativeToken.deposit{ value: msg.value }();
// Else, it is a normal ERC20. In this case pull the token from the user's wallet as per normal.
// Note: this includes the case where the L2 user has WETH (already wrapped ETH) and wants to bridge them.
// In this case the msg.value will be set to 0, indicating a "normal" ERC20 bridging action.
} else {
IERC20Upgradeable(originToken.toAddress()).safeTransferFrom(msg.sender, address(this), amount);
IERC20Upgradeable(originToken).safeTransferFrom(msg.sender, address(this), amount);
}

emit V3FundsDeposited(
originToken, // inputToken
originToken.toBytes32(), // inputToken
bytes32(0), // outputToken. Setting this to 0x0 means that the outputToken should be assumed to be the
// canonical token for the destination chain matching the inputToken. Therefore, this deposit
// can always be slow filled.
Expand All @@ -1337,8 +1334,8 @@ abstract contract SpokePool is
// expired deposits refunds could be a breaking change for existing users of this function.
0, // exclusivityDeadline. Setting this to 0 along with the exclusiveRelayer to 0x0 means that there
// is no exclusive deadline
depositor,
recipient,
depositor.toBytes32(),
recipient.toBytes32(),
bytes32(0), // exclusiveRelayer. Setting this to 0x0 will signal to off-chain validator that there
// is no exclusive relayer.
message
Expand All @@ -1350,14 +1347,14 @@ abstract contract SpokePool is
uint256 amountToReturn,
uint256[] memory refundAmounts,
uint32 leafId,
bytes32 l2TokenAddress,
bytes32[] memory refundAddresses
address l2TokenAddress,
address[] memory refundAddresses
) internal returns (bool deferredRefunds) {
uint256 numRefunds = refundAmounts.length;
if (refundAddresses.length != numRefunds) revert InvalidMerkleLeaf();

if (numRefunds > 0) {
uint256 spokeStartBalance = IERC20Upgradeable(l2TokenAddress.toAddress()).balanceOf(address(this));
uint256 spokeStartBalance = IERC20Upgradeable(l2TokenAddress).balanceOf(address(this));
uint256 totalRefundedAmount = 0; // Track the total amount refunded.

// Send each relayer refund address the associated refundAmount for the L2 token address.
Expand All @@ -1371,11 +1368,7 @@ abstract contract SpokePool is
// prevents can only re-pay some of the relayers.
if (totalRefundedAmount > spokeStartBalance) revert InsufficientSpokePoolBalanceToExecuteLeaf();

bool success = _noRevertTransfer(
l2TokenAddress.toAddress(),
refundAddresses[i].toAddress(),
refundAmounts[i]
);
bool success = _noRevertTransfer(l2TokenAddress, refundAddresses[i], refundAmounts[i]);

// If the transfer failed then track a deferred transfer for the relayer. Given this function would
// have revered if there was insufficient balance, this will only happen if the transfer call
Expand All @@ -1391,9 +1384,9 @@ abstract contract SpokePool is
// If leaf's amountToReturn is positive, then send L2 --> L1 message to bridge tokens back via
// chain-specific bridging method.
if (amountToReturn > 0) {
_bridgeTokensToHubPool(amountToReturn, l2TokenAddress.toAddress());
_bridgeTokensToHubPool(amountToReturn, l2TokenAddress);

emit TokensBridged(amountToReturn, _chainId, leafId, l2TokenAddress, msg.sender);
emit TokensBridged(amountToReturn, _chainId, leafId, l2TokenAddress.toBytes32(), msg.sender);
}
}

Expand Down Expand Up @@ -1429,7 +1422,7 @@ abstract contract SpokePool is
emit SetWithdrawalRecipient(newWithdrawalRecipient);
}

function _preExecuteLeafHook(bytes32) internal virtual {
function _preExecuteLeafHook(address) internal virtual {
// This method by default is a no-op. Different child spoke pools might want to execute functionality here
// such as wrapping any native tokens owned by the contract into wrapped tokens before proceeding with
// executing the leaf.
Expand Down Expand Up @@ -1633,11 +1626,11 @@ abstract contract SpokePool is

// Determine whether the combination of exlcusiveRelayer and exclusivityDeadline implies active exclusivity.
function _fillIsExclusive(
bytes32 exclusiveRelayer,
address exclusiveRelayer,
uint32 exclusivityDeadline,
uint32 currentTime
) internal pure returns (bool) {
return exclusivityDeadline >= currentTime && exclusiveRelayer != bytes32(0);
return exclusivityDeadline >= currentTime && exclusiveRelayer != address(0);
}

// Implementing contract needs to override this to ensure that only the appropriate cross chain admin can execute
Expand Down
5 changes: 2 additions & 3 deletions contracts/ZkSync_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ interface IL2ETH {
* @custom:security-contact bugs@across.to
*/
contract ZkSync_SpokePool is SpokePool {
using AddressToBytes32 for address;
// On Ethereum, avoiding constructor parameters and putting them into constants reduces some of the gas cost
// upon contract deployment. On zkSync the opposite is true: deploying the same bytecode for contracts,
// while changing only constructor parameters can lead to substantial fee savings. So, the following params
Expand Down Expand Up @@ -89,8 +88,8 @@ contract ZkSync_SpokePool is SpokePool {
* @notice Wraps any ETH into WETH before executing base function. This is necessary because SpokePool receives
* ETH over the canonical token bridge instead of WETH.
*/
function _preExecuteLeafHook(bytes32 l2TokenAddress) internal override {
if (l2TokenAddress == address(wrappedNativeToken).toBytes32()) _depositEthToWeth();
function _preExecuteLeafHook(address l2TokenAddress) internal override {
if (l2TokenAddress == address(wrappedNativeToken)) _depositEthToWeth();
}

// Wrap any ETH owned by this contract so we can send expected L2 token to recipient. This is necessary because
Expand Down
4 changes: 2 additions & 2 deletions contracts/interfaces/SpokePoolInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ interface SpokePoolInterface {
// Used as the index in the bitmap to track whether this leaf has been executed or not.
uint32 leafId;
// The associated L2TokenAddress that these claims apply to.
bytes32 l2TokenAddress;
address l2TokenAddress;
// Must be same length as refundAmounts and designates each address that must be refunded.
bytes32[] refundAddresses;
address[] refundAddresses;
}

// Stores collection of merkle roots that can be published to this contract from the HubPool, which are referenced
Expand Down
8 changes: 4 additions & 4 deletions contracts/test/MockSpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeabl
uint256 amountToReturn,
uint256[] memory refundAmounts,
uint32 leafId,
bytes32 l2TokenAddress,
bytes32[] memory refundAddresses
address l2TokenAddress,
address[] memory refundAddresses
) external {
_distributeRelayerRefunds(_chainId, amountToReturn, refundAmounts, leafId, l2TokenAddress, refundAddresses);
}
Expand Down Expand Up @@ -152,8 +152,8 @@ contract MockSpokePool is SpokePool, MockV2SpokePoolInterface, OwnableUpgradeabl
return currentTime;
}

function _preExecuteLeafHook(bytes32 token) internal override {
emit PreLeafExecuteHook(token);
function _preExecuteLeafHook(address token) internal override {
emit PreLeafExecuteHook(token.toBytes32());
}

function _bridgeTokensToHubPool(uint256 amount, address token) internal override {
Expand Down
Loading
Loading