Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9f7e40c
fix(ZkSync_SpokePool): Add __gap (#907)
nicholaspai Mar 10, 2025
46824c4
Add `OFTTransportAdapter` to support cross-chain token transfers of `…
grasphoper Mar 12, 2025
53661d5
Merge branch 'master' into march-25-evm-audit
nicholaspai Mar 13, 2025
b9a298e
feat(SpokePoolPeriphery): Support multiple exchanges (#777)
nicholaspai Mar 13, 2025
3281dd5
Single AddressBook for all adapters (#919)
grasphoper Mar 13, 2025
5bedb25
Merge branch 'master' into march-25-evm-audit
nicholaspai Mar 17, 2025
62c4d96
feat: add xERC20 standard support via Hyperlane (#914)
grasphoper Mar 26, 2025
42a4459
feat: remove XERC20 code (#991)
nicholaspai May 6, 2025
5c8c458
Merge branch 'master' into march-25-evm-audit
nicholaspai May 6, 2025
a16a30f
add OFT to universl adapter
nicholaspai May 6, 2025
b5a42c4
remove extra AdapterStore
nicholaspai May 6, 2025
ef00ae3
Update Universal_SpokePool.json
nicholaspai May 6, 2025
7325c1e
Add OFT unit tests to Universal spoke
nicholaspai May 6, 2025
c391159
Revert "feat(SpokePoolPeriphery): Support multiple exchanges (#777)"
mrice32 May 16, 2025
49eec45
Merge branch 'master' into may-19-audit
mrice32 May 16, 2025
c5d7541
adjust comments: remove xERC20 references
grasphoper May 17, 2025
1edcaeb
add amountSentLD check (#1027)
grasphoper Jun 5, 2025
646d255
update doc comment (#1030)
grasphoper Jun 5, 2025
e1e0034
add human error protection to AdapterStore.sol (#1033)
grasphoper Jun 5, 2025
e7b5091
require that returned lzTokenFee is zero. Add tests for fee cap check…
grasphoper Jun 5, 2025
347af15
fix typos (#1035)
grasphoper Jun 6, 2025
84d3614
use explicit import syntax (#1036)
grasphoper Jun 6, 2025
8159660
add security contact doc comments (#1037)
grasphoper Jun 6, 2025
e67df8d
[M-04] Insufficient Test Coverage (#1038)
grasphoper Jun 6, 2025
3374d62
[M-02] Compromised Messengers Cannot Be Removed (#1034)
grasphoper Jun 6, 2025
f2d0115
update __gap comment (#1039)
grasphoper Jun 6, 2025
84f87be
add missing docstrings
grasphoper Jun 7, 2025
e07de15
restrict function visibility (#1041)
grasphoper Jun 9, 2025
49b67d8
[N-08] Missing Named Parameters in Mappings (#1040)
grasphoper Jun 9, 2025
2e1e39c
bump solhint to solhint@^3.6.2 to align with master
grasphoper Aug 5, 2025
64baa11
more updates to package.json from master
grasphoper Aug 5, 2025
182cc09
revert last 2 commits
grasphoper Aug 5, 2025
23ed955
reconcile packages with master
grasphoper Aug 5, 2025
73f8e39
merge master
grasphoper Aug 5, 2025
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
4 changes: 2 additions & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ jobs:
name: evm-artifacts-${{ runner.os }}-node-${{ env.NODE_VERSION }}
- name: Test evm-hardhat
shell: bash
run: yarn test-evm
run: yarn test-evm-hardhat
test-svm-verified:
name: Test verified SVM build
needs: upload-svm-artifacts
Expand Down Expand Up @@ -212,4 +212,4 @@ jobs:
- name: Inspect storage layouts
run: ./scripts/checkStorageLayout.sh
- name: Test evm-foundry
run: forge test --match-path test/evm/foundry/local/**/*.t.sol
run: yarn test-evm-foundry
120 changes: 120 additions & 0 deletions contracts/AdapterStore.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOFT } from "./interfaces/IOFT.sol";

/**
* @title MessengerTypes
* @notice Library containing messenger type constants
* @custom:security-contact bugs@across.to
*/
library MessengerTypes {
/** @notice Identifier for OFT (Omni-chain Fungible Token by LayerZero) messenger type */
bytes32 public constant OFT_MESSENGER = bytes32("OFT_MESSENGER");
}

/**
* @dev A helper contract for chain adapters on the hub chain that support OFT messaging. Handles
* @dev token => messenger mapping storage. Adapters can't store this themselves as they're called
* @dev via `delegateCall` and their storage is not part of available context.
* @custom:security-contact bugs@across.to
*/
contract AdapterStore is Ownable {
/** @notice Maps messenger type and destination domain to token-messenger pairs */
mapping(bytes32 messengerType => mapping(uint256 dstDomainId => mapping(address srcChainToken => address messengerAddress)))
public crossChainMessengers;

/**
* @notice Emitted when a messenger is set for a specific token and destination
* @param messengerType Type of messenger being set
* @param dstDomainId Destination domain ID
* @param srcChainToken Source chain token address
* @param srcChainMessenger Source chain messenger address
*/
event MessengerSet(
bytes32 indexed messengerType,
uint256 indexed dstDomainId,
address indexed srcChainToken,
address srcChainMessenger
);

/** @notice Thrown when array lengths don't match in batch operations */
error ArrayLengthMismatch();

/** @notice Thrown when IOFT messenger's token doesn't match expected token */
error IOFTTokenMismatch();

/** @notice Thrown when messenger type is not supported */
error NonExistentMessengerType();

/**
* @notice Sets a messenger for a specific token and destination domain
* @param messengerType Type of messenger to set
* @param dstDomainId Destination domain ID
* @param srcChainToken Source chain token address
* @param srcChainMessenger Source chain messenger address
*/
function setMessenger(
bytes32 messengerType,
uint256 dstDomainId,
address srcChainToken,
address srcChainMessenger
) external onlyOwner {
_setMessenger(messengerType, dstDomainId, srcChainToken, srcChainMessenger);
}

/**
* @notice Sets multiple messengers in a single transaction
* @param messengerTypes Array of messenger types
* @param dstDomainIds Array of destination domain IDs
* @param srcChainTokens Array of source chain token addresses
* @param srcChainMessengers Array of source chain messenger addresses
*/
function batchSetMessengers(
bytes32[] calldata messengerTypes,
uint256[] calldata dstDomainIds,
address[] calldata srcChainTokens,
address[] calldata srcChainMessengers
) external onlyOwner {
if (
messengerTypes.length != dstDomainIds.length ||
messengerTypes.length != srcChainTokens.length ||
messengerTypes.length != srcChainMessengers.length
) {
revert ArrayLengthMismatch();
}

for (uint256 i = 0; i < dstDomainIds.length; i++) {
_setMessenger(messengerTypes[i], dstDomainIds[i], srcChainTokens[i], srcChainMessengers[i]);
}
}

/**
* @notice Internal function to set a messenger with validation
* @param _messengerType Type of messenger to set
* @param _dstDomainId Destination domain ID
* @param _srcChainToken Source chain token address
* @param _srcChainMessenger Source chain messenger address
*/
function _setMessenger(
bytes32 _messengerType,
uint256 _dstDomainId,
address _srcChainToken,
address _srcChainMessenger
) internal {
// @dev Always allow zero-messenger to be set: this can be used to 'remove' a stored token <> messenger relationship
if (_srcChainMessenger != address(0)) {
if (_messengerType == MessengerTypes.OFT_MESSENGER) {
// @dev Protect against human error: check that IOFT messenger's token matches the expected one
if (IOFT(_srcChainMessenger).token() != _srcChainToken) {
revert IOFTTokenMismatch();
}
} else {
revert NonExistentMessengerType();
}
}
crossChainMessengers[_messengerType][_dstDomainId][_srcChainToken] = _srcChainMessenger;
emit MessengerSet(_messengerType, _dstDomainId, _srcChainToken, _srcChainMessenger);
}
}
8 changes: 6 additions & 2 deletions contracts/AlephZero_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ contract AlephZero_SpokePool is Arbitrum_SpokePool {
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer,
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
ITokenMessenger _cctpTokenMessenger,
uint32 _oftDstEid,
uint256 _oftFeeCap
)
Arbitrum_SpokePool(
_wrappedNativeTokenAddress,
_depositQuoteTimeBuffer,
_fillDeadlineBuffer,
_l2Usdc,
_cctpTokenMessenger
_cctpTokenMessenger,
_oftDstEid,
_oftFeeCap
)
{} // solhint-disable-line no-empty-blocks
}
17 changes: 15 additions & 2 deletions contracts/Arbitrum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ contract Arbitrum_SpokePool is SpokePool, CircleCCTPAdapter {
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer,
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
ITokenMessenger _cctpTokenMessenger,
uint32 _oftDstEid,
uint256 _oftFeeCap
)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer, _oftDstEid, _oftFeeCap)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)
{} // solhint-disable-line no-empty-blocks

Expand Down Expand Up @@ -83,9 +85,13 @@ contract Arbitrum_SpokePool is SpokePool, CircleCCTPAdapter {
**************************************/

function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal override {
address oftMessenger = _getOftMessenger(l2TokenAddress);

// If the l2TokenAddress is UDSC, we need to use the CCTP bridge.
if (_isCCTPEnabled() && l2TokenAddress == address(usdcToken)) {
_transferUsdc(withdrawalRecipient, amountToReturn);
} else if (oftMessenger != address(0)) {
_transferViaOFT(IERC20(l2TokenAddress), IOFT(oftMessenger), withdrawalRecipient, amountToReturn);
} else {
// Check that the Ethereum counterpart of the L2 token is stored on this contract.
address ethereumTokenToBridge = whitelistedTokens[l2TokenAddress];
Expand All @@ -112,4 +118,11 @@ contract Arbitrum_SpokePool is SpokePool, CircleCCTPAdapter {

// Apply AVM-specific transformation to cross domain admin address on L1.
function _requireAdminSender() internal override onlyFromCrossDomainAdmin {}

// Reserve storage slots for future versions of this base contract to add state variables without
// affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables
// are added, so that the total number of slots taken by this contract remains constant. Per-contract
// storage layout information can be found in storage-layouts/
// This is at bottom of contract to make sure it's always at the end of storage.
uint256[1000] private __gap;
}
11 changes: 10 additions & 1 deletion contracts/Ethereum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,16 @@ contract Ethereum_SpokePool is SpokePool, OwnableUpgradeable {
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
)
SpokePool(
_wrappedNativeTokenAddress,
_depositQuoteTimeBuffer,
_fillDeadlineBuffer,
// Ethereum_SpokePool does not use OFT messaging; setting destination eid and fee cap to 0
0,
0
)
{} // solhint-disable-line no-empty-blocks

/**
* @notice Construct the Ethereum SpokePool.
Expand Down
9 changes: 8 additions & 1 deletion contracts/Linea_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,14 @@ contract Linea_SpokePool is SpokePool, CircleCCTPAdapter {
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)
SpokePool(
_wrappedNativeTokenAddress,
_depositQuoteTimeBuffer,
_fillDeadlineBuffer,
// Linea_SpokePool does not use OFT messaging; setting destination eid and fee cap to 0
0,
0
)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)
{} // solhint-disable-line no-empty-blocks

Expand Down
14 changes: 12 additions & 2 deletions contracts/Ovm_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface IL2ERC20Bridge {
*/
contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
using SafeERC20 for IERC20;

// "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.
uint32 public l1Gas;
Expand Down Expand Up @@ -71,7 +72,14 @@ contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)
SpokePool(
_wrappedNativeTokenAddress,
_depositQuoteTimeBuffer,
_fillDeadlineBuffer,
// Ovm_SpokePool does not use OFT messaging; setting destination eid and fee cap to 0
0,
0
)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)
{} // solhint-disable-line no-empty-blocks

Expand Down Expand Up @@ -210,6 +218,8 @@ contract Ovm_SpokePool is SpokePool, CircleCCTPAdapter {

// Reserve storage slots for future versions of this base contract to add state variables without
// affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables
// are added. This is at bottom of contract to make sure its always at the end of storage.
// are added, so that the total number of slots taken by this contract remains constant. Per-contract
// storage layout information can be found in storage-layouts/
// This is at bottom of contract to make sure it's always at the end of storage.
uint256[999] private __gap;
}
11 changes: 10 additions & 1 deletion contracts/PolygonZkEVM_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,16 @@ contract PolygonZkEVM_SpokePool is SpokePool, IBridgeMessageReceiver {
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
)
SpokePool(
_wrappedNativeTokenAddress,
_depositQuoteTimeBuffer,
_fillDeadlineBuffer,
// PolygonZkEVM_SpokePool does not use OFT messaging; setting destination eid and fee cap to 0
0,
0
)
{} // solhint-disable-line no-empty-blocks

/**
* @notice Construct the Polygon zkEVM SpokePool.
Expand Down
9 changes: 8 additions & 1 deletion contracts/Polygon_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,14 @@ contract Polygon_SpokePool is IFxMessageProcessor, SpokePool, CircleCCTPAdapter
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)
SpokePool(
_wrappedNativeTokenAddress,
_depositQuoteTimeBuffer,
_fillDeadlineBuffer,
// Polygon_SpokePool does not use OFT messaging; setting destination eid and fee cap to 0
0,
0
)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)
{} // solhint-disable-line no-empty-blocks

Expand Down
11 changes: 10 additions & 1 deletion contracts/Scroll_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,16 @@ contract Scroll_SpokePool is SpokePool {
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
)
SpokePool(
_wrappedNativeTokenAddress,
_depositQuoteTimeBuffer,
_fillDeadlineBuffer,
// Scroll_SpokePool does not use OFT messaging, setting destination id and fee cap to 0
0,
0
)
{} // solhint-disable-line no-empty-blocks

/**
* @notice Construct the Scroll SpokePool.
Expand Down
Loading
Loading