Skip to content

Commit f4f28c8

Browse files
Fix non native tokens updated (#15)
* initial commit * Refactor deposit validation logic in L1ERC20Bridge.sol * fix Should not allow depositing zero amount * fix l1_erc20_bridge tests * Refactor withdrawal handling in MailboxFacet * fixes * fix deployL2 * fix l2 weth init * fix weth bridge tests * Restore preprocessor conditional --------- Co-authored-by: Jmunoz <[email protected]>
1 parent 79f4c20 commit f4f28c8

File tree

13 files changed

+117
-53
lines changed

13 files changed

+117
-53
lines changed

l1-contracts/contracts/bridge/L1ERC20Bridge.sol

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,18 @@ contract L1ERC20Bridge is IL1Bridge, IL1BridgeLegacy, ReentrancyGuard {
8787
uint256 _deployBridgeProxyFee,
8888
uint256 _amount
8989
) external payable reentrancyGuardInitializer {
90+
bool nativeErc20 = _amount != 0;
91+
9092
require(_l2TokenBeacon != address(0), "nf");
9193
require(_governor != address(0), "nh");
9294
// We are expecting to see the exact three bytecodes that are needed to initialize the bridge
9395
require(_factoryDeps.length == 3, "mk");
9496
// The caller miscalculated deploy transactions fees
95-
require(_amount == _deployBridgeImplementationFee + _deployBridgeProxyFee, "fee");
97+
if (nativeErc20) {
98+
require(_amount == _deployBridgeImplementationFee + _deployBridgeProxyFee, "fee");
99+
} else {
100+
require(msg.value == _deployBridgeImplementationFee + _deployBridgeProxyFee, "fee");
101+
}
96102
l2TokenProxyBytecodeHash = L2ContractHelper.hashL2Bytecode(_factoryDeps[2]);
97103
l2TokenBeacon = _l2TokenBeacon;
98104

@@ -149,9 +155,18 @@ contract L1ERC20Bridge is IL1Bridge, IL1BridgeLegacy, ReentrancyGuard {
149155
address _l1Token,
150156
uint256 _amount,
151157
uint256 _l2TxGasLimit,
152-
uint256 _l2TxGasPerPubdataByte
158+
uint256 _l2TxGasPerPubdataByte,
159+
uint256 _l2MaxFee
153160
) external payable returns (bytes32 l2TxHash) {
154-
l2TxHash = deposit(_l2Receiver, _l1Token, _amount, _l2TxGasLimit, _l2TxGasPerPubdataByte, address(0));
161+
l2TxHash = deposit(
162+
_l2Receiver,
163+
_l1Token,
164+
_amount,
165+
_l2TxGasLimit,
166+
_l2TxGasPerPubdataByte,
167+
address(0),
168+
_l2MaxFee
169+
);
155170
}
156171

157172
/// @notice Initiates a deposit by locking funds on the contract and sending the request
@@ -164,6 +179,7 @@ contract L1ERC20Bridge is IL1Bridge, IL1BridgeLegacy, ReentrancyGuard {
164179
/// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction
165180
/// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction
166181
/// @param _refundRecipient The address on L2 that will receive the refund for the transaction.
182+
/// @param _l2MaxFee The max fee to be paid in L2.
167183
/// @dev If the L2 deposit finalization transaction fails, the `_refundRecipient` will receive the `_l2Value`.
168184
/// Please note, the contract may change the refund recipient's address to eliminate sending funds to addresses
169185
/// out of control.
@@ -184,35 +200,44 @@ contract L1ERC20Bridge is IL1Bridge, IL1BridgeLegacy, ReentrancyGuard {
184200
uint256 _amount,
185201
uint256 _l2TxGasLimit,
186202
uint256 _l2TxGasPerPubdataByte,
187-
address _refundRecipient
203+
address _refundRecipient,
204+
uint256 _l2MaxFee
188205
) public payable nonReentrant returns (bytes32 l2TxHash) {
189206
require(_amount != 0, "2T"); // empty deposit amount
190207
uint256 amount = _depositFunds(msg.sender, IERC20(_l1Token), _amount);
191208
require(amount == _amount, "1T"); // The token has non-standard transfer logic
192209

210+
l2TxHash = _getRefundRecipientAndRequestL2Transaction(_refundRecipient, _l2MaxFee, _l2Receiver, _l1Token, _l2TxGasLimit, _l2TxGasPerPubdataByte, amount);
211+
// Save the deposited amount to claim funds on L1 if the deposit failed on L2
212+
depositAmount[msg.sender][_l1Token][l2TxHash] = amount;
213+
emit DepositInitiated(l2TxHash, msg.sender, _l2Receiver, _l1Token, amount);
214+
}
215+
216+
function _getRefundRecipientAndRequestL2Transaction(address _refundRecipient, uint256 _l2MaxFee, address _l2Receiver, address _l1Token, uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte, uint256 amount) internal returns (bytes32) {
193217
bytes memory l2TxCalldata = _getDepositL2Calldata(msg.sender, _l2Receiver, _l1Token, amount);
194218
// If the refund recipient is not specified, the refund will be sent to the sender of the transaction.
195219
// Otherwise, the refund will be sent to the specified address.
196220
// If the recipient is a contract on L1, the address alias will be applied.
197-
address refundRecipient = _refundRecipient;
198-
if (_refundRecipient == address(0)) {
199-
refundRecipient = msg.sender != tx.origin ? AddressAliasHelper.applyL1ToL2Alias(msg.sender) : msg.sender;
200-
}
201-
l2TxHash = zkSync.requestL2Transaction{value: msg.value}(
221+
address refundRecipient = _getRefundRecipient(_refundRecipient);
222+
223+
return zkSync.requestL2Transaction{value: msg.value}(
202224
l2Bridge,
203225
0, // L2 msg.value
204-
0,
226+
_l2MaxFee,
205227
l2TxCalldata,
206228
_l2TxGasLimit,
207229
_l2TxGasPerPubdataByte,
208230
new bytes[](0),
209231
refundRecipient
210232
);
233+
}
211234

212-
// Save the deposited amount to claim funds on L1 if the deposit failed on L2
213-
depositAmount[msg.sender][_l1Token][l2TxHash] = amount;
214-
215-
emit DepositInitiated(l2TxHash, msg.sender, _l2Receiver, _l1Token, amount);
235+
// Refund recipient logic
236+
function _getRefundRecipient(address _refundRecipient) internal view returns (address) {
237+
return
238+
_refundRecipient == address(0)
239+
? (msg.sender != tx.origin ? AddressAliasHelper.applyL1ToL2Alias(msg.sender) : msg.sender)
240+
: _refundRecipient;
216241
}
217242

218243
/// @dev Transfers tokens from the depositor address to the smart contract address

l1-contracts/contracts/bridge/L1WethBridge.sol

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,20 @@ contract L1WethBridge is IL1Bridge, ReentrancyGuard {
8585
uint256 _deployBridgeProxyFee,
8686
uint256 _amount
8787
) external payable reentrancyGuardInitializer {
88+
bool nativeErc20 = _amount != 0;
89+
8890
require(_l2WethAddress != address(0), "L2 WETH address cannot be zero");
8991
require(_governor != address(0), "Governor address cannot be zero");
9092
require(_factoryDeps.length == 2, "Invalid factory deps length provided");
91-
require(
92-
_amount == _deployBridgeImplementationFee + _deployBridgeProxyFee,
93-
"Miscalculated deploy transactions fees"
94-
);
93+
94+
if (nativeErc20) {
95+
require(
96+
_amount == _deployBridgeImplementationFee + _deployBridgeProxyFee,
97+
"Miscalculated deploy transactions fees"
98+
);
99+
} else {
100+
require(msg.value == _deployBridgeImplementationFee + _deployBridgeProxyFee, "Miscalculated deploy transactions fees");
101+
}
95102

96103
l2WethAddress = _l2WethAddress;
97104

@@ -164,7 +171,8 @@ contract L1WethBridge is IL1Bridge, ReentrancyGuard {
164171
uint256 _amount,
165172
uint256 _l2TxGasLimit,
166173
uint256 _l2TxGasPerPubdataByte,
167-
address _refundRecipient
174+
address _refundRecipient,
175+
uint256 _l2MaxFee
168176
) external payable nonReentrant returns (bytes32 txHash) {
169177
require(_l1Token == l1WethAddress, "Invalid L1 token address");
170178
require(_amount != 0, "Amount cannot be zero");
@@ -187,7 +195,7 @@ contract L1WethBridge is IL1Bridge, ReentrancyGuard {
187195
txHash = zkSync.requestL2Transaction{value: _amount + msg.value}(
188196
l2Bridge,
189197
_amount,
190-
0,
198+
_l2MaxFee,
191199
l2TxCalldata,
192200
_l2TxGasLimit,
193201
_l2TxGasPerPubdataByte,

l1-contracts/contracts/bridge/interfaces/IL1Bridge.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ interface IL1Bridge {
2626
uint256 _amount,
2727
uint256 _l2TxGasLimit,
2828
uint256 _l2TxGasPerPubdataByte,
29-
address _refundRecipient
29+
address _refundRecipient,
30+
uint256 _l2MaxFee
3031
) external payable returns (bytes32 txHash);
3132

3233
function claimFailedDeposit(

l1-contracts/contracts/bridge/interfaces/IL1BridgeLegacy.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface IL1BridgeLegacy {
1111
address _l1Token,
1212
uint256 _amount,
1313
uint256 _l2TxGasLimit,
14-
uint256 _l2TxGasPerPubdataByte
14+
uint256 _l2TxGasPerPubdataByte,
15+
uint256 _l2MaxFee
1516
) external payable returns (bytes32 txHash);
1617
}

l1-contracts/contracts/zksync/facets/Mailbox.sol

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
2222
// While formally the following import is not used, it is needed to inherit documentation from it
2323
import {IBase} from "../interfaces/IBase.sol";
2424

25+
bool constant NATIVE_ERC20 = $(NATIVE_ERC20);
26+
2527
/// @title zkSync Mailbox contract providing interfaces for L1 <-> L2 interaction.
2628
/// @author Matter Labs
2729
/// @custom:security-contact [email protected]
@@ -173,8 +175,7 @@ contract MailboxFacet is Base, IMailbox {
173175
bytes calldata _message,
174176
bytes32[] calldata _merkleProof
175177
) external nonReentrant {
176-
// #def TOKEN_TYPE 'ERC20'
177-
// #if TOKEN_TYPE == 'ETH'
178+
// #if NATIVE_ERC20 == false
178179
require(!s.isEthWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "jj");
179180

180181
L2Message memory l2ToL1Message = L2Message({
@@ -183,16 +184,16 @@ contract MailboxFacet is Base, IMailbox {
183184
data: _message
184185
});
185186

186-
(address _l1WithdrawReceiver, address _t, uint256 _amount) = _parseL2WithdrawalMessage(_message);
187+
(address _l1WithdrawReceiver, uint256 _amount) = _parseL2WithdrawalMessage(_message);
187188
{
188189
bool proofValid = proveL2MessageInclusion(_l2BatchNumber, _l2MessageIndex, l2ToL1Message, _merkleProof);
189190
require(proofValid, "pi"); // Failed to verify that withdrawal was actually initialized on L2
190191
}
191192
s.isEthWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex] = true;
192193
_withdrawFunds(_l1WithdrawReceiver, _amount);
193-
194+
194195
emit EthWithdrawalFinalized(_l1WithdrawReceiver, _amount);
195-
// #elif TOKEN_TYPE == 'ERC20'
196+
// #elif NATIVE_ERC20 == true
196197
require(!s.isEthWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "pw");
197198

198199
L2Message memory l2ToL1Message = L2Message({
@@ -230,10 +231,10 @@ contract MailboxFacet is Base, IMailbox {
230231
) external payable nonReentrant returns (bytes32 canonicalTxHash) {
231232
// Change the sender address if it is a smart contract to prevent address collision between L1 and L2.
232233
// Please note, currently zkSync address derivation is different from Ethereum one, but it may be changed in the future.
233-
// address sender = msg.sender;
234-
// if (sender != tx.origin) {
235-
// sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
236-
// }
234+
address sender = msg.sender;
235+
if (sender != tx.origin) {
236+
sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
237+
}
237238

238239
// Enforcing that `_l2GasPerPubdataByteLimit` equals to a certain constant number. This is needed
239240
// to ensure that users do not get used to using "exotic" numbers for _l2GasPerPubdataByteLimit, e.g. 1-2, etc.
@@ -243,7 +244,7 @@ contract MailboxFacet is Base, IMailbox {
243244
require(_l2GasPerPubdataByteLimit == REQUIRED_L2_GAS_PRICE_PER_PUBDATA, "qp");
244245

245246
canonicalTxHash = _requestL2Transaction(
246-
msg.sender,
247+
sender,
247248
_contractL2,
248249
_l2Value,
249250
_amount,
@@ -273,12 +274,15 @@ contract MailboxFacet is Base, IMailbox {
273274
// Here we manually assign fields for the struct to prevent "stack too deep" error
274275
WritePriorityOpParams memory params;
275276

277+
uint256 amount = _amount != 0 ? _amount : msg.value;
278+
// uint256 amount = msg.value;
279+
276280
// Checking that the user provided enough ether to pay for the transaction.
277281
// Using a new scope to prevent "stack too deep" error
278282
{
279283
params.l2GasPrice = _isFree ? 0 : _deriveL2GasPrice(tx.gasprice, _l2GasPerPubdataByteLimit);
280284
uint256 baseCost = params.l2GasPrice * _l2GasLimit;
281-
require(_amount >= baseCost + _l2Value, "mv"); // The `msg.value` doesn't cover the transaction cost
285+
require(amount >= baseCost + _l2Value, "mv"); // The `amount` doesn't cover the transaction cost
282286
}
283287

284288
// If the `_refundRecipient` is not provided, we use the `_sender` as the recipient.
@@ -288,22 +292,25 @@ contract MailboxFacet is Base, IMailbox {
288292
refundRecipient = AddressAliasHelper.applyL1ToL2Alias(refundRecipient);
289293
}
290294

291-
// The address of the token that is used in the L2 as native.
292-
address nativeTokenAddress = address($(L1_NATIVE_TOKEN_ADDRESS));
293-
// Check balance and allowance.
294-
require(IERC20(nativeTokenAddress).balanceOf(tx.origin) >= _amount, "Not enough balance");
295-
require(IERC20(nativeTokenAddress).allowance(tx.origin, address(this)) >= _amount, "Not enough allowance");
295+
// Check if we are operating with native tokens.
296+
if (_amount != 0) {
297+
// The address of the token that is used in the L2 as native.
298+
address nativeTokenAddress = address($(L1_NATIVE_TOKEN_ADDRESS));
299+
// Check balance and allowance.
300+
require(IERC20(nativeTokenAddress).balanceOf(tx.origin) >= amount, "Not enough balance");
301+
require(IERC20(nativeTokenAddress).allowance(tx.origin, address(this)) >= amount, "Not enough allowance");
296302

297-
// Transfer tokens to the contract.
298-
IERC20(nativeTokenAddress).safeTransferFrom(tx.origin, address(this), _amount);
303+
// Transfer tokens to the contract.
304+
IERC20(nativeTokenAddress).safeTransferFrom(tx.origin, address(this), amount);
305+
}
299306

300307
params.sender = _sender;
301308
params.txId = s.priorityQueue.getTotalPriorityTxs();
302309
params.l2Value = _l2Value;
303310
params.contractAddressL2 = _contractAddressL2;
304311
params.expirationTimestamp = uint64(block.timestamp + PRIORITY_EXPIRATION);
305312
params.l2GasLimit = _l2GasLimit;
306-
params.valueToMint = _amount;
313+
params.valueToMint = amount;
307314
params.l2GasPricePerPubdata = _l2GasPerPubdataByteLimit;
308315
params.refundRecipient = refundRecipient;
309316

l1-contracts/hardhat.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export default {
116116
}
117117

118118
return {
119+
NATIVE_ERC20: process.env.NATIVE_ERC20,
119120
NATIVE_ERC20_ADDRESS: address,
120121
...systemParams,
121122
...defs,

l1-contracts/scripts/initialize-bridges.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,18 @@ async function main() {
6161
.option("--gas-price <gas-price>")
6262
.option("--nonce <nonce>")
6363
.option("--erc20-bridge <erc20-bridge>")
64+
.option("--native-erc20")
6465
.action(async (cmd) => {
6566
const deployWallet = cmd.privateKey
6667
? new Wallet(cmd.privateKey, provider)
6768
: Wallet.fromMnemonic(
6869
process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic,
6970
"m/44'/60'/0'/0/0"
7071
).connect(provider);
72+
73+
const nativeErc20impl = cmd.nativeErc20 ? true : false;
74+
console.log(`Using native erc20: ${nativeErc20impl}`);
75+
7176
console.log(`Using deployer wallet: ${deployWallet.address}`);
7277

7378
const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice();
@@ -152,7 +157,7 @@ async function main() {
152157
zkSync.requestL2Transaction(
153158
ethers.constants.AddressZero,
154159
0,
155-
requiredValueToPublishBytecodes,
160+
nativeErc20impl ? requiredValueToPublishBytecodes : 0,
156161
"0x",
157162
priorityTxMaxGasLimit,
158163
SYSTEM_CONFIG.requiredL2GasPricePerPubdata,
@@ -166,15 +171,14 @@ async function main() {
166171
l2GovernorAddress,
167172
requiredValueToInitializeBridge,
168173
requiredValueToInitializeBridge,
169-
requiredValueToInitializeBridge.mul(2),
174+
nativeErc20impl ? requiredValueToInitializeBridge.mul(2) : 0,
170175
{
171176
gasPrice,
172177
nonce: nonce + 1,
173178
value: requiredValueToInitializeBridge.mul(2),
174179
}
175180
),
176181
];
177-
178182
const txs = await Promise.all(independentInitialization);
179183
for (const tx of txs) {
180184
console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...`);

l1-contracts/scripts/initialize-l2-weth-token.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ async function main() {
124124
.option("--private-key <private-key>")
125125
.option("--gas-price <gas-price>")
126126
.option("--nonce <nonce>")
127+
.option('--native-erc20')
127128
.action(async (cmd) => {
128129
if (!l1WethTokenAddress) {
129130
console.log("Base Layer WETH address not provided. Skipping.");
@@ -136,6 +137,10 @@ async function main() {
136137
process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic,
137138
"m/44'/60'/0'/0/1"
138139
).connect(provider);
140+
141+
const nativeErc20impl = cmd.nativeErc20 ? true : false;
142+
console.log(`Using native erc20: ${nativeErc20impl}`);
143+
139144
console.log(`Using deployer wallet: ${deployWallet.address}`);
140145

141146
const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice();
@@ -160,7 +165,7 @@ async function main() {
160165
const tx = await zkSync.requestL2Transaction(
161166
l2WethTokenProxyAddress,
162167
0,
163-
requiredValueToInitializeBridge,
168+
nativeErc20impl? requiredValueToInitializeBridge : 0,
164169
calldata,
165170
DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT,
166171
SYSTEM_CONFIG.requiredL2GasPricePerPubdata,

l1-contracts/scripts/initialize-weth-bridges.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,18 @@ async function main() {
3939
.option("--private-key <private-key>")
4040
.option("--gas-price <gas-price>")
4141
.option("--nonce <nonce>")
42+
.option("--native-erc20")
4243
.action(async (cmd) => {
4344
const deployWallet = cmd.privateKey
4445
? new Wallet(cmd.privateKey, provider)
4546
: Wallet.fromMnemonic(
4647
process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic,
4748
"m/44'/60'/0'/0/0"
4849
).connect(provider);
50+
51+
const nativeErc20impl = cmd.nativeErc20 ? true : false;
52+
console.log(`Using native erc20: ${nativeErc20impl}`);
53+
4954
console.log(`Using deployer wallet: ${deployWallet.address}`);
5055

5156
const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice();
@@ -82,7 +87,7 @@ async function main() {
8287
l2GovernorAddress,
8388
requiredValueToInitializeBridge,
8489
requiredValueToInitializeBridge,
85-
requiredValueToInitializeBridge.mul(2),
90+
nativeErc20impl ? requiredValueToInitializeBridge.mul(2) : 0,
8691
{
8792
gasPrice,
8893
value: requiredValueToInitializeBridge.mul(2),

0 commit comments

Comments
 (0)