From 94d11948cf60f7284a5b70b69c72bae97f04ebfe Mon Sep 17 00:00:00 2001 From: Chris Buckland Date: Thu, 22 Aug 2024 13:23:22 +0100 Subject: [PATCH 1/3] Added a burner contract in case the dao decides to burn the funds instead of collecting --- src/express-lane-auction/Burner.sol | 20 ++++++ test/foundry/ExpressLaneAuction.t.sol | 89 ++++++++++++++++++++++++++- test/foundry/ExpressLaneBurner.t.sol | 44 +++++++++++++ 3 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 src/express-lane-auction/Burner.sol create mode 100644 test/foundry/ExpressLaneBurner.t.sol diff --git a/src/express-lane-auction/Burner.sol b/src/express-lane-auction/Burner.sol new file mode 100644 index 00000000..721b16d6 --- /dev/null +++ b/src/express-lane-auction/Burner.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { + ERC20BurnableUpgradeable +} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; + +/// @notice A simple contract that can burn any tokens that are transferred to it +contract Burner { + ERC20BurnableUpgradeable public immutable token; + + constructor(address _token) { + token = ERC20BurnableUpgradeable(_token); + } + + /// @notice Can be called at any time by anyone to burn any tokens held by this burner + function burn() external { + token.burn(token.balanceOf(address(this))); + } +} diff --git a/test/foundry/ExpressLaneAuction.t.sol b/test/foundry/ExpressLaneAuction.t.sol index c8f0b168..f173faa0 100644 --- a/test/foundry/ExpressLaneAuction.t.sol +++ b/test/foundry/ExpressLaneAuction.t.sol @@ -3,13 +3,18 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "../../src/express-lane-auction/ExpressLaneAuction.sol"; -import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { + ERC20Burnable, + IERC20 +} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "../../src/express-lane-auction/Burner.sol"; -contract MockERC20 is ERC20 { +contract MockERC20 is ERC20Burnable { constructor() ERC20("LANE", "LNE") { _mint(msg.sender, 1_000_000); } @@ -970,6 +975,86 @@ contract ExpressLaneAuctionTest is Test { rs.auction.flushBeneficiaryBalance(); } + function testFlushToBurner() public { + vm.chainId(137); + Bid memory bid0; + Bid memory bid1; + ExpressLaneAuction auction; + MockERC20 erc20 = new MockERC20(); + Burner burner = new Burner(address(erc20)); + { + ProxyAdmin proxyAdmin = new ProxyAdmin(); + ExpressLaneAuction impl = new ExpressLaneAuction(); + + auction = ExpressLaneAuction( + address(new TransparentUpgradeableProxy(address(impl), address(proxyAdmin), "")) + ); + InitArgs memory args = createArgs(address(erc20)); + args._beneficiary = address(burner); + auction.initialize(args); + + erc20.transfer(bidders[0].addr, bidders[0].amount); + erc20.transfer(bidders[1].addr, bidders[1].amount); + + vm.startPrank(bidders[0].addr); + erc20.approve(address(auction), bidders[0].amount); + auction.deposit(bidders[0].amount); + vm.stopPrank(); + + vm.startPrank(bidders[1].addr); + erc20.approve(address(auction), bidders[1].amount); + auction.deposit(bidders[1].amount); + vm.stopPrank(); + + (int64 o, uint64 roundDurationSeconds, uint64 auctionClosingSeconds, ) = auction + .roundTimingInfo(); + vm.warp( + uint64(o) + + (roundDurationSeconds * testRound) + + roundDurationSeconds - + auctionClosingSeconds + ); + uint64 biddingForRound = auction.currentRound() + 1; + + bytes32 h0 = auction.getBidHash(biddingForRound, bidders[0].elc, bidders[0].amount / 2); + bid0 = Bid({ + amount: bidders[0].amount / 2, + expressLaneController: bidders[0].elc, + signature: sign(bidders[0].privKey, h0) + }); + bytes32 h1 = auction.getBidHash(biddingForRound, bidders[1].elc, bidders[1].amount / 2); + bid1 = Bid({ + amount: bidders[1].amount / 2, + expressLaneController: bidders[1].elc, + signature: sign(bidders[1].privKey, h1) + }); + } + + vm.prank(auctioneer); + auction.resolveMultiBidAuction(bid1, bid0); + + assertFalse(auction.beneficiaryBalance() == 0, "bal before"); + uint256 auctionBalanceBefore = erc20.balanceOf(address(auction)); + uint256 beneficiaryBalanceBefore = erc20.balanceOf(auction.beneficiary()); + assertEq(erc20.balanceOf(address(burner)), 0); + + // any random address should be able to call this + vm.prank(vm.addr(34567890)); + auction.flushBeneficiaryBalance(); + + uint256 auctionBalanceAfter = erc20.balanceOf(address(auction)); + uint256 beneficiaryBalanceAfter = erc20.balanceOf(auction.beneficiary()); + assertTrue(auction.beneficiaryBalance() == 0, "bal after"); + assertEq(beneficiaryBalanceAfter - beneficiaryBalanceBefore, bid0.amount); + assertEq(auctionBalanceBefore - auctionBalanceAfter, bid0.amount); + assertEq(erc20.balanceOf(address(burner)), bid0.amount); + + vm.prank(vm.addr(948765)); + burner.burn(); + + assertEq(erc20.balanceOf(address(burner)), 0); + } + function testCannotResolveNotAuctioneer() public { ResolveSetup memory rs = deployDepositAndBids(); vm.stopPrank(); diff --git a/test/foundry/ExpressLaneBurner.t.sol b/test/foundry/ExpressLaneBurner.t.sol new file mode 100644 index 00000000..def66f85 --- /dev/null +++ b/test/foundry/ExpressLaneBurner.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import { + ERC20BurnableUpgradeable +} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import {Burner} from "../../src/express-lane-auction/Burner.sol"; + +contract MockERC20 is ERC20BurnableUpgradeable { + function initialize() public initializer { + __ERC20_init("LANE", "LNE"); + _mint(msg.sender, 1_000_000); + } +} + +contract ExpressLaneBurner is Test { + event Transfer(address indexed from, address indexed to, uint256 value); + + function testBurn() public { + MockERC20 erc20 = new MockERC20(); + erc20.initialize(); + Burner burner = new Burner(address(erc20)); + + erc20.transfer(address(burner), 20); + + uint256 totalSupplyBefore = erc20.totalSupply(); + assertEq(erc20.balanceOf(address(burner)), 20); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(burner), address(0), 20); + vm.prank(vm.addr(137)); + burner.burn(); + + assertEq(totalSupplyBefore - erc20.totalSupply(), 20); + assertEq(erc20.balanceOf(address(burner)), 0); + + // can burn 0 if we want to + vm.expectEmit(true, true, true, true); + emit Transfer(address(burner), address(0), 0); + vm.prank(vm.addr(138)); + burner.burn(); + } +} From 893f40dea61e589c3aece68e9f4fe7a7cb316591 Mon Sep 17 00:00:00 2001 From: Chris Buckland Date: Tue, 27 Aug 2024 10:46:25 +0100 Subject: [PATCH 2/3] Burner tests --- src/express-lane-auction/Burner.sol | 4 ++++ src/express-lane-auction/Errors.sol | 1 + test/foundry/ExpressLaneBurner.t.sol | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/src/express-lane-auction/Burner.sol b/src/express-lane-auction/Burner.sol index 721b16d6..5fb437bb 100644 --- a/src/express-lane-auction/Burner.sol +++ b/src/express-lane-auction/Burner.sol @@ -4,12 +4,16 @@ pragma solidity ^0.8.0; import { ERC20BurnableUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import "./Errors.sol"; /// @notice A simple contract that can burn any tokens that are transferred to it contract Burner { ERC20BurnableUpgradeable public immutable token; constructor(address _token) { + if (_token == address(0)) { + revert ZeroAddress(); + } token = ERC20BurnableUpgradeable(_token); } diff --git a/src/express-lane-auction/Errors.sol b/src/express-lane-auction/Errors.sol index c7066c1f..fb619acd 100644 --- a/src/express-lane-auction/Errors.sol +++ b/src/express-lane-auction/Errors.sol @@ -28,3 +28,4 @@ error RoundTooLong(uint64 roundDurationSeconds); error ZeroAuctionClosingSeconds(); error NegativeOffset(); error NegativeRoundStart(int64 roundStart); +error ZeroAddress(); diff --git a/test/foundry/ExpressLaneBurner.t.sol b/test/foundry/ExpressLaneBurner.t.sol index def66f85..e6bb2221 100644 --- a/test/foundry/ExpressLaneBurner.t.sol +++ b/test/foundry/ExpressLaneBurner.t.sol @@ -6,6 +6,7 @@ import { ERC20BurnableUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; import {Burner} from "../../src/express-lane-auction/Burner.sol"; +import "../../src/express-lane-auction/Errors.sol"; contract MockERC20 is ERC20BurnableUpgradeable { function initialize() public initializer { @@ -18,9 +19,13 @@ contract ExpressLaneBurner is Test { event Transfer(address indexed from, address indexed to, uint256 value); function testBurn() public { + vm.expectRevert(ZeroAddress.selector); + new Burner(address(0)); + MockERC20 erc20 = new MockERC20(); erc20.initialize(); Burner burner = new Burner(address(erc20)); + assertEq(address(burner.token()), address(erc20)); erc20.transfer(address(burner), 20); From e3b24b7de83fa32613d418bc48e6e2ad16232ffa Mon Sep 17 00:00:00 2001 From: Chris Buckland Date: Tue, 27 Aug 2024 10:54:35 +0100 Subject: [PATCH 3/3] Added comment --- src/express-lane-auction/Burner.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/express-lane-auction/Burner.sol b/src/express-lane-auction/Burner.sol index 5fb437bb..e3fe3fb8 100644 --- a/src/express-lane-auction/Burner.sol +++ b/src/express-lane-auction/Burner.sol @@ -7,6 +7,7 @@ import { import "./Errors.sol"; /// @notice A simple contract that can burn any tokens that are transferred to it +/// Token must support the ERC20BurnableUpgradeable.burn(uint256) interface contract Burner { ERC20BurnableUpgradeable public immutable token;