Created
May 25, 2023 15:00
-
-
Save bzpassersby/20fed702d2498bf3c5b9a60e8da9b6f8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* eslint-disable camelcase */ | |
| import { ethers, upgrades } from 'hardhat' | |
| import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' | |
| import { AddressZero } from '@ethersproject/constants' | |
| import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from './utils/constants' | |
| import { expect } from 'chai' | |
| import { ERROR_STRINGS } from './utils/errors' | |
| import { deployL2Test, transferOwnershipTest } from './utils/fixtures' | |
| import { L2ECO, L2ECOBridge, ProxyAdmin } from '../typechain-types' | |
| const hre = require('hardhat') | |
| const DUMMY_L1_ERC20_ADDRESS = NON_ZERO_ADDRESS | |
| const DUMMY_L1_BRIDGE_ADDRESS = '0xACDCacDcACdCaCDcacdcacdCaCdcACdCAcDcaCdc' | |
| describe('L2ECOApproveTransferRevert', () => { | |
| const INITIAL_TOTAL_SUPPLY = 100000e18 | |
| const ALICE_INITIAL_BALANCE = ethers.utils.parseEther('50000') | |
| let alice: SignerWithAddress | |
| let bob: SignerWithAddress | |
| let attacker: SignerWithAddress | |
| let l2BridgeImpersonator: SignerWithAddress | |
| let eco: L2ECO | |
| const baseInflationMult = 1e18 | |
| beforeEach(async () => { | |
| // Create a special signer which will enable us to send messages from the L2Messenger contract | |
| ;[alice, bob, l2BridgeImpersonator, attacker] = await ethers.getSigners() | |
| const ecoFactory = await ethers.getContractFactory('L2ECO') | |
| eco = (await upgrades.deployProxy( | |
| ecoFactory, | |
| [AddressZero, l2BridgeImpersonator.address], | |
| { | |
| initializer: 'initialize', | |
| } | |
| )) as L2ECO | |
| await eco.deployed() | |
| await expect( | |
| eco | |
| .connect(l2BridgeImpersonator) | |
| .mint(alice.address, ALICE_INITIAL_BALANCE) | |
| ) | |
| .to.emit(eco, 'Transfer') | |
| .withArgs(AddressZero, alice.address, ALICE_INITIAL_BALANCE) | |
| expect(await eco.balanceOf(alice.address)).to.equal(ALICE_INITIAL_BALANCE) | |
| }) | |
| describe('approveTransfer', () => { | |
| it('confirm initial inflation multiplier', async () => { | |
| expect(await eco.linearInflationMultiplier()).to.equal( | |
| baseInflationMult.toString() | |
| ) | |
| }) | |
| it('confirm change of inflation multiplier', async () => { | |
| //set rebase to 0.5 | |
| let newInflationMult = 0.5e18 | |
| await eco | |
| .connect(l2BridgeImpersonator) | |
| .rebase(newInflationMult.toString()) | |
| expect(await eco.linearInflationMultiplier()).to.equal( | |
| newInflationMult.toString() | |
| ) | |
| }) | |
| it('approveTransfer passes when no change of inflation multiplier', async () => { | |
| await eco.connect(alice).approve(bob.address, ALICE_INITIAL_BALANCE) | |
| await eco | |
| .connect(bob) | |
| .transferFrom(alice.address, bob.address, ALICE_INITIAL_BALANCE) | |
| expect(await eco.balanceOf(bob.address)).to.equal(ALICE_INITIAL_BALANCE) ////when no change of inflation multiplier, approveTransfer passes | |
| }) | |
| it('approveTransfer reverts when inflation multiplier increases and token approved prior to increase', async () => { | |
| await eco.connect(alice).approve(bob.address, ALICE_INITIAL_BALANCE) | |
| //set rebase to 2, scale balance in half | |
| let newInflationMult = 2e18 | |
| await eco | |
| .connect(l2BridgeImpersonator) | |
| .rebase(newInflationMult.toString()) | |
| await expect( | |
| eco | |
| .connect(bob) | |
| .transferFrom(alice.address, bob.address, ALICE_INITIAL_BALANCE) | |
| ).to.be.revertedWith(ERROR_STRINGS.L2ECO.TRANSFER_EXCEEDS_BALANCE) ////Transfer reverts due to change of inflation multiplier!Input amount is approved amount(50000), but balance is 25000 | |
| }) | |
| it('approveTransfer reverts when inflation multiplier increases due to MEV attacks', async () => { | |
| //prepare attacker with initial funds | |
| await expect( | |
| eco | |
| .connect(l2BridgeImpersonator) | |
| .mint(attacker.address, ALICE_INITIAL_BALANCE) | |
| ) | |
| ////1. The attacker arrange their own transaction before the rebase, taking advantage of the old inflation multiplier | |
| //// Note that both the approve and transferFrom could be in the same transaction. | |
| await eco.connect(attacker).approve(bob.address, ALICE_INITIAL_BALANCE) | |
| await eco | |
| .connect(bob) | |
| .transferFrom(attacker.address, bob.address, ALICE_INITIAL_BALANCE) | |
| expect(await eco.balanceOf(bob.address)).to.equal(ALICE_INITIAL_BALANCE) //// attacker's transaction succeeds | |
| ////2. Rebase is executed in l2, scale balance in half | |
| let newInflationMult = 2e18 | |
| await eco | |
| .connect(l2BridgeImpersonator) | |
| .rebase(newInflationMult.toString()) | |
| ////3. The attacker arrange alice's transaction after the rebase, causing it to revert due to insufficient balance. | |
| //// Note that both the approve and transferFrom could be in the same transaction | |
| await eco.connect(alice).approve(bob.address, ALICE_INITIAL_BALANCE) | |
| await expect( | |
| eco | |
| .connect(bob) | |
| .transferFrom(alice.address, bob.address, ALICE_INITIAL_BALANCE) | |
| ).to.be.revertedWith(ERROR_STRINGS.L2ECO.TRANSFER_EXCEEDS_BALANCE) | |
| //// Alice's transaction reverts due to change of inflation multiplier and MEV attack: input amount is approved amount(50000), but balance is 25000. | |
| //// This kind of attack could happen in many contexts where L2ECO transfer is required. One exmaple is L2ECO trading on AMMs. | |
| //// Although context might not be directly on ECO, the attack is enabled by a temporary break between two accounting systems both used in L2ECO contract. | |
| //// In addition, the timing of the attack is predictable due the governance process of rebase. And it's likely profitable for the attacker, causing the victim to lose profits. | |
| }) | |
| }) | |
| }) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment