Commit 7f52efb9 authored by smartcontracts's avatar smartcontracts Committed by GitHub

maint(ct): clean up L2StdBridge tests (#2491)

Cleans up the L2StandardBridge tests using the same techniques as
previous cleanup PRs.
Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent 373d0e6f
/* External Imports */
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract } from 'ethers' import { Contract } from 'ethers'
import { smock, FakeContract, MockContract } from '@defi-wonderland/smock' import { smock, FakeContract, MockContract } from '@defi-wonderland/smock'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
/* Internal Imports */
import { expect } from '../../../setup' import { expect } from '../../../setup'
import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../../../helpers' import { deploy, NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../../../helpers'
import { getContractInterface } from '../../../../src' import { getContractInterface, predeploys } from '../../../../src'
const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated' // TODO: Maybe we should consider automatically generating these and exporting them?
const ERR_INVALID_X_DOMAIN_MSG_SENDER = const ERROR_STRINGS = {
'OVM_XCHAIN: wrong sender of cross-domain message' INVALID_MESSENGER: 'OVM_XCHAIN: messenger contract unauthenticated',
const DUMMY_L1BRIDGE_ADDRESS: string = INVALID_X_DOMAIN_MSG_SENDER:
'0x1234123412341234123412341234123412341234' 'OVM_XCHAIN: wrong sender of cross-domain message',
const DUMMY_L1TOKEN_ADDRESS: string = }
'0x2234223412342234223422342234223422342234'
const OVM_ETH_ADDRESS: string = '0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000' const DUMMY_L1_ERC20_ADDRESS = '0xaBBAABbaaBbAABbaABbAABbAABbaAbbaaBbaaBBa'
const DUMMY_L1_BRIDGE_ADDRESS = '0xACDCacDcACdCaCDcacdcacdCaCdcACdCAcDcaCdc'
describe('L2StandardBridge', () => { describe('L2StandardBridge', () => {
let alice: Signer
let aliceAddress: string
let bob: Signer
let bobsAddress: string
let l2MessengerImpersonator: Signer
let Factory__L1StandardBridge: ContractFactory
const INITIAL_TOTAL_SUPPLY = 100_000 const INITIAL_TOTAL_SUPPLY = 100_000
const ALICE_INITIAL_BALANCE = 50_000 const ALICE_INITIAL_BALANCE = 50_000
let alice: SignerWithAddress
let bob: SignerWithAddress
let l2MessengerImpersonator: SignerWithAddress
before(async () => { before(async () => {
// Create a special signer which will enable us to send messages from the L2Messenger contract // Create a special signer which will enable us to send messages from the L2Messenger contract
;[alice, bob, l2MessengerImpersonator] = await ethers.getSigners() ;[alice, bob, l2MessengerImpersonator] = await ethers.getSigners()
aliceAddress = await alice.getAddress()
bobsAddress = await bob.getAddress()
Factory__L1StandardBridge = await ethers.getContractFactory(
'L1StandardBridge'
)
// get an L2ER20Bridge Interface
getContractInterface('IL2ERC20Bridge')
}) })
let L2StandardBridge: Contract let L2StandardBridge: Contract
...@@ -51,14 +41,19 @@ describe('L2StandardBridge', () => { ...@@ -51,14 +41,19 @@ describe('L2StandardBridge', () => {
) )
// Deploy the contract under test // Deploy the contract under test
L2StandardBridge = await ( L2StandardBridge = await deploy('L2StandardBridge', {
await ethers.getContractFactory('L2StandardBridge') args: [Fake__L2CrossDomainMessenger.address, DUMMY_L1_BRIDGE_ADDRESS],
).deploy(Fake__L2CrossDomainMessenger.address, DUMMY_L1BRIDGE_ADDRESS) })
// Deploy an L2 ERC20 // Deploy an L2 ERC20
L2ERC20 = await ( L2ERC20 = await deploy('L2StandardERC20', {
await ethers.getContractFactory('L2StandardERC20', alice) args: [
).deploy(L2StandardBridge.address, DUMMY_L1TOKEN_ADDRESS, 'L2Token', 'L2T') L2StandardBridge.address,
DUMMY_L1_ERC20_ADDRESS,
'L2Token',
'L2T',
],
})
}) })
// test the transfer flow of moving a token from L2 to L1 // test the transfer flow of moving a token from L2 to L1
...@@ -66,14 +61,14 @@ describe('L2StandardBridge', () => { ...@@ -66,14 +61,14 @@ describe('L2StandardBridge', () => {
it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L2 account', async () => { it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L2 account', async () => {
await expect( await expect(
L2StandardBridge.finalizeDeposit( L2StandardBridge.finalizeDeposit(
DUMMY_L1TOKEN_ADDRESS, DUMMY_L1_ERC20_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
0, 0,
NON_NULL_BYTES32 NON_NULL_BYTES32
) )
).to.be.revertedWith(ERR_INVALID_MESSENGER) ).to.be.revertedWith(ERROR_STRINGS.INVALID_MESSENGER)
}) })
it('onlyFromCrossDomainAccount: should revert on calls from the right crossDomainMessenger, but wrong xDomainMessageSender (ie. not the L1L1StandardBridge)', async () => { it('onlyFromCrossDomainAccount: should revert on calls from the right crossDomainMessenger, but wrong xDomainMessageSender (ie. not the L1L1StandardBridge)', async () => {
...@@ -83,7 +78,7 @@ describe('L2StandardBridge', () => { ...@@ -83,7 +78,7 @@ describe('L2StandardBridge', () => {
await expect( await expect(
L2StandardBridge.connect(l2MessengerImpersonator).finalizeDeposit( L2StandardBridge.connect(l2MessengerImpersonator).finalizeDeposit(
DUMMY_L1TOKEN_ADDRESS, DUMMY_L1_ERC20_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
...@@ -93,19 +88,17 @@ describe('L2StandardBridge', () => { ...@@ -93,19 +88,17 @@ describe('L2StandardBridge', () => {
from: Fake__L2CrossDomainMessenger.address, from: Fake__L2CrossDomainMessenger.address,
} }
) )
).to.be.revertedWith(ERR_INVALID_X_DOMAIN_MSG_SENDER) ).to.be.revertedWith(ERROR_STRINGS.INVALID_X_DOMAIN_MSG_SENDER)
}) })
it('should initialize a withdrawal if the L2 token is not compliant', async () => { it('should initialize a withdrawal if the L2 token is not compliant', async () => {
// Deploy a non compliant ERC20 // Deploy a non compliant ERC20
const NonCompliantERC20 = await ( const NonCompliantERC20 = await deploy('ERC20', {
await ethers.getContractFactory( args: ['L2Token', 'L2T'],
'@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20' })
)
).deploy('L2Token', 'L2T')
L2StandardBridge.connect(l2MessengerImpersonator).finalizeDeposit( L2StandardBridge.connect(l2MessengerImpersonator).finalizeDeposit(
DUMMY_L1TOKEN_ADDRESS, DUMMY_L1_ERC20_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
...@@ -117,14 +110,14 @@ describe('L2StandardBridge', () => { ...@@ -117,14 +110,14 @@ describe('L2StandardBridge', () => {
) )
Fake__L2CrossDomainMessenger.xDomainMessageSender.returns( Fake__L2CrossDomainMessenger.xDomainMessageSender.returns(
() => DUMMY_L1BRIDGE_ADDRESS DUMMY_L1_BRIDGE_ADDRESS
) )
await L2StandardBridge.connect(l2MessengerImpersonator).finalizeDeposit( await L2StandardBridge.connect(l2MessengerImpersonator).finalizeDeposit(
DUMMY_L1TOKEN_ADDRESS, DUMMY_L1_ERC20_ADDRESS,
NonCompliantERC20.address, NonCompliantERC20.address,
aliceAddress, alice.address,
bobsAddress, bob.address,
100, 100,
NON_NULL_BYTES32, NON_NULL_BYTES32,
{ {
...@@ -132,37 +125,37 @@ describe('L2StandardBridge', () => { ...@@ -132,37 +125,37 @@ describe('L2StandardBridge', () => {
} }
) )
const withdrawalCallToMessenger = expect(
Fake__L2CrossDomainMessenger.sendMessage.getCall(1) Fake__L2CrossDomainMessenger.sendMessage.getCall(1).args
).to.deep.equal([
expect(withdrawalCallToMessenger.args[0]).to.equal(DUMMY_L1BRIDGE_ADDRESS) DUMMY_L1_BRIDGE_ADDRESS,
expect(withdrawalCallToMessenger.args[1]).to.equal( getContractInterface('L1StandardBridge').encodeFunctionData(
Factory__L1StandardBridge.interface.encodeFunctionData(
'finalizeERC20Withdrawal', 'finalizeERC20Withdrawal',
[ [
DUMMY_L1TOKEN_ADDRESS, DUMMY_L1_ERC20_ADDRESS,
NonCompliantERC20.address, NonCompliantERC20.address,
bobsAddress, bob.address,
aliceAddress, alice.address,
100, 100,
NON_NULL_BYTES32, NON_NULL_BYTES32,
] ]
) ),
) 0,
])
}) })
it('should credit funds to the depositor', async () => { it('should credit funds to the depositor', async () => {
const depositAmount = 100 const depositAmount = 100
Fake__L2CrossDomainMessenger.xDomainMessageSender.returns( Fake__L2CrossDomainMessenger.xDomainMessageSender.returns(
() => DUMMY_L1BRIDGE_ADDRESS DUMMY_L1_BRIDGE_ADDRESS
) )
await L2StandardBridge.connect(l2MessengerImpersonator).finalizeDeposit( await L2StandardBridge.connect(l2MessengerImpersonator).finalizeDeposit(
DUMMY_L1TOKEN_ADDRESS, DUMMY_L1_ERC20_ADDRESS,
L2ERC20.address, L2ERC20.address,
aliceAddress, alice.address,
bobsAddress, bob.address,
depositAmount, depositAmount,
NON_NULL_BYTES32, NON_NULL_BYTES32,
{ {
...@@ -170,37 +163,35 @@ describe('L2StandardBridge', () => { ...@@ -170,37 +163,35 @@ describe('L2StandardBridge', () => {
} }
) )
const bobsBalance = await L2ERC20.balanceOf(bobsAddress) expect(await L2ERC20.balanceOf(bob.address)).to.equal(depositAmount)
bobsBalance.should.equal(depositAmount)
}) })
}) })
describe('withdrawals', () => { describe('withdrawals', () => {
const withdrawAmount = 1_000 const withdrawAmount = 1_000
let Mock__L2Token: MockContract<Contract>
let Fake__OVM_ETH
let Fake__OVM_ETH: FakeContract<Contract>
before(async () => { before(async () => {
Fake__OVM_ETH = await smock.fake('OVM_ETH', { Fake__OVM_ETH = await smock.fake('OVM_ETH', {
address: OVM_ETH_ADDRESS, address: predeploys.OVM_ETH,
}) })
}) })
let Mock__L2Token: MockContract<Contract>
beforeEach(async () => { beforeEach(async () => {
// Deploy a smodded gateway so we can give some balances to withdraw // Deploy a smodded gateway so we can give some balances to withdraw
Mock__L2Token = await ( Mock__L2Token = await (
await smock.mock('L2StandardERC20') await smock.mock('L2StandardERC20')
).deploy( ).deploy(
L2StandardBridge.address, L2StandardBridge.address,
DUMMY_L1TOKEN_ADDRESS, DUMMY_L1_ERC20_ADDRESS,
'L2Token', 'L2Token',
'L2T' 'L2T'
) )
await Mock__L2Token.setVariable('_totalSupply', INITIAL_TOTAL_SUPPLY) await Mock__L2Token.setVariable('_totalSupply', INITIAL_TOTAL_SUPPLY)
await Mock__L2Token.setVariable('_balances', { await Mock__L2Token.setVariable('_balances', {
[aliceAddress]: ALICE_INITIAL_BALANCE, [alice.address]: ALICE_INITIAL_BALANCE,
}) })
await Mock__L2Token.setVariable('l2Bridge', L2StandardBridge.address) await Mock__L2Token.setVariable('l2Bridge', L2StandardBridge.address)
}) })
...@@ -213,25 +204,16 @@ describe('L2StandardBridge', () => { ...@@ -213,25 +204,16 @@ describe('L2StandardBridge', () => {
NON_NULL_BYTES32 NON_NULL_BYTES32
) )
const withdrawalCallToMessenger = expect(
Fake__L2CrossDomainMessenger.sendMessage.getCall(0) Fake__L2CrossDomainMessenger.sendMessage.getCall(0).args
).to.deep.equal([
// Assert the correct cross-chain call was sent: DUMMY_L1_BRIDGE_ADDRESS,
// Message should be sent to the L1L1StandardBridge on L1 getContractInterface('L1StandardBridge').encodeFunctionData(
expect(withdrawalCallToMessenger.args[0]).to.equal(DUMMY_L1BRIDGE_ADDRESS)
// Message data should be a call telling the L1StandardBridge to finalize the withdrawal
expect(withdrawalCallToMessenger.args[1]).to.equal(
Factory__L1StandardBridge.interface.encodeFunctionData(
'finalizeETHWithdrawal', 'finalizeETHWithdrawal',
[ [alice.address, alice.address, 0, NON_NULL_BYTES32]
await alice.getAddress(), ),
await alice.getAddress(),
0, 0,
NON_NULL_BYTES32, ])
]
)
)
}) })
it('withdraw() burns and sends the correct withdrawal message', async () => { it('withdraw() burns and sends the correct withdrawal message', async () => {
...@@ -241,112 +223,94 @@ describe('L2StandardBridge', () => { ...@@ -241,112 +223,94 @@ describe('L2StandardBridge', () => {
0, 0,
NON_NULL_BYTES32 NON_NULL_BYTES32
) )
const withdrawalCallToMessenger =
Fake__L2CrossDomainMessenger.sendMessage.getCall(0)
// Assert Alice's balance went down expect(
const aliceBalance = await Mock__L2Token.balanceOf( Fake__L2CrossDomainMessenger.sendMessage.getCall(0).args
await alice.getAddress() ).to.deep.equal([
) DUMMY_L1_BRIDGE_ADDRESS,
expect(aliceBalance).to.deep.equal( getContractInterface('L1StandardBridge').encodeFunctionData(
ethers.BigNumber.from(ALICE_INITIAL_BALANCE - withdrawAmount)
)
// Assert totalSupply went down
const newTotalSupply = await Mock__L2Token.totalSupply()
expect(newTotalSupply).to.deep.equal(
ethers.BigNumber.from(INITIAL_TOTAL_SUPPLY - withdrawAmount)
)
// Assert the correct cross-chain call was sent:
// Message should be sent to the L1L1StandardBridge on L1
expect(withdrawalCallToMessenger.args[0]).to.equal(DUMMY_L1BRIDGE_ADDRESS)
// Message data should be a call telling the L1L1StandardBridge to finalize the withdrawal
expect(withdrawalCallToMessenger.args[1]).to.equal(
Factory__L1StandardBridge.interface.encodeFunctionData(
'finalizeERC20Withdrawal', 'finalizeERC20Withdrawal',
[ [
DUMMY_L1TOKEN_ADDRESS, DUMMY_L1_ERC20_ADDRESS,
Mock__L2Token.address, Mock__L2Token.address,
await alice.getAddress(), alice.address,
await alice.getAddress(), alice.address,
withdrawAmount, withdrawAmount,
NON_NULL_BYTES32, NON_NULL_BYTES32,
] ]
),
0,
])
// Assert Alice's balance went down
expect(await Mock__L2Token.balanceOf(alice.address)).to.deep.equal(
ethers.BigNumber.from(ALICE_INITIAL_BALANCE - withdrawAmount)
) )
// Assert totalSupply went down
expect(await Mock__L2Token.totalSupply()).to.deep.equal(
ethers.BigNumber.from(INITIAL_TOTAL_SUPPLY - withdrawAmount)
) )
// gaslimit should be correct
expect(withdrawalCallToMessenger.args[2]).to.equal(0)
}) })
it('withdrawTo() burns and sends the correct withdrawal message', async () => { it('withdrawTo() burns and sends the correct withdrawal message', async () => {
await L2StandardBridge.withdrawTo( await L2StandardBridge.withdrawTo(
Mock__L2Token.address, Mock__L2Token.address,
await bob.getAddress(), bob.address,
withdrawAmount, withdrawAmount,
0, 0,
NON_NULL_BYTES32 NON_NULL_BYTES32
) )
const withdrawalCallToMessenger =
Fake__L2CrossDomainMessenger.sendMessage.getCall(0)
// Assert Alice's balance went down
const aliceBalance = await Mock__L2Token.balanceOf(
await alice.getAddress()
)
expect(aliceBalance).to.deep.equal(
ethers.BigNumber.from(ALICE_INITIAL_BALANCE - withdrawAmount)
)
// Assert totalSupply went down expect(
const newTotalSupply = await Mock__L2Token.totalSupply() Fake__L2CrossDomainMessenger.sendMessage.getCall(0).args
expect(newTotalSupply).to.deep.equal( ).to.deep.equal([
ethers.BigNumber.from(INITIAL_TOTAL_SUPPLY - withdrawAmount) DUMMY_L1_BRIDGE_ADDRESS,
) getContractInterface('L1StandardBridge').encodeFunctionData(
// Assert the correct cross-chain call was sent.
// Message should be sent to the L1L1StandardBridge on L1
expect(withdrawalCallToMessenger.args[0]).to.equal(DUMMY_L1BRIDGE_ADDRESS)
// The message data should be a call telling the L1L1StandardBridge to finalize the withdrawal
expect(withdrawalCallToMessenger.args[1]).to.equal(
Factory__L1StandardBridge.interface.encodeFunctionData(
'finalizeERC20Withdrawal', 'finalizeERC20Withdrawal',
[ [
DUMMY_L1TOKEN_ADDRESS, DUMMY_L1_ERC20_ADDRESS,
Mock__L2Token.address, Mock__L2Token.address,
await alice.getAddress(), alice.address,
await bob.getAddress(), bob.address,
withdrawAmount, withdrawAmount,
NON_NULL_BYTES32, NON_NULL_BYTES32,
] ]
),
0,
])
// Assert Alice's balance went down
expect(await Mock__L2Token.balanceOf(alice.address)).to.deep.equal(
ethers.BigNumber.from(ALICE_INITIAL_BALANCE - withdrawAmount)
) )
// Assert totalSupply went down
expect(await Mock__L2Token.totalSupply()).to.deep.equal(
ethers.BigNumber.from(INITIAL_TOTAL_SUPPLY - withdrawAmount)
) )
// gas value is ignored and set to 0.
expect(withdrawalCallToMessenger.args[2]).to.equal(0)
}) })
}) })
describe('standard erc20', () => { describe('standard erc20', () => {
it('should not allow anyone but the L2 bridge to mint and burn', async () => { it('should not allow anyone but the L2 bridge to mint and burn', async () => {
expect(L2ERC20.connect(alice).mint(aliceAddress, 100)).to.be.revertedWith( expect(
'Only L2 Bridge can mint and burn' L2ERC20.connect(alice).mint(alice.address, 100)
) ).to.be.revertedWith('Only L2 Bridge can mint and burn')
expect(L2ERC20.connect(alice).burn(aliceAddress, 100)).to.be.revertedWith(
'Only L2 Bridge can mint and burn' expect(
) L2ERC20.connect(alice).burn(alice.address, 100)
).to.be.revertedWith('Only L2 Bridge can mint and burn')
}) })
it('should return the correct interface support', async () => { it('should return the correct interface support', async () => {
const supportsERC165 = await L2ERC20.supportsInterface(0x01ffc9a7) // ERC165
expect(supportsERC165).to.be.true expect(await L2ERC20.supportsInterface(0x01ffc9a7)).to.be.true
const supportsL2TokenInterface = await L2ERC20.supportsInterface( // L2StandardERC20
0x1d1d8b63 expect(await L2ERC20.supportsInterface(0x1d1d8b63)).to.be.true
)
expect(supportsL2TokenInterface).to.be.true
const badSupports = await L2ERC20.supportsInterface(0xffffffff) expect(await L2ERC20.supportsInterface(0xffffffff)).to.be.false
expect(badSupports).to.be.false
}) })
}) })
}) })
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment