Skip to content

Commit 8bef3ca

Browse files
authored
feat(Linea): Add CCTP V2 support to Linea (#910) (#921)
* feat(Linea): Add CCTP V2 support to Linea (#910) * fix(CCTPAdapter): Fix feeRecipient check to determine V2 vs V1 - The casting to bytes20 will fetch the first 20 bytes of the return value. However, the functions in Solidity return abi.encoded data, which means in particular, that if an address is returned from the feeRecipient function, the returned feeRecipient.length will be equal to 32 and the address will be included in the last 20 bytes of that data (will be padded with 12 zeroes at the beginning). As a result, casting feeRecipient to bytes20 will capture only the first 8 bytes of the returned address. You can get the full address by casting the data to uint160 instead (you may need to cast it to bytes32 and uint256 first). - The feeRecipient.length is not validated. It means that for example, in case of a call to nonexistent contract or a call to a contract with a fallback function which doesn't return any data, the code will attempt to cast empty data to bytes20. It seems that casting empty bytes object to bytes20 will return 0, but it would be safer to not rely on that behaviour and validate that the length of feeRecipient object equals 32. - Even after fixing the issues above, this check will not be fully reliable as it could for example succeed in case when a TokenMessenger V1 contract had a fallback function returning 32 bytes. This is a very low risk, hence you may keep this logic if you accept the risk, but a fully correct solution would involve introducing an additional parameter to the constructor, which could be then used to determine the CCTP version (this could be an uint256 as it is possible that more CCTP versions will appear in the future). We understand that you wanted to avoid adding this extra parameter (and hence introducing a very large diff), but the most reliable solution would involve introducing it in the constructor. * Revert "fix(CCTPAdapter): Fix feeRecipient check to determine V2 vs V1" This reverts commit cd36e36. * fix(CCTPAdapter): Fix feeRecipient check to determine V2 vs V1 (#924) - The casting to bytes20 will fetch the first 20 bytes of the return value. However, the functions in Solidity return abi.encoded data, which means in particular, that if an address is returned from the feeRecipient function, the returned feeRecipient.length will be equal to 32 and the address will be included in the last 20 bytes of that data (will be padded with 12 zeroes at the beginning). As a result, casting feeRecipient to bytes20 will capture only the first 8 bytes of the returned address. You can get the full address by casting the data to uint160 instead (you may need to cast it to bytes32 and uint256 first). - The feeRecipient.length is not validated. It means that for example, in case of a call to nonexistent contract or a call to a contract with a fallback function which doesn't return any data, the code will attempt to cast empty data to bytes20. It seems that casting empty bytes object to bytes20 will return 0, but it would be safer to not rely on that behaviour and validate that the length of feeRecipient object equals 32. - Even after fixing the issues above, this check will not be fully reliable as it could for example succeed in case when a TokenMessenger V1 contract had a fallback function returning 32 bytes. This is a very low risk, hence you may keep this logic if you accept the risk, but a fully correct solution would involve introducing an additional parameter to the constructor, which could be then used to determine the CCTP version (this could be an uint256 as it is possible that more CCTP versions will appear in the future). We understand that you wanted to avoid adding this extra parameter (and hence introducing a very large diff), but the most reliable solution would involve introducing it in the constructor. * improve(CCTPAdapter): Add comment about not casting tokenMinter to V1/V2 (#928) * improve(cctpAdapter): Fix misleading comments (#929) * improve(cctpAdapter): typographical errors (#930) * improve(CCTPAdapter): Remove comment (#931) * fix(CCTPAdapter): Fix feeRecipient check to determine V2 vs V1 - The casting to bytes20 will fetch the first 20 bytes of the return value. However, the functions in Solidity return abi.encoded data, which means in particular, that if an address is returned from the feeRecipient function, the returned feeRecipient.length will be equal to 32 and the address will be included in the last 20 bytes of that data (will be padded with 12 zeroes at the beginning). As a result, casting feeRecipient to bytes20 will capture only the first 8 bytes of the returned address. You can get the full address by casting the data to uint160 instead (you may need to cast it to bytes32 and uint256 first). - The feeRecipient.length is not validated. It means that for example, in case of a call to nonexistent contract or a call to a contract with a fallback function which doesn't return any data, the code will attempt to cast empty data to bytes20. It seems that casting empty bytes object to bytes20 will return 0, but it would be safer to not rely on that behaviour and validate that the length of feeRecipient object equals 32. - Even after fixing the issues above, this check will not be fully reliable as it could for example succeed in case when a TokenMessenger V1 contract had a fallback function returning 32 bytes. This is a very low risk, hence you may keep this logic if you accept the risk, but a fully correct solution would involve introducing an additional parameter to the constructor, which could be then used to determine the CCTP version (this could be an uint256 as it is possible that more CCTP versions will appear in the future). We understand that you wanted to avoid adding this extra parameter (and hence introducing a very large diff), but the most reliable solution would involve introducing it in the constructor. * Remove comment about cctp domain IDs * fix(CCTPAdapter): Typographical errors (#935)
1 parent 681d377 commit 8bef3ca

File tree

12 files changed

+341
-166
lines changed

12 files changed

+341
-166
lines changed

contracts/Linea_SpokePool.sol

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
pragma solidity ^0.8.19;
66

77
import "./SpokePool.sol";
8+
import "./libraries/CircleCCTPAdapter.sol";
89
import { IMessageService, ITokenBridge, IUSDCBridge } from "./external/interfaces/LineaInterfaces.sol";
910
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1011
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
@@ -13,7 +14,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
1314
* @notice Linea specific SpokePool.
1415
* @custom:security-contact [email protected]
1516
*/
16-
contract Linea_SpokePool is SpokePool {
17+
contract Linea_SpokePool is SpokePool, CircleCCTPAdapter {
1718
using SafeERC20 for IERC20;
1819

1920
/**
@@ -29,14 +30,13 @@ contract Linea_SpokePool is SpokePool {
2930
/**
3031
* @notice Address of Linea's USDC Bridge contract on L2.
3132
*/
32-
IUSDCBridge public l2UsdcBridge;
33+
IUSDCBridge private DEPRECATED_l2UsdcBridge;
3334

3435
/**************************************
3536
* EVENTS *
3637
**************************************/
3738
event SetL2TokenBridge(address indexed newTokenBridge, address oldTokenBridge);
3839
event SetL2MessageService(address indexed newMessageService, address oldMessageService);
39-
event SetL2UsdcBridge(address indexed newUsdcBridge, address oldUsdcBridge);
4040

4141
/**
4242
* @notice Construct Linea-specific SpokePool.
@@ -50,16 +50,20 @@ contract Linea_SpokePool is SpokePool {
5050
constructor(
5151
address _wrappedNativeTokenAddress,
5252
uint32 _depositQuoteTimeBuffer,
53-
uint32 _fillDeadlineBuffer
54-
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
53+
uint32 _fillDeadlineBuffer,
54+
IERC20 _l2Usdc,
55+
ITokenMessenger _cctpTokenMessenger
56+
)
57+
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)
58+
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)
59+
{} // solhint-disable-line no-empty-blocks
5560

5661
/**
5762
* @notice Initialize Linea-specific SpokePool.
5863
* @param _initialDepositId Starting deposit ID. Set to 0 unless this is a re-deployment in order to mitigate
5964
* relay hash collisions.
6065
* @param _l2MessageService Address of Canonical Message Service. Can be reset by admin.
6166
* @param _l2TokenBridge Address of Canonical Token Bridge. Can be reset by admin.
62-
* @param _l2UsdcBridge Address of USDC Bridge. Can be reset by admin.
6367
* @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.
6468
* @param _withdrawalRecipient Address which receives token withdrawals. Can be changed by admin. For Spoke Pools on L2, this will
6569
* likely be the hub pool.
@@ -68,14 +72,12 @@ contract Linea_SpokePool is SpokePool {
6872
uint32 _initialDepositId,
6973
IMessageService _l2MessageService,
7074
ITokenBridge _l2TokenBridge,
71-
IUSDCBridge _l2UsdcBridge,
7275
address _crossDomainAdmin,
7376
address _withdrawalRecipient
7477
) public initializer {
7578
__SpokePool_init(_initialDepositId, _crossDomainAdmin, _withdrawalRecipient);
7679
_setL2TokenBridge(_l2TokenBridge);
7780
_setL2MessageService(_l2MessageService);
78-
_setL2UsdcBridge(_l2UsdcBridge);
7981
}
8082

8183
/**
@@ -106,14 +108,6 @@ contract Linea_SpokePool is SpokePool {
106108
_setL2MessageService(_l2MessageService);
107109
}
108110

109-
/**
110-
* @notice Change L2 USDC bridge address. Callable only by admin.
111-
* @param _l2UsdcBridge New address of L2 USDC bridge.
112-
*/
113-
function setL2UsdcBridge(IUSDCBridge _l2UsdcBridge) public onlyAdmin nonReentrant {
114-
_setL2UsdcBridge(_l2UsdcBridge);
115-
}
116-
117111
/**************************************
118112
* INTERNAL FUNCTIONS *
119113
**************************************/
@@ -139,27 +133,32 @@ contract Linea_SpokePool is SpokePool {
139133
function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal override {
140134
// Linea's L2 Canonical Message Service, requires a minimum fee to be set.
141135
uint256 minFee = minimumFeeInWei();
142-
// We require that the caller pass in the fees as msg.value instead of pulling ETH out of this contract's balance.
143-
// Using the contract's balance would require a separate accounting system to keep LP funds separated from system funds
144-
// used to pay for L2->L1 messages.
145-
require(msg.value == minFee, "MESSAGE_FEE_MISMATCH");
146136

147137
// SpokePool is expected to receive ETH from the L1 HubPool, then we need to first unwrap it to ETH and then
148138
// send ETH directly via the Canonical Message Service.
149139
if (l2TokenAddress == address(wrappedNativeToken)) {
140+
// We require that the caller pass in the fees as msg.value instead of pulling ETH out of this contract's balance.
141+
// Using the contract's balance would require a separate accounting system to keep LP funds separated from system funds
142+
// used to pay for L2->L1 messages.
143+
require(msg.value == minFee, "MESSAGE_FEE_MISMATCH");
144+
150145
// msg.value is added here because the entire native balance (including msg.value) is auto-wrapped
151146
// before the execution of any wrapped token refund leaf. So it must be unwrapped before being sent as a
152147
// fee to the l2MessageService.
153148
WETH9Interface(l2TokenAddress).withdraw(amountToReturn + msg.value); // Unwrap into ETH.
154149
l2MessageService.sendMessage{ value: amountToReturn + msg.value }(withdrawalRecipient, msg.value, "");
155150
}
156-
// If the l1Token is USDC, then we need sent it via the USDC Bridge.
157-
else if (l2TokenAddress == l2UsdcBridge.usdc()) {
158-
IERC20(l2TokenAddress).safeIncreaseAllowance(address(l2UsdcBridge), amountToReturn);
159-
l2UsdcBridge.depositTo{ value: msg.value }(amountToReturn, withdrawalRecipient);
151+
// If the l2Token is USDC, then we need sent it via the USDC Bridge.
152+
else if (l2TokenAddress == address(usdcToken) && _isCCTPEnabled()) {
153+
_transferUsdc(withdrawalRecipient, amountToReturn);
160154
}
161155
// For other tokens, we can use the Canonical Token Bridge.
162156
else {
157+
// We require that the caller pass in the fees as msg.value instead of pulling ETH out of this contract's balance.
158+
// Using the contract's balance would require a separate accounting system to keep LP funds separated from system funds
159+
// used to pay for L2->L1 messages.
160+
require(msg.value == minFee, "MESSAGE_FEE_MISMATCH");
161+
163162
IERC20(l2TokenAddress).safeIncreaseAllowance(address(l2TokenBridge), amountToReturn);
164163
l2TokenBridge.bridgeToken{ value: msg.value }(l2TokenAddress, amountToReturn, withdrawalRecipient);
165164
}
@@ -178,12 +177,6 @@ contract Linea_SpokePool is SpokePool {
178177
emit SetL2TokenBridge(address(_l2TokenBridge), oldTokenBridge);
179178
}
180179

181-
function _setL2UsdcBridge(IUSDCBridge _l2UsdcBridge) internal {
182-
address oldUsdcBridge = address(l2UsdcBridge);
183-
l2UsdcBridge = _l2UsdcBridge;
184-
emit SetL2UsdcBridge(address(_l2UsdcBridge), oldUsdcBridge);
185-
}
186-
187180
function _setL2MessageService(IMessageService _l2MessageService) internal {
188181
address oldMessageService = address(l2MessageService);
189182
l2MessageService = _l2MessageService;

contracts/chain-adapters/Linea_Adapter.sol

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity ^0.8.0;
33

44
import "./interfaces/AdapterInterface.sol";
55
import "../external/interfaces/WETH9Interface.sol";
6+
import "../libraries/CircleCCTPAdapter.sol";
67

78
import { IMessageService, ITokenBridge, IUSDCBridge } from "../external/interfaces/LineaInterfaces.sol";
89

@@ -14,31 +15,29 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
1415
* @custom:security-contact [email protected]
1516
*/
1617
// solhint-disable-next-line contract-name-camelcase
17-
contract Linea_Adapter is AdapterInterface {
18+
contract Linea_Adapter is AdapterInterface, CircleCCTPAdapter {
1819
using SafeERC20 for IERC20;
1920

2021
WETH9Interface public immutable L1_WETH;
2122
IMessageService public immutable L1_MESSAGE_SERVICE;
2223
ITokenBridge public immutable L1_TOKEN_BRIDGE;
23-
IUSDCBridge public immutable L1_USDC_BRIDGE;
2424

2525
/**
2626
* @notice Constructs new Adapter.
2727
* @param _l1Weth WETH address on L1.
2828
* @param _l1MessageService Canonical message service contract on L1.
2929
* @param _l1TokenBridge Canonical token bridge contract on L1.
30-
* @param _l1UsdcBridge L1 USDC Bridge to ConsenSys's L2 Linea.
3130
*/
3231
constructor(
3332
WETH9Interface _l1Weth,
3433
IMessageService _l1MessageService,
3534
ITokenBridge _l1TokenBridge,
36-
IUSDCBridge _l1UsdcBridge
37-
) {
35+
IERC20 _l1Usdc,
36+
ITokenMessenger _cctpTokenMessenger
37+
) CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, CircleDomainIds.Linea) {
3838
L1_WETH = _l1Weth;
3939
L1_MESSAGE_SERVICE = _l1MessageService;
4040
L1_TOKEN_BRIDGE = _l1TokenBridge;
41-
L1_USDC_BRIDGE = _l1UsdcBridge;
4241
}
4342

4443
/**
@@ -67,17 +66,15 @@ contract Linea_Adapter is AdapterInterface {
6766
uint256 amount,
6867
address to
6968
) external payable override {
69+
if (l1Token == address(usdcToken) && _isCCTPEnabled()) {
70+
_transferUsdc(to, amount);
71+
}
7072
// If the l1Token is WETH then unwrap it to ETH then send the ETH directly
7173
// via the Canoncial Message Service.
72-
if (l1Token == address(L1_WETH)) {
74+
else if (l1Token == address(L1_WETH)) {
7375
L1_WETH.withdraw(amount);
7476
L1_MESSAGE_SERVICE.sendMessage{ value: amount }(to, 0, "");
7577
}
76-
// If the l1Token is USDC, then we need sent it via the USDC Bridge.
77-
else if (l1Token == L1_USDC_BRIDGE.usdc()) {
78-
IERC20(l1Token).safeIncreaseAllowance(address(L1_USDC_BRIDGE), amount);
79-
L1_USDC_BRIDGE.depositTo(amount, to);
80-
}
8178
// For other tokens, we can use the Canonical Token Bridge.
8279
else {
8380
IERC20(l1Token).safeIncreaseAllowance(address(L1_TOKEN_BRIDGE), amount);

contracts/external/interfaces/CCTPInterfaces.sol

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,39 @@ interface ITokenMessenger {
5757
function localMinter() external view returns (ITokenMinter minter);
5858
}
5959

60+
// Source: https://github.com/circlefin/evm-cctp-contracts/blob/63ab1f0ac06ce0793c0bbfbb8d09816bc211386d/src/v2/TokenMessengerV2.sol#L138C1-L166C15
61+
interface ITokenMessengerV2 {
62+
/**
63+
* @notice Deposits and burns tokens from sender to be minted on destination domain.
64+
* Emits a `DepositForBurn` event.
65+
* @dev reverts if:
66+
* - given burnToken is not supported
67+
* - given destinationDomain has no TokenMessenger registered
68+
* - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance
69+
* to this contract is less than `amount`.
70+
* - burn() reverts. For example, if `amount` is 0.
71+
* - maxFee is greater than or equal to `amount`.
72+
* - MessageTransmitterV2#sendMessage reverts.
73+
* @param amount amount of tokens to burn
74+
* @param destinationDomain destination domain to receive message on
75+
* @param mintRecipient address of mint recipient on destination domain
76+
* @param burnToken token to burn `amount` of, on local domain
77+
* @param destinationCaller authorized caller on the destination domain, as bytes32. If equal to bytes32(0),
78+
* any address can broadcast the message.
79+
* @param maxFee maximum fee to pay on the destination domain, specified in units of burnToken
80+
* @param minFinalityThreshold the minimum finality at which a burn message will be attested to.
81+
*/
82+
function depositForBurn(
83+
uint256 amount,
84+
uint32 destinationDomain,
85+
bytes32 mintRecipient,
86+
address burnToken,
87+
bytes32 destinationCaller,
88+
uint256 maxFee,
89+
uint32 minFinalityThreshold
90+
) external;
91+
}
92+
6093
/**
6194
* A TokenMessenger stores a TokenMinter contract which extends the TokenController contract. The TokenController
6295
* contract has a burnLimitsPerMessage public mapping which can be queried to find the per-message burn limit

contracts/libraries/CircleCCTPAdapter.sol

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ library CircleDomainIds {
1414
uint32 public constant Base = 6;
1515
uint32 public constant Polygon = 7;
1616
uint32 public constant DoctorWho = 10;
17-
// Use this value for placeholder purposes only for adapters that extend this adapter but haven't yet been
18-
// assigned a domain ID by Circle.
17+
uint32 public constant Linea = 11;
1918
uint32 public constant UNINITIALIZED = type(uint32).max;
2019
}
2120

@@ -50,6 +49,13 @@ abstract contract CircleCCTPAdapter {
5049
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
5150
ITokenMessenger public immutable cctpTokenMessenger;
5251

52+
/**
53+
* @notice Indicates if the CCTP V2 TokenMessenger is being used.
54+
* @dev This is determined by checking if the feeRecipient() function exists and returns a non-zero address.
55+
*/
56+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
57+
bool public immutable cctpV2;
58+
5359
/**
5460
* @notice intiailizes the CircleCCTPAdapter contract.
5561
* @param _usdcToken USDC address on the current chain.
@@ -59,12 +65,30 @@ abstract contract CircleCCTPAdapter {
5965
/// @custom:oz-upgrades-unsafe-allow constructor
6066
constructor(
6167
IERC20 _usdcToken,
68+
/// @dev This should ideally be an address but it's kept as an ITokenMessenger to avoid rippling changes to the
69+
/// constructors for every SpokePool/Adapter.
6270
ITokenMessenger _cctpTokenMessenger,
6371
uint32 _recipientCircleDomainId
6472
) {
6573
usdcToken = _usdcToken;
6674
cctpTokenMessenger = _cctpTokenMessenger;
6775
recipientCircleDomainId = _recipientCircleDomainId;
76+
77+
// Only the CCTP V2 TokenMessenger has a feeRecipient() function, so we use it to
78+
// figure out if we are using CCTP V2 or V1. `success` can be true even if the contract doesn't
79+
// implement feeRecipient but it has a fallback function so to be extra safe, we check the return value
80+
// of feeRecipient() as well.
81+
(bool success, bytes memory feeRecipient) = address(cctpTokenMessenger).staticcall(
82+
abi.encodeWithSignature("feeRecipient()")
83+
);
84+
// In case of a call to nonexistent contract or a call to a contract with a fallback function which
85+
// doesn't return any data, feeRecipient can be empty so check its length.
86+
// Even with this check, it's possible that the contract has implemented a fallback function that returns
87+
// 32 bytes of data but its not actually the feeRecipient address. This is extremely low risk but worth
88+
// mentioning that the following check is not 100% safe.
89+
cctpV2 = (success &&
90+
feeRecipient.length == 32 &&
91+
address(uint160(uint256(bytes32(feeRecipient)))) != address(0));
6892
}
6993

7094
/**
@@ -94,14 +118,37 @@ abstract contract CircleCCTPAdapter {
94118
function _transferUsdc(bytes32 to, uint256 amount) internal {
95119
// Only approve the exact amount to be transferred
96120
usdcToken.safeIncreaseAllowance(address(cctpTokenMessenger), amount);
97-
// Submit the amount to be transferred to bridged via the TokenMessenger.
121+
// Submit the amount to be transferred to bridge via the TokenMessenger.
98122
// If the amount to send exceeds the burn limit per message, then split the message into smaller parts.
123+
// @dev We do not care about casting cctpTokenMessenger to ITokenMessengerV2 since both V1 and V2
124+
// expose a localMinter() view function that returns either an ITokenMinterV1 or ITokenMinterV2. Regardless,
125+
// we only care about the burnLimitsPerMessage function which is available in both versions and performs
126+
// the same logic, therefore we purposefully do not re-cast the cctpTokenMessenger and cctpMinter
127+
// to the specific version.
99128
ITokenMinter cctpMinter = cctpTokenMessenger.localMinter();
100129
uint256 burnLimit = cctpMinter.burnLimitsPerMessage(address(usdcToken));
101130
uint256 remainingAmount = amount;
102131
while (remainingAmount > 0) {
103132
uint256 partAmount = remainingAmount > burnLimit ? burnLimit : remainingAmount;
104-
cctpTokenMessenger.depositForBurn(partAmount, recipientCircleDomainId, to, address(usdcToken));
133+
if (cctpV2) {
134+
// Uses the CCTP V2 "standard transfer" speed and
135+
// therefore pays no additional fee for the transfer to be sped up.
136+
ITokenMessengerV2(address(cctpTokenMessenger)).depositForBurn(
137+
partAmount,
138+
recipientCircleDomainId,
139+
to,
140+
address(usdcToken),
141+
// The following parameters are new in this function from V2 to V1, can read more here:
142+
// https://developers.circle.com/stablecoins/evm-smart-contracts
143+
bytes32(0), // destinationCaller is set to bytes32(0) to indicate that anyone can call
144+
// receiveMessage on the destination to finalize the transfer
145+
0, // maxFee can be set to 0 for a "standard transfer"
146+
2000 // minFinalityThreshold can be set to 2000 for a "standard transfer",
147+
// https://github.com/circlefin/evm-cctp-contracts/blob/63ab1f0ac06ce0793c0bbfbb8d09816bc211386d/src/v2/FinalityThresholds.sol#L21
148+
);
149+
} else {
150+
cctpTokenMessenger.depositForBurn(partAmount, recipientCircleDomainId, to, address(usdcToken));
151+
}
105152
remainingAmount -= partAmount;
106153
}
107154
}

deploy/028_deploy_linea_adapter.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { L1_ADDRESS_MAP, WETH } from "./consts";
1+
import { L1_ADDRESS_MAP, WETH, USDCe } from "./consts";
22
import { DeployFunction } from "hardhat-deploy/types";
33
import { HardhatRuntimeEnvironment } from "hardhat/types";
44

@@ -14,7 +14,10 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
1414
WETH[chainId],
1515
L1_ADDRESS_MAP[chainId].lineaMessageService,
1616
L1_ADDRESS_MAP[chainId].lineaTokenBridge,
17-
L1_ADDRESS_MAP[chainId].lineaUsdcBridge,
17+
// TODO: USDC.e on Linea will be upgraded to USDC so eventually we should add a USDC entry for Linea in consts
18+
// and read from there instead of using the L1 USDC.e address.
19+
USDCe[chainId],
20+
L1_ADDRESS_MAP[chainId].cctpV2TokenMessenger,
1821
],
1922
});
2023
};

deploy/029_deploy_linea_spokepool.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DeployFunction } from "hardhat-deploy/types";
22
import { HardhatRuntimeEnvironment } from "hardhat/types";
33
import { deployNewProxy, getSpokePoolDeploymentInfo } from "../utils/utils.hre";
4-
import { FILL_DEADLINE_BUFFER, L2_ADDRESS_MAP, QUOTE_TIME_BUFFER, WETH } from "./consts";
4+
import { FILL_DEADLINE_BUFFER, L2_ADDRESS_MAP, QUOTE_TIME_BUFFER, WETH, USDCe } from "./consts";
55

66
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
77
const { hubPool } = await getSpokePoolDeploymentInfo(hre);
@@ -14,11 +14,18 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
1414
1_000_000,
1515
L2_ADDRESS_MAP[chainId].lineaMessageService,
1616
L2_ADDRESS_MAP[chainId].lineaTokenBridge,
17-
L2_ADDRESS_MAP[chainId].lineaUsdcBridge,
1817
hubPool.address,
1918
hubPool.address,
2019
];
21-
const constructorArgs = [WETH[chainId], QUOTE_TIME_BUFFER, FILL_DEADLINE_BUFFER];
20+
const constructorArgs = [
21+
WETH[chainId],
22+
QUOTE_TIME_BUFFER,
23+
FILL_DEADLINE_BUFFER,
24+
// TODO: USDC.e on Linea will be upgraded to USDC so eventually we should add a USDC entry for Linea in consts
25+
// and read from there instead of using the L1 USDC.e address.
26+
USDCe[chainId],
27+
L2_ADDRESS_MAP[chainId].cctpV2TokenMessenger,
28+
];
2229

2330
await deployNewProxy("Linea_SpokePool", constructorArgs, initArgs);
2431
};

0 commit comments

Comments
 (0)