Skip to content

Commit 71ddfa7

Browse files
committed
test(integration): withdrawing a fake L2 token
1 parent eb926e2 commit 71ddfa7

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@eth-optimism/integration-tests': patch
3+
---
4+
5+
Add an integration test showing the infeasability of withdrawing a fake token in exchange for a legitimate token.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.9;
3+
4+
contract FakeL2StandardERC20 {
5+
6+
address public immutable l1Token;
7+
8+
constructor(address _l1Token) {
9+
l1Token = _l1Token;
10+
}
11+
12+
// Burn will be called by the L2 Bridge to burn the tokens we are bridging to L1
13+
function burn(address, uint256) external {}
14+
}

integration-tests/test/bridged-tokens.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,58 @@ describe('Bridged tokens', () => {
128128
)
129129
}
130130
)
131+
132+
// This test demonstrates that an apparent withdrawal bug is in fact non-existent.
133+
// Specifically, the L2 bridge does not check that the L2 token being burned corresponds
134+
// with the L1 token which is specified for the withdrawal.
135+
withdrawalTest(
136+
'should not allow an arbitrary L2 token to be withdrawn in exchange for a legitimate L1 token',
137+
async () => {
138+
before(async () => {
139+
// First deposit some of the L1 token to L2, so that there is something which could be stolen.
140+
const depositTx = await env.l1Bridge
141+
.connect(env.l1Wallet)
142+
.depositERC20(
143+
L1__ERC20.address,
144+
L2__ERC20.address,
145+
1000,
146+
2000000,
147+
'0x'
148+
)
149+
await env.waitForXDomainTransaction(depositTx, Direction.L1ToL2)
150+
expect(await L2__ERC20.balanceOf(env.l2Wallet.address)).to.deep.equal(
151+
BigNumber.from(1000)
152+
)
153+
})
154+
155+
// Deploy a Fake L2 token, which:
156+
// - returns the address of a legitimate L1 token from its l1Token() getter.
157+
// - allows the L2 bridge to call its burn() function.
158+
const fakeToken = await (
159+
await ethers.getContractFactory('FakeL2StandardERC20', env.l2Wallet)
160+
).deploy(L1__ERC20.address)
161+
await fakeToken.deployed()
162+
163+
const balBefore = await L1__ERC20.balanceOf(otherWalletL1.address)
164+
165+
// Withdraw some of the Fake L2 token, hoping to receive the same amount of the legitimate
166+
// token on L1.
167+
const withdrawalTx = await env.l2Bridge
168+
.connect(otherWalletL2)
169+
.withdrawTo(
170+
fakeToken.address,
171+
otherWalletL1.address,
172+
500,
173+
1_000_000,
174+
'0x'
175+
)
176+
await env.relayXDomainMessages(withdrawalTx)
177+
await env.waitForXDomainTransaction(withdrawalTx, Direction.L2ToL1)
178+
179+
// Ensure that the L1 recipient address has not received any additional L1 token balance.
180+
expect(await L1__ERC20.balanceOf(otherWalletL1.address)).to.deep.equal(
181+
balBefore
182+
)
183+
}
184+
)
131185
})

0 commit comments

Comments
 (0)