From 0dc006d60563d3b137ce2bc872032dceffbae6a8 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sun, 9 Oct 2022 22:49:38 -0500 Subject: [PATCH 01/31] [initial] Variable Supply Auctions --- .../IVariableSupplyAuction.sol | 12 ++ .../VariableSupplyAuction.sol | 14 ++ .../VariableSupplyAuction.feature | 15 ++ .../VariableSupplyAuction.integration.t.sol | 137 ++++++++++++++++++ .../VariableSupplyAuction.t.sol | 101 +++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol create mode 100644 contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol create mode 100644 contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature create mode 100644 contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol create mode 100644 contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol new file mode 100644 index 00000000..2a56a2af --- /dev/null +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.10; + +/// @title IVariableSupplyAuction +/// @author neodaoist +/// @notice Interface for Variable Supply Auction +interface IVariableSupplyAuction { + // + /// @notice Says hello + /// @return a friendly greeting + function hello() external pure returns (bytes32); +} diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol new file mode 100644 index 00000000..3b54cf71 --- /dev/null +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.10; + +import {IVariableSupplyAuction} from "./IVariableSupplyAuction.sol"; + +/// @title Variable Supply Auction +/// @author neodaoist +/// @notice Module for variable supply, seller's choice, sealed bid auctions in ETH for ERC-721 tokens +contract VariableSupplyAuction is IVariableSupplyAuction { + // + function hello() public pure returns (bytes32) { + return bytes32("hello world"); + } +} diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature new file mode 100644 index 00000000..2b8526c2 --- /dev/null +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature @@ -0,0 +1,15 @@ +Feature: Variable Supply Auctions + + As a X + I want Y + so that Z + + Background: ABC + Given a + And b + And c + + Scenario: Z1 + Given za + When zb + Then zc diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol new file mode 100644 index 00000000..bd062390 --- /dev/null +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.10; + +import {DSTest} from "ds-test/test.sol"; + +import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; +import {Zorb} from "../../utils/users/Zorb.sol"; +import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; +import {ZoraModuleManager} from "../../../ZoraModuleManager.sol"; +import {ZoraProtocolFeeSettings} from "../../../auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol"; +import {ERC20TransferHelper} from "../../../transferHelpers/ERC20TransferHelper.sol"; +import {ERC721TransferHelper} from "../../../transferHelpers/ERC721TransferHelper.sol"; +import {RoyaltyEngine} from "../../utils/modules/RoyaltyEngine.sol"; +import {TestERC721} from "../../utils/tokens/TestERC721.sol"; +import {WETH} from "../../utils/tokens/WETH.sol"; +import {VM} from "../../utils/VM.sol"; + +/// @title VariableSupplyAuctionIntegrationTest +/// @notice Integration Tests for Variable Supply Auctions +contract VariableSupplyAuctionIntegrationTest is DSTest { + // + VM internal vm; + + ZoraRegistrar internal registrar; + ZoraProtocolFeeSettings internal ZPFS; + ZoraModuleManager internal ZMM; + ERC20TransferHelper internal erc20TransferHelper; + ERC721TransferHelper internal erc721TransferHelper; + RoyaltyEngine internal royaltyEngine; + + VariableSupplyAuction internal auctions; + TestERC721 internal token; + WETH internal weth; + + Zorb internal seller; + Zorb internal sellerFundsRecipient; + Zorb internal finder; + Zorb internal royaltyRecipient; + Zorb internal bidder; + Zorb internal otherBidder; + Zorb internal protocolFeeRecipient; + + function setUp() public { + // Cheatcodes + vm = VM(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Deploy V3 + registrar = new ZoraRegistrar(); + ZPFS = new ZoraProtocolFeeSettings(); + ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); + erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); + erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); + + // Init V3 + registrar.init(ZMM); + ZPFS.init(address(ZMM), address(0)); + + // Create users + seller = new Zorb(address(ZMM)); + sellerFundsRecipient = new Zorb(address(ZMM)); + bidder = new Zorb(address(ZMM)); + otherBidder = new Zorb(address(ZMM)); + royaltyRecipient = new Zorb(address(ZMM)); + protocolFeeRecipient = new Zorb(address(ZMM)); + + // Deploy mocks + royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); + token = new TestERC721(); + weth = new WETH(); + + // Deploy Reserve Auction Core ETH + auctions = new VariableSupplyAuction(); + registrar.registerModule(address(auctions)); + + // Set module fee + vm.prank(address(registrar)); + ZPFS.setFeeParams(address(auctions), address(protocolFeeRecipient), 1); + + // Set balances + vm.deal(address(seller), 100 ether); + vm.deal(address(bidder), 100 ether); + vm.deal(address(otherBidder), 100 ether); + + // Mint seller token + token.mint(address(seller), 1); + + // Bidder swap 50 ETH <> 50 WETH + vm.prank(address(bidder)); + weth.deposit{value: 50 ether}(); + + // otherBidder swap 50 ETH <> 50 WETH + vm.prank(address(otherBidder)); + weth.deposit{value: 50 ether}(); + + // Users approve ReserveAuction module + seller.setApprovalForModule(address(auctions), true); + bidder.setApprovalForModule(address(auctions), true); + otherBidder.setApprovalForModule(address(auctions), true); + + // Seller approve ERC721TransferHelper + vm.prank(address(seller)); + token.setApprovalForAll(address(erc721TransferHelper), true); + + // Bidder approve ERC20TransferHelper + vm.prank(address(bidder)); + weth.approve(address(erc20TransferHelper), 50 ether); + + // otherBidder approve ERC20TransferHelper + vm.prank(address(otherBidder)); + weth.approve(address(erc20TransferHelper), 50 ether); + } + + function runETH() public { + vm.prank(address(seller)); + auctions.hello(); + } + + function test_ETHIntegration() public { + uint256 beforeSellerBalance = address(sellerFundsRecipient).balance; + uint256 beforeBidderBalance = address(bidder).balance; + uint256 beforeOtherBidderBalance = address(otherBidder).balance; + uint256 beforeRoyaltyRecipientBalance = address(royaltyRecipient).balance; + uint256 beforeProtocolFeeRecipient = address(protocolFeeRecipient).balance; + address beforeTokenOwner = token.ownerOf(1); + + runETH(); + + uint256 afterSellerBalance = address(sellerFundsRecipient).balance; + uint256 afterBidderBalance = address(bidder).balance; + uint256 afterOtherBidderBalance = address(otherBidder).balance; + uint256 afterRoyaltyRecipientBalance = address(royaltyRecipient).balance; + uint256 afterProtocolFeeRecipient = address(protocolFeeRecipient).balance; + address afterTokenOwner = token.ownerOf(1); + + assertEq(beforeSellerBalance, afterSellerBalance); + } +} diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol new file mode 100644 index 00000000..60cd01d6 --- /dev/null +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.10; + +import {DSTest} from "ds-test/test.sol"; + +import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; +import {Zorb} from "../../utils/users/Zorb.sol"; +import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; +import {ZoraModuleManager} from "../../../ZoraModuleManager.sol"; +import {ZoraProtocolFeeSettings} from "../../../auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol"; +import {ERC20TransferHelper} from "../../../transferHelpers/ERC20TransferHelper.sol"; +import {ERC721TransferHelper} from "../../../transferHelpers/ERC721TransferHelper.sol"; +import {RoyaltyEngine} from "../../utils/modules/RoyaltyEngine.sol"; +import {TestERC721} from "../../utils/tokens/TestERC721.sol"; +import {WETH} from "../../utils/tokens/WETH.sol"; +import {VM} from "../../utils/VM.sol"; + +/// @title VariableSupplyAuctionTest +/// @notice Unit Tests for Variable Supply Auctions +contract VariableSupplyAuctionTest is DSTest { + // + VM internal vm; + + ZoraRegistrar internal registrar; + ZoraProtocolFeeSettings internal ZPFS; + ZoraModuleManager internal ZMM; + ERC20TransferHelper internal erc20TransferHelper; + ERC721TransferHelper internal erc721TransferHelper; + RoyaltyEngine internal royaltyEngine; + + VariableSupplyAuction internal auctions; + TestERC721 internal token; + WETH internal weth; + + Zorb internal seller; + Zorb internal sellerFundsRecipient; + Zorb internal operator; + Zorb internal finder; + Zorb internal royaltyRecipient; + Zorb internal bidder; + Zorb internal otherBidder; + + function setUp() public { + // Cheatcodes + vm = VM(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Deploy V3 + registrar = new ZoraRegistrar(); + ZPFS = new ZoraProtocolFeeSettings(); + ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); + erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); + erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); + + // Init V3 + registrar.init(ZMM); + ZPFS.init(address(ZMM), address(0)); + + // Create users + seller = new Zorb(address(ZMM)); + sellerFundsRecipient = new Zorb(address(ZMM)); + operator = new Zorb(address(ZMM)); + bidder = new Zorb(address(ZMM)); + otherBidder = new Zorb(address(ZMM)); + finder = new Zorb(address(ZMM)); + royaltyRecipient = new Zorb(address(ZMM)); + + // Deploy mocks + royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); + token = new TestERC721(); + weth = new WETH(); + + // Deploy Variable Supply Auction module + auctions = new VariableSupplyAuction(); + registrar.registerModule(address(auctions)); + + // Set balances + vm.deal(address(seller), 100 ether); + vm.deal(address(bidder), 100 ether); + vm.deal(address(otherBidder), 100 ether); + + // Mint seller token + token.mint(address(seller), 1); + + // Users approve module + seller.setApprovalForModule(address(auctions), true); + bidder.setApprovalForModule(address(auctions), true); + otherBidder.setApprovalForModule(address(auctions), true); + + // Seller approve ERC721TransferHelper + vm.prank(address(seller)); + token.setApprovalForAll(address(erc721TransferHelper), true); + } + + /*////////////////////////////////////////////////////////////// + Hello World + //////////////////////////////////////////////////////////////*/ + + function test_Hello() public { + assertEq(auctions.hello(), bytes32("hello world")); + } +} From 1eda347df9d7f7846ba21142b048e9a04e6a1a94 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 11 Oct 2022 00:36:17 -0500 Subject: [PATCH 02/31] [draft] Add happy path BDD tests --- .../VariableSupplyAuction.feature | 151 ++++++++++++++++-- 1 file changed, 138 insertions(+), 13 deletions(-) diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature index 2b8526c2..69a12969 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature @@ -1,15 +1,140 @@ Feature: Variable Supply Auctions - As a X - I want Y - so that Z - - Background: ABC - Given a - And b - And c - - Scenario: Z1 - Given za - When zb - Then zc + As a creator + I want to run a Variable Supply Auction + so that I can conduct price discovery and right-size the market when selling my work + + Auction phases: + - Created + - Bid Phase + - Reveal Phase + - Sizing Phase + - Completed / Cancelled + + Background: VSA creation and bidding + Given Seller creates a Variable Supply Auction + And Seller and all Bidder account balances are 10 ETH + And The following sealed bids are placed + | account | bid amount | sent value | + | Bidder1 | 0.01 ETH | 0.01 ETH | + | Bidder2 | 0.01 ETH | 0.09 ETH | + | Bidder3 | 0.01 ETH | 0.08 ETH | + | Bidder4 | 0.01 ETH | 0.07 ETH | + | Bidder5 | 0.01 ETH | 0.06 ETH | + | Bidder6 | 0.01 ETH | 0.05 ETH | + | Bidder7 | 0.01 ETH | 0.04 ETH | + | Bidder8 | 0.01 ETH | 0.03 ETH | + | Bidder9 | 0.01 ETH | 0.02 ETH | + | Bidder10 | 0.01 ETH | 1.00 ETH | + | Bidder11 | 0.06 ETH | 0.06 ETH | + | Bidder12 | 0.06 ETH | 0.12 ETH | + | Bidder13 | 0.11 ETH | 0.12 ETH | + + Scenario: Bidders reveal bids + When All bids are revealed + Then The account balances should be + | account | balance | + | Seller | 10 ETH | + | Bidder1 | 9.99 ETH | + | Bidder2 | 9.99 ETH | + | Bidder3 | 9.99 ETH | + | Bidder4 | 9.99 ETH | + | Bidder5 | 9.99 ETH | + | Bidder6 | 9.99 ETH | + | Bidder7 | 9.99 ETH | + | Bidder8 | 9.99 ETH | + | Bidder9 | 9.99 ETH | + | Bidder10 | 9.94 ETH | + | Bidder12 | 9.94 ETH | + | Bidder13 | 9.89 ETH | + + Scenario: Seller settles VSA at 0.01 ETH + When All bids are revealed + And Seller settles auction at 0.01 ETH + Then The NFT contract should be an edition of 13 + And The account balances should be + | account | balance | + | Seller | 10.13 ETH | + | Bidder1 | 9.99 ETH | + | Bidder2 | 9.99 ETH | + | Bidder3 | 9.99 ETH | + | Bidder4 | 9.99 ETH | + | Bidder5 | 9.99 ETH | + | Bidder6 | 9.99 ETH | + | Bidder7 | 9.99 ETH | + | Bidder8 | 9.99 ETH | + | Bidder9 | 9.99 ETH | + | Bidder10 | 9.99 ETH | + | Bidder11 | 9.99 ETH | + | Bidder12 | 9.99 ETH | + | Bidder13 | 9.99 ETH | + And The following accounts should own an NFT + | account | + | Bidder1 | + | Bidder2 | + | Bidder3 | + | Bidder4 | + | Bidder5 | + | Bidder6 | + | Bidder7 | + | Bidder8 | + | Bidder9 | + | Bidder10 | + | Bidder11 | + | Bidder12 | + | Bidder13 | + + Scenario: Seller settles VSA at 0.06 ETH + When All bids are revealed + And Seller settles auction at 0.06 ETH + Then The NFT contract should be an edition of 3 + And The account balances should be + | account | balance | + | Seller | 10.18 ETH | + | Bidder1 | 10 ETH | + | Bidder2 | 10 ETH | + | Bidder3 | 10 ETH | + | Bidder4 | 10 ETH | + | Bidder5 | 10 ETH | + | Bidder6 | 10 ETH | + | Bidder7 | 10 ETH | + | Bidder8 | 10 ETH | + | Bidder9 | 10 ETH | + | Bidder10 | 10 ETH | + | Bidder11 | 9.94 ETH | + | Bidder12 | 9.94 ETH | + | Bidder13 | 9.94 ETH | + And The following accounts should own an NFT + | Bidder11 | + | Bidder12 | + | Bidder13 | + + Scenario: Seller settles VSA at 0.11 ETH + When All bids are revealed + And Seller settles auction at 0.11 ETH + Then The NFT contract should be a 1 of 1 + And The account balances should be + | account | balance | + | Seller | 10.11 ETH | + | Bidder1 | 10 ETH | + | Bidder2 | 10 ETH | + | Bidder3 | 10 ETH | + | Bidder4 | 10 ETH | + | Bidder5 | 10 ETH | + | Bidder6 | 10 ETH | + | Bidder7 | 10 ETH | + | Bidder8 | 10 ETH | + | Bidder9 | 10 ETH | + | Bidder10 | 10 ETH | + | Bidder11 | 10 ETH | + | Bidder12 | 10 ETH | + | Bidder13 | 9.89 ETH | + And The following accounts should own an NFT + | Bidder13 | + +# TODO handle bid space bounding +## Seller sets maximum edition size commitment +## Bidder sets maximum edition size interest +## Seller sets minimum viable revenue +# TODO address cancel auction sad path +# TODO address failure-to-reveal sad paths From 82b06c7b478b94d3be10d87e0d846bed4853133d Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sat, 15 Oct 2022 00:15:58 -0500 Subject: [PATCH 03/31] [draft] Add createAuction functionality --- .gas-snapshot | 338 ++++++++++++++++++ .../IVariableSupplyAuction.sol | 13 +- .../VariableSupplyAuction.sol | 82 ++++- .../VariableSupplyAuction.feature | 2 +- .../VariableSupplyAuction.integration.t.sol | 47 ++- .../VariableSupplyAuction.t.sol | 138 ++++++- 6 files changed, 580 insertions(+), 40 deletions(-) create mode 100644 .gas-snapshot diff --git a/.gas-snapshot b/.gas-snapshot new file mode 100644 index 00000000..26352389 --- /dev/null +++ b/.gas-snapshot @@ -0,0 +1,338 @@ +ZoraModuleManagerTest:testFail_CannotApproveModuleNotRegistered() (gas: 15441) +ZoraModuleManagerTest:testRevert_CannotSetRegistrarToAddressZero() (gas: 18088) +ZoraModuleManagerTest:testRevert_ModuleAlreadyRegistered() (gas: 97890) +ZoraModuleManagerTest:test_RegisterModule() (gas: 93957) +ZoraModuleManagerTest:test_SetApproval() (gas: 126996) +ZoraModuleManagerTest:test_SetBatchApproval() (gas: 291617) +ZoraModuleManagerTest:test_SetRegistrar() (gas: 19963) +ZoraProtocolFeeSettingsTest:testRevert_AlreadyInitialized() (gas: 66106) +ZoraProtocolFeeSettingsTest:testRevert_InitOnlyOwner() (gas: 17078) +ZoraProtocolFeeSettingsTest:testRevert_OnlyMinter() (gas: 67369) +ZoraProtocolFeeSettingsTest:testRevert_SetMetadataOnlyOwner() (gas: 65084) +ZoraProtocolFeeSettingsTest:testRevert_SetOwnerOnlyOwner() (gas: 65040) +ZoraProtocolFeeSettingsTest:testRevert_SetParamsFeeRecipientMustBeNonZero() (gas: 116695) +ZoraProtocolFeeSettingsTest:testRevert_SetParamsMustBeLessThanHundred() (gas: 118728) +ZoraProtocolFeeSettingsTest:testRevert_SetParamsOnlyOwner() (gas: 118138) +ZoraProtocolFeeSettingsTest:test_Init() (gas: 65621) +ZoraProtocolFeeSettingsTest:test_MintToken() (gas: 116242) +ZoraProtocolFeeSettingsTest:test_ResetParamsToZero() (gas: 143751) +ZoraProtocolFeeSettingsTest:test_SetFeeParams() (gas: 143749) +ZoraProtocolFeeSettingsTest:test_SetMetadata() (gas: 67392) +ZoraProtocolFeeSettingsTest:test_SetOwner() (gas: 70191) +AsksCoreErc20IntegrationTest:test_ERC20Integration() (gas: 236883) +AsksCoreErc20Test:testGas_CreateAsk() (gas: 71928) +AsksCoreErc20Test:testRevert_AskMustBeActiveToFill() (gas: 216930) +AsksCoreErc20Test:testRevert_CannotUpdateCanceledAsk() (gas: 66082) +AsksCoreErc20Test:testRevert_CannotUpdateFilledAsk() (gas: 211872) +AsksCoreErc20Test:testRevert_CannotUpdateInvalidPrice() (gas: 78181) +AsksCoreErc20Test:testRevert_CurrencyMismatch() (gas: 79787) +AsksCoreErc20Test:testRevert_MaxAskPrice() (gas: 28137) +AsksCoreErc20Test:testRevert_MustApproveERC721TransferHelper() (gas: 201259) +AsksCoreErc20Test:testRevert_MustApproveModule() (gas: 200208) +AsksCoreErc20Test:testRevert_MustBeOwnerOrOperator() (gas: 28737) +AsksCoreErc20Test:testRevert_MustBeSellerOrOwner() (gas: 78062) +AsksCoreErc20Test:testRevert_OnlySellerCanSetAskPrice() (gas: 77042) +AsksCoreErc20Test:testRevert_PriceMismatch() (gas: 79791) +AsksCoreErc20Test:test_CancelAskOwner() (gas: 96662) +AsksCoreErc20Test:test_CancelAskSeller() (gas: 64812) +AsksCoreErc20Test:test_CreateAsk() (gas: 73982) +AsksCoreErc20Test:test_CreateAskAndOverridePrevious() (gas: 184828) +AsksCoreErc20Test:test_CreateAskFromTokenOperator() (gas: 138070) +AsksCoreErc20Test:test_CreateMaxAskPrice() (gas: 73622) +AsksCoreErc20Test:test_DecreaseAskPrice() (gas: 79159) +AsksCoreErc20Test:test_FillAsk() (gas: 202920) +AsksCoreErc20Test:test_IncreaseAskPrice() (gas: 79204) +AsksCoreErc20Test:test_UpdateAskCurrency() (gas: 63488) +AsksCoreEthIntegrationTest:test_ETHIntegration() (gas: 152230) +AsksCoreEthTest:testGas_CreateAsk() (gas: 47002) +AsksCoreEthTest:testRevert_AskMustBeActiveToFill() (gas: 137808) +AsksCoreEthTest:testRevert_CannotUpdateCanceledAsk() (gas: 45400) +AsksCoreEthTest:testRevert_CannotUpdateFilledAsk() (gas: 131680) +AsksCoreEthTest:testRevert_MustApproveERC721TransferHelper() (gas: 120597) +AsksCoreEthTest:testRevert_MustApproveModule() (gas: 119535) +AsksCoreEthTest:testRevert_MustBeOwnerOrOperator() (gas: 26509) +AsksCoreEthTest:testRevert_MustBeSellerOrOwner() (gas: 52993) +AsksCoreEthTest:testRevert_MustMeetPrice() (gas: 61188) +AsksCoreEthTest:testRevert_OnlySellerCanSetAskPrice() (gas: 51870) +AsksCoreEthTest:test_CancelAskOwner() (gas: 76748) +AsksCoreEthTest:test_CancelAskSeller() (gas: 43884) +AsksCoreEthTest:test_CoreETHCreateAsk() (gas: 48621) +AsksCoreEthTest:test_CreateAskAndOverridePrevious() (gas: 158356) +AsksCoreEthTest:test_CreateAskFromTokenOperator() (gas: 112687) +AsksCoreEthTest:test_CreateMaxAskPrice() (gas: 48470) +AsksCoreEthTest:test_DecreaseAskPrice() (gas: 52843) +AsksCoreEthTest:test_FillAsk() (gas: 127440) +AsksCoreEthTest:test_IncreaseAskPrice() (gas: 52888) +AsksDataStorageTest:test_AskStorageInit() (gas: 952344) +AsksDataStorageTest:test_AskStorageMinimalInit() (gas: 545565) +AsksDataStorageTest:test_AskStorageUpdatePrice() (gas: 960641) +AsksOmnibusTest:testRevert_CreateAskMinimalModuleNotApproved() (gas: 44114) +AsksOmnibusTest:testRevert_CreateAskMinimalNotTokenOwnerOrOperator() (gas: 28996) +AsksOmnibusTest:testRevert_CreateAskMinimalTransferHelperNotApproved() (gas: 41754) +AsksOmnibusTest:testRevert_CreateAskModuleNotApproved() (gas: 56282) +AsksOmnibusTest:testRevert_CreateAskNotTokenOwnerOrOperator() (gas: 41219) +AsksOmnibusTest:testRevert_CreateAskTransferHelperNotApproved() (gas: 53902) +AsksOmnibusTest:test_CancelAsk() (gas: 241551) +AsksOmnibusTest:test_CreateAsk() (gas: 267206) +AsksOmnibusTest:test_CreateAskMinimal() (gas: 91572) +AsksOmnibusTest:test_FillAsk() (gas: 442150) +AsksOmnibusTest:test_SetPrice() (gas: 279068) +AsksPrivateEthIntegrationTest:test_ETHIntegration() (gas: 160335) +AsksPrivateEthTest:testRevert_AskMustBeActiveToFill() (gas: 148940) +AsksPrivateEthTest:testRevert_CannotUpdateCanceledAsk() (gas: 65665) +AsksPrivateEthTest:testRevert_CannotUpdateFilledAsk() (gas: 143877) +AsksPrivateEthTest:testRevert_MustApproveERC721TransferHelper() (gas: 143469) +AsksPrivateEthTest:testRevert_MustApproveModule() (gas: 142450) +AsksPrivateEthTest:testRevert_MustBeOwnerOrOperator() (gas: 28725) +AsksPrivateEthTest:testRevert_MustBeSellerOrOwner() (gas: 77576) +AsksPrivateEthTest:testRevert_MustMeetPrice() (gas: 84131) +AsksPrivateEthTest:testRevert_OnlyBuyer() (gas: 83548) +AsksPrivateEthTest:testRevert_OnlySellerCanSetAskPrice() (gas: 76546) +AsksPrivateEthTest:test_CancelAskOwner() (gas: 97204) +AsksPrivateEthTest:test_CancelAskSeller() (gas: 64513) +AsksPrivateEthTest:test_CreateAsk() (gas: 73699) +AsksPrivateEthTest:test_CreateAskAndOverridePrevious() (gas: 186514) +AsksPrivateEthTest:test_CreateAskFromTokenOperator() (gas: 137854) +AsksPrivateEthTest:test_CreateMaxAskPrice() (gas: 73406) +AsksPrivateEthTest:test_DecreaseAskPrice() (gas: 78258) +AsksPrivateEthTest:test_FillAsk() (gas: 140503) +AsksPrivateEthTest:test_IncreaseAskPrice() (gas: 78237) +AsksV1_1IntegrationTest:test_ERC20Integration() (gas: 282954) +AsksV1_1IntegrationTest:test_ETHIntegration() (gas: 206858) +AsksV1_1Test:testFail_CannotUpdateCanceledAsk() (gas: 147517) +AsksV1_1Test:testFail_CannotUpdateFilledAsk() (gas: 259924) +AsksV1_1Test:testFail_MustBeTokenOwnerOrOperator() (gas: 23995) +AsksV1_1Test:testGas_CreateAsk() (gas: 130362) +AsksV1_1Test:testRevert_AskMustBeActiveToFill() (gas: 209188) +AsksV1_1Test:testRevert_AskMustExistToCancel() (gas: 20014) +AsksV1_1Test:testRevert_FillAmountMustMatchAsk() (gas: 142427) +AsksV1_1Test:testRevert_FillCurrencyMustMatchAsk() (gas: 142405) +AsksV1_1Test:testRevert_FindersFeeBPSCannotExceed10000() (gas: 40468) +AsksV1_1Test:testRevert_MsgSenderMustBeApprovedToCancelAsk() (gas: 139435) +AsksV1_1Test:testRevert_MustApproveERC721TransferHelper() (gas: 44295) +AsksV1_1Test:testRevert_OnlySellerCanSetAskPrice() (gas: 135358) +AsksV1_1Test:testRevert_SellerFundsRecipientCannotBeZeroAddress() (gas: 38430) +AsksV1_1Test:test_CancelAsk() (gas: 113992) +AsksV1_1Test:test_CreateAskAndCancelPreviousOwners() (gas: 269333) +AsksV1_1Test:test_CreateAskFromTokenOperator() (gas: 192484) +AsksV1_1Test:test_CreateAskFromTokenOwner() (gas: 132851) +AsksV1_1Test:test_FillAsk() (gas: 200648) +AsksV1_1Test:test_UpdateAskPrice() (gas: 138453) +OffersV1IntegrationTest:test_ERC20Integration() (gas: 312703) +OffersV1IntegrationTest:test_ETHIntegration() (gas: 236623) +OffersV1Test:testFail_CannotCreateOfferWithInvalidFindersFeeBps() (gas: 20027) +OffersV1Test:testFail_CannotCreateOfferWithoutAttachingFunds() (gas: 20106) +OffersV1Test:testGas_CreateOffer() (gas: 154389) +OffersV1Test:testRevert_AcceptAmountMustMatchOffer() (gas: 177321) +OffersV1Test:testRevert_AcceptCurrencyMustMatchOffer() (gas: 179405) +OffersV1Test:testRevert_CannotCancelInactiveOffer() (gas: 232084) +OffersV1Test:testRevert_CannotFillInactiveOffer() (gas: 232405) +OffersV1Test:testRevert_CannotIncreaseOfferWithoutAttachingFunds() (gas: 167625) +OffersV1Test:testRevert_CannotUpdateInactiveOffer() (gas: 231928) +OffersV1Test:testRevert_CannotUpdateOfferWithPreviousAmount() (gas: 174541) +OffersV1Test:testRevert_OnlySellerCanCancelOffer() (gas: 166764) +OffersV1Test:testRevert_OnlySellerCanUpdateOffer() (gas: 166529) +OffersV1Test:testRevert_OnlyTokenHolderCanFillOffer() (gas: 174639) +OffersV1Test:test_CancelNFTOffer() (gas: 144736) +OffersV1Test:test_CreateOffer() (gas: 163556) +OffersV1Test:test_DecreaseETHOffer() (gas: 179435) +OffersV1Test:test_DecreaseETHOfferWithERC20() (gas: 233707) +OffersV1Test:test_FillNFTOffer() (gas: 229399) +OffersV1Test:test_IncreaseETHOffer() (gas: 177310) +OffersV1Test:test_IncreaseETHOfferWithERC20() (gas: 233662) +ReserveAuctionCoreErc20IntegrationTest:test_ERC20Integration() (gas: 436073) +ReserveAuctionCoreErc20Test:testGas_CreateERC20Auction() (gas: 144891) +ReserveAuctionCoreErc20Test:testRevert_AuctionNotOver() (gas: 275473) +ReserveAuctionCoreErc20Test:testRevert_AuctionNotStarted() (gas: 150505) +ReserveAuctionCoreErc20Test:testRevert_BidMustBe10PercentGreaterThanPrevious() (gas: 278530) +ReserveAuctionCoreErc20Test:testRevert_BidMustMeetReservePrice() (gas: 152924) +ReserveAuctionCoreErc20Test:testRevert_CannotBidOnAuctionNotActive() (gas: 20114) +ReserveAuctionCoreErc20Test:testRevert_CannotBidOnAuctionNotStarted() (gas: 152476) +ReserveAuctionCoreErc20Test:testRevert_CannotBidOnExpiredAuction() (gas: 278153) +ReserveAuctionCoreErc20Test:testRevert_CannotCancelActiveAuction() (gas: 275993) +ReserveAuctionCoreErc20Test:testRevert_CannotUpdateActiveAuction() (gas: 275586) +ReserveAuctionCoreErc20Test:testRevert_CannotUpdateAuctionDoesNotExist() (gas: 22230) +ReserveAuctionCoreErc20Test:testRevert_InvalidTransferBeforeFirstBid() (gas: 203080) +ReserveAuctionCoreErc20Test:testRevert_MustApproveModule() (gas: 171124) +ReserveAuctionCoreErc20Test:testRevert_MustBeTokenOwnerOrOperator() (gas: 31053) +ReserveAuctionCoreErc20Test:testRevert_MustSpecifySellerFundsRecipient() (gas: 28371) +ReserveAuctionCoreErc20Test:testRevert_OnlySellerCanCancel() (gas: 151648) +ReserveAuctionCoreErc20Test:testRevert_SellerMustApproveERC721TransferHelper() (gas: 172222) +ReserveAuctionCoreErc20Test:testRevert_UpdateMustBeSeller() (gas: 149945) +ReserveAuctionCoreErc20Test:test_CancelAuction() (gas: 124988) +ReserveAuctionCoreErc20Test:test_CreateERC20AuctionAndCancelPrevious() (gas: 255035) +ReserveAuctionCoreErc20Test:test_CreateFirstBid() (gas: 269271) +ReserveAuctionCoreErc20Test:test_CreateFutureERC20Auction() (gas: 147661) +ReserveAuctionCoreErc20Test:test_CreateInstantERC20Auction() (gas: 148370) +ReserveAuctionCoreErc20Test:test_ExtendAuction() (gas: 307916) +ReserveAuctionCoreErc20Test:test_RefundPreviousBidder() (gas: 306678) +ReserveAuctionCoreErc20Test:test_SetReservePrice() (gas: 154859) +ReserveAuctionCoreErc20Test:test_SettleAuction() (gas: 360481) +ReserveAuctionCoreErc20Test:test_StoreTimeOfFirstBid() (gas: 272510) +ReserveAuctionCoreErc20Test:test_TransferNFTIntoEscrow() (gas: 270475) +ReserveAuctionCoreEthIntegrationTest:test_ETHIntegration() (gas: 262093) +ReserveAuctionCoreEthTest:testGas_CreateAuction() (gas: 95889) +ReserveAuctionCoreEthTest:testRevert_AuctionNotOver() (gas: 163805) +ReserveAuctionCoreEthTest:testRevert_AuctionNotStarted() (gas: 101160) +ReserveAuctionCoreEthTest:testRevert_BidMustBe10PercentGreaterThanPrevious() (gas: 173507) +ReserveAuctionCoreEthTest:testRevert_BidMustMeetReservePrice() (gas: 110434) +ReserveAuctionCoreEthTest:testRevert_CannotBidOnAuctionNotActive() (gas: 20080) +ReserveAuctionCoreEthTest:testRevert_CannotBidOnAuctionNotStarted() (gas: 103408) +ReserveAuctionCoreEthTest:testRevert_CannotBidOnExpiredAuction() (gas: 173318) +ReserveAuctionCoreEthTest:testRevert_CannotCancelActiveAuction() (gas: 164281) +ReserveAuctionCoreEthTest:testRevert_CannotUpdateActiveAuction() (gas: 164229) +ReserveAuctionCoreEthTest:testRevert_InvalidTransferBeforeFirstBid() (gas: 160525) +ReserveAuctionCoreEthTest:testRevert_MustApproveModule() (gas: 128656) +ReserveAuctionCoreEthTest:testRevert_MustBeTokenOwnerOrOperator() (gas: 28878) +ReserveAuctionCoreEthTest:testRevert_MustSpecifySellerFundsRecipient() (gas: 26182) +ReserveAuctionCoreEthTest:testRevert_OnlySellerCanCancel() (gas: 102192) +ReserveAuctionCoreEthTest:testRevert_SellerMustApproveERC721TransferHelper() (gas: 129732) +ReserveAuctionCoreEthTest:testRevert_UpdateMustBeSeller() (gas: 100899) +ReserveAuctionCoreEthTest:test_CancelAuction() (gas: 84599) +ReserveAuctionCoreEthTest:test_CreateAuctionAndCancelPrevious() (gas: 204523) +ReserveAuctionCoreEthTest:test_CreateFirstBid() (gas: 157911) +ReserveAuctionCoreEthTest:test_CreateFutureAuction() (gas: 98357) +ReserveAuctionCoreEthTest:test_CreateInstantAuction() (gas: 98929) +ReserveAuctionCoreEthTest:test_ExtendAuction() (gas: 187331) +ReserveAuctionCoreEthTest:test_RefundPreviousBidder() (gas: 184561) +ReserveAuctionCoreEthTest:test_SetReservePrice() (gas: 105142) +ReserveAuctionCoreEthTest:test_SettleAuction() (gas: 220341) +ReserveAuctionCoreEthTest:test_StoreTimeOfFirstBid() (gas: 160899) +ReserveAuctionCoreEthTest:test_TransferNFTIntoEscrow() (gas: 159115) +ReserveAuctionFindersErc20IntegrationTest:test_ERC20Integration() (gas: 483225) +ReserveAuctionFindersErc20Test:testRevert_AuctionNotOver() (gas: 302584) +ReserveAuctionFindersErc20Test:testRevert_AuctionNotStarted() (gas: 154202) +ReserveAuctionFindersErc20Test:testRevert_BidMustBe10PercentGreaterThanPrevious() (gas: 305207) +ReserveAuctionFindersErc20Test:testRevert_BidMustMeetReservePrice() (gas: 158588) +ReserveAuctionFindersErc20Test:testRevert_CannotBidOnAuctionNotStarted() (gas: 178129) +ReserveAuctionFindersErc20Test:testRevert_CannotBidOnExpiredAuction() (gas: 304874) +ReserveAuctionFindersErc20Test:testRevert_CannotBidOnNonExistentAuction() (gas: 22341) +ReserveAuctionFindersErc20Test:testRevert_CannotCancelActiveAuction() (gas: 302670) +ReserveAuctionFindersErc20Test:testRevert_CannotUpdateActiveAuction() (gas: 301714) +ReserveAuctionFindersErc20Test:testRevert_CannotUpdateAuctionDoesNotExist() (gas: 22295) +ReserveAuctionFindersErc20Test:testRevert_FindersFeeBPSCannotExceed10000() (gas: 30504) +ReserveAuctionFindersErc20Test:testRevert_InvalidTransferBeforeFirstBid() (gas: 208744) +ReserveAuctionFindersErc20Test:testRevert_MustApproveModule() (gas: 176877) +ReserveAuctionFindersErc20Test:testRevert_MustBeTokenOwnerOrOperator() (gas: 31069) +ReserveAuctionFindersErc20Test:testRevert_MustSpecifySellerFundsRecipient() (gas: 28366) +ReserveAuctionFindersErc20Test:testRevert_OnlySellerOrOwnerCanCancel() (gas: 155346) +ReserveAuctionFindersErc20Test:testRevert_SellerMustApproveERC721TransferHelper() (gas: 177886) +ReserveAuctionFindersErc20Test:testRevert_UpdateMustBeSeller() (gas: 153505) +ReserveAuctionFindersErc20Test:test_CancelAuction() (gas: 129206) +ReserveAuctionFindersErc20Test:test_CreateAuction() (gas: 152271) +ReserveAuctionFindersErc20Test:test_CreateAuctionAndCancelPrevious() (gas: 260477) +ReserveAuctionFindersErc20Test:test_CreateFirstBid() (gas: 295811) +ReserveAuctionFindersErc20Test:test_CreateFutureAuction() (gas: 171439) +ReserveAuctionFindersErc20Test:test_ExtendAuction() (gas: 335829) +ReserveAuctionFindersErc20Test:test_RefundPreviousBidder() (gas: 334319) +ReserveAuctionFindersErc20Test:test_SetReservePrice() (gas: 159441) +ReserveAuctionFindersErc20Test:test_SettleAuction() (gas: 404903) +ReserveAuctionFindersErc20Test:test_StoreTimeOfFirstBid() (gas: 299387) +ReserveAuctionFindersErc20Test:test_TransferNFTIntoEscrow() (gas: 296993) +ReserveAuctionFindersEthIntegrationTest:test_ETHIntegration() (gas: 295825) +ReserveAuctionFindersEthTest:testRevert_AuctionNotOver() (gas: 191252) +ReserveAuctionFindersEthTest:testRevert_AuctionNotStarted() (gas: 125030) +ReserveAuctionFindersEthTest:testRevert_BidMustBe10PercentGreaterThanPrevious() (gas: 200604) +ReserveAuctionFindersEthTest:testRevert_BidMustMeetReservePrice() (gas: 136366) +ReserveAuctionFindersEthTest:testRevert_CannotBidOnAuctionNotActive() (gas: 28945) +ReserveAuctionFindersEthTest:testRevert_CannotBidOnAuctionNotStarted() (gas: 129283) +ReserveAuctionFindersEthTest:testRevert_CannotBidOnExpiredAuction() (gas: 200393) +ReserveAuctionFindersEthTest:testRevert_CannotCancelActiveAuction() (gas: 191316) +ReserveAuctionFindersEthTest:testRevert_CannotUpdateActiveAuction() (gas: 190659) +ReserveAuctionFindersEthTest:testRevert_CannotUpdateAuctionDoesNotExist() (gas: 22211) +ReserveAuctionFindersEthTest:testRevert_FindersFeeBPSCannotExceed10000() (gas: 28321) +ReserveAuctionFindersEthTest:testRevert_InvalidTransferBeforeFirstBid() (gas: 186522) +ReserveAuctionFindersEthTest:testRevert_MustApproveModule() (gas: 154566) +ReserveAuctionFindersEthTest:testRevert_MustBeTokenOwnerOrOperator() (gas: 28908) +ReserveAuctionFindersEthTest:testRevert_MustSpecifySellerFundsRecipient() (gas: 26202) +ReserveAuctionFindersEthTest:testRevert_OnlySellerOrOwnerCanCancel() (gas: 126152) +ReserveAuctionFindersEthTest:testRevert_SellerMustApproveERC721TransferHelper() (gas: 155664) +ReserveAuctionFindersEthTest:testRevert_UpdateMustBeSeller() (gas: 124588) +ReserveAuctionFindersEthTest:test_CancelAuction() (gas: 104976) +ReserveAuctionFindersEthTest:test_CreateAuction() (gas: 122927) +ReserveAuctionFindersEthTest:test_CreateAuctionAndCancelPrevious() (gas: 230372) +ReserveAuctionFindersEthTest:test_CreateFirstBid() (gas: 184750) +ReserveAuctionFindersEthTest:test_CreateFutureAuction() (gas: 122335) +ReserveAuctionFindersEthTest:test_ExtendAuction() (gas: 215573) +ReserveAuctionFindersEthTest:test_RefundPreviousBidder() (gas: 212564) +ReserveAuctionFindersEthTest:test_SetReservePrice() (gas: 129705) +ReserveAuctionFindersEthTest:test_SettleAuction() (gas: 252786) +ReserveAuctionFindersEthTest:test_StoreTimeOfFirstBid() (gas: 188020) +ReserveAuctionFindersEthTest:test_TransferNFTIntoEscrow() (gas: 185932) +ReserveAuctionListingErc20IntegrationTest:test_ERC20Integration() (gas: 482645) +ReserveAuctionListingErc20Test:testRevert_AuctionNotOver() (gas: 302693) +ReserveAuctionListingErc20Test:testRevert_AuctionNotStarted() (gas: 156728) +ReserveAuctionListingErc20Test:testRevert_BidMustBe10PercentGreaterThanPrevious() (gas: 305124) +ReserveAuctionListingErc20Test:testRevert_BidMustMeetReservePrice() (gas: 158944) +ReserveAuctionListingErc20Test:testRevert_CannotBidOnAuctionNotStarted() (gas: 178485) +ReserveAuctionListingErc20Test:testRevert_CannotBidOnExpiredAuction() (gas: 304724) +ReserveAuctionListingErc20Test:testRevert_CannotBidOnNonExistentAuction() (gas: 20102) +ReserveAuctionListingErc20Test:testRevert_CannotCancelActiveAuction() (gas: 302757) +ReserveAuctionListingErc20Test:testRevert_CannotUpdateActiveAuction() (gas: 301801) +ReserveAuctionListingErc20Test:testRevert_CannotUpdateAuctionDoesNotExist() (gas: 22208) +ReserveAuctionListingErc20Test:testRevert_InvalidTransferBeforeFirstBid() (gas: 209100) +ReserveAuctionListingErc20Test:testRevert_ListingFeeBPSCannotExceed10000() (gas: 32734) +ReserveAuctionListingErc20Test:testRevert_MustApproveModule() (gas: 177144) +ReserveAuctionListingErc20Test:testRevert_MustBeTokenOwnerOrOperator() (gas: 33320) +ReserveAuctionListingErc20Test:testRevert_MustSpecifySellerFundsRecipient() (gas: 30614) +ReserveAuctionListingErc20Test:testRevert_OnlySellerOrOwnerCanCancel() (gas: 157850) +ReserveAuctionListingErc20Test:testRevert_SellerMustApproveERC721TransferHelper() (gas: 178242) +ReserveAuctionListingErc20Test:testRevert_UpdateMustBeSeller() (gas: 155987) +ReserveAuctionListingErc20Test:test_CancelAuction() (gas: 131224) +ReserveAuctionListingErc20Test:test_CreateAuction() (gas: 154884) +ReserveAuctionListingErc20Test:test_CreateAuctionAndCancelPrevious() (gas: 263479) +ReserveAuctionListingErc20Test:test_CreateFirstBid() (gas: 295898) +ReserveAuctionListingErc20Test:test_CreateFutureAuction() (gas: 173940) +ReserveAuctionListingErc20Test:test_ExtendAuction() (gas: 335518) +ReserveAuctionListingErc20Test:test_RefundPreviousBidder() (gas: 334011) +ReserveAuctionListingErc20Test:test_SetReservePrice() (gas: 161942) +ReserveAuctionListingErc20Test:test_SettleAuction() (gas: 427020) +ReserveAuctionListingErc20Test:test_StoreTimeOfFirstBid() (gas: 299471) +ReserveAuctionListingErc20Test:test_TransferNFTIntoEscrow() (gas: 297080) +ReserveAuctionListingEthIntegrationTest:test_ETHIntegration() (gas: 295097) +ReserveAuctionListingEthTest:testRevert_AuctionNotOver() (gas: 191240) +ReserveAuctionListingEthTest:testRevert_AuctionNotStarted() (gas: 127467) +ReserveAuctionListingEthTest:testRevert_BidMustBe10PercentGreaterThanPrevious() (gas: 200424) +ReserveAuctionListingEthTest:testRevert_BidMustMeetReservePrice() (gas: 136657) +ReserveAuctionListingEthTest:testRevert_CannotBidOnAuctionNotActive() (gas: 26777) +ReserveAuctionListingEthTest:testRevert_CannotBidOnAuctionNotStarted() (gas: 129485) +ReserveAuctionListingEthTest:testRevert_CannotBidOnExpiredAuction() (gas: 200235) +ReserveAuctionListingEthTest:testRevert_CannotCancelActiveAuction() (gas: 191326) +ReserveAuctionListingEthTest:testRevert_CannotUpdateActiveAuction() (gas: 190603) +ReserveAuctionListingEthTest:testRevert_CannotUpdateAuctionDoesNotExist() (gas: 22233) +ReserveAuctionListingEthTest:testRevert_InvalidTransferBeforeFirstBid() (gas: 186813) +ReserveAuctionListingEthTest:testRevert_ListingFeeBPSCannotExceed10000() (gas: 30506) +ReserveAuctionListingEthTest:testRevert_MustApproveModule() (gas: 154857) +ReserveAuctionListingEthTest:testRevert_MustBeTokenOwnerOrOperator() (gas: 31114) +ReserveAuctionListingEthTest:testRevert_MustSpecifySellerFundsRecipient() (gas: 28411) +ReserveAuctionListingEthTest:testRevert_OnlySellerOrOwnerCanCancel() (gas: 128611) +ReserveAuctionListingEthTest:testRevert_SellerMustApproveERC721TransferHelper() (gas: 155955) +ReserveAuctionListingEthTest:testRevert_UpdateMustBeSeller() (gas: 127047) +ReserveAuctionListingEthTest:test_CancelAuction() (gas: 106905) +ReserveAuctionListingEthTest:test_CreateAuction() (gas: 125517) +ReserveAuctionListingEthTest:test_CreateAuctionAndCancelPrevious() (gas: 233306) +ReserveAuctionListingEthTest:test_CreateFirstBid() (gas: 184760) +ReserveAuctionListingEthTest:test_CreateFutureAuction() (gas: 124813) +ReserveAuctionListingEthTest:test_ExtendAuction() (gas: 215175) +ReserveAuctionListingEthTest:test_RefundPreviousBidder() (gas: 212060) +ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) +ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) +ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) +ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 64096) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 18276) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenSellerHasLiveAuction() (gas: 69307) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 66146) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 71137) +ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) +ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) +ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) +ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferSingle() (gas: 57446) +ERC1155TransferHelperTest:test_ERC1155TransferBatch() (gas: 163062) +ERC1155TransferHelperTest:test_ERC1155TransferSingle() (gas: 115980) +ERC20TransferHelperTest:testFail_UserMustApproveTransferHelper() (gas: 66199) +ERC20TransferHelperTest:testRevert_UserMustApproveModule() (gas: 56916) +ERC20TransferHelperTest:test_ERC20Transfer() (gas: 119403) +ERC721TransferHelperTest:testFail_UserMustApproveTransferHelper() (gas: 67544) +ERC721TransferHelperTest:testRevert_UserMustApproveModule() (gas: 56878) +ERC721TransferHelperTest:test_ERC721Transfer() (gas: 121779) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index 2a56a2af..3b695980 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -6,7 +6,14 @@ pragma solidity 0.8.10; /// @notice Interface for Variable Supply Auction interface IVariableSupplyAuction { // - /// @notice Says hello - /// @return a friendly greeting - function hello() external pure returns (bytes32); + + /// + function createAuction( + uint256 _minimumRevenue, + address _sellerFundsRecipient, + uint256 _startTime, + uint256 _bidPhaseDuration, + uint256 _revealPhaseDuration, + uint256 _settlePhaseDuration + ) external; } diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 3b54cf71..89bbacc5 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -3,12 +3,88 @@ pragma solidity 0.8.10; import {IVariableSupplyAuction} from "./IVariableSupplyAuction.sol"; +import {ReentrancyGuard} from "@rari-capital/solmate/src/utils/ReentrancyGuard.sol"; + /// @title Variable Supply Auction /// @author neodaoist /// @notice Module for variable supply, seller's choice, sealed bid auctions in ETH for ERC-721 tokens -contract VariableSupplyAuction is IVariableSupplyAuction { +contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // - function hello() public pure returns (bytes32) { - return bytes32("hello world"); + + /*////////////////////////////////////////////////////////////// + AUCTION STORAGE + //////////////////////////////////////////////////////////////*/ + + /// @notice The metadata for a given auction + /// @param minimumRevenue The minimum revenue the seller needs to generate in this auction + /// @param sellerFundsRecipient The address where funds are sent after the auction + /// @param startTime The unix timestamp after which the first bid can be placed + /// @param endOfBidPhase The unix timestamp until which bids can be placed + /// @param endOfRevealPhase The unix timestamp until which placed bids can be revealed + /// @param endOfSettlePhase The unix timestamp until which the seller can settle the auction (TODO clarify can vs. must) + /// @param firstBidTime The unix timestamp when the first bid is placed + struct Auction { + uint96 minimumRevenue; + address sellerFundsRecipient; + uint32 startTime; + uint32 endOfBidPhase; + uint32 endOfRevealPhase; + uint32 endOfSettlePhase; + uint32 firstBidTime; + // bids + } + + /// @notice The auction for a given seller, if one exists + /// @dev Only one auction per seller is allowed at once + mapping(address => Auction) public auctionForSeller; + + /*////////////////////////////////////////////////////////////// + CREATE AUCTION + //////////////////////////////////////////////////////////////*/ + + // TODO add UML diagram + + /// @notice Emitted when an auction is created + /// @param seller The seller of the created auction + /// @param auction The metadata of the created auction + event AuctionCreated(address indexed seller, Auction auction); + + /// @notice Creates a variable supply auction + /// @dev Seller can only have 1 live auction at any one time + /// @param _minimumRevenue The minimum revenue the seller aims to generate in this auction -- + /// they can settle the auction below this value, but they cannot _not_ settle if the revenue + /// generated by any price point + edition size combination would be at least this value + /// @param _sellerFundsRecipient The address to send funds to once the auction is complete + /// @param _startTime The time that users can begin placing bids + /// @param _bidPhaseDuration The length of time of the bid phase in seconds + /// @param _revealPhaseDuration The length of time of the reveal phase in seconds + /// @param _settlePhaseDuration The length of time of the settle phase in seconds + function createAuction( + // TODO add tokenContract via Drops integration + uint256 _minimumRevenue, + address _sellerFundsRecipient, + uint256 _startTime, + uint256 _bidPhaseDuration, + uint256 _revealPhaseDuration, + uint256 _settlePhaseDuration + ) external nonReentrant { + // Ensure the seller does not already have a live auction + require(auctionForSeller[msg.sender].startTime == 0, "ONLY_ONE_LIVE_AUCTION_PER_SELLER"); + + // Ensure the funds recipient is specified + require(_sellerFundsRecipient != address(0), "INVALID_FUNDS_RECIPIENT"); + + // Get the auction's storage pointer + Auction storage auction = auctionForSeller[msg.sender]; + + // Store the associated metadata + auction.minimumRevenue = uint96(_minimumRevenue); + auction.sellerFundsRecipient = _sellerFundsRecipient; + auction.startTime = uint32(_startTime); + auction.endOfBidPhase = uint32(_startTime + _bidPhaseDuration); + auction.endOfRevealPhase = uint32(_startTime + _bidPhaseDuration + _revealPhaseDuration); + auction.endOfSettlePhase = uint32(_startTime + _bidPhaseDuration + _revealPhaseDuration + _settlePhaseDuration); + + emit AuctionCreated(msg.sender, auction); } } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature index 69a12969..cec660c1 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature @@ -68,7 +68,7 @@ Feature: Variable Supply Auctions | Bidder11 | 9.99 ETH | | Bidder12 | 9.99 ETH | | Bidder13 | 9.99 ETH | - And The following accounts should own an NFT + And The following accounts should own 1 NFT | account | | Bidder1 | | Bidder2 | diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol index bd062390..5a826538 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol @@ -110,28 +110,27 @@ contract VariableSupplyAuctionIntegrationTest is DSTest { weth.approve(address(erc20TransferHelper), 50 ether); } - function runETH() public { - vm.prank(address(seller)); - auctions.hello(); - } - - function test_ETHIntegration() public { - uint256 beforeSellerBalance = address(sellerFundsRecipient).balance; - uint256 beforeBidderBalance = address(bidder).balance; - uint256 beforeOtherBidderBalance = address(otherBidder).balance; - uint256 beforeRoyaltyRecipientBalance = address(royaltyRecipient).balance; - uint256 beforeProtocolFeeRecipient = address(protocolFeeRecipient).balance; - address beforeTokenOwner = token.ownerOf(1); - - runETH(); - - uint256 afterSellerBalance = address(sellerFundsRecipient).balance; - uint256 afterBidderBalance = address(bidder).balance; - uint256 afterOtherBidderBalance = address(otherBidder).balance; - uint256 afterRoyaltyRecipientBalance = address(royaltyRecipient).balance; - uint256 afterProtocolFeeRecipient = address(protocolFeeRecipient).balance; - address afterTokenOwner = token.ownerOf(1); - - assertEq(beforeSellerBalance, afterSellerBalance); - } + // function runETH() public { + // // + // } + + // function test_ETHIntegration() public { + // uint256 beforeSellerBalance = address(sellerFundsRecipient).balance; + // uint256 beforeBidderBalance = address(bidder).balance; + // uint256 beforeOtherBidderBalance = address(otherBidder).balance; + // uint256 beforeRoyaltyRecipientBalance = address(royaltyRecipient).balance; + // uint256 beforeProtocolFeeRecipient = address(protocolFeeRecipient).balance; + // address beforeTokenOwner = token.ownerOf(1); + + // runETH(); + + // uint256 afterSellerBalance = address(sellerFundsRecipient).balance; + // uint256 afterBidderBalance = address(bidder).balance; + // uint256 afterOtherBidderBalance = address(otherBidder).balance; + // uint256 afterRoyaltyRecipientBalance = address(royaltyRecipient).balance; + // uint256 afterProtocolFeeRecipient = address(protocolFeeRecipient).balance; + // address afterTokenOwner = token.ownerOf(1); + + // assertEq(beforeSellerBalance, afterSellerBalance); + // } } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 60cd01d6..f541660b 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; -import {DSTest} from "ds-test/test.sol"; +import "forge-std/Test.sol"; import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; + import {Zorb} from "../../utils/users/Zorb.sol"; import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; import {ZoraModuleManager} from "../../../ZoraModuleManager.sol"; @@ -17,9 +18,8 @@ import {VM} from "../../utils/VM.sol"; /// @title VariableSupplyAuctionTest /// @notice Unit Tests for Variable Supply Auctions -contract VariableSupplyAuctionTest is DSTest { +contract VariableSupplyAuctionTest is Test { // - VM internal vm; ZoraRegistrar internal registrar; ZoraProtocolFeeSettings internal ZPFS; @@ -41,9 +41,6 @@ contract VariableSupplyAuctionTest is DSTest { Zorb internal otherBidder; function setUp() public { - // Cheatcodes - vm = VM(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - // Deploy V3 registrar = new ZoraRegistrar(); ZPFS = new ZoraProtocolFeeSettings(); @@ -92,10 +89,133 @@ contract VariableSupplyAuctionTest is DSTest { } /*////////////////////////////////////////////////////////////// - Hello World + Create Auction //////////////////////////////////////////////////////////////*/ - function test_Hello() public { - assertEq(auctions.hello(), bytes32("hello world")); + function testGas_CreateAuction() public { + vm.prank(address(seller)); + auctions.createAuction({ + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: block.timestamp, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + } + + function test_CreateAuction_WhenInstant() public { + Auction memory auction = Auction({ + minimumRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + firstBidTime: uint32(0 days) + }); + + vm.expectEmit(true, true, true, true); + emit AuctionCreated(address(seller), auction); + + vm.prank(address(seller)); + auctions.createAuction({ + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: block.timestamp, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + + ( + uint256 minimumRevenue, + address sellerFundsRecipientReturned, + uint256 startTime, + uint256 endOfBidPhase, + uint256 endOfRevealPhase, + uint256 endOfSettlePhase, + uint256 firstBidTime + // bids + ) = auctions.auctionForSeller(address(seller)); + + assertEq(minimumRevenue, auction.minimumRevenue); + assertEq(sellerFundsRecipientReturned, auction.sellerFundsRecipient); + assertEq(startTime, auction.startTime); + assertEq(endOfBidPhase, auction.endOfBidPhase); + assertEq(endOfRevealPhase, auction.endOfRevealPhase); + assertEq(endOfSettlePhase, auction.endOfSettlePhase); + assertEq(firstBidTime, 0); + // bids + } + + function test_CreateAuction_WhenFuture() public { + vm.prank(address(seller)); + auctions.createAuction({ + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: 1 days, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + + (, , uint32 startTime, , , , ) = auctions.auctionForSeller(address(seller)); + require(startTime == 1 days); } + + function testRevert_CreateAuction_WhenSellerHasLiveAuction() public { + vm.prank(address(seller)); + auctions.createAuction({ + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: 1 days, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + + vm.expectRevert("ONLY_ONE_LIVE_AUCTION_PER_SELLER"); + + vm.prank(address(seller)); + auctions.createAuction({ + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: 1 days, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + } + + function testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() public { + vm.expectRevert("INVALID_FUNDS_RECIPIENT"); + + vm.prank(address(seller)); + auctions.createAuction({ + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(0), + _startTime: 1 days, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + } + + /*////////////////////////////////////////////////////////////// + NOT DRY -- TODO Use better pattern + //////////////////////////////////////////////////////////////*/ + + struct Auction { + uint96 minimumRevenue; + address sellerFundsRecipient; + uint32 startTime; + uint32 endOfBidPhase; + uint32 endOfRevealPhase; + uint32 endOfSettlePhase; + uint32 firstBidTime; + // bids + } + + event AuctionCreated(address indexed seller, Auction auction); } From 55ae7d7cb2e590e252ffeb6d6ef1b6f84ec034e5 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sat, 15 Oct 2022 21:10:57 -0500 Subject: [PATCH 04/31] [draft] Add placeBid; Add ERC721Drop; Switch from auctionForSeller to auctionForDrop --- .gas-snapshot | 13 +- .../IVariableSupplyAuction.sol | 1 + .../VariableSupplyAuction.sol | 76 +++- .../VariableSupplyAuction/temp-ERC721Drop.sol | 66 +++ .../VariableSupplyAuction.t.sol | 385 ++++++++++++++++-- 5 files changed, 482 insertions(+), 59 deletions(-) create mode 100644 contracts/modules/VariableSupplyAuction/temp-ERC721Drop.sol diff --git a/.gas-snapshot b/.gas-snapshot index 26352389..23a77aad 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,11 +319,14 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 64096) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 18276) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenSellerHasLiveAuction() (gas: 69307) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 66146) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 71137) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 88736) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20579) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94132) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 91319) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96630) +VariableSupplyAuctionTest:test_DropInitial() (gas: 30784) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 288501) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 162279) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index 3b695980..4e448cd5 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -9,6 +9,7 @@ interface IVariableSupplyAuction { /// function createAuction( + address _tokenContract, uint256 _minimumRevenue, address _sellerFundsRecipient, uint256 _startTime, diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 89bbacc5..7258d088 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.10; import {IVariableSupplyAuction} from "./IVariableSupplyAuction.sol"; +import {ERC721Drop} from "./temp-ERC721Drop.sol"; import {ReentrancyGuard} from "@rari-capital/solmate/src/utils/ReentrancyGuard.sol"; @@ -16,27 +17,42 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { //////////////////////////////////////////////////////////////*/ /// @notice The metadata for a given auction + /// @param seller The seller of this auction /// @param minimumRevenue The minimum revenue the seller needs to generate in this auction /// @param sellerFundsRecipient The address where funds are sent after the auction /// @param startTime The unix timestamp after which the first bid can be placed /// @param endOfBidPhase The unix timestamp until which bids can be placed /// @param endOfRevealPhase The unix timestamp until which placed bids can be revealed /// @param endOfSettlePhase The unix timestamp until which the seller can settle the auction (TODO clarify can vs. must) - /// @param firstBidTime The unix timestamp when the first bid is placed struct Auction { + address seller; uint96 minimumRevenue; address sellerFundsRecipient; uint32 startTime; uint32 endOfBidPhase; uint32 endOfRevealPhase; uint32 endOfSettlePhase; - uint32 firstBidTime; - // bids + uint96 totalBalance; + // mapping(address => mapping(address => uint96)) balanceOf; + // mapping(address => mapping(address => Bid)) bidOf; + } + + /// + struct Bid { + bytes32 commitment; + uint96 revealed; } /// @notice The auction for a given seller, if one exists /// @dev Only one auction per seller is allowed at once - mapping(address => Auction) public auctionForSeller; + // mapping(address => Auction) public auctionForSeller; + mapping(address => Auction) public auctionForDrop; + + /// + mapping(address => mapping(address => uint96)) public balanceOf; + + /// + mapping(address => mapping(address => Bid)) public bidOf; /*////////////////////////////////////////////////////////////// CREATE AUCTION @@ -45,12 +61,13 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // TODO add UML diagram /// @notice Emitted when an auction is created - /// @param seller The seller of the created auction + /// @param drop The ERC721Drop contract address of the created auction /// @param auction The metadata of the created auction - event AuctionCreated(address indexed seller, Auction auction); + event AuctionCreated(address indexed drop, Auction auction); /// @notice Creates a variable supply auction - /// @dev Seller can only have 1 live auction at any one time + /// @dev Drop can only have 1 live auction at any one time + /// @param _tokenContract The address of the ERC721Drop contract /// @param _minimumRevenue The minimum revenue the seller aims to generate in this auction -- /// they can settle the auction below this value, but they cannot _not_ settle if the revenue /// generated by any price point + edition size combination would be at least this value @@ -60,7 +77,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { /// @param _revealPhaseDuration The length of time of the reveal phase in seconds /// @param _settlePhaseDuration The length of time of the settle phase in seconds function createAuction( - // TODO add tokenContract via Drops integration + address _tokenContract, uint256 _minimumRevenue, address _sellerFundsRecipient, uint256 _startTime, @@ -68,16 +85,17 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { uint256 _revealPhaseDuration, uint256 _settlePhaseDuration ) external nonReentrant { - // Ensure the seller does not already have a live auction - require(auctionForSeller[msg.sender].startTime == 0, "ONLY_ONE_LIVE_AUCTION_PER_SELLER"); + // Ensure the drop does not already have a live auction + require(auctionForDrop[_tokenContract].startTime == 0, "ONLY_ONE_LIVE_AUCTION_PER_DROP"); // Ensure the funds recipient is specified require(_sellerFundsRecipient != address(0), "INVALID_FUNDS_RECIPIENT"); // Get the auction's storage pointer - Auction storage auction = auctionForSeller[msg.sender]; + Auction storage auction = auctionForDrop[_tokenContract]; // Store the associated metadata + auction.seller = msg.sender; auction.minimumRevenue = uint96(_minimumRevenue); auction.sellerFundsRecipient = _sellerFundsRecipient; auction.startTime = uint32(_startTime); @@ -85,6 +103,40 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { auction.endOfRevealPhase = uint32(_startTime + _bidPhaseDuration + _revealPhaseDuration); auction.endOfSettlePhase = uint32(_startTime + _bidPhaseDuration + _revealPhaseDuration + _settlePhaseDuration); - emit AuctionCreated(msg.sender, auction); + emit AuctionCreated(_tokenContract, auction); + } + + /*////////////////////////////////////////////////////////////// + PLACE BID + //////////////////////////////////////////////////////////////*/ + + // TODO add UML + + /// @notice Emitted when a bid is placed + /// @param tokenContract The ERC-721 drop token contract of the auction + /// @param bidder The address that placed a sealed bid + /// @param auction The metadata of the auction + event AuctionBid(address indexed tokenContract, address indexed bidder, Auction auction); + + /// + function placeBid(address _tokenContract, bytes32 _commitment) public payable nonReentrant { + // Get the auction for the specified drop + Auction storage auction = auctionForDrop[_tokenContract]; + + // TODO checks + + // Store the full amount of incoming ether for this auction + auction.totalBalance += uint96(msg.value); + + // Store the amount of incoming ether for this bidder + balanceOf[_tokenContract][msg.sender] = uint96(msg.value); + + // Store the committed / unrevealed bid for this bidder + bidOf[_tokenContract][msg.sender] = Bid({ + commitment: _commitment, + revealed: 0 + }); + + emit AuctionBid(_tokenContract, msg.sender, auction); } } diff --git a/contracts/modules/VariableSupplyAuction/temp-ERC721Drop.sol b/contracts/modules/VariableSupplyAuction/temp-ERC721Drop.sol new file mode 100644 index 00000000..0d9a592a --- /dev/null +++ b/contracts/modules/VariableSupplyAuction/temp-ERC721Drop.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.10; + +/*////////////////////////////////////////////////////////////// + TEMP ERC721Drop interface (incomplete and incorrect =) +//////////////////////////////////////////////////////////////*/ + +contract ERC721Drop { + // + struct Configuration { + // IMetadataRenderer metadataRenderer; + uint64 editionSize; + uint16 royaltyBPS; + address payable fundsRecipient; + } + + string public name; + string public symbol; + address public owner; + Configuration public config; + + // TODO use better mocking pattern for OZ AccessControlEnumerable + + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + address internal minter; + + function getRoleMember(bytes32 /*role*/, uint256 /*index*/) public view returns (address) { + return minter; + } + + function grantRole(bytes32 /*role*/, address account) public { + minter = account; + } + + // TODO add few more needed IERC721 view/tx functions + + function initialize( + string memory _contractName, + string memory _contractSymbol, + address _initialOwner, + address payable _fundsRecipient, + uint64 _editionSize, + uint16 _royaltyBPS + // SalesConfiguration memory _salesConfig, + // IMetadataRenderer _metadataRenderer, + // bytes memory _metadataRendererInit + ) public { + name = _contractName; + symbol = _contractSymbol; + owner = _initialOwner; + config = Configuration({ + editionSize: _editionSize, + royaltyBPS: _royaltyBPS, + fundsRecipient: _fundsRecipient + }); + } + + // TODO ask Iain for gist of actual possible approach + function setEditionSize(uint64 _editionSize) public { + + } + + function adminMint(address to, uint256 quantity) public returns (uint256) { + + } +} diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index f541660b..830c1ce8 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; +import {ERC721Drop} from "../../../modules/VariableSupplyAuction/temp-ERC721Drop.sol"; import {Zorb} from "../../utils/users/Zorb.sol"; import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; @@ -24,29 +25,42 @@ contract VariableSupplyAuctionTest is Test { ZoraRegistrar internal registrar; ZoraProtocolFeeSettings internal ZPFS; ZoraModuleManager internal ZMM; - ERC20TransferHelper internal erc20TransferHelper; - ERC721TransferHelper internal erc721TransferHelper; + // ERC20TransferHelper internal erc20TransferHelper; + // ERC721TransferHelper internal erc721TransferHelper; RoyaltyEngine internal royaltyEngine; VariableSupplyAuction internal auctions; - TestERC721 internal token; - WETH internal weth; + ERC721Drop internal drop; + // WETH internal weth; Zorb internal seller; Zorb internal sellerFundsRecipient; Zorb internal operator; Zorb internal finder; Zorb internal royaltyRecipient; - Zorb internal bidder; - Zorb internal otherBidder; + Zorb internal bidder1; + Zorb internal bidder2; + Zorb internal bidder3; + Zorb internal bidder4; + Zorb internal bidder5; + Zorb internal bidder6; + Zorb internal bidder7; + Zorb internal bidder8; + Zorb internal bidder9; + Zorb internal bidder10; + Zorb internal bidder11; + Zorb internal bidder12; + Zorb internal bidder13; + Zorb internal bidder14; + Zorb internal bidder15; function setUp() public { // Deploy V3 registrar = new ZoraRegistrar(); ZPFS = new ZoraProtocolFeeSettings(); ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); - erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); - erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); + // erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); + // erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); // Init V3 registrar.init(ZMM); @@ -56,45 +70,124 @@ contract VariableSupplyAuctionTest is Test { seller = new Zorb(address(ZMM)); sellerFundsRecipient = new Zorb(address(ZMM)); operator = new Zorb(address(ZMM)); - bidder = new Zorb(address(ZMM)); - otherBidder = new Zorb(address(ZMM)); + bidder1 = new Zorb(address(ZMM)); + bidder2 = new Zorb(address(ZMM)); + bidder3 = new Zorb(address(ZMM)); + bidder4 = new Zorb(address(ZMM)); + bidder5 = new Zorb(address(ZMM)); + bidder6 = new Zorb(address(ZMM)); + bidder7 = new Zorb(address(ZMM)); + bidder8 = new Zorb(address(ZMM)); + bidder9 = new Zorb(address(ZMM)); + bidder10 = new Zorb(address(ZMM)); + bidder11 = new Zorb(address(ZMM)); + bidder12 = new Zorb(address(ZMM)); + bidder13 = new Zorb(address(ZMM)); + bidder14 = new Zorb(address(ZMM)); + bidder15 = new Zorb(address(ZMM)); finder = new Zorb(address(ZMM)); royaltyRecipient = new Zorb(address(ZMM)); - // Deploy mocks - royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); - token = new TestERC721(); - weth = new WETH(); + // Set balances + vm.deal(address(seller), 100 ether); + vm.deal(address(bidder1), 100 ether); + vm.deal(address(bidder2), 100 ether); + vm.deal(address(bidder3), 100 ether); + vm.deal(address(bidder4), 100 ether); + vm.deal(address(bidder5), 100 ether); + vm.deal(address(bidder6), 100 ether); + vm.deal(address(bidder7), 100 ether); + vm.deal(address(bidder8), 100 ether); + vm.deal(address(bidder9), 100 ether); + vm.deal(address(bidder10), 100 ether); + vm.deal(address(bidder11), 100 ether); + vm.deal(address(bidder12), 100 ether); + vm.deal(address(bidder13), 100 ether); + vm.deal(address(bidder14), 100 ether); + vm.deal(address(bidder15), 100 ether); // Deploy Variable Supply Auction module + // (moved before Deploy mocks so auctions address is available) auctions = new VariableSupplyAuction(); registrar.registerModule(address(auctions)); - // Set balances - vm.deal(address(seller), 100 ether); - vm.deal(address(bidder), 100 ether); - vm.deal(address(otherBidder), 100 ether); - - // Mint seller token - token.mint(address(seller), 1); + // Deploy mocks + royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); + drop = new ERC721Drop(); + drop.initialize({ + _contractName: "Test Mutant Ninja Turtles", + _contractSymbol: "TMNT", + _initialOwner: address(seller), + _fundsRecipient: payable(sellerFundsRecipient), + _editionSize: 1, + _royaltyBPS: 1000 + // _metadataRenderer: dummyRenderer, + // _metadataRendererInit: "", + // _salesConfig: IERC721Drop.SalesConfiguration({ + // publicSaleStart: 0, + // publicSaleEnd: 0, + // presaleStart: 0, + // presaleEnd: 0, + // publicSalePrice: 0, + // maxSalePurchasePerAddress: 0, + // presaleMerkleRoot: bytes32(0) + // }) + }); + vm.prank(address(seller)); + drop.grantRole(drop.MINTER_ROLE(), address(auctions)); + // weth = new WETH(); // Users approve module seller.setApprovalForModule(address(auctions), true); - bidder.setApprovalForModule(address(auctions), true); - otherBidder.setApprovalForModule(address(auctions), true); + bidder1.setApprovalForModule(address(auctions), true); + bidder2.setApprovalForModule(address(auctions), true); + bidder3.setApprovalForModule(address(auctions), true); + bidder4.setApprovalForModule(address(auctions), true); + bidder5.setApprovalForModule(address(auctions), true); + bidder6.setApprovalForModule(address(auctions), true); + bidder7.setApprovalForModule(address(auctions), true); + bidder8.setApprovalForModule(address(auctions), true); + bidder9.setApprovalForModule(address(auctions), true); + bidder10.setApprovalForModule(address(auctions), true); + bidder11.setApprovalForModule(address(auctions), true); + bidder12.setApprovalForModule(address(auctions), true); + bidder13.setApprovalForModule(address(auctions), true); + bidder14.setApprovalForModule(address(auctions), true); + bidder15.setApprovalForModule(address(auctions), true); // Seller approve ERC721TransferHelper - vm.prank(address(seller)); - token.setApprovalForAll(address(erc721TransferHelper), true); + // vm.prank(address(seller)); + // token.setApprovalForAll(address(erc721TransferHelper), true); + } + + function test_DropInitial() public { + assertEq(drop.name(), "Test Mutant Ninja Turtles"); + assertEq(drop.symbol(), "TMNT"); + + assertEq(drop.owner(), address(seller)); + assertEq(drop.getRoleMember(drop.MINTER_ROLE(), 0), address(auctions)); + + ( + // IMetadataRenderer renderer, + uint64 editionSize, + uint16 royaltyBPS, + address payable fundsRecipient + ) = drop.config(); + + // assertEq(address(renderer), address(dummyRenderer)); + assertEq(editionSize, 1); + assertEq(royaltyBPS, 1000); + assertEq(fundsRecipient, payable(sellerFundsRecipient)); } /*////////////////////////////////////////////////////////////// - Create Auction + CREATE AUCTION //////////////////////////////////////////////////////////////*/ function testGas_CreateAuction() public { vm.prank(address(seller)); auctions.createAuction({ + _tokenContract: address(drop), _minimumRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: block.timestamp, @@ -106,20 +199,22 @@ contract VariableSupplyAuctionTest is Test { function test_CreateAuction_WhenInstant() public { Auction memory auction = Auction({ + seller: address(seller), minimumRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(block.timestamp), endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - firstBidTime: uint32(0 days) + totalBalance: uint96(0) }); vm.expectEmit(true, true, true, true); - emit AuctionCreated(address(seller), auction); + emit AuctionCreated(address(drop), auction); vm.prank(address(seller)); auctions.createAuction({ + _tokenContract: address(drop), _minimumRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: block.timestamp, @@ -129,29 +224,30 @@ contract VariableSupplyAuctionTest is Test { }); ( + address sellerStored, uint256 minimumRevenue, - address sellerFundsRecipientReturned, + address sellerFundsRecipientStored, uint256 startTime, uint256 endOfBidPhase, uint256 endOfRevealPhase, uint256 endOfSettlePhase, - uint256 firstBidTime - // bids - ) = auctions.auctionForSeller(address(seller)); + uint96 totalBalance + ) = auctions.auctionForDrop(address(drop)); + assertEq(sellerStored, auction.seller); assertEq(minimumRevenue, auction.minimumRevenue); - assertEq(sellerFundsRecipientReturned, auction.sellerFundsRecipient); + assertEq(sellerFundsRecipientStored, auction.sellerFundsRecipient); assertEq(startTime, auction.startTime); assertEq(endOfBidPhase, auction.endOfBidPhase); assertEq(endOfRevealPhase, auction.endOfRevealPhase); assertEq(endOfSettlePhase, auction.endOfSettlePhase); - assertEq(firstBidTime, 0); - // bids + assertEq(totalBalance, 0); } function test_CreateAuction_WhenFuture() public { vm.prank(address(seller)); auctions.createAuction({ + _tokenContract: address(drop), _minimumRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: 1 days, @@ -160,13 +256,26 @@ contract VariableSupplyAuctionTest is Test { _settlePhaseDuration: 1 days }); - (, , uint32 startTime, , , , ) = auctions.auctionForSeller(address(seller)); - require(startTime == 1 days); + ( + , + , + , + uint32 startTime, + uint32 endOfBidPhase, + uint32 endOfRevealPhase, + uint32 endOfSettlePhase, + ) = auctions.auctionForDrop(address(drop)); + + assertEq(startTime, 1 days); + assertEq(endOfBidPhase, 1 days + 3 days); + assertEq(endOfRevealPhase, 1 days + 3 days + 2 days); + assertEq(endOfSettlePhase, 1 days + 3 days + 2 days + 1 days); } - function testRevert_CreateAuction_WhenSellerHasLiveAuction() public { + function testRevert_CreateAuction_WhenDropHasLiveAuction() public { vm.prank(address(seller)); auctions.createAuction({ + _tokenContract: address(drop), _minimumRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: 1 days, @@ -175,10 +284,11 @@ contract VariableSupplyAuctionTest is Test { _settlePhaseDuration: 1 days }); - vm.expectRevert("ONLY_ONE_LIVE_AUCTION_PER_SELLER"); + vm.expectRevert("ONLY_ONE_LIVE_AUCTION_PER_DROP"); vm.prank(address(seller)); auctions.createAuction({ + _tokenContract: address(drop), _minimumRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: 1 days, @@ -193,6 +303,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.createAuction({ + _tokenContract: address(drop), _minimumRevenue: 1 ether, _sellerFundsRecipient: address(0), _startTime: 1 days, @@ -202,20 +313,210 @@ contract VariableSupplyAuctionTest is Test { }); } + /*////////////////////////////////////////////////////////////// + CANCEL AUCTION + //////////////////////////////////////////////////////////////*/ + + // TODO cancelAuction + + /*////////////////////////////////////////////////////////////// + PLACE BID + //////////////////////////////////////////////////////////////*/ + + function test_PlaceBid_WhenSingle() public { + Auction memory auction = Auction({ + seller: address(seller), + minimumRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: uint96(1 ether) // expecting this totalBalance based on bid + }); + + vm.prank(address(seller)); + auctions.createAuction({ + _tokenContract: address(drop), + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: block.timestamp, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + + bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + + vm.expectEmit(true, true, true, true); + emit AuctionBid(address(drop), address(bidder1), auction); + + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + + ( + , + , + , + , + , + , + , + uint256 totalBalance + ) = auctions.auctionForDrop(address(drop)); + (bytes32 commitmentStored, ) = auctions.bidOf(address(drop), address(bidder1)); + + assertEq(address(auctions).balance, 1 ether); + assertEq(totalBalance, 1 ether); + assertEq(auctions.balanceOf(address(drop), address(bidder1)), 1 ether); + assertEq(commitmentStored, commitment); + } + + function test_PlaceBid_WhenMultiple() public { + Auction memory auction = Auction({ + seller: address(seller), + minimumRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: uint96(6 ether) // expecting this totalBalance based on bids + }); + + vm.prank(address(seller)); + auctions.createAuction({ + _tokenContract: address(drop), + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: block.timestamp, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + + // NOTE sealed bid amount can be less than sent ether amount (allows for hiding bid amount until reveal) + bytes32 commitment1 = genSealedBid(1 ether, bytes32("setec astronomy")); + bytes32 commitment2 = genSealedBid(1 ether, bytes32("too many secrets")); + bytes32 commitment3 = genSealedBid(1 ether, bytes32("cray tomes on set")); + + // TODO come up with pattern to assert events as totalBalance changes + + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment1); + vm.prank(address(bidder2)); + auctions.placeBid{value: 2 ether}(address(drop), commitment2); + vm.prank(address(bidder3)); + auctions.placeBid{value: 3 ether}(address(drop), commitment3); + + ( + , + , + , + , + , + , + , + uint256 totalBalance + ) = auctions.auctionForDrop(address(drop)); + (bytes32 commitmentStored1, ) = auctions.bidOf(address(drop), address(bidder1)); + (bytes32 commitmentStored2, ) = auctions.bidOf(address(drop), address(bidder2)); + (bytes32 commitmentStored3, ) = auctions.bidOf(address(drop), address(bidder3)); + + assertEq(address(auctions).balance, 6 ether); + assertEq(totalBalance, 6 ether); + assertEq(auctions.balanceOf(address(drop), address(bidder1)), 1 ether); + assertEq(auctions.balanceOf(address(drop), address(bidder2)), 2 ether); + assertEq(auctions.balanceOf(address(drop), address(bidder3)), 3 ether); + assertEq(commitmentStored1, commitment1); + assertEq(commitmentStored2, commitment2); + assertEq(commitmentStored3, commitment3); + } + + // function testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() public { + + // } + + // function testRevert_PlaceBid_WhenNoEtherIncluded() public { + + // } + + // function testRevert_PlaceBid_WhenSellerDidNotApproveModule() public { + + // } + + // function testRevert_PlaceBid_WhenAuctionInRevealPhase() public { + + // } + + // function testRevert_PlaceBid_WhenAuctionInSettlePhase() public { + + // } + + // function testRevert_PlaceBid_WhenAuctionIsCompleted() public { + + // } + + // function testRevert_PlaceBid_WhenAuctionIsCancelled() public { + + // } + + // function testRevert_PlaceBid_WhenAuctionDoesNotExist() public { + + // } + + /*////////////////////////////////////////////////////////////// + REVEAL BID + //////////////////////////////////////////////////////////////*/ + + // TODO revealBid + + /*////////////////////////////////////////////////////////////// + FAILURE TO REVEAL BID + //////////////////////////////////////////////////////////////*/ + + // TODO bidder failure to reveal bid sad paths + + /*////////////////////////////////////////////////////////////// + SETTLE AUCTION + //////////////////////////////////////////////////////////////*/ + + // TODO settleAuction + + /*////////////////////////////////////////////////////////////// + FAILURE TO SETTLE AUCTION + //////////////////////////////////////////////////////////////*/ + + // TODO seller failure to settle auction sad paths + + /*////////////////////////////////////////////////////////////// + Helper Functions + //////////////////////////////////////////////////////////////*/ + + function genSealedBid(uint256 _amount, bytes32 _salt) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(_amount, _salt)); + } + /*////////////////////////////////////////////////////////////// NOT DRY -- TODO Use better pattern //////////////////////////////////////////////////////////////*/ struct Auction { + address seller; uint96 minimumRevenue; address sellerFundsRecipient; uint32 startTime; uint32 endOfBidPhase; uint32 endOfRevealPhase; uint32 endOfSettlePhase; - uint32 firstBidTime; - // bids + uint96 totalBalance; + } + + struct Bid { + bytes32 commitment; + uint256 revealed; } - event AuctionCreated(address indexed seller, Auction auction); + event AuctionCreated(address indexed drop, Auction auction); + event AuctionBid(address indexed tokenContract, address indexed bidder, Auction auction); } From 9a43eebb733131edfd759c484ba58f1a23a6a1e0 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sun, 16 Oct 2022 01:50:38 -0400 Subject: [PATCH 05/31] [refactor] Extract event assertions into dedicated tests --- .../VariableSupplyAuction.t.sol | 140 +++++++++++------- 1 file changed, 89 insertions(+), 51 deletions(-) diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 830c1ce8..981f07a3 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -198,20 +198,6 @@ contract VariableSupplyAuctionTest is Test { } function test_CreateAuction_WhenInstant() public { - Auction memory auction = Auction({ - seller: address(seller), - minimumRevenue: 1 ether, - sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - totalBalance: uint96(0) - }); - - vm.expectEmit(true, true, true, true); - emit AuctionCreated(address(drop), auction); - vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), @@ -234,14 +220,14 @@ contract VariableSupplyAuctionTest is Test { uint96 totalBalance ) = auctions.auctionForDrop(address(drop)); - assertEq(sellerStored, auction.seller); - assertEq(minimumRevenue, auction.minimumRevenue); - assertEq(sellerFundsRecipientStored, auction.sellerFundsRecipient); - assertEq(startTime, auction.startTime); - assertEq(endOfBidPhase, auction.endOfBidPhase); - assertEq(endOfRevealPhase, auction.endOfRevealPhase); - assertEq(endOfSettlePhase, auction.endOfSettlePhase); - assertEq(totalBalance, 0); + assertEq(sellerStored, address(seller)); + assertEq(minimumRevenue, 1 ether); + assertEq(sellerFundsRecipientStored, address(sellerFundsRecipient)); + assertEq(startTime, uint32(block.timestamp)); + assertEq(endOfBidPhase, uint32(block.timestamp + 3 days)); + assertEq(endOfRevealPhase, uint32(block.timestamp + 3 days + 2 days)); + assertEq(endOfSettlePhase, uint32(block.timestamp + 3 days + 2 days + 1 days)); + assertEq(totalBalance, uint96(0)); } function test_CreateAuction_WhenFuture() public { @@ -272,6 +258,33 @@ contract VariableSupplyAuctionTest is Test { assertEq(endOfSettlePhase, 1 days + 3 days + 2 days + 1 days); } + function testEvent_createAuction() public { + Auction memory auction = Auction({ + seller: address(seller), + minimumRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: uint96(0) + }); + + vm.expectEmit(true, true, true, true); + emit AuctionCreated(address(drop), auction); + + vm.prank(address(seller)); + auctions.createAuction({ + _tokenContract: address(drop), + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: block.timestamp, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + } + function testRevert_CreateAuction_WhenDropHasLiveAuction() public { vm.prank(address(seller)); auctions.createAuction({ @@ -324,17 +337,6 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ function test_PlaceBid_WhenSingle() public { - Auction memory auction = Auction({ - seller: address(seller), - minimumRevenue: 1 ether, - sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - totalBalance: uint96(1 ether) // expecting this totalBalance based on bid - }); - vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), @@ -348,9 +350,6 @@ contract VariableSupplyAuctionTest is Test { bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); - vm.expectEmit(true, true, true, true); - emit AuctionBid(address(drop), address(bidder1), auction); - vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -373,17 +372,6 @@ contract VariableSupplyAuctionTest is Test { } function test_PlaceBid_WhenMultiple() public { - Auction memory auction = Auction({ - seller: address(seller), - minimumRevenue: 1 ether, - sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - totalBalance: uint96(6 ether) // expecting this totalBalance based on bids - }); - vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), @@ -400,8 +388,6 @@ contract VariableSupplyAuctionTest is Test { bytes32 commitment2 = genSealedBid(1 ether, bytes32("too many secrets")); bytes32 commitment3 = genSealedBid(1 ether, bytes32("cray tomes on set")); - // TODO come up with pattern to assert events as totalBalance changes - vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); vm.prank(address(bidder2)); @@ -433,6 +419,56 @@ contract VariableSupplyAuctionTest is Test { assertEq(commitmentStored3, commitment3); } + function testEvent_PlaceBid_WhenMultiple() public { + Auction memory auction = Auction({ + seller: address(seller), + minimumRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: uint96(1 ether) // expect this totalBalance in event based on first bid + }); + + vm.prank(address(seller)); + auctions.createAuction({ + _tokenContract: address(drop), + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: block.timestamp, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + + bytes32 commitment1 = genSealedBid(1 ether, bytes32("setec astronomy")); + bytes32 commitment2 = genSealedBid(1 ether, bytes32("too many secrets")); + bytes32 commitment3 = genSealedBid(1 ether, bytes32("cray tomes on set")); + + vm.expectEmit(true, true, true, true); + emit AuctionBid(address(drop), address(bidder1), auction); + + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment1); + + auction.totalBalance = 3 ether; // 2 ether more from bidder 2 + + vm.expectEmit(true, true, true, true); + emit AuctionBid(address(drop), address(bidder2), auction); + + vm.prank(address(bidder2)); + auctions.placeBid{value: 2 ether}(address(drop), commitment2); + + auction.totalBalance = 6 ether; // 3 ether more from bidder 3 + + vm.expectEmit(true, true, true, true); + emit AuctionBid(address(drop), address(bidder3), auction); + + vm.prank(address(bidder3)); + auctions.placeBid{value: 3 ether}(address(drop), commitment3); + } + // function testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() public { // } @@ -490,15 +526,17 @@ contract VariableSupplyAuctionTest is Test { // TODO seller failure to settle auction sad paths /*////////////////////////////////////////////////////////////// - Helper Functions + TEST HELPERS //////////////////////////////////////////////////////////////*/ + // TODO add modifier to concisely create auctions + function genSealedBid(uint256 _amount, bytes32 _salt) internal pure returns (bytes32) { return keccak256(abi.encodePacked(_amount, _salt)); } /*////////////////////////////////////////////////////////////// - NOT DRY -- TODO Use better pattern + TODO use better pattern to DRY up //////////////////////////////////////////////////////////////*/ struct Auction { From 5ee3c44d7cfaecfa4394115a37e679af22f84eee Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sun, 16 Oct 2022 02:29:15 -0400 Subject: [PATCH 06/31] [draft] Add placeBid sad paths; Extract modifier to setup test auction --- .gas-snapshot | 23 ++- .../VariableSupplyAuction.sol | 13 +- .../VariableSupplyAuction.t.sol | 170 +++++++++--------- 3 files changed, 111 insertions(+), 95 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 23a77aad..72de914b 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,14 +319,21 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 88736) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20579) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94132) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 91319) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96630) -VariableSupplyAuctionTest:test_DropInitial() (gas: 30784) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 288501) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 162279) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 291771) +VariableSupplyAuctionTest:testEvent_createAuction() (gas: 93637) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 88714) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20557) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94128) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27135) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 103163) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 103108) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 163661) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 98359) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 91363) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 92328) +VariableSupplyAuctionTest:test_DropInitial() (gas: 30829) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 289589) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 157489) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 7258d088..501e58c1 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -123,9 +123,20 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; - // TODO checks + // Ensure the auction exists + require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + + // Ensure the auction is still in bid phase + require(block.timestamp < auction.endOfBidPhase, "BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); + + // Ensure the bidder has not placed a bid in auction already + require(balanceOf[_tokenContract][msg.sender] == 0, "ALREADY_PLACED_BID_IN_AUCTION"); + + // Ensure the bid is valid and includes some ether + require(msg.value > 0 ether, "VALID_BIDS_MUST_INCLUDE_ETHER"); // Store the full amount of incoming ether for this auction + // (may be less than actual bid amount once revealed) auction.totalBalance += uint96(msg.value); // Store the amount of incoming ether for this bidder diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 981f07a3..3442956d 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -106,11 +106,6 @@ contract VariableSupplyAuctionTest is Test { vm.deal(address(bidder14), 100 ether); vm.deal(address(bidder15), 100 ether); - // Deploy Variable Supply Auction module - // (moved before Deploy mocks so auctions address is available) - auctions = new VariableSupplyAuction(); - registrar.registerModule(address(auctions)); - // Deploy mocks royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); drop = new ERC721Drop(); @@ -133,9 +128,15 @@ contract VariableSupplyAuctionTest is Test { // presaleMerkleRoot: bytes32(0) // }) }); + // weth = new WETH(); + + // Deploy Variable Supply Auction module + auctions = new VariableSupplyAuction(); + registrar.registerModule(address(auctions)); + + // Grant auction minter role on drop contract vm.prank(address(seller)); drop.grantRole(drop.MINTER_ROLE(), address(auctions)); - // weth = new WETH(); // Users approve module seller.setApprovalForModule(address(auctions), true); @@ -185,6 +186,7 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ function testGas_CreateAuction() public { + // NOTE this basic setup can be applied to tests via setupBasicAuction modifier vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), @@ -197,18 +199,7 @@ contract VariableSupplyAuctionTest is Test { }); } - function test_CreateAuction_WhenInstant() public { - vm.prank(address(seller)); - auctions.createAuction({ - _tokenContract: address(drop), - _minimumRevenue: 1 ether, - _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: block.timestamp, - _bidPhaseDuration: 3 days, - _revealPhaseDuration: 2 days, - _settlePhaseDuration: 1 days - }); - + function test_CreateAuction_WhenInstant() public setupBasicAuction { ( address sellerStored, uint256 minimumRevenue, @@ -285,18 +276,7 @@ contract VariableSupplyAuctionTest is Test { }); } - function testRevert_CreateAuction_WhenDropHasLiveAuction() public { - vm.prank(address(seller)); - auctions.createAuction({ - _tokenContract: address(drop), - _minimumRevenue: 1 ether, - _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: 1 days, - _bidPhaseDuration: 3 days, - _revealPhaseDuration: 2 days, - _settlePhaseDuration: 1 days - }); - + function testRevert_CreateAuction_WhenDropHasLiveAuction() public setupBasicAuction { vm.expectRevert("ONLY_ONE_LIVE_AUCTION_PER_DROP"); vm.prank(address(seller)); @@ -336,18 +316,7 @@ contract VariableSupplyAuctionTest is Test { PLACE BID //////////////////////////////////////////////////////////////*/ - function test_PlaceBid_WhenSingle() public { - vm.prank(address(seller)); - auctions.createAuction({ - _tokenContract: address(drop), - _minimumRevenue: 1 ether, - _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: block.timestamp, - _bidPhaseDuration: 3 days, - _revealPhaseDuration: 2 days, - _settlePhaseDuration: 1 days - }); - + function test_PlaceBid_WhenSingle() public setupBasicAuction { bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); vm.prank(address(bidder1)); @@ -371,18 +340,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(commitmentStored, commitment); } - function test_PlaceBid_WhenMultiple() public { - vm.prank(address(seller)); - auctions.createAuction({ - _tokenContract: address(drop), - _minimumRevenue: 1 ether, - _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: block.timestamp, - _bidPhaseDuration: 3 days, - _revealPhaseDuration: 2 days, - _settlePhaseDuration: 1 days - }); - + function test_PlaceBid_WhenMultiple() public setupBasicAuction { // NOTE sealed bid amount can be less than sent ether amount (allows for hiding bid amount until reveal) bytes32 commitment1 = genSealedBid(1 ether, bytes32("setec astronomy")); bytes32 commitment2 = genSealedBid(1 ether, bytes32("too many secrets")); @@ -419,7 +377,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(commitmentStored3, commitment3); } - function testEvent_PlaceBid_WhenMultiple() public { + function testEvent_PlaceBid_WhenMultiple() public setupBasicAuction { Auction memory auction = Auction({ seller: address(seller), minimumRevenue: 1 ether, @@ -431,17 +389,6 @@ contract VariableSupplyAuctionTest is Test { totalBalance: uint96(1 ether) // expect this totalBalance in event based on first bid }); - vm.prank(address(seller)); - auctions.createAuction({ - _tokenContract: address(drop), - _minimumRevenue: 1 ether, - _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: block.timestamp, - _bidPhaseDuration: 3 days, - _revealPhaseDuration: 2 days, - _settlePhaseDuration: 1 days - }); - bytes32 commitment1 = genSealedBid(1 ether, bytes32("setec astronomy")); bytes32 commitment2 = genSealedBid(1 ether, bytes32("too many secrets")); bytes32 commitment3 = genSealedBid(1 ether, bytes32("cray tomes on set")); @@ -469,36 +416,72 @@ contract VariableSupplyAuctionTest is Test { auctions.placeBid{value: 3 ether}(address(drop), commitment3); } - // function testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() public { - - // } + function testRevert_PlaceBid_WhenAuctionDoesNotExist() public { + vm.expectRevert("AUCTION_DOES_NOT_EXIST"); - // function testRevert_PlaceBid_WhenNoEtherIncluded() public { - - // } + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + } - // function testRevert_PlaceBid_WhenSellerDidNotApproveModule() public { - - // } + function testRevert_PlaceBid_WhenAuctionInRevealPhase() public setupBasicAuction { + vm.warp(3 days + 1 seconds); // reveal phase - // function testRevert_PlaceBid_WhenAuctionInRevealPhase() public { - - // } + vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); - // function testRevert_PlaceBid_WhenAuctionInSettlePhase() public { - - // } + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + } + + function testRevert_PlaceBid_WhenAuctionInSettlePhase() public setupBasicAuction { + vm.warp(3 days + 2 days + 1 seconds); // settle phase + + vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); + + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + } + + function testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() public setupBasicAuction { + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); - // function testRevert_PlaceBid_WhenAuctionIsCompleted() public { + vm.expectRevert("ALREADY_PLACED_BID_IN_AUCTION"); + + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + } + + function testRevert_PlaceBid_WhenNoEtherIncluded() public setupBasicAuction { + vm.expectRevert("VALID_BIDS_MUST_INCLUDE_ETHER"); + + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + vm.prank(address(bidder1)); + auctions.placeBid(address(drop), commitment); + } + + // TODO once settleAuction is written + // function testRevert_PlaceBid_WhenAuctionIsCompleted() public setupBasicAuction { // } - // function testRevert_PlaceBid_WhenAuctionIsCancelled() public { + // TODO once cancelAuction is written + // function testRevert_PlaceBid_WhenAuctionIsCancelled() public setupBasicAuction { // } - // function testRevert_PlaceBid_WhenAuctionDoesNotExist() public { - + // TODO revist – test may become relevant if we move minter role granting into TransferHelper + // function testRevert_PlaceBid_WhenSellerDidNotApproveModule() public setupBasicAuction { + // seller.setApprovalForModule(address(auctions), false); + + // vm.expectRevert("module has not been approved by user"); + + // bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + // vm.prank(address(bidder1)); + // auctions.placeBid{value: 1 ether}(address(drop), commitment); // } /*////////////////////////////////////////////////////////////// @@ -529,7 +512,22 @@ contract VariableSupplyAuctionTest is Test { TEST HELPERS //////////////////////////////////////////////////////////////*/ - // TODO add modifier to concisely create auctions + // TODO improve modifier pattern to include parameters (could combine w/ fuzzing) + + modifier setupBasicAuction() { + vm.prank(address(seller)); + auctions.createAuction({ + _tokenContract: address(drop), + _minimumRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: block.timestamp, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + + _; + } function genSealedBid(uint256 _amount, bytes32 _salt) internal pure returns (bytes32) { return keccak256(abi.encodePacked(_amount, _salt)); From 348c855524b6d05ce09394221c64829a61699974 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sun, 16 Oct 2022 03:05:54 -0400 Subject: [PATCH 07/31] [draft] Begin work on revealBid --- .gas-snapshot | 35 +++-- .../VariableSupplyAuction.sol | 48 +++++-- .../VariableSupplyAuction.t.sol | 123 +++++++++++++++++- 3 files changed, 177 insertions(+), 29 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 72de914b..8a4a3c98 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,21 +319,28 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 291771) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 291793) VariableSupplyAuctionTest:testEvent_createAuction() (gas: 93637) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 88714) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20557) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94128) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27135) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 103163) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 103108) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 163661) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 98359) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 91363) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 92328) -VariableSupplyAuctionTest:test_DropInitial() (gas: 30829) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 289589) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 157489) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 88758) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20601) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94150) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27157) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 103141) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 103130) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 163706) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 98337) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 156781) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 157174) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 98767) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 158127) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 157920) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 158150) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 91342) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 92372) +VariableSupplyAuctionTest:test_DropInitial() (gas: 30806) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 289588) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 157533) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 176793) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 501e58c1..2af40832 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -39,8 +39,8 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { /// struct Bid { - bytes32 commitment; - uint96 revealed; + bytes32 commitmentHash; + uint96 revealedBidAmount; } /// @notice The auction for a given seller, if one exists @@ -116,10 +116,10 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { /// @param tokenContract The ERC-721 drop token contract of the auction /// @param bidder The address that placed a sealed bid /// @param auction The metadata of the auction - event AuctionBid(address indexed tokenContract, address indexed bidder, Auction auction); + event BidPlaced(address indexed tokenContract, address indexed bidder, Auction auction); - /// - function placeBid(address _tokenContract, bytes32 _commitment) public payable nonReentrant { + /// TODO + function placeBid(address _tokenContract, bytes32 _commitmentHash) public payable nonReentrant { // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; @@ -144,10 +144,42 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // Store the committed / unrevealed bid for this bidder bidOf[_tokenContract][msg.sender] = Bid({ - commitment: _commitment, - revealed: 0 + commitmentHash: _commitmentHash, + revealedBidAmount: 0 }); - emit AuctionBid(_tokenContract, msg.sender, auction); + emit BidPlaced(_tokenContract, msg.sender, auction); + } + + /*////////////////////////////////////////////////////////////// + REVEAL BID + //////////////////////////////////////////////////////////////*/ + + // TODO add UML + + /// + event BidRevealed(); + + /// + function revealBid(address _tokenContract, uint256 _bidAmount, bytes32 _salt) public nonReentrant { + // Get the auction for the specified drop + Auction storage auction = auctionForDrop[_tokenContract]; + + // Ensure auction is in reveal phase + require(block.timestamp >= auction.endOfBidPhase && block.timestamp < auction.endOfRevealPhase, "REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); + + // Get the bid for the specified bidder + Bid storage bid = bidOf[_tokenContract][msg.sender]; + + // Ensure bidder placed bid in auction + require(balanceOf[_tokenContract][msg.sender] > 0 ether, "NO_PLACED_BID_FOUND_FOR_ADDRESS"); + + // Ensure revealed bid amount is not greater than sent ether + require(_bidAmount <= balanceOf[_tokenContract][msg.sender], "REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); + + // Ensure revealed bid matches sealed bid + require(keccak256(abi.encodePacked(_bidAmount, _salt)) == bid.commitmentHash, "REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); + + bid.revealedBidAmount = uint96(_bidAmount); } } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 3442956d..f8fbe8c4 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -394,7 +394,7 @@ contract VariableSupplyAuctionTest is Test { bytes32 commitment3 = genSealedBid(1 ether, bytes32("cray tomes on set")); vm.expectEmit(true, true, true, true); - emit AuctionBid(address(drop), address(bidder1), auction); + emit BidPlaced(address(drop), address(bidder1), auction); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); @@ -402,7 +402,7 @@ contract VariableSupplyAuctionTest is Test { auction.totalBalance = 3 ether; // 2 ether more from bidder 2 vm.expectEmit(true, true, true, true); - emit AuctionBid(address(drop), address(bidder2), auction); + emit BidPlaced(address(drop), address(bidder2), auction); vm.prank(address(bidder2)); auctions.placeBid{value: 2 ether}(address(drop), commitment2); @@ -410,7 +410,7 @@ contract VariableSupplyAuctionTest is Test { auction.totalBalance = 6 ether; // 3 ether more from bidder 3 vm.expectEmit(true, true, true, true); - emit AuctionBid(address(drop), address(bidder3), auction); + emit BidPlaced(address(drop), address(bidder3), auction); vm.prank(address(bidder3)); auctions.placeBid{value: 3 ether}(address(drop), commitment3); @@ -488,7 +488,116 @@ contract VariableSupplyAuctionTest is Test { REVEAL BID //////////////////////////////////////////////////////////////*/ - // TODO revealBid + function test_RevealBid_WhenSingle() public setupBasicAuction { + bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1.1 ether}(address(drop), commitment); + + vm.warp(3 days + 1 seconds); + + vm.prank(address(bidder1)); + auctions.revealBid(address (drop), 1 ether, bytes32("setec astronomy")); + + (, uint256 bidAmount) = auctions.bidOf(address(drop), address(bidder1)); + + assertEq(bidAmount, 1 ether); + } + + // TODO + + // function test_RevealBid_WhenMultiple() public setupBasicAuction { + + // } + + // function testEvent_PlaceBid_WhenSingle() public setupBasicAuction { + + // } + + // function testEvent_PlaceBid_WhenMultiple() public setupBasicAuction { + + // } + + function testRevert_RevealBid_WhenAuctionInBidPhase() public setupBasicAuction { + bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1.1 ether}(address(drop), commitment); + + vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); + + vm.prank(address(bidder1)); + auctions.revealBid(address (drop), 1 ether, bytes32("setec astronomy")); + } + + function testRevert_RevealBid_WhenAuctionInSettlePhase() public setupBasicAuction { + bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1.1 ether}(address(drop), commitment); + + vm.warp(3 days + 2 days + 1 seconds); + + vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); + + vm.prank(address(bidder1)); + auctions.revealBid(address (drop), 1 ether, bytes32("setec astronomy")); + } + + // TODO once settleAuction is written + // function testRevert_RevealBid_WhenAuctionIsCompleted() public setupBasicAuction { + + // } + + // TODO once cancelAuction is written + // function testRevert_RevealBid_WhenAuctionIsCancelled() public setupBasicAuction { + + // } + + function testRevert_RevealBid_WhenNoCommittedBid() public setupBasicAuction { + vm.warp(3 days + 1 seconds); + + vm.expectRevert("NO_PLACED_BID_FOUND_FOR_ADDRESS"); + + vm.prank(address(bidder1)); + auctions.revealBid(address (drop), 1.1 ether, bytes32("setec astronomy")); + } + + function testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() public setupBasicAuction { + bytes32 commitment = genSealedBid(1.1 ether, bytes32("setec astronomy")); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + + vm.warp(3 days + 1 seconds); + + vm.expectRevert("REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); + + vm.prank(address(bidder1)); + auctions.revealBid(address (drop), 1.1 ether, bytes32("setec astronomy")); + } + + function testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() public setupBasicAuction { + bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + + vm.warp(3 days + 1 seconds); + + vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); + + vm.prank(address(bidder1)); + auctions.revealBid(address (drop), 0.9 ether, bytes32("setec astronomy")); // wrong amount + } + + function testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() public setupBasicAuction { + bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + + vm.warp(3 days + 1 seconds); + + vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); + + vm.prank(address(bidder1)); + auctions.revealBid(address (drop), 1 ether, bytes32("too many secrets")); // wrong salt + } /*////////////////////////////////////////////////////////////// FAILURE TO REVEAL BID @@ -549,10 +658,10 @@ contract VariableSupplyAuctionTest is Test { } struct Bid { - bytes32 commitment; - uint256 revealed; + bytes32 commitmentHash; + uint96 revealedBidAmount; } event AuctionCreated(address indexed drop, Auction auction); - event AuctionBid(address indexed tokenContract, address indexed bidder, Auction auction); + event BidPlaced(address indexed tokenContract, address indexed bidder, Auction auction); } From 5dfc48c87c42256d55dc631e17764a9f92f6da9a Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sun, 16 Oct 2022 18:20:18 -0400 Subject: [PATCH 08/31] [refactor] Improve bidder usability by using string instead of bytes32 for salt param --- .../IVariableSupplyAuction.sol | 6 +++ .../VariableSupplyAuction.sol | 4 +- .../VariableSupplyAuction.t.sol | 46 +++++++++---------- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index 4e448cd5..9f3126c7 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -17,4 +17,10 @@ interface IVariableSupplyAuction { uint256 _revealPhaseDuration, uint256 _settlePhaseDuration ) external; + + /// + function placeBid(address _tokenContract, bytes32 _commitmentHash) external payable; + + /// + function revealBid(address _tokenContract, uint256 _bidAmount, string calldata _salt) external; } diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 2af40832..2fd246ec 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -161,7 +161,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { event BidRevealed(); /// - function revealBid(address _tokenContract, uint256 _bidAmount, bytes32 _salt) public nonReentrant { + function revealBid(address _tokenContract, uint256 _bidAmount, string calldata _salt) public nonReentrant { // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; @@ -178,7 +178,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { require(_bidAmount <= balanceOf[_tokenContract][msg.sender], "REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); // Ensure revealed bid matches sealed bid - require(keccak256(abi.encodePacked(_bidAmount, _salt)) == bid.commitmentHash, "REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); + require(keccak256(abi.encodePacked(_bidAmount, bytes(_salt))) == bid.commitmentHash, "REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); bid.revealedBidAmount = uint96(_bidAmount); } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index f8fbe8c4..bfb34d95 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -317,7 +317,7 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ function test_PlaceBid_WhenSingle() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -342,9 +342,9 @@ contract VariableSupplyAuctionTest is Test { function test_PlaceBid_WhenMultiple() public setupBasicAuction { // NOTE sealed bid amount can be less than sent ether amount (allows for hiding bid amount until reveal) - bytes32 commitment1 = genSealedBid(1 ether, bytes32("setec astronomy")); - bytes32 commitment2 = genSealedBid(1 ether, bytes32("too many secrets")); - bytes32 commitment3 = genSealedBid(1 ether, bytes32("cray tomes on set")); + bytes32 commitment1 = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment2 = genSealedBid(1 ether, "too many secrets"); + bytes32 commitment3 = genSealedBid(1 ether, "cray tomes on set"); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); @@ -389,9 +389,9 @@ contract VariableSupplyAuctionTest is Test { totalBalance: uint96(1 ether) // expect this totalBalance in event based on first bid }); - bytes32 commitment1 = genSealedBid(1 ether, bytes32("setec astronomy")); - bytes32 commitment2 = genSealedBid(1 ether, bytes32("too many secrets")); - bytes32 commitment3 = genSealedBid(1 ether, bytes32("cray tomes on set")); + bytes32 commitment1 = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment2 = genSealedBid(1 ether, "too many secrets"); + bytes32 commitment3 = genSealedBid(1 ether, "cray tomes on set"); vm.expectEmit(true, true, true, true); emit BidPlaced(address(drop), address(bidder1), auction); @@ -489,14 +489,14 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ function test_RevealBid_WhenSingle() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); vm.warp(3 days + 1 seconds); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1 ether, bytes32("setec astronomy")); + auctions.revealBid(address (drop), 1 ether, "setec astronomy"); (, uint256 bidAmount) = auctions.bidOf(address(drop), address(bidder1)); @@ -518,18 +518,18 @@ contract VariableSupplyAuctionTest is Test { // } function testRevert_RevealBid_WhenAuctionInBidPhase() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1 ether, bytes32("setec astronomy")); + auctions.revealBid(address (drop), 1 ether, "setec astronomy"); } function testRevert_RevealBid_WhenAuctionInSettlePhase() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); @@ -538,7 +538,7 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1 ether, bytes32("setec astronomy")); + auctions.revealBid(address (drop), 1 ether, "setec astronomy"); } // TODO once settleAuction is written @@ -557,11 +557,11 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("NO_PLACED_BID_FOUND_FOR_ADDRESS"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1.1 ether, bytes32("setec astronomy")); + auctions.revealBid(address (drop), 1.1 ether, "setec astronomy"); } function testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() public setupBasicAuction { - bytes32 commitment = genSealedBid(1.1 ether, bytes32("setec astronomy")); + bytes32 commitment = genSealedBid(1.1 ether, "setec astronomy"); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -570,11 +570,11 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1.1 ether, bytes32("setec astronomy")); + auctions.revealBid(address (drop), 1.1 ether, "setec astronomy"); } function testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -583,11 +583,11 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 0.9 ether, bytes32("setec astronomy")); // wrong amount + auctions.revealBid(address (drop), 0.9 ether, "setec astronomy"); // wrong amount } function testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, bytes32("setec astronomy")); + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -596,7 +596,7 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1 ether, bytes32("too many secrets")); // wrong salt + auctions.revealBid(address (drop), 1 ether, "too many secrets"); // wrong salt } /*////////////////////////////////////////////////////////////// @@ -622,7 +622,6 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ // TODO improve modifier pattern to include parameters (could combine w/ fuzzing) - modifier setupBasicAuction() { vm.prank(address(seller)); auctions.createAuction({ @@ -638,8 +637,9 @@ contract VariableSupplyAuctionTest is Test { _; } - function genSealedBid(uint256 _amount, bytes32 _salt) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(_amount, _salt)); + // IDEA could this not be moved to the hyperstructure module for better bidder usability ?! + function genSealedBid(uint256 _amount, string memory _salt) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(_amount, bytes(_salt))); } /*////////////////////////////////////////////////////////////// From 401aacfd7d72f9e74041eb7395a906a54ed9e65b Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sun, 16 Oct 2022 19:12:53 -0400 Subject: [PATCH 09/31] [draft] Add placeBid event tests; Add more NatSpec --- .gas-snapshot | 46 ++++--- .../VariableSupplyAuction.sol | 41 ++++-- .../VariableSupplyAuction.t.sol | 128 ++++++++++++++++-- 3 files changed, 169 insertions(+), 46 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 8a4a3c98..400ea540 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,28 +319,32 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 291793) -VariableSupplyAuctionTest:testEvent_createAuction() (gas: 93637) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 88758) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20601) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94150) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27157) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 103141) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 103130) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 163706) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 98337) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 156781) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 157174) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 98767) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 158127) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 157920) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 158150) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 91342) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 92372) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 292787) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 157340) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 378532) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 186195) +VariableSupplyAuctionTest:testEvent_createAuction() (gas: 93659) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 88780) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20645) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94172) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27485) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 103510) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 103477) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 164039) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 98692) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 157296) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 157755) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 99009) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 158842) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 158501) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 158798) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 91364) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 92394) VariableSupplyAuctionTest:test_DropInitial() (gas: 30806) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 289588) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 157533) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 176793) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 290576) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 157869) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 367871) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 182024) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 2fd246ec..34deff94 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -37,7 +37,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // mapping(address => mapping(address => Bid)) bidOf; } - /// + /// TODO struct Bid { bytes32 commitmentHash; uint96 revealedBidAmount; @@ -48,10 +48,10 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // mapping(address => Auction) public auctionForSeller; mapping(address => Auction) public auctionForDrop; - /// + /// TODO mapping(address => mapping(address => uint96)) public balanceOf; - /// + /// TODO mapping(address => mapping(address => Bid)) public bidOf; /*////////////////////////////////////////////////////////////// @@ -61,13 +61,13 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // TODO add UML diagram /// @notice Emitted when an auction is created - /// @param drop The ERC721Drop contract address of the created auction + /// @param tokenContract The address of the ERC-721 drop contract /// @param auction The metadata of the created auction - event AuctionCreated(address indexed drop, Auction auction); + event AuctionCreated(address indexed tokenContract, Auction auction); /// @notice Creates a variable supply auction - /// @dev Drop can only have 1 live auction at any one time - /// @param _tokenContract The address of the ERC721Drop contract + /// @dev A given ERC-721 drop contract can have only one live auction at any one time + /// @param _tokenContract The address of the ERC-721 drop contract /// @param _minimumRevenue The minimum revenue the seller aims to generate in this auction -- /// they can settle the auction below this value, but they cannot _not_ settle if the revenue /// generated by any price point + edition size combination would be at least this value @@ -113,12 +113,17 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // TODO add UML /// @notice Emitted when a bid is placed - /// @param tokenContract The ERC-721 drop token contract of the auction + /// @param tokenContract The address of the ERC-721 drop contract /// @param bidder The address that placed a sealed bid /// @param auction The metadata of the auction event BidPlaced(address indexed tokenContract, address indexed bidder, Auction auction); - /// TODO + /// @notice Places a bid in a variable supply auction + /// @dev Note that the included ether amount must be greater than or equal to the sealed bid + /// amount. This allows the bidder to obfuscate their true bid amount until the reveal phase. + /// @param _tokenContract The address of the ERC-721 drop contract + /// @param _commitmentHash The sha256 hash of the sealed bid amount concatenated with + /// a salt string, both of which need to be included in the subsequent reveal bid tx function placeBid(address _tokenContract, bytes32 _commitmentHash) public payable nonReentrant { // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; @@ -136,7 +141,6 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { require(msg.value > 0 ether, "VALID_BIDS_MUST_INCLUDE_ETHER"); // Store the full amount of incoming ether for this auction - // (may be less than actual bid amount once revealed) auction.totalBalance += uint96(msg.value); // Store the amount of incoming ether for this bidder @@ -157,10 +161,18 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // TODO add UML - /// - event BidRevealed(); + /// @notice Emitted when a bid is revealed + /// @param tokenContract The address of the ERC-721 drop contract + /// @param bidder The address that placed a sealed bid + /// @param bidAmount The revealed bid amount + /// @param auction The metadata of the auction + event BidRevealed(address indexed tokenContract, address indexed bidder, uint256 indexed bidAmount, Auction auction); - /// + /// @notice Reveals a previously placed bid + /// @param _tokenContract The address of the ERC-721 drop contract + /// @param _bidAmount The true bid amount + /// @param _salt The string which was used, in combination with the true bid amount, + /// to generate the commitment hash sent with the original placed bid tx function revealBid(address _tokenContract, uint256 _bidAmount, string calldata _salt) public nonReentrant { // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; @@ -180,6 +192,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // Ensure revealed bid matches sealed bid require(keccak256(abi.encodePacked(_bidAmount, bytes(_salt))) == bid.commitmentHash, "REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); + // Store the revealed bid amount bid.revealedBidAmount = uint96(_bidAmount); + + emit BidRevealed(_tokenContract, msg.sender, _bidAmount, auction); } } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index bfb34d95..644c422a 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -340,8 +340,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(commitmentStored, commitment); } - function test_PlaceBid_WhenMultiple() public setupBasicAuction { - // NOTE sealed bid amount can be less than sent ether amount (allows for hiding bid amount until reveal) + function test_PlaceBid_WhenMultiple() public setupBasicAuction { bytes32 commitment1 = genSealedBid(1 ether, "setec astronomy"); bytes32 commitment2 = genSealedBid(1 ether, "too many secrets"); bytes32 commitment3 = genSealedBid(1 ether, "cray tomes on set"); @@ -377,6 +376,26 @@ contract VariableSupplyAuctionTest is Test { assertEq(commitmentStored3, commitment3); } + function testEvent_PlaceBid_WhenSingle() public setupBasicAuction { + Auction memory auction = Auction({ + seller: address(seller), + minimumRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: uint96(1 ether) + }); + + vm.expectEmit(true, true, true, true); + emit BidPlaced(address(drop), address(bidder1), auction); + + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + } + function testEvent_PlaceBid_WhenMultiple() public setupBasicAuction { Auction memory auction = Auction({ seller: address(seller), @@ -503,19 +522,101 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidAmount, 1 ether); } - // TODO + function test_RevealBid_WhenMultiple() public setupBasicAuction { + bytes32 commitment1 = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment2 = genSealedBid(2 ether, "too many secrets"); + bytes32 commitment3 = genSealedBid(3 ether, "cray tomes on set"); - // function test_RevealBid_WhenMultiple() public setupBasicAuction { - - // } + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment1); + vm.prank(address(bidder2)); + auctions.placeBid{value: 3 ether}(address(drop), commitment2); + vm.prank(address(bidder3)); + auctions.placeBid{value: 5 ether}(address(drop), commitment3); - // function testEvent_PlaceBid_WhenSingle() public setupBasicAuction { - - // } + vm.warp(3 days + 1 seconds); - // function testEvent_PlaceBid_WhenMultiple() public setupBasicAuction { - - // } + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, "setec astronomy"); + vm.prank(address(bidder2)); + auctions.revealBid(address(drop), 2 ether, "too many secrets"); + vm.prank(address(bidder3)); + auctions.revealBid(address(drop), 3 ether, "cray tomes on set"); + + (, uint256 bidAmount1) = auctions.bidOf(address(drop), address(bidder1)); + (, uint256 bidAmount2) = auctions.bidOf(address(drop), address(bidder2)); + (, uint256 bidAmount3) = auctions.bidOf(address(drop), address(bidder3)); + + assertEq(bidAmount1, 1 ether); + assertEq(bidAmount2, 2 ether); + assertEq(bidAmount3, 3 ether); + } + + function testEvent_RevealBid_WhenSingle() public setupBasicAuction { + Auction memory auction = Auction({ + seller: address(seller), + minimumRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: uint96(1 ether) + }); + + bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + + vm.warp(3 days + 1 seconds); + + vm.expectEmit(true, true, true, true); + emit BidRevealed(address(drop), address(bidder1), 1 ether, auction); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, "setec astronomy"); + } + + function testEvent_RevealBid_WhenMultiple() public setupBasicAuction { + Auction memory auction = Auction({ + seller: address(seller), + minimumRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: uint96(9 ether) // based on total sent ether amount + }); + + bytes32 commitment1 = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment2 = genSealedBid(2 ether, "too many secrets"); + bytes32 commitment3 = genSealedBid(3 ether, "cray tomes on set"); + + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment1); + vm.prank(address(bidder2)); + auctions.placeBid{value: 3 ether}(address(drop), commitment2); + vm.prank(address(bidder3)); + auctions.placeBid{value: 5 ether}(address(drop), commitment3); + + vm.warp(3 days + 1 seconds); + + // We can assert all events at once, bc stored auction does not change + vm.expectEmit(true, true, true, true); + emit BidRevealed(address(drop), address(bidder1), 1 ether, auction); + vm.expectEmit(true, true, true, true); + emit BidRevealed(address(drop), address(bidder2), 2 ether, auction); + vm.expectEmit(true, true, true, true); + emit BidRevealed(address(drop), address(bidder3), 3 ether, auction); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, "setec astronomy"); + vm.prank(address(bidder2)); + auctions.revealBid(address(drop), 2 ether, "too many secrets"); + vm.prank(address(bidder3)); + auctions.revealBid(address(drop), 3 ether, "cray tomes on set"); + } function testRevert_RevealBid_WhenAuctionInBidPhase() public setupBasicAuction { bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); @@ -560,6 +661,8 @@ contract VariableSupplyAuctionTest is Test { auctions.revealBid(address (drop), 1.1 ether, "setec astronomy"); } + // TODO should we then allow "topping up" the bidder's balance to support their bid? + // likely, no — introduces bad incentives function testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() public setupBasicAuction { bytes32 commitment = genSealedBid(1.1 ether, "setec astronomy"); vm.prank(address(bidder1)); @@ -664,4 +767,5 @@ contract VariableSupplyAuctionTest is Test { event AuctionCreated(address indexed drop, Auction auction); event BidPlaced(address indexed tokenContract, address indexed bidder, Auction auction); + event BidRevealed(address indexed tokenContract, address indexed bidder, uint256 indexed bidAmount, Auction auction); } From cbfeeba07bfa270daf5ca534cb5d6ee1fe6619e8 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sun, 16 Oct 2022 19:16:01 -0400 Subject: [PATCH 10/31] [chore] VSCode tings --- .gitignore | 3 ++- remappings.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 42a1e047..e55cb2e1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ dist .DS_Store coverage coverage.json -.idea \ No newline at end of file +.idea +.vscode \ No newline at end of file diff --git a/remappings.txt b/remappings.txt index b4ca50f9..2a0e77ab 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,4 +1,5 @@ ds-test/=lib/ds-test/src/ +forge-std/=lib/forge-std/src/ @openzeppelin/=node_modules/@openzeppelin/ @rari-capital/=node_modules/@rari-capital/ @manifoldxyz/=node_modules/@manifoldxyz/ \ No newline at end of file From 67fcc79e9fb2913cd3f199cd13f5e6d52606ddb5 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sun, 16 Oct 2022 20:43:01 -0400 Subject: [PATCH 11/31] [draft] Start work on settleAuction --- .../VariableSupplyAuction.feature | 168 ++++++------- .../VariableSupplyAuction.t.sol | 235 ++++++++++++++---- 2 files changed, 276 insertions(+), 127 deletions(-) diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature index cec660c1..4f97d2ff 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature @@ -13,61 +13,62 @@ Feature: Variable Supply Auctions Background: VSA creation and bidding Given Seller creates a Variable Supply Auction - And Seller and all Bidder account balances are 10 ETH + And Seller and all Bidder account balances are 100 ETH And The following sealed bids are placed | account | bid amount | sent value | - | Bidder1 | 0.01 ETH | 0.01 ETH | - | Bidder2 | 0.01 ETH | 0.09 ETH | - | Bidder3 | 0.01 ETH | 0.08 ETH | - | Bidder4 | 0.01 ETH | 0.07 ETH | - | Bidder5 | 0.01 ETH | 0.06 ETH | - | Bidder6 | 0.01 ETH | 0.05 ETH | - | Bidder7 | 0.01 ETH | 0.04 ETH | - | Bidder8 | 0.01 ETH | 0.03 ETH | - | Bidder9 | 0.01 ETH | 0.02 ETH | - | Bidder10 | 0.01 ETH | 1.00 ETH | - | Bidder11 | 0.06 ETH | 0.06 ETH | - | Bidder12 | 0.06 ETH | 0.12 ETH | - | Bidder13 | 0.11 ETH | 0.12 ETH | + | Bidder1 | 1 ETH | 1 ETH | + | Bidder2 | 1 ETH | 9 ETH | + | Bidder3 | 1 ETH | 8 ETH | + | Bidder4 | 1 ETH | 7 ETH | + | Bidder5 | 1 ETH | 6 ETH | + | Bidder6 | 1 ETH | 5 ETH | + | Bidder7 | 1 ETH | 4 ETH | + | Bidder8 | 1 ETH | 3 ETH | + | Bidder9 | 1 ETH | 2 ETH | + | Bidder10 | 1 ETH | 10 ETH | + | Bidder11 | 6 ETH | 6 ETH | + | Bidder12 | 6 ETH | 9 ETH | + | Bidder13 | 11 ETH | 12 ETH | + # TODO tbd if calculate at reveal time, or save for settle time Scenario: Bidders reveal bids When All bids are revealed - Then The account balances should be - | account | balance | - | Seller | 10 ETH | - | Bidder1 | 9.99 ETH | - | Bidder2 | 9.99 ETH | - | Bidder3 | 9.99 ETH | - | Bidder4 | 9.99 ETH | - | Bidder5 | 9.99 ETH | - | Bidder6 | 9.99 ETH | - | Bidder7 | 9.99 ETH | - | Bidder8 | 9.99 ETH | - | Bidder9 | 9.99 ETH | - | Bidder10 | 9.94 ETH | - | Bidder12 | 9.94 ETH | - | Bidder13 | 9.89 ETH | + Then The revealed bids and reimbursements available should be + | account | bid amount | reimbursement available | + | Bidder1 | 1 ETH | 0 ETH | + | Bidder2 | 1 ETH | 8 ETH | + | Bidder3 | 1 ETH | 7 ETH | + | Bidder4 | 1 ETH | 6 ETH | + | Bidder5 | 1 ETH | 5 ETH | + | Bidder6 | 1 ETH | 4 ETH | + | Bidder7 | 1 ETH | 3 ETH | + | Bidder8 | 1 ETH | 2 ETH | + | Bidder9 | 1 ETH | 1 ETH | + | Bidder10 | 1 ETH | 9 ETH | + | Bidder11 | 6 ETH | 0 ETH | + | Bidder12 | 6 ETH | 3 ETH | + | Bidder13 | 11 ETH | 1 ETH | - Scenario: Seller settles VSA at 0.01 ETH + Scenario: Seller settles VSA at 1 ETH When All bids are revealed - And Seller settles auction at 0.01 ETH + And Seller settles auction at 1 ETH Then The NFT contract should be an edition of 13 And The account balances should be - | account | balance | - | Seller | 10.13 ETH | - | Bidder1 | 9.99 ETH | - | Bidder2 | 9.99 ETH | - | Bidder3 | 9.99 ETH | - | Bidder4 | 9.99 ETH | - | Bidder5 | 9.99 ETH | - | Bidder6 | 9.99 ETH | - | Bidder7 | 9.99 ETH | - | Bidder8 | 9.99 ETH | - | Bidder9 | 9.99 ETH | - | Bidder10 | 9.99 ETH | - | Bidder11 | 9.99 ETH | - | Bidder12 | 9.99 ETH | - | Bidder13 | 9.99 ETH | + | account | balance | + | Seller | 113 ETH | + | Bidder1 | 99 ETH | + | Bidder2 | 99 ETH | + | Bidder3 | 99 ETH | + | Bidder4 | 99 ETH | + | Bidder5 | 99 ETH | + | Bidder6 | 99 ETH | + | Bidder7 | 99 ETH | + | Bidder8 | 99 ETH | + | Bidder9 | 99 ETH | + | Bidder10 | 99 ETH | + | Bidder11 | 99 ETH | + | Bidder12 | 99 ETH | + | Bidder13 | 99 ETH | And The following accounts should own 1 NFT | account | | Bidder1 | @@ -84,52 +85,52 @@ Feature: Variable Supply Auctions | Bidder12 | | Bidder13 | - Scenario: Seller settles VSA at 0.06 ETH + Scenario: Seller settles VSA at 6 ETH When All bids are revealed - And Seller settles auction at 0.06 ETH + And Seller settles auction at 6 ETH Then The NFT contract should be an edition of 3 And The account balances should be - | account | balance | - | Seller | 10.18 ETH | - | Bidder1 | 10 ETH | - | Bidder2 | 10 ETH | - | Bidder3 | 10 ETH | - | Bidder4 | 10 ETH | - | Bidder5 | 10 ETH | - | Bidder6 | 10 ETH | - | Bidder7 | 10 ETH | - | Bidder8 | 10 ETH | - | Bidder9 | 10 ETH | - | Bidder10 | 10 ETH | - | Bidder11 | 9.94 ETH | - | Bidder12 | 9.94 ETH | - | Bidder13 | 9.94 ETH | - And The following accounts should own an NFT + | account | balance | + | Seller | 118 ETH | + | Bidder1 | 100 ETH | + | Bidder2 | 100 ETH | + | Bidder3 | 1000 ETH | + | Bidder4 | 100 ETH | + | Bidder5 | 100 ETH | + | Bidder6 | 100 ETH | + | Bidder7 | 100 ETH | + | Bidder8 | 100 ETH | + | Bidder9 | 100 ETH | + | Bidder10 | 100 ETH | + | Bidder11 | 94 ETH | + | Bidder12 | 94 ETH | + | Bidder13 | 94 ETH | + And The following accounts should own 1 NFT | Bidder11 | | Bidder12 | | Bidder13 | - Scenario: Seller settles VSA at 0.11 ETH + Scenario: Seller settles VSA at 11 ETH When All bids are revealed - And Seller settles auction at 0.11 ETH + And Seller settles auction at 11 ETH Then The NFT contract should be a 1 of 1 And The account balances should be - | account | balance | - | Seller | 10.11 ETH | - | Bidder1 | 10 ETH | - | Bidder2 | 10 ETH | - | Bidder3 | 10 ETH | - | Bidder4 | 10 ETH | - | Bidder5 | 10 ETH | - | Bidder6 | 10 ETH | - | Bidder7 | 10 ETH | - | Bidder8 | 10 ETH | - | Bidder9 | 10 ETH | - | Bidder10 | 10 ETH | - | Bidder11 | 10 ETH | - | Bidder12 | 10 ETH | - | Bidder13 | 9.89 ETH | - And The following accounts should own an NFT + | account | balance | + | Seller | 111 ETH | + | Bidder1 | 100 ETH | + | Bidder2 | 100 ETH | + | Bidder3 | 100 ETH | + | Bidder4 | 100 ETH | + | Bidder5 | 100 ETH | + | Bidder6 | 100 ETH | + | Bidder7 | 100 ETH | + | Bidder8 | 100 ETH | + | Bidder9 | 100 ETH | + | Bidder10 | 100 ETH | + | Bidder11 | 100 ETH | + | Bidder12 | 100 ETH | + | Bidder13 | 89 ETH | + And The following accounts should own 1 NFT | Bidder13 | # TODO handle bid space bounding @@ -137,4 +138,5 @@ Feature: Variable Supply Auctions ## Bidder sets maximum edition size interest ## Seller sets minimum viable revenue # TODO address cancel auction sad path -# TODO address failure-to-reveal sad paths +# TODO address failure to reveal sad paths +# TODO address failure to settle sad paths diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 644c422a..9b0aac60 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -54,6 +54,22 @@ contract VariableSupplyAuctionTest is Test { Zorb internal bidder14; Zorb internal bidder15; + string internal constant salt1 = "setec astronomy"; + string internal constant salt2 = "too many secrets"; + string internal constant salt3 = "cray tomes on set"; + string internal constant salt4 = "o no my tesseract"; + string internal constant salt5 = "ye some contrast"; + string internal constant salt6 = "a tron ecosystem"; + string internal constant salt7 = "stonecasty rome"; + string internal constant salt8 = "coy teamster son"; + string internal constant salt9 = "cyanometer toss"; + string internal constant salt10 = "cementatory sos"; + string internal constant salt11 = "my cotoneasters"; + string internal constant salt12 = "ny sec stateroom"; + string internal constant salt13 = "oc attorney mess"; + string internal constant salt14 = "my cots earstones"; + string internal constant salt15 = "easternmost coy"; + function setUp() public { // Deploy V3 registrar = new ZoraRegistrar(); @@ -317,7 +333,7 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ function test_PlaceBid_WhenSingle() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -341,9 +357,9 @@ contract VariableSupplyAuctionTest is Test { } function test_PlaceBid_WhenMultiple() public setupBasicAuction { - bytes32 commitment1 = genSealedBid(1 ether, "setec astronomy"); - bytes32 commitment2 = genSealedBid(1 ether, "too many secrets"); - bytes32 commitment3 = genSealedBid(1 ether, "cray tomes on set"); + bytes32 commitment1 = genSealedBid(1 ether, salt1); + bytes32 commitment2 = genSealedBid(1 ether, salt2); + bytes32 commitment3 = genSealedBid(1 ether, salt3); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); @@ -391,7 +407,7 @@ contract VariableSupplyAuctionTest is Test { vm.expectEmit(true, true, true, true); emit BidPlaced(address(drop), address(bidder1), auction); - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); } @@ -408,9 +424,9 @@ contract VariableSupplyAuctionTest is Test { totalBalance: uint96(1 ether) // expect this totalBalance in event based on first bid }); - bytes32 commitment1 = genSealedBid(1 ether, "setec astronomy"); - bytes32 commitment2 = genSealedBid(1 ether, "too many secrets"); - bytes32 commitment3 = genSealedBid(1 ether, "cray tomes on set"); + bytes32 commitment1 = genSealedBid(1 ether, salt1); + bytes32 commitment2 = genSealedBid(1 ether, salt2); + bytes32 commitment3 = genSealedBid(1 ether, salt3); vm.expectEmit(true, true, true, true); emit BidPlaced(address(drop), address(bidder1), auction); @@ -438,7 +454,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_PlaceBid_WhenAuctionDoesNotExist() public { vm.expectRevert("AUCTION_DOES_NOT_EXIST"); - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); } @@ -448,7 +464,7 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); } @@ -458,13 +474,13 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); } function testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -477,7 +493,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_PlaceBid_WhenNoEtherIncluded() public setupBasicAuction { vm.expectRevert("VALID_BIDS_MUST_INCLUDE_ETHER"); - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid(address(drop), commitment); } @@ -498,7 +514,7 @@ contract VariableSupplyAuctionTest is Test { // vm.expectRevert("module has not been approved by user"); - // bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + // bytes32 commitment = genSealedBid(1 ether, salt1); // vm.prank(address(bidder1)); // auctions.placeBid{value: 1 ether}(address(drop), commitment); // } @@ -508,14 +524,14 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ function test_RevealBid_WhenSingle() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); vm.warp(3 days + 1 seconds); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1 ether, "setec astronomy"); + auctions.revealBid(address (drop), 1 ether, salt1); (, uint256 bidAmount) = auctions.bidOf(address(drop), address(bidder1)); @@ -523,9 +539,9 @@ contract VariableSupplyAuctionTest is Test { } function test_RevealBid_WhenMultiple() public setupBasicAuction { - bytes32 commitment1 = genSealedBid(1 ether, "setec astronomy"); - bytes32 commitment2 = genSealedBid(2 ether, "too many secrets"); - bytes32 commitment3 = genSealedBid(3 ether, "cray tomes on set"); + bytes32 commitment1 = genSealedBid(1 ether, salt1); + bytes32 commitment2 = genSealedBid(2 ether, salt2); + bytes32 commitment3 = genSealedBid(3 ether, salt3); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); @@ -537,11 +553,11 @@ contract VariableSupplyAuctionTest is Test { vm.warp(3 days + 1 seconds); vm.prank(address(bidder1)); - auctions.revealBid(address(drop), 1 ether, "setec astronomy"); + auctions.revealBid(address(drop), 1 ether, salt1); vm.prank(address(bidder2)); - auctions.revealBid(address(drop), 2 ether, "too many secrets"); + auctions.revealBid(address(drop), 2 ether, salt2); vm.prank(address(bidder3)); - auctions.revealBid(address(drop), 3 ether, "cray tomes on set"); + auctions.revealBid(address(drop), 3 ether, salt3); (, uint256 bidAmount1) = auctions.bidOf(address(drop), address(bidder1)); (, uint256 bidAmount2) = auctions.bidOf(address(drop), address(bidder2)); @@ -564,7 +580,7 @@ contract VariableSupplyAuctionTest is Test { totalBalance: uint96(1 ether) }); - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -574,7 +590,7 @@ contract VariableSupplyAuctionTest is Test { emit BidRevealed(address(drop), address(bidder1), 1 ether, auction); vm.prank(address(bidder1)); - auctions.revealBid(address(drop), 1 ether, "setec astronomy"); + auctions.revealBid(address(drop), 1 ether, salt1); } function testEvent_RevealBid_WhenMultiple() public setupBasicAuction { @@ -589,9 +605,9 @@ contract VariableSupplyAuctionTest is Test { totalBalance: uint96(9 ether) // based on total sent ether amount }); - bytes32 commitment1 = genSealedBid(1 ether, "setec astronomy"); - bytes32 commitment2 = genSealedBid(2 ether, "too many secrets"); - bytes32 commitment3 = genSealedBid(3 ether, "cray tomes on set"); + bytes32 commitment1 = genSealedBid(1 ether, salt1); + bytes32 commitment2 = genSealedBid(2 ether, salt2); + bytes32 commitment3 = genSealedBid(3 ether, salt3); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); @@ -611,26 +627,26 @@ contract VariableSupplyAuctionTest is Test { emit BidRevealed(address(drop), address(bidder3), 3 ether, auction); vm.prank(address(bidder1)); - auctions.revealBid(address(drop), 1 ether, "setec astronomy"); + auctions.revealBid(address(drop), 1 ether, salt1); vm.prank(address(bidder2)); - auctions.revealBid(address(drop), 2 ether, "too many secrets"); + auctions.revealBid(address(drop), 2 ether, salt2); vm.prank(address(bidder3)); - auctions.revealBid(address(drop), 3 ether, "cray tomes on set"); + auctions.revealBid(address(drop), 3 ether, salt3); } function testRevert_RevealBid_WhenAuctionInBidPhase() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1 ether, "setec astronomy"); + auctions.revealBid(address (drop), 1 ether, salt1); } function testRevert_RevealBid_WhenAuctionInSettlePhase() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); @@ -639,7 +655,7 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1 ether, "setec astronomy"); + auctions.revealBid(address (drop), 1 ether, salt1); } // TODO once settleAuction is written @@ -658,13 +674,13 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("NO_PLACED_BID_FOUND_FOR_ADDRESS"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1.1 ether, "setec astronomy"); + auctions.revealBid(address (drop), 1.1 ether, salt1); } // TODO should we then allow "topping up" the bidder's balance to support their bid? // likely, no — introduces bad incentives function testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() public setupBasicAuction { - bytes32 commitment = genSealedBid(1.1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1.1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -673,11 +689,11 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1.1 ether, "setec astronomy"); + auctions.revealBid(address (drop), 1.1 ether, salt1); } function testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -686,11 +702,11 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 0.9 ether, "setec astronomy"); // wrong amount + auctions.revealBid(address (drop), 0.9 ether, salt1); // wrong amount } function testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, "setec astronomy"); + bytes32 commitment = genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -699,26 +715,157 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); vm.prank(address(bidder1)); - auctions.revealBid(address (drop), 1 ether, "too many secrets"); // wrong salt + auctions.revealBid(address (drop), 1 ether, salt2); // wrong salt } /*////////////////////////////////////////////////////////////// FAILURE TO REVEAL BID //////////////////////////////////////////////////////////////*/ - // TODO bidder failure to reveal bid sad paths + // TODO bidder failure to reveal sad paths /*////////////////////////////////////////////////////////////// SETTLE AUCTION //////////////////////////////////////////////////////////////*/ - // TODO settleAuction + /* + + Scenario for the following 3 settleAuction unit tests + + Given The following sealed bids are placed + | account | bid amount | sent value | + | Bidder1 | 1 ETH | 1 ETH | + | Bidder2 | 1 ETH | 9 ETH | + | Bidder3 | 1 ETH | 8 ETH | + | Bidder4 | 1 ETH | 7 ETH | + | Bidder5 | 1 ETH | 6 ETH | + | Bidder6 | 1 ETH | 5 ETH | + | Bidder7 | 1 ETH | 4 ETH | + | Bidder8 | 1 ETH | 3 ETH | + | Bidder9 | 1 ETH | 2 ETH | + | Bidder10 | 1 ETH | 10 ETH | + | Bidder11 | 6 ETH | 6 ETH | + | Bidder12 | 6 ETH | 9 ETH | + | Bidder13 | 11 ETH | 12 ETH | + When The seller settles the auction + Then The seller can choose one of the following edition sizes and revenue amounts + | edition size | revenue generated | + | 13 | 13 ether | + | 3 | 18 ether | + | 1 | 11 ether | + + */ + + function test_SettleAuction_WhenSettlingAtHighPriceLowSupply() public setupBasicAuction { + // 10 bids at 1 ether + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), genSealedBid(1 ether, salt1)); + vm.prank(address(bidder2)); + auctions.placeBid{value: 9 ether}(address(drop), genSealedBid(1 ether, salt2)); + vm.prank(address(bidder3)); + auctions.placeBid{value: 8 ether}(address(drop), genSealedBid(1 ether, salt3)); + vm.prank(address(bidder4)); + auctions.placeBid{value: 7 ether}(address(drop), genSealedBid(1 ether, salt4)); + vm.prank(address(bidder5)); + auctions.placeBid{value: 6 ether}(address(drop), genSealedBid(1 ether, salt5)); + vm.prank(address(bidder6)); + auctions.placeBid{value: 5 ether}(address(drop), genSealedBid(1 ether, salt6)); + vm.prank(address(bidder7)); + auctions.placeBid{value: 4 ether}(address(drop), genSealedBid(1 ether, salt7)); + vm.prank(address(bidder8)); + auctions.placeBid{value: 3 ether}(address(drop), genSealedBid(1 ether, salt8)); + vm.prank(address(bidder9)); + auctions.placeBid{value: 2 ether}(address(drop), genSealedBid(1 ether, salt9)); + vm.prank(address(bidder10)); + auctions.placeBid{value: 10 ether}(address(drop), genSealedBid(1 ether, salt10)); + + // 2 bids at 6 ether + vm.prank(address(bidder11)); + auctions.placeBid{value: 6 ether}(address(drop), genSealedBid(6 ether, salt11)); + vm.prank(address(bidder12)); + auctions.placeBid{value: 9 ether}(address(drop), genSealedBid(6 ether, salt12)); + + // 10 bids at 1 ether + vm.prank(address(bidder13)); + auctions.placeBid{value: 12 ether}(address(drop), genSealedBid(11 ether, salt13)); + + vm.warp(3 days + 1 seconds); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + vm.prank(address(bidder2)); + auctions.revealBid(address(drop), 1 ether, salt2); + vm.prank(address(bidder3)); + auctions.revealBid(address(drop), 1 ether, salt3); + vm.prank(address(bidder4)); + auctions.revealBid(address(drop), 1 ether, salt4); + vm.prank(address(bidder5)); + auctions.revealBid(address(drop), 1 ether, salt5); + vm.prank(address(bidder6)); + auctions.revealBid(address(drop), 1 ether, salt6); + vm.prank(address(bidder7)); + auctions.revealBid(address(drop), 1 ether, salt7); + vm.prank(address(bidder8)); + auctions.revealBid(address(drop), 1 ether, salt8); + vm.prank(address(bidder9)); + auctions.revealBid(address(drop), 1 ether, salt9); + vm.prank(address(bidder10)); + auctions.revealBid(address(drop), 1 ether, salt10); + vm.prank(address(bidder11)); + auctions.revealBid(address(drop), 6 ether, salt11); + vm.prank(address(bidder12)); + auctions.revealBid(address(drop), 6 ether, salt12); + vm.prank(address(bidder13)); + auctions.revealBid(address(drop), 11 ether, salt13); + + vm.warp(3 days + 2 days + 1 seconds); + + assertTrue(false); // TODO + + // precondition checks + // all bidders have balance of 0 NFTs + // seller has 100 ETH + // bidder balances are xyz + + // when – seller settles auction at price point of 1 ether + + // assertions + // all bidders have balance of 1 NFT + // seller has 113 ETH + // all bidders have 99 ETH + } + + function test_SettleAuction_WhenSettlingAtMidPriceMidSupply() public setupBasicAuction { + // ... + + // when – seller settles auction at price point of 6 ether + + // assertions + // bidders 11–13 have balance of 1 NFT + // seller has 118 ETH + // bidders 1–10 have 100 ETH, bidders 11–13 have 94 ETH + + assertTrue(false); // TODO + } + + function test_SettleAuction_WhenSettlingAtLowPriceHighSupply() public setupBasicAuction { + // ... + + // when – seller settles auction at price point of 11 ether + + // assertions + // bidder 13 has balance of 1 NFT + // seller has 111 ETH + // bidders 1–12 have 100 ETH, bidder 13 has 89 ETH + + assertTrue(false); // TODO + } /*////////////////////////////////////////////////////////////// FAILURE TO SETTLE AUCTION //////////////////////////////////////////////////////////////*/ - // TODO seller failure to settle auction sad paths + // TODO seller failure to settle sad paths /*////////////////////////////////////////////////////////////// TEST HELPERS From e5f3cca029276762a513963ff96fe4e7d62e6459 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Wed, 19 Oct 2022 01:38:34 -0400 Subject: [PATCH 12/31] [draft] :sparkles: Working v0.1 of settleAuction; Add invariant test scaffolding --- .gas-snapshot | 56 +- .../VariableSupplyAuction.sol | 178 +++++- ...ERC721Drop.sol => temp-MockERC721Drop.sol} | 57 +- .../VariableSupplyAuction.integration.t.sol | 2 +- .../VariableSupplyAuction.invariant | 6 + .../VariableSupplyAuction.invariant.t.sol | 245 +++++++ .../VariableSupplyAuction.t.sol | 603 ++++++++++++------ contracts/test/utils/InvariantTest.sol | 66 ++ foundry.toml | 6 + 9 files changed, 962 insertions(+), 257 deletions(-) rename contracts/modules/VariableSupplyAuction/{temp-ERC721Drop.sol => temp-MockERC721Drop.sol} (56%) create mode 100644 contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant create mode 100644 contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol create mode 100644 contracts/test/utils/InvariantTest.sol diff --git a/.gas-snapshot b/.gas-snapshot index 400ea540..68cd8da4 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,32 +319,36 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 292787) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 157340) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 378532) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 186195) -VariableSupplyAuctionTest:testEvent_createAuction() (gas: 93659) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 88780) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20645) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94172) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27485) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 103510) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 103477) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 164039) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 98692) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 157296) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 157755) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 99009) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 158842) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 158501) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 158798) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 91364) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 92394) -VariableSupplyAuctionTest:test_DropInitial() (gas: 30806) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 290576) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 157869) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 367871) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 182024) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 288707) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 156443) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 406191) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 210496) +VariableSupplyAuctionTest:testEvent_createAuction() (gas: 95077) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 89526) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20583) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94900) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27567) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 104316) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 104261) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 162595) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 99504) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 156123) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 156627) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 99935) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 157359) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 156975) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 157315) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 92417) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 93461) +VariableSupplyAuctionTest:test_DropInitial() (gas: 30925) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 278291) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 152581) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 394186) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 205821) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1382557) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2299792) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2555187) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2342377) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 34deff94..4495c608 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -1,58 +1,93 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; -import {IVariableSupplyAuction} from "./IVariableSupplyAuction.sol"; -import {ERC721Drop} from "./temp-ERC721Drop.sol"; - import {ReentrancyGuard} from "@rari-capital/solmate/src/utils/ReentrancyGuard.sol"; +import {ERC721Drop} from "./temp-MockERC721Drop.sol"; + +import {ERC721TransferHelper} from "../../transferHelpers/ERC721TransferHelper.sol"; +import {FeePayoutSupportV1} from "../../common/FeePayoutSupport/FeePayoutSupportV1.sol"; +import {ModuleNamingSupportV1} from "../../common/ModuleNamingSupport/ModuleNamingSupportV1.sol"; + +import {IVariableSupplyAuction} from "./IVariableSupplyAuction.sol"; /// @title Variable Supply Auction /// @author neodaoist /// @notice Module for variable supply, seller's choice, sealed bid auctions in ETH for ERC-721 tokens -contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { +contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePayoutSupportV1, ModuleNamingSupportV1 { // + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor( + address _erc721TransferHelper, + address _royaltyEngine, + address _protocolFeeSettings, + address _weth + ) + FeePayoutSupportV1(_royaltyEngine, _protocolFeeSettings, _weth, ERC721TransferHelper(_erc721TransferHelper).ZMM().registrar()) + ModuleNamingSupportV1("Variable Supply Auction") + { + // erc721TransferHelper = ERC721TransferHelper(_erc721TransferHelper); + } + /*////////////////////////////////////////////////////////////// AUCTION STORAGE //////////////////////////////////////////////////////////////*/ /// @notice The metadata for a given auction /// @param seller The seller of this auction - /// @param minimumRevenue The minimum revenue the seller needs to generate in this auction + /// @param minimumViableRevenue The minimum revenue the seller needs to generate in this auction /// @param sellerFundsRecipient The address where funds are sent after the auction /// @param startTime The unix timestamp after which the first bid can be placed /// @param endOfBidPhase The unix timestamp until which bids can be placed /// @param endOfRevealPhase The unix timestamp until which placed bids can be revealed /// @param endOfSettlePhase The unix timestamp until which the seller can settle the auction (TODO clarify can vs. must) + /// @param settledRevenue The total revenue generated by the drop + /// @param settledPricePoint The chosen price point for the drop + /// @param settledEditionSize The resulting edition size for the drop struct Auction { address seller; - uint96 minimumRevenue; + uint96 minimumViableRevenue; address sellerFundsRecipient; uint32 startTime; uint32 endOfBidPhase; uint32 endOfRevealPhase; uint32 endOfSettlePhase; - uint96 totalBalance; - // mapping(address => mapping(address => uint96)) balanceOf; - // mapping(address => mapping(address => Bid)) bidOf; + uint96 settledRevenue; + uint96 settledPricePoint; + uint16 settledEditionSize; + // TODO do we still need totalBalance ? } - /// TODO + /// @notice A sealed bid + /// @param commitmentHash The sha256 hash of the sealed bid amount concatenated with + /// a salt string, both of which need to be included in the subsequent reveal bid tx + /// @param bidderBalance The current bidder balance -- before auction has been settled, + /// this is the total amount of ether included with their bid; after auction has been + /// settled, this is the amount of ether available for bidder to claim. More specifically, + /// if bidder was a winner, this is the included amount of ether less the settled + /// price point; if bidder was not a winner, this is total amount of ether originally included. + /// @param revealedBidAmount The revealed bid amount struct Bid { bytes32 commitmentHash; + uint96 bidderBalance; uint96 revealedBidAmount; } - /// @notice The auction for a given seller, if one exists - /// @dev Only one auction per seller is allowed at once - // mapping(address => Auction) public auctionForSeller; + /// @notice The auction for a given ERC-721 drop contract, if one exists + /// (only one auction per token contract is allowed at one time) + /// @dev ERC-721 token contract => Auction mapping(address => Auction) public auctionForDrop; - /// TODO - mapping(address => mapping(address => uint96)) public balanceOf; + /// @notice The bids which have been placed in a given Auction + /// @dev ERC-721 token contract => (bidder address => Bid) + mapping(address => mapping(address => Bid)) public bidsForDrop; - /// TODO - mapping(address => mapping(address => Bid)) public bidOf; + /// @notice The addresses who have placed and revealed a bid in a given auction + /// @dev ERC-721 token contract => all bidders who have revealed their bid + mapping(address => address[]) public revealedBiddersForDrop; /*////////////////////////////////////////////////////////////// CREATE AUCTION @@ -68,7 +103,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { /// @notice Creates a variable supply auction /// @dev A given ERC-721 drop contract can have only one live auction at any one time /// @param _tokenContract The address of the ERC-721 drop contract - /// @param _minimumRevenue The minimum revenue the seller aims to generate in this auction -- + /// @param _minimumViableRevenue The minimum revenue the seller aims to generate in this auction -- /// they can settle the auction below this value, but they cannot _not_ settle if the revenue /// generated by any price point + edition size combination would be at least this value /// @param _sellerFundsRecipient The address to send funds to once the auction is complete @@ -78,7 +113,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { /// @param _settlePhaseDuration The length of time of the settle phase in seconds function createAuction( address _tokenContract, - uint256 _minimumRevenue, + uint256 _minimumViableRevenue, address _sellerFundsRecipient, uint256 _startTime, uint256 _bidPhaseDuration, @@ -96,7 +131,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { // Store the associated metadata auction.seller = msg.sender; - auction.minimumRevenue = uint96(_minimumRevenue); + auction.minimumViableRevenue = uint96(_minimumViableRevenue); auction.sellerFundsRecipient = _sellerFundsRecipient; auction.startTime = uint32(_startTime); auction.endOfBidPhase = uint32(_startTime + _bidPhaseDuration); @@ -106,6 +141,12 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { emit AuctionCreated(_tokenContract, auction); } + /*////////////////////////////////////////////////////////////// + CANCEL AUCTION + //////////////////////////////////////////////////////////////*/ + + // TODO + /*////////////////////////////////////////////////////////////// PLACE BID //////////////////////////////////////////////////////////////*/ @@ -135,20 +176,15 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { require(block.timestamp < auction.endOfBidPhase, "BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); // Ensure the bidder has not placed a bid in auction already - require(balanceOf[_tokenContract][msg.sender] == 0, "ALREADY_PLACED_BID_IN_AUCTION"); + require(bidsForDrop[_tokenContract][msg.sender].bidderBalance == 0, "ALREADY_PLACED_BID_IN_AUCTION"); // Ensure the bid is valid and includes some ether require(msg.value > 0 ether, "VALID_BIDS_MUST_INCLUDE_ETHER"); - - // Store the full amount of incoming ether for this auction - auction.totalBalance += uint96(msg.value); - // Store the amount of incoming ether for this bidder - balanceOf[_tokenContract][msg.sender] = uint96(msg.value); - - // Store the committed / unrevealed bid for this bidder - bidOf[_tokenContract][msg.sender] = Bid({ + // Store the commitment hash and included ether amount + bidsForDrop[_tokenContract][msg.sender] = Bid({ commitmentHash: _commitmentHash, + bidderBalance: uint96(msg.value), revealedBidAmount: 0 }); @@ -181,20 +217,96 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard { require(block.timestamp >= auction.endOfBidPhase && block.timestamp < auction.endOfRevealPhase, "REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); // Get the bid for the specified bidder - Bid storage bid = bidOf[_tokenContract][msg.sender]; + Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; // Ensure bidder placed bid in auction - require(balanceOf[_tokenContract][msg.sender] > 0 ether, "NO_PLACED_BID_FOUND_FOR_ADDRESS"); + require(bid.bidderBalance > 0 ether, "NO_PLACED_BID_FOUND_FOR_ADDRESS"); // Ensure revealed bid amount is not greater than sent ether - require(_bidAmount <= balanceOf[_tokenContract][msg.sender], "REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); + require(_bidAmount <= bid.bidderBalance, "REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); // Ensure revealed bid matches sealed bid require(keccak256(abi.encodePacked(_bidAmount, bytes(_salt))) == bid.commitmentHash, "REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); + // Store the bidder + revealedBiddersForDrop[_tokenContract].push(msg.sender); + // Store the revealed bid amount - bid.revealedBidAmount = uint96(_bidAmount); + uint96 bidAmount = uint96(_bidAmount); + bid.revealedBidAmount = bidAmount; emit BidRevealed(_tokenContract, msg.sender, _bidAmount, auction); } + + /*////////////////////////////////////////////////////////////// + SETTLE AUCTION + //////////////////////////////////////////////////////////////*/ + + // TODO add UML + + // TODO add view function for price point + edition size options based on revealed bids + + /// @notice Emitted when an auction is settled + /// @param tokenContract The address of the ERC-721 drop contract + /// @param auction The metadata of the created auction + event AuctionSettled(address indexed tokenContract, Auction auction); + + function settleAuction(address _tokenContract, uint96 _settlePricePoint) public nonReentrant { + // TODO checks + + // TODO gas optimizations + + // Get the auction + Auction storage auction = auctionForDrop[_tokenContract]; + + // Get the bidders who revealed in this auction + address[] storage bidders = revealedBiddersForDrop[_tokenContract]; + + // Get the balances for this auction + mapping(address => Bid) storage bids = bidsForDrop[_tokenContract]; + + // Loop through bids to determine winners and edition size + // TODO document pragmatic max edition size / winning bidders + address[] memory winningBidders = new address[](1000); + uint16 editionSize; + for (uint256 i = 0; i < bidders.length; i++) { + // Cache the bidder + address bidder = bidders[i]; + + // Check if bid qualifies + if (bidsForDrop[_tokenContract][bidder].revealedBidAmount >= _settlePricePoint) { + // Mark winning bidder and increment edition size + winningBidders[editionSize++] = bidder; + + // Update final revenue + auction.settledRevenue += _settlePricePoint; + + // Update their balance + bids[bidder].bidderBalance -= _settlePricePoint; + } + } + + // Store final auction details + // TODO TBD if worth it + auction.settledPricePoint = _settlePricePoint; + auction.settledEditionSize = editionSize; + + // Update edition size + ERC721Drop(_tokenContract).setEditionSize(uint64(winningBidders.length)); + + // Mint NFTs to winning bidders + ERC721Drop(_tokenContract).adminMintAirdrop(winningBidders); + + // Transfer the auction revenue to the funds recipient + _handleOutgoingTransfer(auction.sellerFundsRecipient, auction.settledRevenue, address(0), 50_000); + + emit AuctionSettled(_tokenContract, auction); + } + + /*////////////////////////////////////////////////////////////// + CLAIM REFUND + //////////////////////////////////////////////////////////////*/ + + // TODO + } diff --git a/contracts/modules/VariableSupplyAuction/temp-ERC721Drop.sol b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol similarity index 56% rename from contracts/modules/VariableSupplyAuction/temp-ERC721Drop.sol rename to contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol index 0d9a592a..2a5608ed 100644 --- a/contracts/modules/VariableSupplyAuction/temp-ERC721Drop.sol +++ b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol @@ -2,22 +2,32 @@ pragma solidity 0.8.10; /*////////////////////////////////////////////////////////////// - TEMP ERC721Drop interface (incomplete and incorrect =) + Temp Mock ERC721Drop interface //////////////////////////////////////////////////////////////*/ +// TODO consider other approaches that keep VSA module ignorant of ERC-721 Drop +// implementation specifics, including wrapping in a new TransferHelper for Drops + contract ERC721Drop { // - struct Configuration { - // IMetadataRenderer metadataRenderer; - uint64 editionSize; - uint16 royaltyBPS; - address payable fundsRecipient; - } + + /*////////////////////////////////////////////////////////////// + OZ ERC-721 + //////////////////////////////////////////////////////////////*/ string public name; string public symbol; + mapping(address => uint256) public balanceOf; + + /*////////////////////////////////////////////////////////////// + OZ Ownable + //////////////////////////////////////////////////////////////*/ + address public owner; - Configuration public config; + + /*////////////////////////////////////////////////////////////// + OZ Access Control + //////////////////////////////////////////////////////////////*/ // TODO use better mocking pattern for OZ AccessControlEnumerable @@ -30,9 +40,20 @@ contract ERC721Drop { function grantRole(bytes32 /*role*/, address account) public { minter = account; + } + + /*////////////////////////////////////////////////////////////// + Zora ERC721Drop + //////////////////////////////////////////////////////////////*/ + + struct Configuration { + // IMetadataRenderer metadataRenderer; + uint64 editionSize; + uint16 royaltyBPS; + address payable fundsRecipient; } - // TODO add few more needed IERC721 view/tx functions + Configuration public config; function initialize( string memory _contractName, @@ -55,12 +76,20 @@ contract ERC721Drop { }); } - // TODO ask Iain for gist of actual possible approach - function setEditionSize(uint64 _editionSize) public { - + function adminMint(address to, uint256 quantity) public returns (uint256) { + balanceOf[to] += quantity; + return 0; } - function adminMint(address to, uint256 quantity) public returns (uint256) { + function adminMintAirdrop(address[] calldata recipients) public returns (uint256) { + for (uint256 i = 0; i < recipients.length; i++) { + adminMint(recipients[i], 1); + } + return 0; + } - } + // TODO ask Iain for gist of actual function name + function setEditionSize(uint64 _editionSize) public { + // + } } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol index 5a826538..44f8fa79 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol @@ -69,7 +69,7 @@ contract VariableSupplyAuctionIntegrationTest is DSTest { weth = new WETH(); // Deploy Reserve Auction Core ETH - auctions = new VariableSupplyAuction(); + auctions = new VariableSupplyAuction(address(erc721TransferHelper), address(royaltyEngine), address(ZPFS), address(weth)); registrar.registerModule(address(auctions)); // Set module fee diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant new file mode 100644 index 00000000..31e2658c --- /dev/null +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant @@ -0,0 +1,6 @@ +Invariant 1: endOfBidPhase > startTime +Invariant 2: endOfRevealPhase > endOfBidPhase +Invariant 3: endOfSettlePhase > endOfRevealPhase +Invariant 4: totalBalance == Σ all bidder balances, while now <= endOfSettlePhase +Invariant 5: bidder balance >= revealedBidAmount, while now > endOfRevealPhase +# TODO diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol new file mode 100644 index 00000000..8df968ee --- /dev/null +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.10; + +import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; + +import {ERC721Drop} from "../../../modules/VariableSupplyAuction/temp-MockERC721Drop.sol"; +import {Zorb} from "../../utils/users/Zorb.sol"; +import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; +import {ZoraModuleManager} from "../../../ZoraModuleManager.sol"; +import {ZoraProtocolFeeSettings} from "../../../auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol"; +import {ERC20TransferHelper} from "../../../transferHelpers/ERC20TransferHelper.sol"; +import {ERC721TransferHelper} from "../../../transferHelpers/ERC721TransferHelper.sol"; +import {RoyaltyEngine} from "../../utils/modules/RoyaltyEngine.sol"; +import {TestERC721} from "../../utils/tokens/TestERC721.sol"; +import {WETH} from "../../utils/tokens/WETH.sol"; +import {InvariantTest} from "../../utils/InvariantTest.sol"; +import {VM} from "../../utils/VM.sol"; + +/// @title VariableSupplyAuctionTest +/// @notice Invariant Tests for Variable Supply Auctions +contract VariableSupplyAuctionInvariantTest is InvariantTest { +// + + ZoraRegistrar internal registrar; + ZoraProtocolFeeSettings internal ZPFS; + ZoraModuleManager internal ZMM; + // ERC20TransferHelper internal erc20TransferHelper; + ERC721TransferHelper internal erc721TransferHelper; + RoyaltyEngine internal royaltyEngine; + + VariableSupplyAuction internal auctions; + ERC721Drop internal drop; + WETH internal weth; + + Zorb internal seller; + Zorb internal sellerFundsRecipient; + Zorb internal operator; + Zorb internal finder; + Zorb internal royaltyRecipient; + Zorb internal bidder1; + Zorb internal bidder2; + Zorb internal bidder3; + Zorb internal bidder4; + Zorb internal bidder5; + Zorb internal bidder6; + Zorb internal bidder7; + Zorb internal bidder8; + Zorb internal bidder9; + Zorb internal bidder10; + Zorb internal bidder11; + Zorb internal bidder12; + Zorb internal bidder13; + Zorb internal bidder14; + Zorb internal bidder15; + + string internal constant salt1 = "setec astronomy"; + string internal constant salt2 = "too many secrets"; + string internal constant salt3 = "cray tomes on set"; + string internal constant salt4 = "o no my tesseract"; + string internal constant salt5 = "ye some contrast"; + string internal constant salt6 = "a tron ecosystem"; + string internal constant salt7 = "stonecasty rome"; + string internal constant salt8 = "coy teamster son"; + string internal constant salt9 = "cyanometer toss"; + string internal constant salt10 = "cementatory sos"; + string internal constant salt11 = "my cotoneasters"; + string internal constant salt12 = "ny sec stateroom"; + string internal constant salt13 = "oc attorney mess"; + string internal constant salt14 = "my cots earstones"; + string internal constant salt15 = "easternmost coy"; + + function setUp() public { + // Deploy V3 + registrar = new ZoraRegistrar(); + ZPFS = new ZoraProtocolFeeSettings(); + ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); + // erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); + erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); + + // Init V3 + registrar.init(ZMM); + ZPFS.init(address(ZMM), address(0)); + + // Create users + seller = new Zorb(address(ZMM)); + sellerFundsRecipient = new Zorb(address(ZMM)); + operator = new Zorb(address(ZMM)); + bidder1 = new Zorb(address(ZMM)); + bidder2 = new Zorb(address(ZMM)); + bidder3 = new Zorb(address(ZMM)); + bidder4 = new Zorb(address(ZMM)); + bidder5 = new Zorb(address(ZMM)); + bidder6 = new Zorb(address(ZMM)); + bidder7 = new Zorb(address(ZMM)); + bidder8 = new Zorb(address(ZMM)); + bidder9 = new Zorb(address(ZMM)); + bidder10 = new Zorb(address(ZMM)); + bidder11 = new Zorb(address(ZMM)); + bidder12 = new Zorb(address(ZMM)); + bidder13 = new Zorb(address(ZMM)); + bidder14 = new Zorb(address(ZMM)); + bidder15 = new Zorb(address(ZMM)); + finder = new Zorb(address(ZMM)); + royaltyRecipient = new Zorb(address(ZMM)); + + // Set balances + vm.deal(address(seller), 100 ether); + vm.deal(address(bidder1), 100 ether); + vm.deal(address(bidder2), 100 ether); + vm.deal(address(bidder3), 100 ether); + vm.deal(address(bidder4), 100 ether); + vm.deal(address(bidder5), 100 ether); + vm.deal(address(bidder6), 100 ether); + vm.deal(address(bidder7), 100 ether); + vm.deal(address(bidder8), 100 ether); + vm.deal(address(bidder9), 100 ether); + vm.deal(address(bidder10), 100 ether); + vm.deal(address(bidder11), 100 ether); + vm.deal(address(bidder12), 100 ether); + vm.deal(address(bidder13), 100 ether); + vm.deal(address(bidder14), 100 ether); + vm.deal(address(bidder15), 100 ether); + + // Deploy mocks + royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); + drop = new ERC721Drop(); + drop.initialize({ + _contractName: "Test Mutant Ninja Turtles", + _contractSymbol: "TMNT", + _initialOwner: address(seller), + _fundsRecipient: payable(sellerFundsRecipient), + _editionSize: 1, + _royaltyBPS: 1000 + // _metadataRenderer: dummyRenderer, + // _metadataRendererInit: "", + // _salesConfig: IERC721Drop.SalesConfiguration({ + // publicSaleStart: 0, + // publicSaleEnd: 0, + // presaleStart: 0, + // presaleEnd: 0, + // publicSalePrice: 0, + // maxSalePurchasePerAddress: 0, + // presaleMerkleRoot: bytes32(0) + // }) + }); + weth = new WETH(); + + // Deploy Variable Supply Auction module + auctions = new VariableSupplyAuction(address(erc721TransferHelper), address(royaltyEngine), address(ZPFS), address(weth)); + registrar.registerModule(address(auctions)); + + // Grant auction minter role on drop contract + vm.prank(address(seller)); + drop.grantRole(drop.MINTER_ROLE(), address(auctions)); + + // Users approve module + seller.setApprovalForModule(address(auctions), true); + bidder1.setApprovalForModule(address(auctions), true); + bidder2.setApprovalForModule(address(auctions), true); + bidder3.setApprovalForModule(address(auctions), true); + bidder4.setApprovalForModule(address(auctions), true); + bidder5.setApprovalForModule(address(auctions), true); + bidder6.setApprovalForModule(address(auctions), true); + bidder7.setApprovalForModule(address(auctions), true); + bidder8.setApprovalForModule(address(auctions), true); + bidder9.setApprovalForModule(address(auctions), true); + bidder10.setApprovalForModule(address(auctions), true); + bidder11.setApprovalForModule(address(auctions), true); + bidder12.setApprovalForModule(address(auctions), true); + bidder13.setApprovalForModule(address(auctions), true); + bidder14.setApprovalForModule(address(auctions), true); + bidder15.setApprovalForModule(address(auctions), true); + + // Seller approve ERC721TransferHelper + // vm.prank(address(seller)); + // token.setApprovalForAll(address(erc721TransferHelper), true); + + // Setup invariant targets + excludeContract(address(registrar)); + excludeContract(address(ZPFS)); + excludeContract(address(ZMM)); + excludeContract(address(erc721TransferHelper)); + + excludeContract(address(royaltyEngine)); + excludeContract(address(drop)); + excludeContract(address(weth)); + + excludeContract(address(seller)); + excludeContract(address(sellerFundsRecipient)); + excludeContract(address(operator)); + excludeContract(address(finder)); + excludeContract(address(royaltyRecipient)); + + excludeContract(address(bidder1)); + excludeContract(address(bidder2)); + excludeContract(address(bidder3)); + excludeContract(address(bidder4)); + excludeContract(address(bidder5)); + excludeContract(address(bidder6)); + excludeContract(address(bidder7)); + excludeContract(address(bidder8)); + excludeContract(address(bidder9)); + excludeContract(address(bidder10)); + excludeContract(address(bidder12)); + excludeContract(address(bidder13)); + excludeContract(address(bidder14)); + excludeContract(address(bidder15)); + + targetContract(address(auctions)); + + // Setup invariant target senders (for actor-based invariant testing) + // TODO + + // Setup one auction + vm.prank(address(seller)); + auctions.createAuction({ + _tokenContract: address(drop), + _minimumViableRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: block.timestamp, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + } + + // function invariant_true_eq_true() public { + // assertTrue(true); + // } + + // function invariant_auctionTotalBalance_lt_1ether() public { + // ( + // address sellerStored, + // uint256 minimumRevenue, + // address sellerFundsRecipientStored, + // uint256 startTime, + // uint256 endOfBidPhase, + // uint256 endOfRevealPhase, + // uint256 endOfSettlePhase, + // uint96 totalBalance + // ) = auctions.auctionForDrop(address(drop)); + + // assertLt(totalBalance, 1 ether); + // } +} diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 9b0aac60..5d6fc4d7 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; -import {ERC721Drop} from "../../../modules/VariableSupplyAuction/temp-ERC721Drop.sol"; +import {ERC721Drop} from "../../../modules/VariableSupplyAuction/temp-MockERC721Drop.sol"; import {Zorb} from "../../utils/users/Zorb.sol"; import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; @@ -26,12 +26,12 @@ contract VariableSupplyAuctionTest is Test { ZoraProtocolFeeSettings internal ZPFS; ZoraModuleManager internal ZMM; // ERC20TransferHelper internal erc20TransferHelper; - // ERC721TransferHelper internal erc721TransferHelper; + ERC721TransferHelper internal erc721TransferHelper; RoyaltyEngine internal royaltyEngine; VariableSupplyAuction internal auctions; ERC721Drop internal drop; - // WETH internal weth; + WETH internal weth; Zorb internal seller; Zorb internal sellerFundsRecipient; @@ -76,7 +76,7 @@ contract VariableSupplyAuctionTest is Test { ZPFS = new ZoraProtocolFeeSettings(); ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); // erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); - // erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); + erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); // Init V3 registrar.init(ZMM); @@ -106,6 +106,7 @@ contract VariableSupplyAuctionTest is Test { // Set balances vm.deal(address(seller), 100 ether); + vm.deal(address(sellerFundsRecipient), 100 ether); vm.deal(address(bidder1), 100 ether); vm.deal(address(bidder2), 100 ether); vm.deal(address(bidder3), 100 ether); @@ -130,7 +131,7 @@ contract VariableSupplyAuctionTest is Test { _contractSymbol: "TMNT", _initialOwner: address(seller), _fundsRecipient: payable(sellerFundsRecipient), - _editionSize: 1, + _editionSize: 1, // will likely be updated during settle phase _royaltyBPS: 1000 // _metadataRenderer: dummyRenderer, // _metadataRendererInit: "", @@ -144,10 +145,10 @@ contract VariableSupplyAuctionTest is Test { // presaleMerkleRoot: bytes32(0) // }) }); - // weth = new WETH(); + weth = new WETH(); // Deploy Variable Supply Auction module - auctions = new VariableSupplyAuction(); + auctions = new VariableSupplyAuction(address(erc721TransferHelper), address(royaltyEngine), address(ZPFS), address(weth)); registrar.registerModule(address(auctions)); // Grant auction minter role on drop contract @@ -202,11 +203,11 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ function testGas_CreateAuction() public { - // NOTE this basic setup can be applied to tests via setupBasicAuction modifier + // Note this basic setup is applied to other tests via the setupBasicAuction modifier vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumRevenue: 1 ether, + _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: block.timestamp, _bidPhaseDuration: 3 days, @@ -218,30 +219,32 @@ contract VariableSupplyAuctionTest is Test { function test_CreateAuction_WhenInstant() public setupBasicAuction { ( address sellerStored, - uint256 minimumRevenue, + uint256 minimumViableRevenue, address sellerFundsRecipientStored, uint256 startTime, uint256 endOfBidPhase, uint256 endOfRevealPhase, uint256 endOfSettlePhase, - uint96 totalBalance + uint96 totalBalance, + uint96 settledRevenue, ) = auctions.auctionForDrop(address(drop)); assertEq(sellerStored, address(seller)); - assertEq(minimumRevenue, 1 ether); + assertEq(minimumViableRevenue, 1 ether); assertEq(sellerFundsRecipientStored, address(sellerFundsRecipient)); assertEq(startTime, uint32(block.timestamp)); assertEq(endOfBidPhase, uint32(block.timestamp + 3 days)); assertEq(endOfRevealPhase, uint32(block.timestamp + 3 days + 2 days)); assertEq(endOfSettlePhase, uint32(block.timestamp + 3 days + 2 days + 1 days)); assertEq(totalBalance, uint96(0)); + assertEq(settledRevenue, uint96(0)); } function test_CreateAuction_WhenFuture() public { vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumRevenue: 1 ether, + _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: 1 days, _bidPhaseDuration: 3 days, @@ -257,6 +260,8 @@ contract VariableSupplyAuctionTest is Test { uint32 endOfBidPhase, uint32 endOfRevealPhase, uint32 endOfSettlePhase, + , + , ) = auctions.auctionForDrop(address(drop)); assertEq(startTime, 1 days); @@ -268,22 +273,24 @@ contract VariableSupplyAuctionTest is Test { function testEvent_createAuction() public { Auction memory auction = Auction({ seller: address(seller), - minimumRevenue: 1 ether, + minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(block.timestamp), endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - totalBalance: uint96(0) + settledRevenue: 0, + settledPricePoint: 0, + settledEditionSize: 0 }); - vm.expectEmit(true, true, true, true); + vm.expectEmit(false, false, false, false); emit AuctionCreated(address(drop), auction); vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumRevenue: 1 ether, + _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: block.timestamp, _bidPhaseDuration: 3 days, @@ -298,7 +305,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumRevenue: 1 ether, + _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: 1 days, _bidPhaseDuration: 3 days, @@ -313,7 +320,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumRevenue: 1 ether, + _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(0), _startTime: 1 days, _bidPhaseDuration: 3 days, @@ -326,40 +333,29 @@ contract VariableSupplyAuctionTest is Test { CANCEL AUCTION //////////////////////////////////////////////////////////////*/ - // TODO cancelAuction + // TODO /*////////////////////////////////////////////////////////////// PLACE BID //////////////////////////////////////////////////////////////*/ function test_PlaceBid_WhenSingle() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); - - ( - , - , - , - , - , - , - , - uint256 totalBalance - ) = auctions.auctionForDrop(address(drop)); - (bytes32 commitmentStored, ) = auctions.bidOf(address(drop), address(bidder1)); + + (bytes32 commitmentStored, uint96 bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); assertEq(address(auctions).balance, 1 ether); - assertEq(totalBalance, 1 ether); - assertEq(auctions.balanceOf(address(drop), address(bidder1)), 1 ether); + assertEq(bidderBalance, 1 ether); assertEq(commitmentStored, commitment); } function test_PlaceBid_WhenMultiple() public setupBasicAuction { - bytes32 commitment1 = genSealedBid(1 ether, salt1); - bytes32 commitment2 = genSealedBid(1 ether, salt2); - bytes32 commitment3 = genSealedBid(1 ether, salt3); + bytes32 commitment1 = _genSealedBid(1 ether, salt1); + bytes32 commitment2 = _genSealedBid(1 ether, salt2); + bytes32 commitment3 = _genSealedBid(1 ether, salt3); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); @@ -368,25 +364,14 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder3)); auctions.placeBid{value: 3 ether}(address(drop), commitment3); - ( - , - , - , - , - , - , - , - uint256 totalBalance - ) = auctions.auctionForDrop(address(drop)); - (bytes32 commitmentStored1, ) = auctions.bidOf(address(drop), address(bidder1)); - (bytes32 commitmentStored2, ) = auctions.bidOf(address(drop), address(bidder2)); - (bytes32 commitmentStored3, ) = auctions.bidOf(address(drop), address(bidder3)); + (bytes32 commitmentStored1, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (bytes32 commitmentStored2, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); + (bytes32 commitmentStored3, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); assertEq(address(auctions).balance, 6 ether); - assertEq(totalBalance, 6 ether); - assertEq(auctions.balanceOf(address(drop), address(bidder1)), 1 ether); - assertEq(auctions.balanceOf(address(drop), address(bidder2)), 2 ether); - assertEq(auctions.balanceOf(address(drop), address(bidder3)), 3 ether); + assertEq(bidderBalance1, 1 ether); + assertEq(bidderBalance2, 2 ether); + assertEq(bidderBalance3, 3 ether); assertEq(commitmentStored1, commitment1); assertEq(commitmentStored2, commitment2); assertEq(commitmentStored3, commitment3); @@ -395,19 +380,21 @@ contract VariableSupplyAuctionTest is Test { function testEvent_PlaceBid_WhenSingle() public setupBasicAuction { Auction memory auction = Auction({ seller: address(seller), - minimumRevenue: 1 ether, + minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(block.timestamp), endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - totalBalance: uint96(1 ether) + settledRevenue: uint96(0), + settledPricePoint: uint96(0), + settledEditionSize: uint16(0) }); vm.expectEmit(true, true, true, true); emit BidPlaced(address(drop), address(bidder1), auction); - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); } @@ -415,18 +402,21 @@ contract VariableSupplyAuctionTest is Test { function testEvent_PlaceBid_WhenMultiple() public setupBasicAuction { Auction memory auction = Auction({ seller: address(seller), - minimumRevenue: 1 ether, + minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(block.timestamp), endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - totalBalance: uint96(1 ether) // expect this totalBalance in event based on first bid + // totalBalance: 1 ether, + settledRevenue: uint96(0), + settledPricePoint: uint96(0), + settledEditionSize: uint16(0) }); - bytes32 commitment1 = genSealedBid(1 ether, salt1); - bytes32 commitment2 = genSealedBid(1 ether, salt2); - bytes32 commitment3 = genSealedBid(1 ether, salt3); + bytes32 commitment1 = _genSealedBid(1 ether, salt1); + bytes32 commitment2 = _genSealedBid(1 ether, salt2); + bytes32 commitment3 = _genSealedBid(1 ether, salt3); vm.expectEmit(true, true, true, true); emit BidPlaced(address(drop), address(bidder1), auction); @@ -434,7 +424,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); - auction.totalBalance = 3 ether; // 2 ether more from bidder 2 + // auction.totalBalance = 3 ether; // 2 ether more from bidder 2 vm.expectEmit(true, true, true, true); emit BidPlaced(address(drop), address(bidder2), auction); @@ -442,7 +432,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder2)); auctions.placeBid{value: 2 ether}(address(drop), commitment2); - auction.totalBalance = 6 ether; // 3 ether more from bidder 3 + // auction.totalBalance = 6 ether; // 3 ether more from bidder 3 vm.expectEmit(true, true, true, true); emit BidPlaced(address(drop), address(bidder3), auction); @@ -454,7 +444,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_PlaceBid_WhenAuctionDoesNotExist() public { vm.expectRevert("AUCTION_DOES_NOT_EXIST"); - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); } @@ -464,7 +454,7 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); } @@ -474,13 +464,23 @@ contract VariableSupplyAuctionTest is Test { vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); } + // TODO once settleAuction is written + // function testRevert_PlaceBid_WhenAuctionIsCompleted() public setupBasicAuction { + + // } + + // TODO once cancelAuction is written + // function testRevert_PlaceBid_WhenAuctionIsCancelled() public setupBasicAuction { + + // } + function testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -493,28 +493,18 @@ contract VariableSupplyAuctionTest is Test { function testRevert_PlaceBid_WhenNoEtherIncluded() public setupBasicAuction { vm.expectRevert("VALID_BIDS_MUST_INCLUDE_ETHER"); - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid(address(drop), commitment); } - // TODO once settleAuction is written - // function testRevert_PlaceBid_WhenAuctionIsCompleted() public setupBasicAuction { - - // } - - // TODO once cancelAuction is written - // function testRevert_PlaceBid_WhenAuctionIsCancelled() public setupBasicAuction { - - // } - // TODO revist – test may become relevant if we move minter role granting into TransferHelper // function testRevert_PlaceBid_WhenSellerDidNotApproveModule() public setupBasicAuction { // seller.setApprovalForModule(address(auctions), false); // vm.expectRevert("module has not been approved by user"); - // bytes32 commitment = genSealedBid(1 ether, salt1); + // bytes32 commitment = _genSealedBid(1 ether, salt1); // vm.prank(address(bidder1)); // auctions.placeBid{value: 1 ether}(address(drop), commitment); // } @@ -524,7 +514,7 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ function test_RevealBid_WhenSingle() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); @@ -533,15 +523,15 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1 ether, salt1); - (, uint256 bidAmount) = auctions.bidOf(address(drop), address(bidder1)); + (,, uint256 bidAmount) = auctions.bidsForDrop(address(drop), address(bidder1)); assertEq(bidAmount, 1 ether); } function test_RevealBid_WhenMultiple() public setupBasicAuction { - bytes32 commitment1 = genSealedBid(1 ether, salt1); - bytes32 commitment2 = genSealedBid(2 ether, salt2); - bytes32 commitment3 = genSealedBid(3 ether, salt3); + bytes32 commitment1 = _genSealedBid(1 ether, salt1); + bytes32 commitment2 = _genSealedBid(2 ether, salt2); + bytes32 commitment3 = _genSealedBid(3 ether, salt3); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); @@ -559,9 +549,9 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder3)); auctions.revealBid(address(drop), 3 ether, salt3); - (, uint256 bidAmount1) = auctions.bidOf(address(drop), address(bidder1)); - (, uint256 bidAmount2) = auctions.bidOf(address(drop), address(bidder2)); - (, uint256 bidAmount3) = auctions.bidOf(address(drop), address(bidder3)); + (,, uint256 bidAmount1) = auctions.bidsForDrop(address(drop), address(bidder1)); + (,, uint256 bidAmount2) = auctions.bidsForDrop(address(drop), address(bidder2)); + (,, uint256 bidAmount3) = auctions.bidsForDrop(address(drop), address(bidder3)); assertEq(bidAmount1, 1 ether); assertEq(bidAmount2, 2 ether); @@ -571,16 +561,18 @@ contract VariableSupplyAuctionTest is Test { function testEvent_RevealBid_WhenSingle() public setupBasicAuction { Auction memory auction = Auction({ seller: address(seller), - minimumRevenue: 1 ether, + minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(block.timestamp), endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - totalBalance: uint96(1 ether) + settledRevenue: uint96(0), + settledPricePoint: uint96(0), + settledEditionSize: uint16(0) }); - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -596,18 +588,20 @@ contract VariableSupplyAuctionTest is Test { function testEvent_RevealBid_WhenMultiple() public setupBasicAuction { Auction memory auction = Auction({ seller: address(seller), - minimumRevenue: 1 ether, + minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(block.timestamp), endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - totalBalance: uint96(9 ether) // based on total sent ether amount + settledRevenue: uint96(0), + settledPricePoint: uint96(0), + settledEditionSize: uint16(0) }); - bytes32 commitment1 = genSealedBid(1 ether, salt1); - bytes32 commitment2 = genSealedBid(2 ether, salt2); - bytes32 commitment3 = genSealedBid(3 ether, salt3); + bytes32 commitment1 = _genSealedBid(1 ether, salt1); + bytes32 commitment2 = _genSealedBid(2 ether, salt2); + bytes32 commitment3 = _genSealedBid(3 ether, salt3); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); @@ -635,7 +629,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_RevealBid_WhenAuctionInBidPhase() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); @@ -646,7 +640,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_RevealBid_WhenAuctionInSettlePhase() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); @@ -680,7 +674,7 @@ contract VariableSupplyAuctionTest is Test { // TODO should we then allow "topping up" the bidder's balance to support their bid? // likely, no — introduces bad incentives function testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() public setupBasicAuction { - bytes32 commitment = genSealedBid(1.1 ether, salt1); + bytes32 commitment = _genSealedBid(1.1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -693,7 +687,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -706,7 +700,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() public setupBasicAuction { - bytes32 commitment = genSealedBid(1 ether, salt1); + bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); @@ -754,40 +748,325 @@ contract VariableSupplyAuctionTest is Test { | 3 | 18 ether | | 1 | 11 ether | + Note settle auction tests use throughRevealPhaseComplex modifier for further test setup + */ - function test_SettleAuction_WhenSettlingAtHighPriceLowSupply() public setupBasicAuction { + function test_SettleAuction_Preconditions() public setupBasicAuction throughRevealPhaseComplex { + // Precondition checks + + // all bidders have 0 NFTs + assertEq(drop.balanceOf(address(bidder1)), 0); + assertEq(drop.balanceOf(address(bidder2)), 0); + assertEq(drop.balanceOf(address(bidder3)), 0); + assertEq(drop.balanceOf(address(bidder4)), 0); + assertEq(drop.balanceOf(address(bidder5)), 0); + assertEq(drop.balanceOf(address(bidder6)), 0); + assertEq(drop.balanceOf(address(bidder7)), 0); + assertEq(drop.balanceOf(address(bidder8)), 0); + assertEq(drop.balanceOf(address(bidder9)), 0); + assertEq(drop.balanceOf(address(bidder10)), 0); + assertEq(drop.balanceOf(address(bidder11)), 0); + assertEq(drop.balanceOf(address(bidder12)), 0); + assertEq(drop.balanceOf(address(bidder13)), 0); + + // seller funds recipient has 100 ether + assertEq(address(sellerFundsRecipient).balance, 100 ether); + + // bidder auction balances still full amount of sent ether + (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); + (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); + (, uint96 bidderBalance4, ) = auctions.bidsForDrop(address(drop), address(bidder4)); + (, uint96 bidderBalance5, ) = auctions.bidsForDrop(address(drop), address(bidder5)); + (, uint96 bidderBalance6, ) = auctions.bidsForDrop(address(drop), address(bidder6)); + (, uint96 bidderBalance7, ) = auctions.bidsForDrop(address(drop), address(bidder7)); + (, uint96 bidderBalance8, ) = auctions.bidsForDrop(address(drop), address(bidder8)); + (, uint96 bidderBalance9, ) = auctions.bidsForDrop(address(drop), address(bidder9)); + (, uint96 bidderBalance10, ) = auctions.bidsForDrop(address(drop), address(bidder10)); + (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); + (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); + (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); + assertEq(bidderBalance1, 1 ether); + assertEq(bidderBalance2, 9 ether); + assertEq(bidderBalance3, 8 ether); + assertEq(bidderBalance4, 7 ether); + assertEq(bidderBalance5, 6 ether); + assertEq(bidderBalance6, 5 ether); + assertEq(bidderBalance7, 4 ether); + assertEq(bidderBalance8, 3 ether); + assertEq(bidderBalance9, 2 ether); + assertEq(bidderBalance10, 10 ether); + assertEq(bidderBalance11, 6 ether); + assertEq(bidderBalance12, 9 ether); + assertEq(bidderBalance13, 12 ether); + } + + function test_SettleAuction_WhenSettlingAtLowPriceHighSupply() public setupBasicAuction throughRevealPhaseComplex { + _expectSettledAuctionEvent(1 ether, 13); + + // When -- seller settles auction at price point of 1 ether + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 1 ether); + + // Then assert -- + // 1) all bidders have 1 NFT + // 2) seller funds recipient has 113 ether + // 3) bidder auction balances (available to withdraw) are their amount of + // sent ether less the settled price point of 1 ether + + assertEq(drop.balanceOf(address(bidder1)), 1); + assertEq(drop.balanceOf(address(bidder2)), 1); + assertEq(drop.balanceOf(address(bidder3)), 1); + assertEq(drop.balanceOf(address(bidder4)), 1); + assertEq(drop.balanceOf(address(bidder5)), 1); + assertEq(drop.balanceOf(address(bidder6)), 1); + assertEq(drop.balanceOf(address(bidder7)), 1); + assertEq(drop.balanceOf(address(bidder8)), 1); + assertEq(drop.balanceOf(address(bidder9)), 1); + assertEq(drop.balanceOf(address(bidder10)), 1); + assertEq(drop.balanceOf(address(bidder11)), 1); + assertEq(drop.balanceOf(address(bidder12)), 1); + assertEq(drop.balanceOf(address(bidder13)), 1); + + assertEq(address(sellerFundsRecipient).balance, 113 ether); + + (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); + (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); + (, uint96 bidderBalance4, ) = auctions.bidsForDrop(address(drop), address(bidder4)); + (, uint96 bidderBalance5, ) = auctions.bidsForDrop(address(drop), address(bidder5)); + (, uint96 bidderBalance6, ) = auctions.bidsForDrop(address(drop), address(bidder6)); + (, uint96 bidderBalance7, ) = auctions.bidsForDrop(address(drop), address(bidder7)); + (, uint96 bidderBalance8, ) = auctions.bidsForDrop(address(drop), address(bidder8)); + (, uint96 bidderBalance9, ) = auctions.bidsForDrop(address(drop), address(bidder9)); + (, uint96 bidderBalance10, ) = auctions.bidsForDrop(address(drop), address(bidder10)); + (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); + (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); + (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); + assertEq(bidderBalance1, 0 ether); + assertEq(bidderBalance2, 8 ether); + assertEq(bidderBalance3, 7 ether); + assertEq(bidderBalance4, 6 ether); + assertEq(bidderBalance5, 5 ether); + assertEq(bidderBalance6, 4 ether); + assertEq(bidderBalance7, 3 ether); + assertEq(bidderBalance8, 2 ether); + assertEq(bidderBalance9, 1 ether); + assertEq(bidderBalance10, 9 ether); + assertEq(bidderBalance11, 5 ether); + assertEq(bidderBalance12, 8 ether); + assertEq(bidderBalance13, 11 ether); + } + + function test_SettleAuction_WhenSettlingAtMidPriceMidSupply() public setupBasicAuction throughRevealPhaseComplex { + _expectSettledAuctionEvent(6 ether, 3); + + // When -- seller settles auction at price point of 6 ether + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 6 ether); + + // Then assert -- + // 1) bidders 11–13 has 1 NFT + // 2) seller funds recipient has 118 ether + // 3) bidders 11–13 balance is their sent ether less settled price point of 6 ether + // 4) bidders 1–10 balances (available to withdraw) are their full sent ether + + assertEq(drop.balanceOf(address(bidder1)), 0); + assertEq(drop.balanceOf(address(bidder2)), 0); + assertEq(drop.balanceOf(address(bidder3)), 0); + assertEq(drop.balanceOf(address(bidder4)), 0); + assertEq(drop.balanceOf(address(bidder5)), 0); + assertEq(drop.balanceOf(address(bidder6)), 0); + assertEq(drop.balanceOf(address(bidder7)), 0); + assertEq(drop.balanceOf(address(bidder8)), 0); + assertEq(drop.balanceOf(address(bidder9)), 0); + assertEq(drop.balanceOf(address(bidder10)), 0); + assertEq(drop.balanceOf(address(bidder11)), 1); + assertEq(drop.balanceOf(address(bidder12)), 1); + assertEq(drop.balanceOf(address(bidder13)), 1); + + assertEq(address(sellerFundsRecipient).balance, 118 ether); + + (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); + (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); + (, uint96 bidderBalance4, ) = auctions.bidsForDrop(address(drop), address(bidder4)); + (, uint96 bidderBalance5, ) = auctions.bidsForDrop(address(drop), address(bidder5)); + (, uint96 bidderBalance6, ) = auctions.bidsForDrop(address(drop), address(bidder6)); + (, uint96 bidderBalance7, ) = auctions.bidsForDrop(address(drop), address(bidder7)); + (, uint96 bidderBalance8, ) = auctions.bidsForDrop(address(drop), address(bidder8)); + (, uint96 bidderBalance9, ) = auctions.bidsForDrop(address(drop), address(bidder9)); + (, uint96 bidderBalance10, ) = auctions.bidsForDrop(address(drop), address(bidder10)); + (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); + (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); + (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); + assertEq(bidderBalance1, 1 ether); + assertEq(bidderBalance2, 9 ether); + assertEq(bidderBalance3, 8 ether); + assertEq(bidderBalance4, 7 ether); + assertEq(bidderBalance5, 6 ether); + assertEq(bidderBalance6, 5 ether); + assertEq(bidderBalance7, 4 ether); + assertEq(bidderBalance8, 3 ether); + assertEq(bidderBalance9, 2 ether); + assertEq(bidderBalance10, 10 ether); + assertEq(bidderBalance11, 0 ether); + assertEq(bidderBalance12, 3 ether); + assertEq(bidderBalance13, 6 ether); + } + + function test_SettleAuction_WhenSettlingAtHighPriceLowSupply() public setupBasicAuction throughRevealPhaseComplex { + _expectSettledAuctionEvent(11 ether, 1); + + // When -- seller settles auction at price point of 11 ether + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 11 ether); + + // Then assert -- + // 1) bidder 13 has 1 NFT + // 2) seller funds recipient has 111 ether + // 3) bidder 13 auction balance is their sent ether less settled price point of 11 ether + // 4) bidders 1–12 auction balances (available to withdraw) are their full sent ether + + assertEq(drop.balanceOf(address(bidder1)), 0); + assertEq(drop.balanceOf(address(bidder2)), 0); + assertEq(drop.balanceOf(address(bidder3)), 0); + assertEq(drop.balanceOf(address(bidder4)), 0); + assertEq(drop.balanceOf(address(bidder5)), 0); + assertEq(drop.balanceOf(address(bidder6)), 0); + assertEq(drop.balanceOf(address(bidder7)), 0); + assertEq(drop.balanceOf(address(bidder8)), 0); + assertEq(drop.balanceOf(address(bidder9)), 0); + assertEq(drop.balanceOf(address(bidder10)), 0); + assertEq(drop.balanceOf(address(bidder11)), 0); + assertEq(drop.balanceOf(address(bidder12)), 0); + assertEq(drop.balanceOf(address(bidder13)), 1); + + assertEq(address(sellerFundsRecipient).balance, 111 ether); + + (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); + (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); + (, uint96 bidderBalance4, ) = auctions.bidsForDrop(address(drop), address(bidder4)); + (, uint96 bidderBalance5, ) = auctions.bidsForDrop(address(drop), address(bidder5)); + (, uint96 bidderBalance6, ) = auctions.bidsForDrop(address(drop), address(bidder6)); + (, uint96 bidderBalance7, ) = auctions.bidsForDrop(address(drop), address(bidder7)); + (, uint96 bidderBalance8, ) = auctions.bidsForDrop(address(drop), address(bidder8)); + (, uint96 bidderBalance9, ) = auctions.bidsForDrop(address(drop), address(bidder9)); + (, uint96 bidderBalance10, ) = auctions.bidsForDrop(address(drop), address(bidder10)); + (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); + (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); + (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); + assertEq(bidderBalance1, 1 ether); + assertEq(bidderBalance2, 9 ether); + assertEq(bidderBalance3, 8 ether); + assertEq(bidderBalance4, 7 ether); + assertEq(bidderBalance5, 6 ether); + assertEq(bidderBalance6, 5 ether); + assertEq(bidderBalance7, 4 ether); + assertEq(bidderBalance8, 3 ether); + assertEq(bidderBalance9, 2 ether); + assertEq(bidderBalance10, 10 ether); + assertEq(bidderBalance11, 6 ether); + assertEq(bidderBalance12, 9 ether); + assertEq(bidderBalance13, 1 ether); + } + + /*////////////////////////////////////////////////////////////// + FAILURE TO SETTLE AUCTION + //////////////////////////////////////////////////////////////*/ + + // TODO seller failure to settle sad paths + + /*////////////////////////////////////////////////////////////// + CLAIM REFUND + //////////////////////////////////////////////////////////////*/ + + // function test_claimRefund() public setupBasicAuction { + // vm.prank(address(bidder1)); + // auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + + // vm.warp(3 days + 1 seconds); + + // vm.prank(address(bidder1)); + // auctions.revealBid(address(drop), 1 ether, salt1); + + // vm.warp(3 days + 2 days + 1 seconds); + + // vm.prank(address(seller)); + // auctions.settleAuction(address(drop), 1 ether); + + // vm.warp(3 days + 2 days + 1 days + 1 seconds); + + // // Precondition checks + // assertEq(address(bidder1).balance, 98 ether); + // assertEq(, expected); + + // vm.prank(address(bidder1)); + + // } + + // TODO add minimum viable revenue sad paths + + /*////////////////////////////////////////////////////////////// + FAILURE TO CLAIM REFUND + //////////////////////////////////////////////////////////////*/ + + // TODO seller failure to claim sad paths + + /*////////////////////////////////////////////////////////////// + TEST HELPERS + //////////////////////////////////////////////////////////////*/ + + // TODO improve modifier pattern to include parameters (could combine w/ fuzzing) + modifier setupBasicAuction() { + vm.prank(address(seller)); + auctions.createAuction({ + _tokenContract: address(drop), + _minimumViableRevenue: 1 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: block.timestamp, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + + _; + } + + modifier throughRevealPhaseComplex() { // 10 bids at 1 ether vm.prank(address(bidder1)); - auctions.placeBid{value: 1 ether}(address(drop), genSealedBid(1 ether, salt1)); + auctions.placeBid{value: 1 ether}(address(drop), _genSealedBid(1 ether, salt1)); vm.prank(address(bidder2)); - auctions.placeBid{value: 9 ether}(address(drop), genSealedBid(1 ether, salt2)); + auctions.placeBid{value: 9 ether}(address(drop), _genSealedBid(1 ether, salt2)); vm.prank(address(bidder3)); - auctions.placeBid{value: 8 ether}(address(drop), genSealedBid(1 ether, salt3)); + auctions.placeBid{value: 8 ether}(address(drop), _genSealedBid(1 ether, salt3)); vm.prank(address(bidder4)); - auctions.placeBid{value: 7 ether}(address(drop), genSealedBid(1 ether, salt4)); + auctions.placeBid{value: 7 ether}(address(drop), _genSealedBid(1 ether, salt4)); vm.prank(address(bidder5)); - auctions.placeBid{value: 6 ether}(address(drop), genSealedBid(1 ether, salt5)); + auctions.placeBid{value: 6 ether}(address(drop), _genSealedBid(1 ether, salt5)); vm.prank(address(bidder6)); - auctions.placeBid{value: 5 ether}(address(drop), genSealedBid(1 ether, salt6)); + auctions.placeBid{value: 5 ether}(address(drop), _genSealedBid(1 ether, salt6)); vm.prank(address(bidder7)); - auctions.placeBid{value: 4 ether}(address(drop), genSealedBid(1 ether, salt7)); + auctions.placeBid{value: 4 ether}(address(drop), _genSealedBid(1 ether, salt7)); vm.prank(address(bidder8)); - auctions.placeBid{value: 3 ether}(address(drop), genSealedBid(1 ether, salt8)); + auctions.placeBid{value: 3 ether}(address(drop), _genSealedBid(1 ether, salt8)); vm.prank(address(bidder9)); - auctions.placeBid{value: 2 ether}(address(drop), genSealedBid(1 ether, salt9)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt9)); vm.prank(address(bidder10)); - auctions.placeBid{value: 10 ether}(address(drop), genSealedBid(1 ether, salt10)); + auctions.placeBid{value: 10 ether}(address(drop), _genSealedBid(1 ether, salt10)); // 2 bids at 6 ether vm.prank(address(bidder11)); - auctions.placeBid{value: 6 ether}(address(drop), genSealedBid(6 ether, salt11)); + auctions.placeBid{value: 6 ether}(address(drop), _genSealedBid(6 ether, salt11)); vm.prank(address(bidder12)); - auctions.placeBid{value: 9 ether}(address(drop), genSealedBid(6 ether, salt12)); + auctions.placeBid{value: 9 ether}(address(drop), _genSealedBid(6 ether, salt12)); // 10 bids at 1 ether vm.prank(address(bidder13)); - auctions.placeBid{value: 12 ether}(address(drop), genSealedBid(11 ether, salt13)); + auctions.placeBid{value: 12 ether}(address(drop), _genSealedBid(11 ether, salt13)); vm.warp(3 days + 1 seconds); @@ -818,93 +1097,50 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder13)); auctions.revealBid(address(drop), 11 ether, salt13); - vm.warp(3 days + 2 days + 1 seconds); - - assertTrue(false); // TODO - - // precondition checks - // all bidders have balance of 0 NFTs - // seller has 100 ETH - // bidder balances are xyz - - // when – seller settles auction at price point of 1 ether - - // assertions - // all bidders have balance of 1 NFT - // seller has 113 ETH - // all bidders have 99 ETH - } - - function test_SettleAuction_WhenSettlingAtMidPriceMidSupply() public setupBasicAuction { - // ... + vm.warp(3 days + 2 days + 1 seconds); - // when – seller settles auction at price point of 6 ether - - // assertions - // bidders 11–13 have balance of 1 NFT - // seller has 118 ETH - // bidders 1–10 have 100 ETH, bidders 11–13 have 94 ETH - - assertTrue(false); // TODO - } - - function test_SettleAuction_WhenSettlingAtLowPriceHighSupply() public setupBasicAuction { - // ... - - // when – seller settles auction at price point of 11 ether - - // assertions - // bidder 13 has balance of 1 NFT - // seller has 111 ETH - // bidders 1–12 have 100 ETH, bidder 13 has 89 ETH - - assertTrue(false); // TODO + _; } - /*////////////////////////////////////////////////////////////// - FAILURE TO SETTLE AUCTION - //////////////////////////////////////////////////////////////*/ - - // TODO seller failure to settle sad paths - - /*////////////////////////////////////////////////////////////// - TEST HELPERS - //////////////////////////////////////////////////////////////*/ - - // TODO improve modifier pattern to include parameters (could combine w/ fuzzing) - modifier setupBasicAuction() { - vm.prank(address(seller)); - auctions.createAuction({ - _tokenContract: address(drop), - _minimumRevenue: 1 ether, - _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: block.timestamp, - _bidPhaseDuration: 3 days, - _revealPhaseDuration: 2 days, - _settlePhaseDuration: 1 days + function _expectSettledAuctionEvent(uint96 _settledPricePoint, uint16 _settledEditionSize) internal { + Auction memory auction = Auction({ + seller: address(seller), + minimumViableRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + settledRevenue: _settledPricePoint * _settledEditionSize, + settledPricePoint: _settledPricePoint, + settledEditionSize: _settledEditionSize }); - _; + vm.expectEmit(true, true, true, false); // TODO troubleshoot auction metadata + emit AuctionSettled(address(drop), auction); } // IDEA could this not be moved to the hyperstructure module for better bidder usability ?! - function genSealedBid(uint256 _amount, string memory _salt) internal pure returns (bytes32) { + function _genSealedBid(uint256 _amount, string memory _salt) internal pure returns (bytes32) { return keccak256(abi.encodePacked(_amount, bytes(_salt))); } /*////////////////////////////////////////////////////////////// - TODO use better pattern to DRY up + TODO DRY up w/ better pattern //////////////////////////////////////////////////////////////*/ struct Auction { address seller; - uint96 minimumRevenue; + uint96 minimumViableRevenue; address sellerFundsRecipient; uint32 startTime; uint32 endOfBidPhase; uint32 endOfRevealPhase; uint32 endOfSettlePhase; - uint96 totalBalance; + uint96 settledRevenue; + uint96 settledPricePoint; + uint16 settledEditionSize; + // TODO do we still need totalBalance ? } struct Bid { @@ -912,7 +1148,8 @@ contract VariableSupplyAuctionTest is Test { uint96 revealedBidAmount; } - event AuctionCreated(address indexed drop, Auction auction); + event AuctionCreated(address indexed tokenContract, Auction auction); event BidPlaced(address indexed tokenContract, address indexed bidder, Auction auction); event BidRevealed(address indexed tokenContract, address indexed bidder, uint256 indexed bidAmount, Auction auction); + event AuctionSettled(address indexed tokenContract, Auction auction); } diff --git a/contracts/test/utils/InvariantTest.sol b/contracts/test/utils/InvariantTest.sol new file mode 100644 index 00000000..e8f78081 --- /dev/null +++ b/contracts/test/utils/InvariantTest.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +// from https://github.com/maple-labs/revenue-distribution-token/tree/add-forge-invariants +contract InvariantTest is Test { + // + struct FuzzSelector { + address addr; + bytes4[] selectors; + } + + address[] private _excludedContracts; + address[] private _excludedSenders; + address[] private _targetedContracts; + address[] private _targetedSenders; + + FuzzSelector[] internal _targetedSelectors; + + function excludeContract(address newExcludedContract_) internal { + _excludedContracts.push(newExcludedContract_); + } + + function excludeContracts() public view returns (address[] memory excludedContracts_) { + require(_excludedContracts.length != uint256(0), "NO_EXCLUDED_CONTRACTS"); + excludedContracts_ = _excludedContracts; + } + + function excludeSender(address newExcludedSender_) internal { + _excludedSenders.push(newExcludedSender_); + } + + function excludeSenders() public view returns (address[] memory excludedSenders_) { + require(_excludedSenders.length != uint256(0), "NO_EXCLUDED_SENDERS"); + excludedSenders_ = _excludedSenders; + } + + function targetContract(address newTargetedContract_) internal { + _targetedContracts.push(newTargetedContract_); + } + + function targetContracts() public view returns (address[] memory targetedContracts_) { + require(_targetedContracts.length != uint256(0), "NO_TARGETED_CONTRACTS"); + targetedContracts_ = _targetedContracts; + } + + function targetSelector(FuzzSelector memory newTargetedSelector_) internal { + _targetedSelectors.push(newTargetedSelector_); + } + + function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors_) { + require(targetedSelectors_.length != uint256(0), "NO_TARGETED_SELECTORS"); + targetedSelectors_ = _targetedSelectors; + } + + function targetSender(address newTargetedSender_) internal { + _targetedSenders.push(newTargetedSender_); + } + + function targetSenders() public view returns (address[] memory targetedSenders_) { + require(_targetedSenders.length != uint256(0), "NO_TARGETED_SENDERS"); + targetedSenders_ = _targetedSenders; + } + +} diff --git a/foundry.toml b/foundry.toml index 33155496..1c89a122 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,3 +6,9 @@ optimizer_runs = 500000 out = 'dist/artifacts' src = 'contracts' test = 'test' + +[invariant] +runs = 10 # The number of times to run the invariant tests +depth = 10 # The number of calls to make in the invariant tests +call_override = false # Override calls +fail_on_revert = false # Fail the test if the contract reverts From 1afdfc44d3d70d4e979dc172512beacfa7574d0f Mon Sep 17 00:00:00 2001 From: neodaoist Date: Wed, 19 Oct 2022 20:58:58 -0400 Subject: [PATCH 13/31] [draft] Add initial cancelAuction --- .gas-snapshot | 62 ++++++------ .../IVariableSupplyAuction.sol | 6 ++ .../VariableSupplyAuction.sol | 41 ++++++-- .../VariableSupplyAuction.jtbd | 13 +++ .../VariableSupplyAuction.t.sol | 98 +++++++++++++++++-- 5 files changed, 178 insertions(+), 42 deletions(-) create mode 100644 contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.jtbd diff --git a/.gas-snapshot b/.gas-snapshot index 68cd8da4..de72000c 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,36 +319,40 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 288707) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 156443) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 406191) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 210496) -VariableSupplyAuctionTest:testEvent_createAuction() (gas: 95077) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 89526) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20583) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 94900) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27567) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 104316) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 104261) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 162595) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 99504) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 156123) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 156627) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 99935) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 157359) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 156975) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 157315) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 92417) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 93461) +VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 84921) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295307) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160314) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 413962) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 214706) +VariableSupplyAuctionTest:testEvent_createAuction() (gas: 97853) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91927) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159685) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99710) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20628) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97412) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27523) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106761) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 106707) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166093) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 101949) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159621) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160125) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102402) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 160834) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160516) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 160836) +VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 84968) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95065) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96300) VariableSupplyAuctionTest:test_DropInitial() (gas: 30925) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 278291) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 152581) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 394186) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 205821) -VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1382557) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2299792) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2555187) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2342377) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 283851) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156057) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 400881) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 209763) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1403606) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2342070) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2597423) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2384613) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index 9f3126c7..93065dd0 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -18,9 +18,15 @@ interface IVariableSupplyAuction { uint256 _settlePhaseDuration ) external; + /// + function cancelAuction(address _tokenContract) external; + /// function placeBid(address _tokenContract, bytes32 _commitmentHash) external payable; /// function revealBid(address _tokenContract, uint256 _bidAmount, string calldata _salt) external; + + /// + function settleAuction(address _tokenContract, uint96 _settlePricePoint) external; } diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 4495c608..3ec76abe 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -44,6 +44,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param endOfBidPhase The unix timestamp until which bids can be placed /// @param endOfRevealPhase The unix timestamp until which placed bids can be revealed /// @param endOfSettlePhase The unix timestamp until which the seller can settle the auction (TODO clarify can vs. must) + /// @param totalBalance The total balance of all sent ether for this auction /// @param settledRevenue The total revenue generated by the drop /// @param settledPricePoint The chosen price point for the drop /// @param settledEditionSize The resulting edition size for the drop @@ -55,10 +56,10 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa uint32 endOfBidPhase; uint32 endOfRevealPhase; uint32 endOfSettlePhase; + uint96 totalBalance; uint96 settledRevenue; uint96 settledPricePoint; uint16 settledEditionSize; - // TODO do we still need totalBalance ? } /// @notice A sealed bid @@ -145,7 +146,30 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa CANCEL AUCTION //////////////////////////////////////////////////////////////*/ - // TODO + // TODO add UML + + /// @notice Emitted when an auction is canceled + /// @param tokenContract The address of the ERC-721 drop contract + /// @param auction The metadata of the canceled auction + event AuctionCanceled(address indexed tokenContract, Auction auction); + + /// @notice Cancels the auction for a given drop + /// @param _tokenContract The address of the ERC-721 drop contract + function cancelAuction(address _tokenContract) external nonReentrant { + // Get the auction for the specified drop + Auction memory auction = auctionForDrop[_tokenContract]; + + // Ensure that no bids have been placed in this auction yet + require(auction.totalBalance == 0, "CANNOT_CANCEL_AUCTION_WITH_BIDS"); + + // Ensure the caller is the seller + require(msg.sender == auction.seller, "ONLY_SELLER"); + + emit AuctionCanceled(_tokenContract, auction); + + // Remove the auction from storage + delete auctionForDrop[_tokenContract]; + } /*////////////////////////////////////////////////////////////// PLACE BID @@ -165,7 +189,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param _tokenContract The address of the ERC-721 drop contract /// @param _commitmentHash The sha256 hash of the sealed bid amount concatenated with /// a salt string, both of which need to be included in the subsequent reveal bid tx - function placeBid(address _tokenContract, bytes32 _commitmentHash) public payable nonReentrant { + function placeBid(address _tokenContract, bytes32 _commitmentHash) external payable nonReentrant { // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; @@ -180,6 +204,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Ensure the bid is valid and includes some ether require(msg.value > 0 ether, "VALID_BIDS_MUST_INCLUDE_ETHER"); + + // Update the total balance for auction + auction.totalBalance += uint96(msg.value); // Store the commitment hash and included ether amount bidsForDrop[_tokenContract][msg.sender] = Bid({ @@ -209,7 +236,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param _bidAmount The true bid amount /// @param _salt The string which was used, in combination with the true bid amount, /// to generate the commitment hash sent with the original placed bid tx - function revealBid(address _tokenContract, uint256 _bidAmount, string calldata _salt) public nonReentrant { + function revealBid(address _tokenContract, uint256 _bidAmount, string calldata _salt) external nonReentrant { // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; @@ -251,7 +278,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param auction The metadata of the created auction event AuctionSettled(address indexed tokenContract, Auction auction); - function settleAuction(address _tokenContract, uint96 _settlePricePoint) public nonReentrant { + function settleAuction(address _tokenContract, uint96 _settlePricePoint) external nonReentrant { // TODO checks // TODO gas optimizations @@ -286,8 +313,8 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa } } - // Store final auction details - // TODO TBD if worth it + // Store the current total balance and final auction details + auction.totalBalance -= auction.settledRevenue; auction.settledPricePoint = _settlePricePoint; auction.settledEditionSize = editionSize; diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.jtbd b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.jtbd new file mode 100644 index 00000000..7af6a48c --- /dev/null +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.jtbd @@ -0,0 +1,13 @@ +Job Executor: Creator + +Core Functional Job-to-be-done: To sell a digital product + +Job Map: TODO +1. Define +2. Locate +3. Prepare +4. Confirm +5. Execute +6. Monitor +7. Modify +8. Conclude diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 5d6fc4d7..1bbaf468 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -227,6 +227,8 @@ contract VariableSupplyAuctionTest is Test { uint256 endOfSettlePhase, uint96 totalBalance, uint96 settledRevenue, + uint96 settledPricePoint, + uint96 settledEditionSize ) = auctions.auctionForDrop(address(drop)); assertEq(sellerStored, address(seller)); @@ -238,6 +240,8 @@ contract VariableSupplyAuctionTest is Test { assertEq(endOfSettlePhase, uint32(block.timestamp + 3 days + 2 days + 1 days)); assertEq(totalBalance, uint96(0)); assertEq(settledRevenue, uint96(0)); + assertEq(settledPricePoint, uint96(0)); + assertEq(settledEditionSize, uint96(0)); } function test_CreateAuction_WhenFuture() public { @@ -261,7 +265,8 @@ contract VariableSupplyAuctionTest is Test { uint32 endOfRevealPhase, uint32 endOfSettlePhase, , - , + , + , ) = auctions.auctionForDrop(address(drop)); assertEq(startTime, 1 days); @@ -279,6 +284,7 @@ contract VariableSupplyAuctionTest is Test { endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: 0, settledRevenue: 0, settledPricePoint: 0, settledEditionSize: 0 @@ -333,7 +339,82 @@ contract VariableSupplyAuctionTest is Test { CANCEL AUCTION //////////////////////////////////////////////////////////////*/ - // TODO + function test_CancelAuction_WhenNoBidsPlacedYet() public setupBasicAuction { + // precondition check + ( + address sellerStored, + , + , + , + , + , + , + , + , + , + ) = auctions.auctionForDrop(address(drop)); + assertEq(sellerStored, address(seller)); + + vm.prank(address(seller)); + auctions.cancelAuction(address(drop)); + + ( + sellerStored, + , + , + , + , + , + , + , + , + , + ) = auctions.auctionForDrop(address(drop)); + assertEq(sellerStored, address(0)); + } + + function testEvent_CancelAuction() public setupBasicAuction { + Auction memory auction = Auction({ + seller: address(seller), + minimumViableRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: 0, + settledRevenue: 0, + settledPricePoint: 0, + settledEditionSize: 0 + }); + + vm.expectEmit(true, true, true, true); + emit AuctionCanceled(address(drop), auction); + + vm.prank(address(seller)); + auctions.cancelAuction(address(drop)); + } + + function testRevert_CancelAuction_WhenNotSeller() public setupBasicAuction { + vm.expectRevert("ONLY_SELLER"); + + vm.prank(address(bidder1)); + auctions.cancelAuction(address(drop)); + } + + function testRevert_CancelAuction_WhenBidAlreadyPlaced() public setupBasicAuction { + bytes32 commitment = _genSealedBid(1 ether, salt1); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + + vm.expectRevert("CANNOT_CANCEL_AUCTION_WITH_BIDS"); + + vm.prank(address(seller)); + auctions.cancelAuction(address(drop)); + } + + // TODO update biz logic to allow one other case -- cancelling auctions + // in settle phase that did not meet minimum viable revenue goal /*////////////////////////////////////////////////////////////// PLACE BID @@ -386,6 +467,7 @@ contract VariableSupplyAuctionTest is Test { endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: 1 ether, settledRevenue: uint96(0), settledPricePoint: uint96(0), settledEditionSize: uint16(0) @@ -408,7 +490,7 @@ contract VariableSupplyAuctionTest is Test { endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - // totalBalance: 1 ether, + totalBalance: 1 ether, settledRevenue: uint96(0), settledPricePoint: uint96(0), settledEditionSize: uint16(0) @@ -424,7 +506,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment1); - // auction.totalBalance = 3 ether; // 2 ether more from bidder 2 + auction.totalBalance = 3 ether; // 2 ether more from bidder 2 vm.expectEmit(true, true, true, true); emit BidPlaced(address(drop), address(bidder2), auction); @@ -432,7 +514,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder2)); auctions.placeBid{value: 2 ether}(address(drop), commitment2); - // auction.totalBalance = 6 ether; // 3 ether more from bidder 3 + auction.totalBalance = 6 ether; // 3 ether more from bidder 3 vm.expectEmit(true, true, true, true); emit BidPlaced(address(drop), address(bidder3), auction); @@ -567,6 +649,7 @@ contract VariableSupplyAuctionTest is Test { endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: 1 ether, settledRevenue: uint96(0), settledPricePoint: uint96(0), settledEditionSize: uint16(0) @@ -594,6 +677,7 @@ contract VariableSupplyAuctionTest is Test { endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: 9 ether, settledRevenue: uint96(0), settledPricePoint: uint96(0), settledEditionSize: uint16(0) @@ -1111,6 +1195,7 @@ contract VariableSupplyAuctionTest is Test { endOfBidPhase: uint32(block.timestamp + 3 days), endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: uint96(0), settledRevenue: _settledPricePoint * _settledEditionSize, settledPricePoint: _settledPricePoint, settledEditionSize: _settledEditionSize @@ -1137,10 +1222,10 @@ contract VariableSupplyAuctionTest is Test { uint32 endOfBidPhase; uint32 endOfRevealPhase; uint32 endOfSettlePhase; + uint96 totalBalance; uint96 settledRevenue; uint96 settledPricePoint; uint16 settledEditionSize; - // TODO do we still need totalBalance ? } struct Bid { @@ -1149,6 +1234,7 @@ contract VariableSupplyAuctionTest is Test { } event AuctionCreated(address indexed tokenContract, Auction auction); + event AuctionCanceled(address indexed tokenContract, Auction auction); event BidPlaced(address indexed tokenContract, address indexed bidder, Auction auction); event BidRevealed(address indexed tokenContract, address indexed bidder, uint256 indexed bidAmount, Auction auction); event AuctionSettled(address indexed tokenContract, Auction auction); From 63dca450cde6dccdb390b3df74e267ca09abb4e0 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Wed, 19 Oct 2022 21:21:33 -0400 Subject: [PATCH 14/31] [draft] Add initial claimRefund --- .gas-snapshot | 73 ++++----- .../IVariableSupplyAuction.sol | 3 + .../VariableSupplyAuction.sol | 37 ++++- .../VariableSupplyAuction.t.sol | 139 ++++++++++++++++-- 4 files changed, 203 insertions(+), 49 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index de72000c..c9253eee 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,40 +319,45 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 84921) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295307) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160314) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 413962) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 214706) -VariableSupplyAuctionTest:testEvent_createAuction() (gas: 97853) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91927) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159685) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99710) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20628) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97412) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27523) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106761) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 106707) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166093) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 101949) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159621) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160125) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102402) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 160834) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160516) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 160836) -VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 84968) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95065) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96300) -VariableSupplyAuctionTest:test_DropInitial() (gas: 30925) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 283851) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156057) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 400881) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 209763) -VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1403606) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2342070) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2597423) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2384613) +VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85002) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295648) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160429) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 414408) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 214923) +VariableSupplyAuctionTest:testEvent_claimRefund() (gas: 1161168) +VariableSupplyAuctionTest:testEvent_createAuction() (gas: 97955) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91962) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159800) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99767) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 1159524) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 997533) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20650) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97425) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27546) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106819) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 106787) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166186) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 101985) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159737) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160196) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102415) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 160972) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160609) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 160929) +VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85049) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95122) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96357) +VariableSupplyAuctionTest:test_DropInitial() (gas: 30947) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 284060) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156150) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401238) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 209891) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1404850) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2343393) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2598746) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2385958) +VariableSupplyAuctionTest:test_claimRefund_WhenNotWinner() (gas: 1253301) +VariableSupplyAuctionTest:test_claimRefund_WhenWinner() (gas: 1158602) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index 93065dd0..1b5d77d7 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -29,4 +29,7 @@ interface IVariableSupplyAuction { /// function settleAuction(address _tokenContract, uint96 _settlePricePoint) external; + + /// + function claimRefund(address _tokenContract) external; } diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 3ec76abe..ac078bd5 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -322,6 +322,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa ERC721Drop(_tokenContract).setEditionSize(uint64(winningBidders.length)); // Mint NFTs to winning bidders + // TODO fix by moving winningBidders into auction storage ERC721Drop(_tokenContract).adminMintAirdrop(winningBidders); // Transfer the auction revenue to the funds recipient @@ -334,6 +335,40 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa CLAIM REFUND //////////////////////////////////////////////////////////////*/ - // TODO + // TODO add UML + + /// @notice Emitted when a refund is claimed + /// @param tokenContract The address of the ERC-721 drop contract + /// @param bidder The address of the bidder claiming their refund + /// @param refundAmount The amount of the refund claimed + /// @param auction The metadata of the created auction + event RefundClaimed(address indexed tokenContract, address indexed bidder, uint96 refundAmount, Auction auction); + + // TODO add checkAvailableRefund(address _tokenContract) external + + /// @notice Claim refund -- if winner, for any additional ether sent above your + /// bid amount; if not winner, for the full amount of ether sent with you bid + /// @dev TODO doc re: temporal checks + /// @param _tokenContract The address of the ERC-721 drop contract + function claimRefund(address _tokenContract) external nonReentrant { + // Get the auction + Auction storage auction = auctionForDrop[_tokenContract]; + // TODO add temporal checks + + // Get the bid for the specified bidder + Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; + uint96 bidderBalance = bid.bidderBalance; + + // Ensure bidder has balance + require(bidderBalance > 0, "NO_REFUND_AVAILABLE"); + + // Clear bidder balance + bid.bidderBalance = 0; + + // Transfer the bidder's available refund balance to the bidder + _handleOutgoingTransfer(msg.sender, bidderBalance, address(0), 50_000); + + emit RefundClaimed(_tokenContract, msg.sender, bidderBalance, auction); + } } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 1bbaf468..65b61043 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -1067,30 +1067,140 @@ contract VariableSupplyAuctionTest is Test { CLAIM REFUND //////////////////////////////////////////////////////////////*/ - // function test_claimRefund() public setupBasicAuction { - // vm.prank(address(bidder1)); - // auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + function test_claimRefund_WhenWinner() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); - // vm.warp(3 days + 1 seconds); + vm.warp(3 days + 1 seconds); - // vm.prank(address(bidder1)); - // auctions.revealBid(address(drop), 1 ether, salt1); + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); - // vm.warp(3 days + 2 days + 1 seconds); + vm.warp(3 days + 2 days + 1 seconds); - // vm.prank(address(seller)); - // auctions.settleAuction(address(drop), 1 ether); + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 1 ether); - // vm.warp(3 days + 2 days + 1 days + 1 seconds); + // Precondition checks + assertEq(address(bidder1).balance, 98 ether); + (, uint96 bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + assertEq(bidderBalance, 1 ether); - // // Precondition checks - // assertEq(address(bidder1).balance, 98 ether); - // assertEq(, expected); + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); - // vm.prank(address(bidder1)); + assertEq(address(bidder1).balance, 99 ether); + (, bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + assertEq(bidderBalance, 0 ether); + } + + function test_claimRefund_WhenNotWinner() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + vm.prank(address(bidder2)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(2 ether, salt2)); + + vm.warp(3 days + 1 seconds); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + vm.prank(address(bidder2)); + auctions.revealBid(address(drop), 2 ether, salt2); + + vm.warp(3 days + 2 days + 1 seconds); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 2 ether); + + // Precondition checks + assertEq(address(bidder1).balance, 98 ether); + (, uint96 bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + assertEq(bidderBalance, 2 ether); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + + assertEq(address(bidder1).balance, 100 ether); // claim their full amount of sent ether + (, bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + assertEq(bidderBalance, 0 ether); + } + + function testEvent_claimRefund() public setupBasicAuction { + Auction memory auction = Auction({ + seller: address(seller), + minimumViableRevenue: 1 ether, + sellerFundsRecipient: address(sellerFundsRecipient), + startTime: uint32(block.timestamp), + endOfBidPhase: uint32(block.timestamp + 3 days), + endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), + endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + totalBalance: 1 ether, + settledRevenue: 1 ether, + settledPricePoint: 1 ether, + settledEditionSize: uint16(1) + }); + + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + vm.warp(3 days + 1 seconds); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + + vm.warp(3 days + 2 days + 1 seconds); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 1 ether); + + vm.expectEmit(true, true, true, true); + emit RefundClaimed(address(drop), address(bidder1), 1 ether, auction); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + } + + function testRevert_ClaimRefund_WhenNoBidPlaced() public setupBasicAuction { + vm.warp(3 days + 2 days + 1 seconds); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 1 ether); + + vm.expectRevert("NO_REFUND_AVAILABLE"); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + } + + // function testRevert_ClaimRefund_WhenNoBidRevealed() public setupBasicAuction { + // // TODO // } + function testRevert_ClaimRefund_WhenAlreadyClaimed() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + + vm.warp(3 days + 1 seconds); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + + vm.warp(3 days + 2 days + 1 seconds); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 1 ether); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + + vm.expectRevert("NO_REFUND_AVAILABLE"); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + } + + // TODO add temporal sad paths + // TODO add minimum viable revenue sad paths /*////////////////////////////////////////////////////////////// @@ -1238,4 +1348,5 @@ contract VariableSupplyAuctionTest is Test { event BidPlaced(address indexed tokenContract, address indexed bidder, Auction auction); event BidRevealed(address indexed tokenContract, address indexed bidder, uint256 indexed bidAmount, Auction auction); event AuctionSettled(address indexed tokenContract, Auction auction); + event RefundClaimed(address indexed tokenContract, address indexed bidder, uint96 refundAmount, Auction auction); } From 1320aa09a7dfb71010a07be95802e8cb958d18ec Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 24 Oct 2022 20:04:56 +0300 Subject: [PATCH 15/31] [draft] Add initial calculateSettleOptions --- .gas-snapshot | 63 +++++++-------- .../VariableSupplyAuction.sol | 78 ++++++++++++++++++- .../VariableSupplyAuction.t.sol | 40 ++++++++++ 3 files changed, 147 insertions(+), 34 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index c9253eee..dd22cec8 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,45 +319,46 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85002) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295648) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160429) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 414408) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 214923) -VariableSupplyAuctionTest:testEvent_claimRefund() (gas: 1161168) +VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85020) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295670) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160451) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 414430) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 214878) +VariableSupplyAuctionTest:testEvent_claimRefund() (gas: 1161220) VariableSupplyAuctionTest:testEvent_createAuction() (gas: 97955) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91962) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159800) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99767) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 1159524) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 997533) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91984) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159756) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99789) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 1159576) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 997563) VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20650) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97425) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27546) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97447) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27568) VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106819) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 106787) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 106809) VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166186) VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 101985) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159737) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159759) VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160196) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102415) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 160972) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160609) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102437) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 160927) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160631) VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 160929) -VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85049) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95122) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96357) -VariableSupplyAuctionTest:test_DropInitial() (gas: 30947) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 284060) +VariableSupplyAuctionTest:test_CalculateSettleOptions() (gas: 1550847) +VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85031) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95144) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96379) +VariableSupplyAuctionTest:test_DropInitial() (gas: 31013) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 284016) VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156150) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401238) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 209891) -VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1404850) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2343393) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2598746) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2385958) -VariableSupplyAuctionTest:test_claimRefund_WhenNotWinner() (gas: 1253301) -VariableSupplyAuctionTest:test_claimRefund_WhenWinner() (gas: 1158602) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401195) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 209848) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1404872) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2343381) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2598753) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2385943) +VariableSupplyAuctionTest:test_claimRefund_WhenNotWinner() (gas: 1253331) +VariableSupplyAuctionTest:test_claimRefund_WhenWinner() (gas: 1158632) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index ac078bd5..134c444e 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -271,16 +271,87 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // TODO add UML - // TODO add view function for price point + edition size options based on revealed bids - /// @notice Emitted when an auction is settled /// @param tokenContract The address of the ERC-721 drop contract /// @param auction The metadata of the created auction event AuctionSettled(address indexed tokenContract, Auction auction); + struct SettleOption { + uint16 editionSize; + uint96 revenue; + } + + uint96[] public settlePricePoints; + + mapping(uint96 => SettleOption) public settleMapping; + + /// @notice Calculate edition size and revenue for each possible price point + /// @dev Cheaper on subsequent calls, after the initial call when + /// calculations have been performed and saved + /// @param _tokenContract The address of the ERC-721 drop contract + /// @return A tuple of 3 arrays representing the settle options -- + /// the possible price points at which to settle, along with the + /// resulting edition sizes and amounts of revenue generated + function calculateSettleOptions(address _tokenContract) public returns (uint96[] memory, uint16[] memory, uint96[] memory) { + + // TODO gas optimization -- algorithm =P + // TODO gas optimization -- consider other, less storage-intensive options + // TODO gas optimization -- don't redo if already calculated + + address[] storage bidders = revealedBiddersForDrop[_tokenContract]; + + for (uint256 i = 0; i < bidders.length; i++) { + address bidder = bidders[i]; + uint96 bidAmount = bidsForDrop[_tokenContract][bidder].revealedBidAmount; + SettleOption storage settleOption = settleMapping[bidAmount]; + + if (settleOption.editionSize == 0) { + settlePricePoints.push(bidAmount); + settleOption.editionSize = 1; + } + } + + for (uint256 j = 0; j < bidders.length; j++) { + address bidder = bidders[j]; + uint96 bidAmount = bidsForDrop[_tokenContract][bidder].revealedBidAmount; + + for (uint256 k = 0; k < settlePricePoints.length; k++) { + uint96 settlePricePoint = settlePricePoints[k]; + SettleOption storage settleOption = settleMapping[settlePricePoint]; + + if (bidAmount >= settlePricePoint) { + settleOption.editionSize++; + settleOption.revenue += settlePricePoint; + } + } + } + + uint96[] memory pricePoints = new uint96[](settlePricePoints.length); + uint16[] memory editionSizes = new uint16[](settlePricePoints.length); + uint96[] memory revenues = new uint96[](settlePricePoints.length); + + for (uint256 m = 0; m < settlePricePoints.length; m++) { + uint96 settlePricePoint = settlePricePoints[m]; + SettleOption storage settleOption = settleMapping[settlePricePoint]; + + pricePoints[m] = settlePricePoint; + editionSizes[m] = settleOption.editionSize - 1; // because 1st bidder at this settle price was double counted + revenues[m] = settleOption.revenue; + + } + + return (pricePoints, editionSizes, revenues); + } + + /// @notice Settle an auction at a given price point + /// @param _tokenContract The address of the ERC-721 drop contract + /// @param _settlePricePoint The price point at which to settle the auction function settleAuction(address _tokenContract, uint96 _settlePricePoint) external nonReentrant { + // TODO checks + // TODO decide how to store the fact that this auction is settled + // TODO gas optimizations // Get the auction @@ -294,6 +365,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Loop through bids to determine winners and edition size // TODO document pragmatic max edition size / winning bidders + // TODO switch to dynamically sized array address[] memory winningBidders = new address[](1000); uint16 editionSize; for (uint256 i = 0; i < bidders.length; i++) { @@ -322,7 +394,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa ERC721Drop(_tokenContract).setEditionSize(uint64(winningBidders.length)); // Mint NFTs to winning bidders - // TODO fix by moving winningBidders into auction storage + // TODO consider moving winningBidders into storage ERC721Drop(_tokenContract).adminMintAirdrop(winningBidders); // Transfer the auction revenue to the funds recipient diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 65b61043..a5d8d88a 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -420,6 +420,8 @@ contract VariableSupplyAuctionTest is Test { PLACE BID //////////////////////////////////////////////////////////////*/ + // TODO add more assertions around new storage variables + function test_PlaceBid_WhenSingle() public setupBasicAuction { bytes32 commitment = _genSealedBid(1 ether, salt1); @@ -806,6 +808,44 @@ contract VariableSupplyAuctionTest is Test { SETTLE AUCTION //////////////////////////////////////////////////////////////*/ + function test_CalculateSettleOptions() public setupBasicAuction throughRevealPhaseComplex { + (uint96[] memory pricePoints, uint16[] memory editionSizes, uint96[] memory revenues) = + auctions.calculateSettleOptions(address(drop)); + + assertEq(editionSizes.length, pricePoints.length); + assertEq(revenues.length, pricePoints.length); + + // for (uint256 i = 0; i < pricePoints.length; i++) { + // emit log_string("Option -------------"); + // emit log_named_uint("Price point", pricePoints[i] / 1 ether); + // emit log_named_uint("Edition size", editionSizes[i]); + // emit log_named_uint("Revenue", revenues[i] / 1 ether); + // } + + /* + + Expected output: + + [Price Point] [Edition Size] [Revenue] + [1] [13] [13] + [6] [3] [18] + [11] [1] [11] + + */ + + assertEq(pricePoints[0], 1 ether); + assertEq(editionSizes[0], 13); + assertEq(revenues[0], 13 ether); + + assertEq(pricePoints[1], 6 ether); + assertEq(editionSizes[1], 3); + assertEq(revenues[1], 18 ether); + + assertEq(pricePoints[2], 11 ether); + assertEq(editionSizes[2], 1); + assertEq(revenues[2], 11 ether); + } + /* Scenario for the following 3 settleAuction unit tests From 4294ec34e3ba04c23999b2e3c34c7f4e99f883e0 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 24 Oct 2022 20:23:35 +0300 Subject: [PATCH 16/31] [draft] Add checkAvailableRefund --- .gas-snapshot | 47 ++++++++++--------- .../IVariableSupplyAuction.sol | 6 +++ .../VariableSupplyAuction.sol | 20 ++++++-- .../VariableSupplyAuction.t.sol | 38 +++++++++++++-- 4 files changed, 80 insertions(+), 31 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index dd22cec8..9ba9f888 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -320,45 +320,46 @@ ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85020) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295670) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160451) +VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 1161244) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295625) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160473) VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 414430) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 214878) -VariableSupplyAuctionTest:testEvent_claimRefund() (gas: 1161220) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 214900) VariableSupplyAuctionTest:testEvent_createAuction() (gas: 97955) VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91984) VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159756) VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99789) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 1159576) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 997563) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 1159688) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 997608) VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20650) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97447) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27568) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97469) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27546) VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106819) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 106809) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 106787) VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166186) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 101985) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159759) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102007) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159714) VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160196) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102437) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 160927) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160631) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 160929) -VariableSupplyAuctionTest:test_CalculateSettleOptions() (gas: 1550847) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102394) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 160949) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160609) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 160951) +VariableSupplyAuctionTest:test_CalculateSettleOptions() (gas: 1550891) VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85031) +VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 1355824) +VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 1253353) +VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 1158701) VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95144) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96379) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96334) VariableSupplyAuctionTest:test_DropInitial() (gas: 31013) VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 284016) VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156150) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401195) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 209848) -VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1404872) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401217) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 209870) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1404894) VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2343381) VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2598753) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2385943) -VariableSupplyAuctionTest:test_claimRefund_WhenNotWinner() (gas: 1253331) -VariableSupplyAuctionTest:test_claimRefund_WhenWinner() (gas: 1158632) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2385988) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index 1b5d77d7..29972c7a 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -27,9 +27,15 @@ interface IVariableSupplyAuction { /// function revealBid(address _tokenContract, uint256 _bidAmount, string calldata _salt) external; + /// + function calculateSettleOptions(address _tokenContract) external returns (uint96[] memory, uint16[] memory, uint96[] memory); + /// function settleAuction(address _tokenContract, uint96 _settlePricePoint) external; + /// + function checkAvailableRefund(address _tokenContract) external view returns (uint96); + /// function claimRefund(address _tokenContract) external; } diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 134c444e..aa442cca 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -292,7 +292,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @return A tuple of 3 arrays representing the settle options -- /// the possible price points at which to settle, along with the /// resulting edition sizes and amounts of revenue generated - function calculateSettleOptions(address _tokenContract) public returns (uint96[] memory, uint16[] memory, uint96[] memory) { + function calculateSettleOptions(address _tokenContract) external returns (uint96[] memory, uint16[] memory, uint96[] memory) { // TODO gas optimization -- algorithm =P // TODO gas optimization -- consider other, less storage-intensive options @@ -416,10 +416,20 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param auction The metadata of the created auction event RefundClaimed(address indexed tokenContract, address indexed bidder, uint96 refundAmount, Auction auction); - // TODO add checkAvailableRefund(address _tokenContract) external + /// @notice Check available refund -- if winner, any additional ether sent above your + /// bid amount; if not winner, the full amount of ether sent with your bid + /// @param _tokenContract The address of the ERC-721 drop contract + function checkAvailableRefund(address _tokenContract) external view returns (uint96) { + // TODO add checks - /// @notice Claim refund -- if winner, for any additional ether sent above your - /// bid amount; if not winner, for the full amount of ether sent with you bid + // Get the balance for the specified bidder + Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; + + return bid.bidderBalance; + } + + /// @notice Claim refund -- if winner, any additional ether sent above your + /// bid amount; if not winner, the full amount of ether sent with your bid /// @dev TODO doc re: temporal checks /// @param _tokenContract The address of the ERC-721 drop contract function claimRefund(address _tokenContract) external nonReentrant { @@ -428,7 +438,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // TODO add temporal checks - // Get the bid for the specified bidder + // Get the balance for the specified bidder Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; uint96 bidderBalance = bid.bidderBalance; diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index a5d8d88a..d6ac7d15 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -1107,7 +1107,39 @@ contract VariableSupplyAuctionTest is Test { CLAIM REFUND //////////////////////////////////////////////////////////////*/ - function test_claimRefund_WhenWinner() public setupBasicAuction { + function test_CheckAvailableRefund() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + vm.prank(address(bidder2)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(2 ether, salt2)); + vm.prank(address(bidder3)); + auctions.placeBid{value: 3 ether}(address(drop), _genSealedBid(2 ether, salt3)); + + vm.warp(3 days + 1 seconds); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + vm.prank(address(bidder2)); + auctions.revealBid(address(drop), 2 ether, salt2); + vm.prank(address(bidder3)); + auctions.revealBid(address(drop), 2 ether, salt3); + + vm.warp(3 days + 2 days + 1 seconds); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 2 ether); + + vm.prank(address(bidder1)); + assertEq(auctions.checkAvailableRefund(address(drop)), 2 ether); // = full amount sent, not winning bid + + vm.prank(address(bidder2)); + assertEq(auctions.checkAvailableRefund(address(drop)), 0 ether); // = 2 ether sent - 2 ether winning bid + + vm.prank(address(bidder3)); + assertEq(auctions.checkAvailableRefund(address(drop)), 1 ether); // = 3 ether sent - 2 ether winning bid + } + + function test_ClaimRefund_WhenWinner() public setupBasicAuction { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); @@ -1134,7 +1166,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance, 0 ether); } - function test_claimRefund_WhenNotWinner() public setupBasicAuction { + function test_ClaimRefund_WhenNotWinner() public setupBasicAuction { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); vm.prank(address(bidder2)); @@ -1165,7 +1197,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance, 0 ether); } - function testEvent_claimRefund() public setupBasicAuction { + function testEvent_ClaimRefund() public setupBasicAuction { Auction memory auction = Auction({ seller: address(seller), minimumViableRevenue: 1 ether, From eacdc1a30ba4c7681702f66bba1c99091d0b9fb9 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 24 Oct 2022 21:42:55 +0300 Subject: [PATCH 17/31] [draft] Add initial UML diagrams --- .../VariableSupplyAuction.sol | 13 ++++++++----- .../VariableSupplyAuction.feature | 3 ++- uml/VariableSupplyAuction/1-createAuction.txt | 11 +++++++++++ uml/VariableSupplyAuction/2-cancelAuction.txt | 11 +++++++++++ uml/VariableSupplyAuction/3-placeBid.txt | 11 +++++++++++ uml/VariableSupplyAuction/4-revealBid.txt | 12 ++++++++++++ .../5-calculateSettleOptions.txt | 17 +++++++++++++++++ uml/VariableSupplyAuction/6-settleAuction.txt | 19 +++++++++++++++++++ .../7-checkAvailableRefund.txt | 10 ++++++++++ uml/VariableSupplyAuction/8-claimRefund.txt | 12 ++++++++++++ 10 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 uml/VariableSupplyAuction/1-createAuction.txt create mode 100644 uml/VariableSupplyAuction/2-cancelAuction.txt create mode 100644 uml/VariableSupplyAuction/3-placeBid.txt create mode 100644 uml/VariableSupplyAuction/4-revealBid.txt create mode 100644 uml/VariableSupplyAuction/5-calculateSettleOptions.txt create mode 100644 uml/VariableSupplyAuction/6-settleAuction.txt create mode 100644 uml/VariableSupplyAuction/7-checkAvailableRefund.txt create mode 100644 uml/VariableSupplyAuction/8-claimRefund.txt diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index aa442cca..92b9b0ea 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -363,11 +363,13 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Get the balances for this auction mapping(address => Bid) storage bids = bidsForDrop[_tokenContract]; - // Loop through bids to determine winners and edition size + // Loop through bids to determine edition size and winning bidders + // TODO consolidate business logic with calculateSettleOptions // TODO document pragmatic max edition size / winning bidders // TODO switch to dynamically sized array - address[] memory winningBidders = new address[](1000); + // TODO consider moving winningBidders into storage uint16 editionSize; + address[] memory winningBidders = new address[](1000); for (uint256 i = 0; i < bidders.length; i++) { // Cache the bidder address bidder = bidders[i]; @@ -394,7 +396,6 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa ERC721Drop(_tokenContract).setEditionSize(uint64(winningBidders.length)); // Mint NFTs to winning bidders - // TODO consider moving winningBidders into storage ERC721Drop(_tokenContract).adminMintAirdrop(winningBidders); // Transfer the auction revenue to the funds recipient @@ -420,11 +421,11 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// bid amount; if not winner, the full amount of ether sent with your bid /// @param _tokenContract The address of the ERC-721 drop contract function checkAvailableRefund(address _tokenContract) external view returns (uint96) { - // TODO add checks + // TODO add checks, including cleanup phase // Get the balance for the specified bidder Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; - + return bid.bidderBalance; } @@ -453,4 +454,6 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa emit RefundClaimed(_tokenContract, msg.sender, bidderBalance, auction); } + + // TODO consider cleanup function } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature index 4f97d2ff..0e9af083 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature @@ -8,7 +8,8 @@ Feature: Variable Supply Auctions - Created - Bid Phase - Reveal Phase - - Sizing Phase + - Settle Phase + - TODO consider Cleanup phase - Completed / Cancelled Background: VSA creation and bidding diff --git a/uml/VariableSupplyAuction/1-createAuction.txt b/uml/VariableSupplyAuction/1-createAuction.txt new file mode 100644 index 00000000..a90b04d8 --- /dev/null +++ b/uml/VariableSupplyAuction/1-createAuction.txt @@ -0,0 +1,11 @@ +@startuml +actor Seller +participant VariableSupplyAuction + +Seller -> VariableSupplyAuction : createAuction() + +VariableSupplyAuction -> VariableSupplyAuction : calculate phase end times +VariableSupplyAuction -> VariableSupplyAuction : store auction metadata +VariableSupplyAuction -> VariableSupplyAuction : emit AuctionCreated() + +@enduml diff --git a/uml/VariableSupplyAuction/2-cancelAuction.txt b/uml/VariableSupplyAuction/2-cancelAuction.txt new file mode 100644 index 00000000..154cc9e0 --- /dev/null +++ b/uml/VariableSupplyAuction/2-cancelAuction.txt @@ -0,0 +1,11 @@ +@startuml +actor Seller +participant VariableSupplyAuction + +Seller -> VariableSupplyAuction : cancelAuction() + +VariableSupplyAuction -> VariableSupplyAuction : validate no bids placed yet +VariableSupplyAuction -> VariableSupplyAuction : emit AuctionCanceled() +VariableSupplyAuction -> VariableSupplyAuction : delete auction + +@enduml diff --git a/uml/VariableSupplyAuction/3-placeBid.txt b/uml/VariableSupplyAuction/3-placeBid.txt new file mode 100644 index 00000000..528e2528 --- /dev/null +++ b/uml/VariableSupplyAuction/3-placeBid.txt @@ -0,0 +1,11 @@ +@startuml +actor bidder +participant VariableSupplyAuction + +Bidder -> VariableSupplyAuction : placeBid() + +VariableSupplyAuction -> VariableSupplyAuction : validate auction is in bid phase +VariableSupplyAuction -> VariableSupplyAuction : update auction balance and store sealed bid +VariableSupplyAuction -> VariableSupplyAuction : emit BidPlaced() + +@enduml diff --git a/uml/VariableSupplyAuction/4-revealBid.txt b/uml/VariableSupplyAuction/4-revealBid.txt new file mode 100644 index 00000000..0b5fbe66 --- /dev/null +++ b/uml/VariableSupplyAuction/4-revealBid.txt @@ -0,0 +1,12 @@ +@startuml +actor bidder +participant VariableSupplyAuction + +Bidder -> VariableSupplyAuction : revealBid() + +VariableSupplyAuction -> VariableSupplyAuction : validate auction is in reveal phase +VariableSupplyAuction -> VariableSupplyAuction : validate revealed bid matches sealed bid +VariableSupplyAuction -> VariableSupplyAuction : store revealed bid +VariableSupplyAuction -> VariableSupplyAuction : emit BidRevealed() + +@enduml diff --git a/uml/VariableSupplyAuction/5-calculateSettleOptions.txt b/uml/VariableSupplyAuction/5-calculateSettleOptions.txt new file mode 100644 index 00000000..41c01e23 --- /dev/null +++ b/uml/VariableSupplyAuction/5-calculateSettleOptions.txt @@ -0,0 +1,17 @@ +@startuml +actor caller +participant VariableSupplyAuction + +Caller -> VariableSupplyAuction : calculateSettleOptions() + +alt Not calculated yet? + + VariableSupplyAuction -> VariableSupplyAuction : calculate and store settle options + +else noop + +end + + VariableSupplyAuction -> VariableSupplyAuction : return stored settle options + +@enduml diff --git a/uml/VariableSupplyAuction/6-settleAuction.txt b/uml/VariableSupplyAuction/6-settleAuction.txt new file mode 100644 index 00000000..27b3d6d0 --- /dev/null +++ b/uml/VariableSupplyAuction/6-settleAuction.txt @@ -0,0 +1,19 @@ +@startuml +actor seller +participant VariableSupplyAuction +participant ERC721Drop + +Seller -> VariableSupplyAuction : settleAuction() + +VariableSupplyAuction -> VariableSupplyAuction : validate auction is in settle phase +VariableSupplyAuction -> VariableSupplyAuction : validate auction not settled yet +VariableSupplyAuction -> VariableSupplyAuction : based on chosen price point, calculate and store final edition size and winning bidders +VariableSupplyAuction -> VariableSupplyAuction : update bidder balances to final available refund amounts +VariableSupplyAuction -> ERC721Drop : setEditionSize() +ERC721Drop -> ERC721Drop : update drop edition size +VariableSupplyAuction -> ERC721Drop : adminMintAirdrop() +ERC721Drop -> ERC721Drop : mint NFT to winning bidders +VariableSupplyAuction -> VariableSupplyAuction : handle seller funds recipient payout +VariableSupplyAuction -> VariableSupplyAuction : emit AuctionSettled() + +@enduml diff --git a/uml/VariableSupplyAuction/7-checkAvailableRefund.txt b/uml/VariableSupplyAuction/7-checkAvailableRefund.txt new file mode 100644 index 00000000..8bcc25be --- /dev/null +++ b/uml/VariableSupplyAuction/7-checkAvailableRefund.txt @@ -0,0 +1,10 @@ +@startuml +actor bidder +participant VariableSupplyAuction + +Bidder -> VariableSupplyAuction : checkAvailableRefund() + +VariableSupplyAuction -> VariableSupplyAuction : validate auction is in cleanup phase +VariableSupplyAuction -> VariableSupplyAuction : return stored available refund for bidder + +@enduml diff --git a/uml/VariableSupplyAuction/8-claimRefund.txt b/uml/VariableSupplyAuction/8-claimRefund.txt new file mode 100644 index 00000000..7c5b4fa2 --- /dev/null +++ b/uml/VariableSupplyAuction/8-claimRefund.txt @@ -0,0 +1,12 @@ +@startuml +actor bidder +participant VariableSupplyAuction + +Bidder -> VariableSupplyAuction : claimRefund() + +VariableSupplyAuction -> VariableSupplyAuction : validate auction is in cleanup phase +VariableSupplyAuction -> VariableSupplyAuction : clear bidder available refund +VariableSupplyAuction -> VariableSupplyAuction : handle bidder available refund payout +VariableSupplyAuction -> VariableSupplyAuction : emit RefundClaimed() + +@enduml From bdb38af4b3c3763caf64558fa6203147b4680461 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 25 Oct 2022 10:37:39 -0400 Subject: [PATCH 18/31] [draft] :sparkles: Add generated PlantUML ascii diagrams --- .../1-createAuction.atxt | 26 ++++++++++ uml/VariableSupplyAuction/1-createAuction.txt | 2 +- .../2-cancelAuction.atxt | 26 ++++++++++ uml/VariableSupplyAuction/2-cancelAuction.txt | 2 +- uml/VariableSupplyAuction/3-placeBid.atxt | 26 ++++++++++ uml/VariableSupplyAuction/3-placeBid.txt | 4 +- uml/VariableSupplyAuction/4-revealBid.atxt | 30 +++++++++++ uml/VariableSupplyAuction/4-revealBid.txt | 4 +- .../5-calculateSettleOptions.atxt | 28 ++++++++++ .../5-calculateSettleOptions.txt | 4 +- .../6-settleAuction.atxt | 52 +++++++++++++++++++ uml/VariableSupplyAuction/6-settleAuction.txt | 4 +- .../7-checkAvailableRefund.atxt | 22 ++++++++ .../7-checkAvailableRefund.txt | 4 +- uml/VariableSupplyAuction/8-claimRefund.atxt | 30 +++++++++++ uml/VariableSupplyAuction/8-claimRefund.txt | 4 +- 16 files changed, 254 insertions(+), 14 deletions(-) create mode 100644 uml/VariableSupplyAuction/1-createAuction.atxt create mode 100644 uml/VariableSupplyAuction/2-cancelAuction.atxt create mode 100644 uml/VariableSupplyAuction/3-placeBid.atxt create mode 100644 uml/VariableSupplyAuction/4-revealBid.atxt create mode 100644 uml/VariableSupplyAuction/5-calculateSettleOptions.atxt create mode 100644 uml/VariableSupplyAuction/6-settleAuction.atxt create mode 100644 uml/VariableSupplyAuction/7-checkAvailableRefund.atxt create mode 100644 uml/VariableSupplyAuction/8-claimRefund.atxt diff --git a/uml/VariableSupplyAuction/1-createAuction.atxt b/uml/VariableSupplyAuction/1-createAuction.atxt new file mode 100644 index 00000000..32580d14 --- /dev/null +++ b/uml/VariableSupplyAuction/1-createAuction.atxt @@ -0,0 +1,26 @@ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Seller `----------+----------' + | createAuction() | + | ----------------------->| + | | + | ----. + | | calculate phase end times + | <---' + | | + | ----. + | | store auction metadata + | <---' + | | + | ----. + | | emit AuctionCreated() + | <---' + Seller ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/1-createAuction.txt b/uml/VariableSupplyAuction/1-createAuction.txt index a90b04d8..ab9b09f5 100644 --- a/uml/VariableSupplyAuction/1-createAuction.txt +++ b/uml/VariableSupplyAuction/1-createAuction.txt @@ -8,4 +8,4 @@ VariableSupplyAuction -> VariableSupplyAuction : calculate phase end times VariableSupplyAuction -> VariableSupplyAuction : store auction metadata VariableSupplyAuction -> VariableSupplyAuction : emit AuctionCreated() -@enduml +@enduml \ No newline at end of file diff --git a/uml/VariableSupplyAuction/2-cancelAuction.atxt b/uml/VariableSupplyAuction/2-cancelAuction.atxt new file mode 100644 index 00000000..11e48fc8 --- /dev/null +++ b/uml/VariableSupplyAuction/2-cancelAuction.atxt @@ -0,0 +1,26 @@ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Seller `----------+----------' + | cancelAuction() | + | ----------------------->| + | | + | ----. + | | validate no bids placed yet + | <---' + | | + | ----. + | | emit AuctionCanceled() + | <---' + | | + | ----. + | | delete auction + | <---' + Seller ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/2-cancelAuction.txt b/uml/VariableSupplyAuction/2-cancelAuction.txt index 154cc9e0..017eff02 100644 --- a/uml/VariableSupplyAuction/2-cancelAuction.txt +++ b/uml/VariableSupplyAuction/2-cancelAuction.txt @@ -8,4 +8,4 @@ VariableSupplyAuction -> VariableSupplyAuction : validate no bids placed yet VariableSupplyAuction -> VariableSupplyAuction : emit AuctionCanceled() VariableSupplyAuction -> VariableSupplyAuction : delete auction -@enduml +@enduml \ No newline at end of file diff --git a/uml/VariableSupplyAuction/3-placeBid.atxt b/uml/VariableSupplyAuction/3-placeBid.atxt new file mode 100644 index 00000000..2cc997d4 --- /dev/null +++ b/uml/VariableSupplyAuction/3-placeBid.atxt @@ -0,0 +1,26 @@ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Bidder `----------+----------' + | placeBid() | + | ----------------------->| + | | + | ----. + | | validate auction is in bid phase + | <---' + | | + | ----. + | | update auction balance and store sealed bid + | <---' + | | + | ----. + | | emit BidPlaced() + | <---' + Bidder ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/3-placeBid.txt b/uml/VariableSupplyAuction/3-placeBid.txt index 528e2528..b4ae7433 100644 --- a/uml/VariableSupplyAuction/3-placeBid.txt +++ b/uml/VariableSupplyAuction/3-placeBid.txt @@ -1,5 +1,5 @@ @startuml -actor bidder +actor Bidder participant VariableSupplyAuction Bidder -> VariableSupplyAuction : placeBid() @@ -8,4 +8,4 @@ VariableSupplyAuction -> VariableSupplyAuction : validate auction is in bid phas VariableSupplyAuction -> VariableSupplyAuction : update auction balance and store sealed bid VariableSupplyAuction -> VariableSupplyAuction : emit BidPlaced() -@enduml +@enduml \ No newline at end of file diff --git a/uml/VariableSupplyAuction/4-revealBid.atxt b/uml/VariableSupplyAuction/4-revealBid.atxt new file mode 100644 index 00000000..41dd9d4e --- /dev/null +++ b/uml/VariableSupplyAuction/4-revealBid.atxt @@ -0,0 +1,30 @@ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Bidder `----------+----------' + | revealBid() | + | ----------------------->| + | | + | ----. + | | validate auction is in reveal phase + | <---' + | | + | ----. + | | validate revealed bid matches sealed bid + | <---' + | | + | ----. + | | store revealed bid + | <---' + | | + | ----. + | | emit BidRevealed() + | <---' + Bidder ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/4-revealBid.txt b/uml/VariableSupplyAuction/4-revealBid.txt index 0b5fbe66..11d2fa66 100644 --- a/uml/VariableSupplyAuction/4-revealBid.txt +++ b/uml/VariableSupplyAuction/4-revealBid.txt @@ -1,5 +1,5 @@ @startuml -actor bidder +actor Bidder participant VariableSupplyAuction Bidder -> VariableSupplyAuction : revealBid() @@ -9,4 +9,4 @@ VariableSupplyAuction -> VariableSupplyAuction : validate revealed bid matches s VariableSupplyAuction -> VariableSupplyAuction : store revealed bid VariableSupplyAuction -> VariableSupplyAuction : emit BidRevealed() -@enduml +@enduml \ No newline at end of file diff --git a/uml/VariableSupplyAuction/5-calculateSettleOptions.atxt b/uml/VariableSupplyAuction/5-calculateSettleOptions.atxt new file mode 100644 index 00000000..74df66f2 --- /dev/null +++ b/uml/VariableSupplyAuction/5-calculateSettleOptions.atxt @@ -0,0 +1,28 @@ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Caller `----------+----------' + | calculateSettleOptions()| + | ------------------------> + | | + | | + | _________________________________________________________________ + | ! ALT / Not calculated yet? ! + | !_____/ | ! + | ! |----. ! + | ! | | calculate and store settle options ! + | ! |<---' ! + | !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! + | !~[noop]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! + | | + | |----. + | | | return stored settle options + | |<---' + Caller ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/5-calculateSettleOptions.txt b/uml/VariableSupplyAuction/5-calculateSettleOptions.txt index 41c01e23..8aaa3253 100644 --- a/uml/VariableSupplyAuction/5-calculateSettleOptions.txt +++ b/uml/VariableSupplyAuction/5-calculateSettleOptions.txt @@ -1,5 +1,5 @@ @startuml -actor caller +actor Caller participant VariableSupplyAuction Caller -> VariableSupplyAuction : calculateSettleOptions() @@ -14,4 +14,4 @@ end VariableSupplyAuction -> VariableSupplyAuction : return stored settle options -@enduml +@enduml \ No newline at end of file diff --git a/uml/VariableSupplyAuction/6-settleAuction.atxt b/uml/VariableSupplyAuction/6-settleAuction.atxt new file mode 100644 index 00000000..ca00ee6d --- /dev/null +++ b/uml/VariableSupplyAuction/6-settleAuction.atxt @@ -0,0 +1,52 @@ + ,-. + `-' + /|\ + | ,---------------------. ,----------. + / \ |VariableSupplyAuction| |ERC721Drop| + Seller `----------+----------' `----+-----' + | settleAuction() | | + | ----------------------->| | + | | | + | ----. | + | | validate auction is in settle phase | + | <---' | + | | | + | ----. | + | | validate auction not settled yet | + | <---' | + | | | + | ----. + | | based on chosen price point, calculate and store final edition size and winning bidders + | <---' + | | | + | ----. | + | | update bidder balances to final available refund amounts | + | <---' | + | | | + | | setEditionSize() | + | |-------------------------------------------------------------------------------------------> + | | | + | | |----. + | | | | update drop edition size + | | |<---' + | | | + | | adminMintAirdrop() | + | |-------------------------------------------------------------------------------------------> + | | | + | | |----. + | | | | mint NFT to winning bidders + | | |<---' + | | | + | ----. | + | | handle seller funds recipient payout | + | <---' | + | | | + | ----. | + | | emit AuctionSettled() | + | <---' | + Seller ,----------+----------. ,----+-----. + ,-. |VariableSupplyAuction| |ERC721Drop| + `-' `---------------------' `----------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/6-settleAuction.txt b/uml/VariableSupplyAuction/6-settleAuction.txt index 27b3d6d0..c8fbcb38 100644 --- a/uml/VariableSupplyAuction/6-settleAuction.txt +++ b/uml/VariableSupplyAuction/6-settleAuction.txt @@ -1,5 +1,5 @@ @startuml -actor seller +actor Seller participant VariableSupplyAuction participant ERC721Drop @@ -16,4 +16,4 @@ ERC721Drop -> ERC721Drop : mint NFT to winning bidders VariableSupplyAuction -> VariableSupplyAuction : handle seller funds recipient payout VariableSupplyAuction -> VariableSupplyAuction : emit AuctionSettled() -@enduml +@enduml \ No newline at end of file diff --git a/uml/VariableSupplyAuction/7-checkAvailableRefund.atxt b/uml/VariableSupplyAuction/7-checkAvailableRefund.atxt new file mode 100644 index 00000000..bbfe1a59 --- /dev/null +++ b/uml/VariableSupplyAuction/7-checkAvailableRefund.atxt @@ -0,0 +1,22 @@ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Bidder `----------+----------' + | checkAvailableRefund() | + | ----------------------->| + | | + | ----. + | | validate auction is in cleanup phase + | <---' + | | + | ----. + | | return stored available refund for bidder + | <---' + Bidder ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/7-checkAvailableRefund.txt b/uml/VariableSupplyAuction/7-checkAvailableRefund.txt index 8bcc25be..d3661ff3 100644 --- a/uml/VariableSupplyAuction/7-checkAvailableRefund.txt +++ b/uml/VariableSupplyAuction/7-checkAvailableRefund.txt @@ -1,5 +1,5 @@ @startuml -actor bidder +actor Bidder participant VariableSupplyAuction Bidder -> VariableSupplyAuction : checkAvailableRefund() @@ -7,4 +7,4 @@ Bidder -> VariableSupplyAuction : checkAvailableRefund() VariableSupplyAuction -> VariableSupplyAuction : validate auction is in cleanup phase VariableSupplyAuction -> VariableSupplyAuction : return stored available refund for bidder -@enduml +@enduml \ No newline at end of file diff --git a/uml/VariableSupplyAuction/8-claimRefund.atxt b/uml/VariableSupplyAuction/8-claimRefund.atxt new file mode 100644 index 00000000..ee74a432 --- /dev/null +++ b/uml/VariableSupplyAuction/8-claimRefund.atxt @@ -0,0 +1,30 @@ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Bidder `----------+----------' + | claimRefund() | + | ----------------------->| + | | + | ----. + | | validate auction is in cleanup phase + | <---' + | | + | ----. + | | clear bidder available refund + | <---' + | | + | ----. + | | handle bidder available refund payout + | <---' + | | + | ----. + | | emit RefundClaimed() + | <---' + Bidder ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/8-claimRefund.txt b/uml/VariableSupplyAuction/8-claimRefund.txt index 7c5b4fa2..69b811a4 100644 --- a/uml/VariableSupplyAuction/8-claimRefund.txt +++ b/uml/VariableSupplyAuction/8-claimRefund.txt @@ -1,5 +1,5 @@ @startuml -actor bidder +actor Bidder participant VariableSupplyAuction Bidder -> VariableSupplyAuction : claimRefund() @@ -9,4 +9,4 @@ VariableSupplyAuction -> VariableSupplyAuction : clear bidder available refund VariableSupplyAuction -> VariableSupplyAuction : handle bidder available refund payout VariableSupplyAuction -> VariableSupplyAuction : emit RefundClaimed() -@enduml +@enduml \ No newline at end of file From 30536fc03ebe794fb1d6222807bf0edccce323f0 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 25 Oct 2022 11:41:38 -0400 Subject: [PATCH 19/31] [draft] General cleanup; Inline UML diagrams --- .../VariableSupplyAuction.sol | 237 ++++++++++++++++-- .../temp-MockERC721Drop.sol | 10 +- .../1-VariableSupplyAuction.jtbd | 17 ++ ...eature => 2-VariableSupplyAuction.feature} | 83 ++++-- ...iant => 3-VariableSupplyAuction.invariant} | 2 +- .../VariableSupplyAuction.invariant.t.sol | 3 +- .../VariableSupplyAuction.jtbd | 13 - .../VariableSupplyAuction.t.sol | 90 +++---- 8 files changed, 333 insertions(+), 122 deletions(-) create mode 100644 contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd rename contracts/test/modules/VariableSupplyAuction/{VariableSupplyAuction.feature => 2-VariableSupplyAuction.feature} (60%) rename contracts/test/modules/VariableSupplyAuction/{VariableSupplyAuction.invariant => 3-VariableSupplyAuction.invariant} (85%) delete mode 100644 contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.jtbd diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 92b9b0ea..a4412bac 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -28,9 +28,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa ) FeePayoutSupportV1(_royaltyEngine, _protocolFeeSettings, _weth, ERC721TransferHelper(_erc721TransferHelper).ZMM().registrar()) ModuleNamingSupportV1("Variable Supply Auction") - { - // erc721TransferHelper = ERC721TransferHelper(_erc721TransferHelper); - } + {} /*////////////////////////////////////////////////////////////// AUCTION STORAGE @@ -43,7 +41,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param startTime The unix timestamp after which the first bid can be placed /// @param endOfBidPhase The unix timestamp until which bids can be placed /// @param endOfRevealPhase The unix timestamp until which placed bids can be revealed - /// @param endOfSettlePhase The unix timestamp until which the seller can settle the auction (TODO clarify can vs. must) + /// @param endOfSettlePhase The unix timestamp until which the seller must settle the auction /// @param totalBalance The total balance of all sent ether for this auction /// @param settledRevenue The total revenue generated by the drop /// @param settledPricePoint The chosen price point for the drop @@ -94,7 +92,32 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa CREATE AUCTION //////////////////////////////////////////////////////////////*/ - // TODO add UML diagram + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Seller `----------+----------' + // | createAuction() | + // | ----------------------->| + // | | + // | ----. + // | | calculate phase end times + // | <---' + // | | + // | ----. + // | | store auction metadata + // | <---' + // | | + // | ----. + // | | emit AuctionCreated() + // | <---' + // Seller ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when an auction is created /// @param tokenContract The address of the ERC-721 drop contract @@ -146,7 +169,32 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa CANCEL AUCTION //////////////////////////////////////////////////////////////*/ - // TODO add UML + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Seller `----------+----------' + // | cancelAuction() | + // | ----------------------->| + // | | + // | ----. + // | | validate no bids placed yet + // | <---' + // | | + // | ----. + // | | emit AuctionCanceled() + // | <---' + // | | + // | ----. + // | | delete auction + // | <---' + // Seller ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when an auction is canceled /// @param tokenContract The address of the ERC-721 drop contract @@ -175,7 +223,32 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa PLACE BID //////////////////////////////////////////////////////////////*/ - // TODO add UML + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Bidder `----------+----------' + // | placeBid() | + // | ----------------------->| + // | | + // | ----. + // | | validate auction is in bid phase + // | <---' + // | | + // | ----. + // | | update auction balance and store sealed bid + // | <---' + // | | + // | ----. + // | | emit BidPlaced() + // | <---' + // Bidder ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when a bid is placed /// @param tokenContract The address of the ERC-721 drop contract @@ -222,7 +295,36 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa REVEAL BID //////////////////////////////////////////////////////////////*/ - // TODO add UML + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Bidder `----------+----------' + // | revealBid() | + // | ----------------------->| + // | | + // | ----. + // | | validate auction is in reveal phase + // | <---' + // | | + // | ----. + // | | validate revealed bid matches sealed bid + // | <---' + // | | + // | ----. + // | | store revealed bid + // | <---' + // | | + // | ----. + // | | emit BidRevealed() + // | <---' + // Bidder ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when a bid is revealed /// @param tokenContract The address of the ERC-721 drop contract @@ -269,7 +371,58 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa SETTLE AUCTION //////////////////////////////////////////////////////////////*/ - // TODO add UML + // ,-. + // `-' + // /|\ + // | ,---------------------. ,----------. + // / \ |VariableSupplyAuction| |ERC721Drop| + // Seller `----------+----------' `----+-----' + // | settleAuction() | | + // | ----------------------->| | + // | | | + // | ----. | + // | | validate auction is in settle phase | + // | <---' | + // | | | + // | ----. | + // | | validate auction not settled yet | + // | <---' | + // | | | + // | ----. + // | | based on chosen price point, calculate and store final edition size and winning bidders + // | <---' + // | | | + // | ----. | + // | | update bidder balances to final available refund amounts | + // | <---' | + // | | | + // | | setEditionSize() | + // | |-------------------------------------------------------------------------------------------> + // | | | + // | | |----. + // | | | | update drop edition size + // | | |<---' + // | | | + // | | adminMintAirdrop() | + // | |-------------------------------------------------------------------------------------------> + // | | | + // | | |----. + // | | | | mint NFT to winning bidders + // | | |<---' + // | | | + // | ----. | + // | | handle seller funds recipient payout | + // | <---' | + // | | | + // | ----. | + // | | emit AuctionSettled() | + // | <---' | + // Seller ,----------+----------. ,----+-----. + // ,-. |VariableSupplyAuction| |ERC721Drop| + // `-' `---------------------' `----------' + // /|\ + // | + // / \ /// @notice Emitted when an auction is settled /// @param tokenContract The address of the ERC-721 drop contract @@ -294,9 +447,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// resulting edition sizes and amounts of revenue generated function calculateSettleOptions(address _tokenContract) external returns (uint96[] memory, uint16[] memory, uint96[] memory) { - // TODO gas optimization -- algorithm =P - // TODO gas optimization -- consider other, less storage-intensive options - // TODO gas optimization -- don't redo if already calculated + // TODO x gas optimization -- algorithm =P + // TODO x gas optimization -- consider other, less storage-intensive options + // TODO x gas optimization -- don't redo if already calculated address[] storage bidders = revealedBiddersForDrop[_tokenContract]; @@ -348,11 +501,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param _settlePricePoint The price point at which to settle the auction function settleAuction(address _tokenContract, uint96 _settlePricePoint) external nonReentrant { - // TODO checks - - // TODO decide how to store the fact that this auction is settled - - // TODO gas optimizations + // TODO x checks + // TODO x decide how to store the fact that this auction is settled + // TODO x gas optimizations // Get the auction Auction storage auction = auctionForDrop[_tokenContract]; @@ -363,11 +514,11 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Get the balances for this auction mapping(address => Bid) storage bids = bidsForDrop[_tokenContract]; + // TODO x consolidate business logic with calculateSettleOptions + // TODO x document pragmatic max edition size / winning bidders + // TODO x switch to dynamically sized array + // TODO x consider moving winningBidders into storage // Loop through bids to determine edition size and winning bidders - // TODO consolidate business logic with calculateSettleOptions - // TODO document pragmatic max edition size / winning bidders - // TODO switch to dynamically sized array - // TODO consider moving winningBidders into storage uint16 editionSize; address[] memory winningBidders = new address[](1000); for (uint256 i = 0; i < bidders.length; i++) { @@ -408,7 +559,36 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa CLAIM REFUND //////////////////////////////////////////////////////////////*/ - // TODO add UML + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Bidder `----------+----------' + // | claimRefund() | + // | ----------------------->| + // | | + // | ----. + // | | validate auction is in cleanup phase + // | <---' + // | | + // | ----. + // | | clear bidder available refund + // | <---' + // | | + // | ----. + // | | handle bidder available refund payout + // | <---' + // | | + // | ----. + // | | emit RefundClaimed() + // | <---' + // Bidder ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when a refund is claimed /// @param tokenContract The address of the ERC-721 drop contract @@ -421,7 +601,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// bid amount; if not winner, the full amount of ether sent with your bid /// @param _tokenContract The address of the ERC-721 drop contract function checkAvailableRefund(address _tokenContract) external view returns (uint96) { - // TODO add checks, including cleanup phase + + // TODO x add checks, including cleanup phase + // TODO x consolidate with claimRefund // Get the balance for the specified bidder Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; @@ -431,13 +613,12 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @notice Claim refund -- if winner, any additional ether sent above your /// bid amount; if not winner, the full amount of ether sent with your bid - /// @dev TODO doc re: temporal checks /// @param _tokenContract The address of the ERC-721 drop contract function claimRefund(address _tokenContract) external nonReentrant { // Get the auction Auction storage auction = auctionForDrop[_tokenContract]; - // TODO add temporal checks + // TODO x add temporal checks // Get the balance for the specified bidder Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; @@ -455,5 +636,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa emit RefundClaimed(_tokenContract, msg.sender, bidderBalance, auction); } - // TODO consider cleanup function + /*////////////////////////////////////////////////////////////// + CLEANUP AUCTION + //////////////////////////////////////////////////////////////*/ + + // TODO consider Cleanup function to delete auction, once all refunds have been claimed } diff --git a/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol index 2a5608ed..b97b6759 100644 --- a/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol +++ b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol @@ -6,7 +6,12 @@ pragma solidity 0.8.10; //////////////////////////////////////////////////////////////*/ // TODO consider other approaches that keep VSA module ignorant of ERC-721 Drop -// implementation specifics, including wrapping in a new TransferHelper for Drops +// implementation specifics, including wrapping in a ERC721DropHelper or similar, +// which could extend BaseTransferHelper + +// TODO improve mocking pattern for OZ AccessControlEnumerable + +// TODO get the function for setting edition sizes post-initialization from Iain contract ERC721Drop { // @@ -29,8 +34,6 @@ contract ERC721Drop { OZ Access Control //////////////////////////////////////////////////////////////*/ - // TODO use better mocking pattern for OZ AccessControlEnumerable - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); address internal minter; @@ -88,7 +91,6 @@ contract ERC721Drop { return 0; } - // TODO ask Iain for gist of actual function name function setEditionSize(uint64 _editionSize) public { // } diff --git a/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd b/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd new file mode 100644 index 00000000..f1f497b1 --- /dev/null +++ b/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd @@ -0,0 +1,17 @@ +Job Executor: Creator + +Core Functional Job-to-be-done: To discover optimal price point and edition size when selling a digital product + +Job Map: +1. (Define) Create digital product +2. (Prepare) Decide on drop parameters: + - Content -- HMW help seller preview their content to potential bidders? + - Metadata -- name, symbol, initial owner, royalty bips, funds recipient, metadata renderer, and sales config +3. (Prepare) Decide on auction parameters: + - Money -- minimum viable revenue and seller funds recipient + - Time -- start time, bid phase duration, reveal phase duration, settle phase duration +4. (Confirm) Confirm auction parameters look good +5. (Execute) Create Variable Supply Auction +6. (Monitor) Review settle options based on revealed bids +7. (Modify) Settle auction at a given price point and edition size +8. (Conclude) Cleanup auction once all bidder refunds have been claimed diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature similarity index 60% rename from contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature rename to contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature index 0e9af083..d645f92c 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature +++ b/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature @@ -9,7 +9,6 @@ Feature: Variable Supply Auctions - Bid Phase - Reveal Phase - Settle Phase - - TODO consider Cleanup phase - Completed / Cancelled Background: VSA creation and bidding @@ -31,24 +30,23 @@ Feature: Variable Supply Auctions | Bidder12 | 6 ETH | 9 ETH | | Bidder13 | 11 ETH | 12 ETH | - # TODO tbd if calculate at reveal time, or save for settle time Scenario: Bidders reveal bids When All bids are revealed - Then The revealed bids and reimbursements available should be - | account | bid amount | reimbursement available | - | Bidder1 | 1 ETH | 0 ETH | - | Bidder2 | 1 ETH | 8 ETH | - | Bidder3 | 1 ETH | 7 ETH | - | Bidder4 | 1 ETH | 6 ETH | - | Bidder5 | 1 ETH | 5 ETH | - | Bidder6 | 1 ETH | 4 ETH | - | Bidder7 | 1 ETH | 3 ETH | - | Bidder8 | 1 ETH | 2 ETH | - | Bidder9 | 1 ETH | 1 ETH | - | Bidder10 | 1 ETH | 9 ETH | - | Bidder11 | 6 ETH | 0 ETH | - | Bidder12 | 6 ETH | 3 ETH | - | Bidder13 | 11 ETH | 1 ETH | + Then The revealed bids should be + | account | bid amount | + | Bidder1 | 1 ETH | + | Bidder2 | 1 ETH | + | Bidder3 | 1 ETH | + | Bidder4 | 1 ETH | + | Bidder5 | 1 ETH | + | Bidder6 | 1 ETH | + | Bidder7 | 1 ETH | + | Bidder8 | 1 ETH | + | Bidder9 | 1 ETH | + | Bidder10 | 1 ETH | + | Bidder11 | 6 ETH | + | Bidder12 | 6 ETH | + | Bidder13 | 11 ETH | Scenario: Seller settles VSA at 1 ETH When All bids are revealed @@ -85,6 +83,21 @@ Feature: Variable Supply Auctions | Bidder11 | | Bidder12 | | Bidder13 | + And The available refunds should be + | account | available refund | + | Bidder1 | 0 ETH | + | Bidder2 | 8 ETH | + | Bidder3 | 7 ETH | + | Bidder4 | 6 ETH | + | Bidder5 | 5 ETH | + | Bidder6 | 4 ETH | + | Bidder7 | 3 ETH | + | Bidder8 | 2 ETH | + | Bidder9 | 1 ETH | + | Bidder10 | 9 ETH | + | Bidder11 | 5 ETH | + | Bidder12 | 8 ETH | + | Bidder13 | 11 ETH | Scenario: Seller settles VSA at 6 ETH When All bids are revealed @@ -110,6 +123,21 @@ Feature: Variable Supply Auctions | Bidder11 | | Bidder12 | | Bidder13 | + And The available refunds should be + | account | available refund | + | Bidder1 | 1 ETH | + | Bidder2 | 9 ETH | + | Bidder3 | 8 ETH | + | Bidder4 | 7 ETH | + | Bidder5 | 6 ETH | + | Bidder6 | 5 ETH | + | Bidder7 | 4 ETH | + | Bidder8 | 3 ETH | + | Bidder9 | 2 ETH | + | Bidder10 | 10 ETH | + | Bidder11 | 0 ETH | + | Bidder12 | 3 ETH | + | Bidder13 | 6 ETH | Scenario: Seller settles VSA at 11 ETH When All bids are revealed @@ -133,11 +161,26 @@ Feature: Variable Supply Auctions | Bidder13 | 89 ETH | And The following accounts should own 1 NFT | Bidder13 | + And The available refunds should be + | account | available refund | + | Bidder1 | 1 ETH | + | Bidder2 | 9 ETH | + | Bidder3 | 8 ETH | + | Bidder4 | 7 ETH | + | Bidder5 | 6 ETH | + | Bidder6 | 5 ETH | + | Bidder7 | 4 ETH | + | Bidder8 | 3 ETH | + | Bidder9 | 2 ETH | + | Bidder10 | 10 ETH | + | Bidder11 | 6 ETH | + | Bidder12 | 9 ETH | + | Bidder13 | 1 ETH | -# TODO handle bid space bounding +# TODO handle additional bid space bounding, beyond minimum viable revenue ## Seller sets maximum edition size commitment ## Bidder sets maximum edition size interest -## Seller sets minimum viable revenue -# TODO address cancel auction sad path # TODO address failure to reveal sad paths # TODO address failure to settle sad paths +# TODO consider Cleanup function to delete auction, once all refunds have been claimed +# TODO add Cucumber feature for bidder functionality diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant b/contracts/test/modules/VariableSupplyAuction/3-VariableSupplyAuction.invariant similarity index 85% rename from contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant rename to contracts/test/modules/VariableSupplyAuction/3-VariableSupplyAuction.invariant index 31e2658c..0667b792 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant +++ b/contracts/test/modules/VariableSupplyAuction/3-VariableSupplyAuction.invariant @@ -1,6 +1,6 @@ +# TODO run workshop for generating VSA invariants Invariant 1: endOfBidPhase > startTime Invariant 2: endOfRevealPhase > endOfBidPhase Invariant 3: endOfSettlePhase > endOfRevealPhase Invariant 4: totalBalance == Σ all bidder balances, while now <= endOfSettlePhase Invariant 5: bidder balance >= revealedBidAmount, while now > endOfRevealPhase -# TODO diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol index 8df968ee..a75eee3a 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol @@ -208,8 +208,7 @@ contract VariableSupplyAuctionInvariantTest is InvariantTest { targetContract(address(auctions)); - // Setup invariant target senders (for actor-based invariant testing) - // TODO + // TODO x setup invariant target senders (for actor-based invariant testing) // Setup one auction vm.prank(address(seller)); diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.jtbd b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.jtbd deleted file mode 100644 index 7af6a48c..00000000 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.jtbd +++ /dev/null @@ -1,13 +0,0 @@ -Job Executor: Creator - -Core Functional Job-to-be-done: To sell a digital product - -Job Map: TODO -1. Define -2. Locate -3. Prepare -4. Confirm -5. Execute -6. Monitor -7. Modify -8. Conclude diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index d6ac7d15..94fda4b2 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; +import "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; import {ERC721Drop} from "../../../modules/VariableSupplyAuction/temp-MockERC721Drop.sol"; import {Zorb} from "../../utils/users/Zorb.sol"; @@ -17,6 +17,11 @@ import {TestERC721} from "../../utils/tokens/TestERC721.sol"; import {WETH} from "../../utils/tokens/WETH.sol"; import {VM} from "../../utils/VM.sol"; +// TODO x more temporal checks +// TODO x more auction metadata test assertions +// TODO x improve settle auction biz logic and storage +// TODO x review + /// @title VariableSupplyAuctionTest /// @notice Unit Tests for Variable Supply Auctions contract VariableSupplyAuctionTest is Test { @@ -276,7 +281,7 @@ contract VariableSupplyAuctionTest is Test { } function testEvent_createAuction() public { - Auction memory auction = Auction({ + VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), @@ -374,7 +379,7 @@ contract VariableSupplyAuctionTest is Test { } function testEvent_CancelAuction() public setupBasicAuction { - Auction memory auction = Auction({ + VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), @@ -413,14 +418,14 @@ contract VariableSupplyAuctionTest is Test { auctions.cancelAuction(address(drop)); } - // TODO update biz logic to allow one other case -- cancelling auctions + // TODO x update biz logic to allow one other case -- cancelling auctions // in settle phase that did not meet minimum viable revenue goal /*////////////////////////////////////////////////////////////// PLACE BID //////////////////////////////////////////////////////////////*/ - // TODO add more assertions around new storage variables + // TODO x add more assertions around new storage variables function test_PlaceBid_WhenSingle() public setupBasicAuction { bytes32 commitment = _genSealedBid(1 ether, salt1); @@ -461,7 +466,7 @@ contract VariableSupplyAuctionTest is Test { } function testEvent_PlaceBid_WhenSingle() public setupBasicAuction { - Auction memory auction = Auction({ + VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), @@ -484,7 +489,7 @@ contract VariableSupplyAuctionTest is Test { } function testEvent_PlaceBid_WhenMultiple() public setupBasicAuction { - Auction memory auction = Auction({ + VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), @@ -553,12 +558,12 @@ contract VariableSupplyAuctionTest is Test { auctions.placeBid{value: 1 ether}(address(drop), commitment); } - // TODO once settleAuction is written + // TODO x once settleAuction is written // function testRevert_PlaceBid_WhenAuctionIsCompleted() public setupBasicAuction { // } - // TODO once cancelAuction is written + // TODO x once cancelAuction is written // function testRevert_PlaceBid_WhenAuctionIsCancelled() public setupBasicAuction { // } @@ -582,7 +587,7 @@ contract VariableSupplyAuctionTest is Test { auctions.placeBid(address(drop), commitment); } - // TODO revist – test may become relevant if we move minter role granting into TransferHelper + // TODO revisit -– may become relevant if we move minter role granting into an ERC721DropTransferHelper // function testRevert_PlaceBid_WhenSellerDidNotApproveModule() public setupBasicAuction { // seller.setApprovalForModule(address(auctions), false); @@ -643,7 +648,7 @@ contract VariableSupplyAuctionTest is Test { } function testEvent_RevealBid_WhenSingle() public setupBasicAuction { - Auction memory auction = Auction({ + VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), @@ -671,7 +676,7 @@ contract VariableSupplyAuctionTest is Test { } function testEvent_RevealBid_WhenMultiple() public setupBasicAuction { - Auction memory auction = Auction({ + VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), @@ -738,12 +743,12 @@ contract VariableSupplyAuctionTest is Test { auctions.revealBid(address (drop), 1 ether, salt1); } - // TODO once settleAuction is written + // TODO x once settleAuction is written // function testRevert_RevealBid_WhenAuctionIsCompleted() public setupBasicAuction { // } - // TODO once cancelAuction is written + // TODO x once cancelAuction is written // function testRevert_RevealBid_WhenAuctionIsCancelled() public setupBasicAuction { // } @@ -757,8 +762,8 @@ contract VariableSupplyAuctionTest is Test { auctions.revealBid(address (drop), 1.1 ether, salt1); } - // TODO should we then allow "topping up" the bidder's balance to support their bid? - // likely, no — introduces bad incentives + // TODO should we allow "topping up" the bidder's balance to support their bid? + // likely, no — could introduce bad incentives function testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() public setupBasicAuction { bytes32 commitment = _genSealedBid(1.1 ether, salt1); vm.prank(address(bidder1)); @@ -1198,7 +1203,7 @@ contract VariableSupplyAuctionTest is Test { } function testEvent_ClaimRefund() public setupBasicAuction { - Auction memory auction = Auction({ + VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), @@ -1245,7 +1250,7 @@ contract VariableSupplyAuctionTest is Test { } // function testRevert_ClaimRefund_WhenNoBidRevealed() public setupBasicAuction { - // // TODO + // // TODO x // } function testRevert_ClaimRefund_WhenAlreadyClaimed() public setupBasicAuction { @@ -1271,21 +1276,13 @@ contract VariableSupplyAuctionTest is Test { auctions.claimRefund(address(drop)); } - // TODO add temporal sad paths - - // TODO add minimum viable revenue sad paths - - /*////////////////////////////////////////////////////////////// - FAILURE TO CLAIM REFUND - //////////////////////////////////////////////////////////////*/ - - // TODO seller failure to claim sad paths + // TODO x add temporal sad paths /*////////////////////////////////////////////////////////////// TEST HELPERS //////////////////////////////////////////////////////////////*/ - // TODO improve modifier pattern to include parameters (could combine w/ fuzzing) + // TODO parameterize modifier pattern to support fuzzing modifier setupBasicAuction() { vm.prank(address(seller)); auctions.createAuction({ @@ -1369,7 +1366,7 @@ contract VariableSupplyAuctionTest is Test { } function _expectSettledAuctionEvent(uint96 _settledPricePoint, uint16 _settledEditionSize) internal { - Auction memory auction = Auction({ + VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), @@ -1387,38 +1384,19 @@ contract VariableSupplyAuctionTest is Test { emit AuctionSettled(address(drop), auction); } - // IDEA could this not be moved to the hyperstructure module for better bidder usability ?! + // IDEA could this be moved onto the hyperstructure module for better bidder usability ?! function _genSealedBid(uint256 _amount, string memory _salt) internal pure returns (bytes32) { return keccak256(abi.encodePacked(_amount, bytes(_salt))); } /*////////////////////////////////////////////////////////////// - TODO DRY up w/ better pattern + EVENTS //////////////////////////////////////////////////////////////*/ - struct Auction { - address seller; - uint96 minimumViableRevenue; - address sellerFundsRecipient; - uint32 startTime; - uint32 endOfBidPhase; - uint32 endOfRevealPhase; - uint32 endOfSettlePhase; - uint96 totalBalance; - uint96 settledRevenue; - uint96 settledPricePoint; - uint16 settledEditionSize; - } - - struct Bid { - bytes32 commitmentHash; - uint96 revealedBidAmount; - } - - event AuctionCreated(address indexed tokenContract, Auction auction); - event AuctionCanceled(address indexed tokenContract, Auction auction); - event BidPlaced(address indexed tokenContract, address indexed bidder, Auction auction); - event BidRevealed(address indexed tokenContract, address indexed bidder, uint256 indexed bidAmount, Auction auction); - event AuctionSettled(address indexed tokenContract, Auction auction); - event RefundClaimed(address indexed tokenContract, address indexed bidder, uint96 refundAmount, Auction auction); + event AuctionCreated(address indexed tokenContract, VariableSupplyAuction.Auction auction); + event AuctionCanceled(address indexed tokenContract, VariableSupplyAuction.Auction auction); + event BidPlaced(address indexed tokenContract, address indexed bidder, VariableSupplyAuction.Auction auction); + event BidRevealed(address indexed tokenContract, address indexed bidder, uint256 indexed bidAmount, VariableSupplyAuction.Auction auction); + event AuctionSettled(address indexed tokenContract, VariableSupplyAuction.Auction auction); + event RefundClaimed(address indexed tokenContract, address indexed bidder, uint96 refundAmount, VariableSupplyAuction.Auction auction); } From 864c9bceb83a75e5e59376849241f50c308330f8 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Thu, 27 Oct 2022 10:40:00 -0400 Subject: [PATCH 20/31] [draft] Improve time test pattern; Add more assertions on settled auction metadata --- .gas-snapshot | 76 +++--- .../IVariableSupplyAuction.sol | 1 + .../VariableSupplyAuction.sol | 8 +- .../VariableSupplyAuction.t.sol | 242 ++++++++++++------ 4 files changed, 208 insertions(+), 119 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 9ba9f888..37c329e8 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,47 +319,47 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85020) -VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 1161244) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295625) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160473) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 414430) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 214900) -VariableSupplyAuctionTest:testEvent_createAuction() (gas: 97955) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91984) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159756) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99789) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 1159688) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 997608) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20650) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97469) +VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85179) +VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 1161772) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295824) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160672) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 414762) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 215232) +VariableSupplyAuctionTest:testEvent_createAuction() (gas: 98154) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91986) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159757) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99790) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 1159973) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 997805) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20647) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97479) VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27546) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106819) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 106787) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166186) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102007) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159714) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160196) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102394) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 160949) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160609) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 160951) -VariableSupplyAuctionTest:test_CalculateSettleOptions() (gas: 1550891) -VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85031) -VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 1355824) -VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 1253353) -VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 1158701) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106941) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 107023) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166187) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102008) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159715) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160433) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102528) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 161083) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160743) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 161085) +VariableSupplyAuctionTest:test_CalculateSettleOptions() (gas: 1551261) +VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85032) +VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 1356199) +VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 1253683) +VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 1159031) VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95144) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96334) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96335) VariableSupplyAuctionTest:test_DropInitial() (gas: 31013) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 284016) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156150) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401217) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 209870) -VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1404894) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2343381) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2598753) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2385988) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 284017) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156151) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401351) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 210004) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1408249) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2347313) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2602685) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2389920) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index 29972c7a..eedb26bf 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -7,6 +7,7 @@ pragma solidity 0.8.10; interface IVariableSupplyAuction { // + // TODO x add NatSpec here also /// function createAuction( address _tokenContract, diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index a4412bac..1094a49b 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -86,7 +86,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @notice The addresses who have placed and revealed a bid in a given auction /// @dev ERC-721 token contract => all bidders who have revealed their bid - mapping(address => address[]) public revealedBiddersForDrop; + mapping(address => address[]) internal _revealedBiddersForDrop; /*////////////////////////////////////////////////////////////// CREATE AUCTION @@ -358,7 +358,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa require(keccak256(abi.encodePacked(_bidAmount, bytes(_salt))) == bid.commitmentHash, "REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); // Store the bidder - revealedBiddersForDrop[_tokenContract].push(msg.sender); + _revealedBiddersForDrop[_tokenContract].push(msg.sender); // Store the revealed bid amount uint96 bidAmount = uint96(_bidAmount); @@ -451,7 +451,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // TODO x gas optimization -- consider other, less storage-intensive options // TODO x gas optimization -- don't redo if already calculated - address[] storage bidders = revealedBiddersForDrop[_tokenContract]; + address[] storage bidders = _revealedBiddersForDrop[_tokenContract]; for (uint256 i = 0; i < bidders.length; i++) { address bidder = bidders[i]; @@ -509,7 +509,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa Auction storage auction = auctionForDrop[_tokenContract]; // Get the bidders who revealed in this auction - address[] storage bidders = revealedBiddersForDrop[_tokenContract]; + address[] storage bidders = _revealedBiddersForDrop[_tokenContract]; // Get the balances for this auction mapping(address => Bid) storage bids = bidsForDrop[_tokenContract]; diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 94fda4b2..ff90df39 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -18,7 +18,6 @@ import {WETH} from "../../utils/tokens/WETH.sol"; import {VM} from "../../utils/VM.sol"; // TODO x more temporal checks -// TODO x more auction metadata test assertions // TODO x improve settle auction biz logic and storage // TODO x review @@ -75,6 +74,8 @@ contract VariableSupplyAuctionTest is Test { string internal constant salt14 = "my cots earstones"; string internal constant salt15 = "easternmost coy"; + uint32 internal constant TIME0 = 1_666_000_000; // now-ish + function setUp() public { // Deploy V3 registrar = new ZoraRegistrar(); @@ -136,7 +137,7 @@ contract VariableSupplyAuctionTest is Test { _contractSymbol: "TMNT", _initialOwner: address(seller), _fundsRecipient: payable(sellerFundsRecipient), - _editionSize: 1, // will likely be updated during settle phase + _editionSize: 1, // to be updated during settle phase _royaltyBPS: 1000 // _metadataRenderer: dummyRenderer, // _metadataRendererInit: "", @@ -181,6 +182,9 @@ contract VariableSupplyAuctionTest is Test { // Seller approve ERC721TransferHelper // vm.prank(address(seller)); // token.setApprovalForAll(address(erc721TransferHelper), true); + + // Start from this time + vm.warp(TIME0); } function test_DropInitial() public { @@ -214,7 +218,7 @@ contract VariableSupplyAuctionTest is Test { _tokenContract: address(drop), _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: block.timestamp, + _startTime: TIME0, _bidPhaseDuration: 3 days, _revealPhaseDuration: 2 days, _settlePhaseDuration: 1 days @@ -285,10 +289,10 @@ contract VariableSupplyAuctionTest is Test { seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + startTime: uint32(TIME0), + endOfBidPhase: uint32(TIME0 + 3 days), + endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), + endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 0, settledRevenue: 0, settledPricePoint: 0, @@ -303,7 +307,7 @@ contract VariableSupplyAuctionTest is Test { _tokenContract: address(drop), _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: block.timestamp, + _startTime: TIME0, _bidPhaseDuration: 3 days, _revealPhaseDuration: 2 days, _settlePhaseDuration: 1 days @@ -318,7 +322,7 @@ contract VariableSupplyAuctionTest is Test { _tokenContract: address(drop), _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: 1 days, + _startTime: TIME0, _bidPhaseDuration: 3 days, _revealPhaseDuration: 2 days, _settlePhaseDuration: 1 days @@ -333,7 +337,7 @@ contract VariableSupplyAuctionTest is Test { _tokenContract: address(drop), _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(0), - _startTime: 1 days, + _startTime: TIME0, _bidPhaseDuration: 3 days, _revealPhaseDuration: 2 days, _settlePhaseDuration: 1 days @@ -383,10 +387,10 @@ contract VariableSupplyAuctionTest is Test { seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + startTime: uint32(TIME0), + endOfBidPhase: uint32(TIME0 + 3 days), + endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), + endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 0, settledRevenue: 0, settledPricePoint: 0, @@ -470,10 +474,10 @@ contract VariableSupplyAuctionTest is Test { seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + startTime: uint32(TIME0), + endOfBidPhase: uint32(TIME0 + 3 days), + endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), + endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, settledRevenue: uint96(0), settledPricePoint: uint96(0), @@ -493,10 +497,10 @@ contract VariableSupplyAuctionTest is Test { seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + startTime: uint32(TIME0), + endOfBidPhase: uint32(TIME0 + 3 days), + endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), + endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, settledRevenue: uint96(0), settledPricePoint: uint96(0), @@ -539,7 +543,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_PlaceBid_WhenAuctionInRevealPhase() public setupBasicAuction { - vm.warp(3 days + 1 seconds); // reveal phase + vm.warp(TIME0 + 3 days); // reveal phase vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); @@ -549,7 +553,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_PlaceBid_WhenAuctionInSettlePhase() public setupBasicAuction { - vm.warp(3 days + 2 days + 1 seconds); // settle phase + vm.warp(TIME0 + 3 days + 2 days); // settle phase vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); @@ -607,7 +611,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1 ether, salt1); @@ -629,7 +633,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder3)); auctions.placeBid{value: 5 ether}(address(drop), commitment3); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.prank(address(bidder1)); auctions.revealBid(address(drop), 1 ether, salt1); @@ -652,10 +656,10 @@ contract VariableSupplyAuctionTest is Test { seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + startTime: uint32(TIME0), + endOfBidPhase: uint32(TIME0 + 3 days), + endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), + endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, settledRevenue: uint96(0), settledPricePoint: uint96(0), @@ -666,7 +670,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.expectEmit(true, true, true, true); emit BidRevealed(address(drop), address(bidder1), 1 ether, auction); @@ -680,10 +684,10 @@ contract VariableSupplyAuctionTest is Test { seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + startTime: uint32(TIME0), + endOfBidPhase: uint32(TIME0 + 3 days), + endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), + endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 9 ether, settledRevenue: uint96(0), settledPricePoint: uint96(0), @@ -701,7 +705,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder3)); auctions.placeBid{value: 5 ether}(address(drop), commitment3); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); // We can assert all events at once, bc stored auction does not change vm.expectEmit(true, true, true, true); @@ -735,7 +739,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); - vm.warp(3 days + 2 days + 1 seconds); + vm.warp(TIME0 + 3 days + 2 days); vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); @@ -754,7 +758,7 @@ contract VariableSupplyAuctionTest is Test { // } function testRevert_RevealBid_WhenNoCommittedBid() public setupBasicAuction { - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.expectRevert("NO_PLACED_BID_FOUND_FOR_ADDRESS"); @@ -769,7 +773,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.expectRevert("REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); @@ -782,7 +786,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); @@ -795,7 +799,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); @@ -881,7 +885,7 @@ contract VariableSupplyAuctionTest is Test { */ - function test_SettleAuction_Preconditions() public setupBasicAuction throughRevealPhaseComplex { + function test_SettleAuction_Preconditions() public setupBasicAuction throughRevealPhaseComplex { // Precondition checks // all bidders have 0 NFTs @@ -902,7 +906,22 @@ contract VariableSupplyAuctionTest is Test { // seller funds recipient has 100 ether assertEq(address(sellerFundsRecipient).balance, 100 ether); - // bidder auction balances still full amount of sent ether + // auction total balance still full amount of sent ether + ( + , + , + , + , + , + , + , + uint96 totalBalance, + , + , + ) = auctions.auctionForDrop(address(drop)); + assertEq(totalBalance, 82 ether); + + // bidder auction balances each still full amount of sent ether (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); @@ -932,7 +951,7 @@ contract VariableSupplyAuctionTest is Test { } function test_SettleAuction_WhenSettlingAtLowPriceHighSupply() public setupBasicAuction throughRevealPhaseComplex { - _expectSettledAuctionEvent(1 ether, 13); + _expectSettledAuctionEvent(82 ether, 1 ether, 13); // When -- seller settles auction at price point of 1 ether vm.prank(address(seller)); @@ -941,7 +960,9 @@ contract VariableSupplyAuctionTest is Test { // Then assert -- // 1) all bidders have 1 NFT // 2) seller funds recipient has 113 ether - // 3) bidder auction balances (available to withdraw) are their amount of + // 3) auction total balance is sent ether less settled revenue of 13 ether + // 4) auction settled revenue, price point, and edition size are correct + // 5) bidder auction balances (available to withdraw) are their amount of // sent ether less the settled price point of 1 ether assertEq(drop.balanceOf(address(bidder1)), 1); @@ -960,6 +981,26 @@ contract VariableSupplyAuctionTest is Test { assertEq(address(sellerFundsRecipient).balance, 113 ether); + ( + , + , + , + , + , + , + , + uint96 totalBalance, + uint96 settledRevenue, + uint96 settledPricePoint, + uint16 settledEditionSize + ) = auctions.auctionForDrop(address(drop)); + + assertEq(totalBalance, 82 ether - 13 ether); + + assertEq(settledRevenue, 13 ether); + assertEq(settledPricePoint, 1 ether); + assertEq(settledEditionSize, 13); + (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); @@ -989,7 +1030,7 @@ contract VariableSupplyAuctionTest is Test { } function test_SettleAuction_WhenSettlingAtMidPriceMidSupply() public setupBasicAuction throughRevealPhaseComplex { - _expectSettledAuctionEvent(6 ether, 3); + _expectSettledAuctionEvent(82 ether, 6 ether, 3); // When -- seller settles auction at price point of 6 ether vm.prank(address(seller)); @@ -998,8 +1039,10 @@ contract VariableSupplyAuctionTest is Test { // Then assert -- // 1) bidders 11–13 has 1 NFT // 2) seller funds recipient has 118 ether - // 3) bidders 11–13 balance is their sent ether less settled price point of 6 ether - // 4) bidders 1–10 balances (available to withdraw) are their full sent ether + // 3) auction total balance is sent ether less settled revenue of 18 ether + // 4) auction settled revenue, price point, and edition size are correct + // 5) bidders 11–13 balance is their sent ether less settled price point of 6 ether + // 6) bidders 1–10 balances (available to withdraw) are their full sent ether assertEq(drop.balanceOf(address(bidder1)), 0); assertEq(drop.balanceOf(address(bidder2)), 0); @@ -1017,6 +1060,26 @@ contract VariableSupplyAuctionTest is Test { assertEq(address(sellerFundsRecipient).balance, 118 ether); + ( + , + , + , + , + , + , + , + uint96 totalBalance, + uint96 settledRevenue, + uint96 settledPricePoint, + uint16 settledEditionSize + ) = auctions.auctionForDrop(address(drop)); + + assertEq(totalBalance, 82 ether - 18 ether); + + assertEq(settledRevenue, 18 ether); + assertEq(settledPricePoint, 6 ether); + assertEq(settledEditionSize, 3); + (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); @@ -1046,7 +1109,7 @@ contract VariableSupplyAuctionTest is Test { } function test_SettleAuction_WhenSettlingAtHighPriceLowSupply() public setupBasicAuction throughRevealPhaseComplex { - _expectSettledAuctionEvent(11 ether, 1); + _expectSettledAuctionEvent(82 ether, 11 ether, 1); // When -- seller settles auction at price point of 11 ether vm.prank(address(seller)); @@ -1055,8 +1118,10 @@ contract VariableSupplyAuctionTest is Test { // Then assert -- // 1) bidder 13 has 1 NFT // 2) seller funds recipient has 111 ether - // 3) bidder 13 auction balance is their sent ether less settled price point of 11 ether - // 4) bidders 1–12 auction balances (available to withdraw) are their full sent ether + // 3) auction total balance is sent ether less settled revenue of 11 ether + // 4) auction settled revenue, price point, and edition size are correct + // 5) bidder 13 auction balance is their sent ether less settled price point of 11 ether + // 6) bidders 1–12 auction balances (available to withdraw) are their full sent ether assertEq(drop.balanceOf(address(bidder1)), 0); assertEq(drop.balanceOf(address(bidder2)), 0); @@ -1074,6 +1139,26 @@ contract VariableSupplyAuctionTest is Test { assertEq(address(sellerFundsRecipient).balance, 111 ether); + ( + , + , + , + , + , + , + , + uint96 totalBalance, + uint96 settledRevenue, + uint96 settledPricePoint, + uint16 settledEditionSize + ) = auctions.auctionForDrop(address(drop)); + + assertEq(totalBalance, 82 ether - 11 ether); + + assertEq(settledRevenue, 11 ether); + assertEq(settledPricePoint, 11 ether); + assertEq(settledEditionSize, 1); + (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); @@ -1120,7 +1205,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder3)); auctions.placeBid{value: 3 ether}(address(drop), _genSealedBid(2 ether, salt3)); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.prank(address(bidder1)); auctions.revealBid(address(drop), 1 ether, salt1); @@ -1129,7 +1214,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder3)); auctions.revealBid(address(drop), 2 ether, salt3); - vm.warp(3 days + 2 days + 1 seconds); + vm.warp(TIME0 + 3 days + 2 days); vm.prank(address(seller)); auctions.settleAuction(address(drop), 2 ether); @@ -1148,12 +1233,12 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.prank(address(bidder1)); auctions.revealBid(address(drop), 1 ether, salt1); - vm.warp(3 days + 2 days + 1 seconds); + vm.warp(TIME0 + 3 days + 2 days); vm.prank(address(seller)); auctions.settleAuction(address(drop), 1 ether); @@ -1177,14 +1262,14 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder2)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(2 ether, salt2)); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.prank(address(bidder1)); auctions.revealBid(address(drop), 1 ether, salt1); vm.prank(address(bidder2)); auctions.revealBid(address(drop), 2 ether, salt2); - vm.warp(3 days + 2 days + 1 seconds); + vm.warp(TIME0 + 3 days + 2 days); vm.prank(address(seller)); auctions.settleAuction(address(drop), 2 ether); @@ -1207,10 +1292,10 @@ contract VariableSupplyAuctionTest is Test { seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), + startTime: uint32(TIME0), + endOfBidPhase: uint32(TIME0 + 3 days), + endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), + endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, settledRevenue: 1 ether, settledPricePoint: 1 ether, @@ -1220,12 +1305,12 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.prank(address(bidder1)); auctions.revealBid(address(drop), 1 ether, salt1); - vm.warp(3 days + 2 days + 1 seconds); + vm.warp(TIME0 + 3 days + 2 days); vm.prank(address(seller)); auctions.settleAuction(address(drop), 1 ether); @@ -1238,7 +1323,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_ClaimRefund_WhenNoBidPlaced() public setupBasicAuction { - vm.warp(3 days + 2 days + 1 seconds); + vm.warp(TIME0 + 3 days + 2 days); vm.prank(address(seller)); auctions.settleAuction(address(drop), 1 ether); @@ -1257,12 +1342,12 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.prank(address(bidder1)); auctions.revealBid(address(drop), 1 ether, salt1); - vm.warp(3 days + 2 days + 1 seconds); + vm.warp(TIME0 + 3 days + 2 days); vm.prank(address(seller)); auctions.settleAuction(address(drop), 1 ether); @@ -1289,7 +1374,7 @@ contract VariableSupplyAuctionTest is Test { _tokenContract: address(drop), _minimumViableRevenue: 1 ether, _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: block.timestamp, + _startTime: TIME0, _bidPhaseDuration: 3 days, _revealPhaseDuration: 2 days, _settlePhaseDuration: 1 days @@ -1331,7 +1416,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder13)); auctions.placeBid{value: 12 ether}(address(drop), _genSealedBid(11 ether, salt13)); - vm.warp(3 days + 1 seconds); + vm.warp(TIME0 + 3 days); vm.prank(address(bidder1)); auctions.revealBid(address(drop), 1 ether, salt1); @@ -1360,27 +1445,30 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder13)); auctions.revealBid(address(drop), 11 ether, salt13); - vm.warp(3 days + 2 days + 1 seconds); + vm.warp(TIME0 + 3 days + 2 days); _; } - function _expectSettledAuctionEvent(uint96 _settledPricePoint, uint16 _settledEditionSize) internal { + function _expectSettledAuctionEvent(uint96 _beforeSettleTotalBalance, uint96 _settledPricePoint, uint16 _settledEditionSize) internal { + uint96 settledRevenue = _settledPricePoint * _settledEditionSize; + uint96 totalBalance = _beforeSettleTotalBalance - settledRevenue; + VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, sellerFundsRecipient: address(sellerFundsRecipient), - startTime: uint32(block.timestamp), - endOfBidPhase: uint32(block.timestamp + 3 days), - endOfRevealPhase: uint32(block.timestamp + 3 days + 2 days), - endOfSettlePhase: uint32(block.timestamp + 3 days + 2 days + 1 days), - totalBalance: uint96(0), - settledRevenue: _settledPricePoint * _settledEditionSize, + startTime: uint32(TIME0), + endOfBidPhase: uint32(TIME0 + 3 days), + endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), + endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), + totalBalance: totalBalance, + settledRevenue: settledRevenue, settledPricePoint: _settledPricePoint, settledEditionSize: _settledEditionSize }); - vm.expectEmit(true, true, true, false); // TODO troubleshoot auction metadata + vm.expectEmit(true, true, true, true); emit AuctionSettled(address(drop), auction); } From 5a7387ead47acda90e959d16a66ce794e8b64314 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Thu, 27 Oct 2022 10:51:05 -0400 Subject: [PATCH 21/31] [draft] Clean up ERC721Drop interface to match actual --- .../temp-MockERC721Drop.sol | 44 +- .../VariableSupplyAuction.invariant.t.sol | 488 +++++++++--------- .../VariableSupplyAuction.t.sol | 34 +- 3 files changed, 300 insertions(+), 266 deletions(-) diff --git a/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol index b97b6759..3dc39a76 100644 --- a/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol +++ b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol @@ -11,7 +11,25 @@ pragma solidity 0.8.10; // TODO improve mocking pattern for OZ AccessControlEnumerable -// TODO get the function for setting edition sizes post-initialization from Iain +// TODO use actual function for setting edition sizes post-initialization from Iain + +interface IMetadataRenderer { + function tokenURI(uint256) external view returns (string memory); + function contractURI() external view returns (string memory); + function initializeWithData(bytes memory initData) external; +} + +contract DummyMetadataRenderer is IMetadataRenderer { + function tokenURI(uint256) external pure override returns (string memory) { + return "DUMMY"; + } + function contractURI() external pure override returns (string memory) { + return "DUMMY"; + } + function initializeWithData(bytes memory data) external { + // no-op + } +} contract ERC721Drop { // @@ -50,13 +68,25 @@ contract ERC721Drop { //////////////////////////////////////////////////////////////*/ struct Configuration { - // IMetadataRenderer metadataRenderer; + IMetadataRenderer metadataRenderer; uint64 editionSize; uint16 royaltyBPS; address payable fundsRecipient; } Configuration public config; + + struct SalesConfiguration { + uint104 publicSalePrice; + uint32 maxSalePurchasePerAddress; + uint64 publicSaleStart; + uint64 publicSaleEnd; + uint64 presaleStart; + uint64 presaleEnd; + bytes32 presaleMerkleRoot; + } + + SalesConfiguration public salesConfig; function initialize( string memory _contractName, @@ -64,19 +94,21 @@ contract ERC721Drop { address _initialOwner, address payable _fundsRecipient, uint64 _editionSize, - uint16 _royaltyBPS - // SalesConfiguration memory _salesConfig, - // IMetadataRenderer _metadataRenderer, - // bytes memory _metadataRendererInit + uint16 _royaltyBPS, + SalesConfiguration memory _salesConfig, + IMetadataRenderer _metadataRenderer, + bytes memory _metadataRendererInit ) public { name = _contractName; symbol = _contractSymbol; owner = _initialOwner; config = Configuration({ + metadataRenderer: _metadataRenderer, editionSize: _editionSize, royaltyBPS: _royaltyBPS, fundsRecipient: _fundsRecipient }); + salesConfig = _salesConfig; } function adminMint(address to, uint256 quantity) public returns (uint256) { diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol index a75eee3a..01bcbbeb 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol @@ -1,244 +1,244 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.10; - -import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; - -import {ERC721Drop} from "../../../modules/VariableSupplyAuction/temp-MockERC721Drop.sol"; -import {Zorb} from "../../utils/users/Zorb.sol"; -import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; -import {ZoraModuleManager} from "../../../ZoraModuleManager.sol"; -import {ZoraProtocolFeeSettings} from "../../../auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol"; -import {ERC20TransferHelper} from "../../../transferHelpers/ERC20TransferHelper.sol"; -import {ERC721TransferHelper} from "../../../transferHelpers/ERC721TransferHelper.sol"; -import {RoyaltyEngine} from "../../utils/modules/RoyaltyEngine.sol"; -import {TestERC721} from "../../utils/tokens/TestERC721.sol"; -import {WETH} from "../../utils/tokens/WETH.sol"; -import {InvariantTest} from "../../utils/InvariantTest.sol"; -import {VM} from "../../utils/VM.sol"; - -/// @title VariableSupplyAuctionTest -/// @notice Invariant Tests for Variable Supply Auctions -contract VariableSupplyAuctionInvariantTest is InvariantTest { -// - - ZoraRegistrar internal registrar; - ZoraProtocolFeeSettings internal ZPFS; - ZoraModuleManager internal ZMM; - // ERC20TransferHelper internal erc20TransferHelper; - ERC721TransferHelper internal erc721TransferHelper; - RoyaltyEngine internal royaltyEngine; - - VariableSupplyAuction internal auctions; - ERC721Drop internal drop; - WETH internal weth; - - Zorb internal seller; - Zorb internal sellerFundsRecipient; - Zorb internal operator; - Zorb internal finder; - Zorb internal royaltyRecipient; - Zorb internal bidder1; - Zorb internal bidder2; - Zorb internal bidder3; - Zorb internal bidder4; - Zorb internal bidder5; - Zorb internal bidder6; - Zorb internal bidder7; - Zorb internal bidder8; - Zorb internal bidder9; - Zorb internal bidder10; - Zorb internal bidder11; - Zorb internal bidder12; - Zorb internal bidder13; - Zorb internal bidder14; - Zorb internal bidder15; - - string internal constant salt1 = "setec astronomy"; - string internal constant salt2 = "too many secrets"; - string internal constant salt3 = "cray tomes on set"; - string internal constant salt4 = "o no my tesseract"; - string internal constant salt5 = "ye some contrast"; - string internal constant salt6 = "a tron ecosystem"; - string internal constant salt7 = "stonecasty rome"; - string internal constant salt8 = "coy teamster son"; - string internal constant salt9 = "cyanometer toss"; - string internal constant salt10 = "cementatory sos"; - string internal constant salt11 = "my cotoneasters"; - string internal constant salt12 = "ny sec stateroom"; - string internal constant salt13 = "oc attorney mess"; - string internal constant salt14 = "my cots earstones"; - string internal constant salt15 = "easternmost coy"; - - function setUp() public { - // Deploy V3 - registrar = new ZoraRegistrar(); - ZPFS = new ZoraProtocolFeeSettings(); - ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); - // erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); - erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); - - // Init V3 - registrar.init(ZMM); - ZPFS.init(address(ZMM), address(0)); - - // Create users - seller = new Zorb(address(ZMM)); - sellerFundsRecipient = new Zorb(address(ZMM)); - operator = new Zorb(address(ZMM)); - bidder1 = new Zorb(address(ZMM)); - bidder2 = new Zorb(address(ZMM)); - bidder3 = new Zorb(address(ZMM)); - bidder4 = new Zorb(address(ZMM)); - bidder5 = new Zorb(address(ZMM)); - bidder6 = new Zorb(address(ZMM)); - bidder7 = new Zorb(address(ZMM)); - bidder8 = new Zorb(address(ZMM)); - bidder9 = new Zorb(address(ZMM)); - bidder10 = new Zorb(address(ZMM)); - bidder11 = new Zorb(address(ZMM)); - bidder12 = new Zorb(address(ZMM)); - bidder13 = new Zorb(address(ZMM)); - bidder14 = new Zorb(address(ZMM)); - bidder15 = new Zorb(address(ZMM)); - finder = new Zorb(address(ZMM)); - royaltyRecipient = new Zorb(address(ZMM)); - - // Set balances - vm.deal(address(seller), 100 ether); - vm.deal(address(bidder1), 100 ether); - vm.deal(address(bidder2), 100 ether); - vm.deal(address(bidder3), 100 ether); - vm.deal(address(bidder4), 100 ether); - vm.deal(address(bidder5), 100 ether); - vm.deal(address(bidder6), 100 ether); - vm.deal(address(bidder7), 100 ether); - vm.deal(address(bidder8), 100 ether); - vm.deal(address(bidder9), 100 ether); - vm.deal(address(bidder10), 100 ether); - vm.deal(address(bidder11), 100 ether); - vm.deal(address(bidder12), 100 ether); - vm.deal(address(bidder13), 100 ether); - vm.deal(address(bidder14), 100 ether); - vm.deal(address(bidder15), 100 ether); - - // Deploy mocks - royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); - drop = new ERC721Drop(); - drop.initialize({ - _contractName: "Test Mutant Ninja Turtles", - _contractSymbol: "TMNT", - _initialOwner: address(seller), - _fundsRecipient: payable(sellerFundsRecipient), - _editionSize: 1, - _royaltyBPS: 1000 - // _metadataRenderer: dummyRenderer, - // _metadataRendererInit: "", - // _salesConfig: IERC721Drop.SalesConfiguration({ - // publicSaleStart: 0, - // publicSaleEnd: 0, - // presaleStart: 0, - // presaleEnd: 0, - // publicSalePrice: 0, - // maxSalePurchasePerAddress: 0, - // presaleMerkleRoot: bytes32(0) - // }) - }); - weth = new WETH(); - - // Deploy Variable Supply Auction module - auctions = new VariableSupplyAuction(address(erc721TransferHelper), address(royaltyEngine), address(ZPFS), address(weth)); - registrar.registerModule(address(auctions)); - - // Grant auction minter role on drop contract - vm.prank(address(seller)); - drop.grantRole(drop.MINTER_ROLE(), address(auctions)); - - // Users approve module - seller.setApprovalForModule(address(auctions), true); - bidder1.setApprovalForModule(address(auctions), true); - bidder2.setApprovalForModule(address(auctions), true); - bidder3.setApprovalForModule(address(auctions), true); - bidder4.setApprovalForModule(address(auctions), true); - bidder5.setApprovalForModule(address(auctions), true); - bidder6.setApprovalForModule(address(auctions), true); - bidder7.setApprovalForModule(address(auctions), true); - bidder8.setApprovalForModule(address(auctions), true); - bidder9.setApprovalForModule(address(auctions), true); - bidder10.setApprovalForModule(address(auctions), true); - bidder11.setApprovalForModule(address(auctions), true); - bidder12.setApprovalForModule(address(auctions), true); - bidder13.setApprovalForModule(address(auctions), true); - bidder14.setApprovalForModule(address(auctions), true); - bidder15.setApprovalForModule(address(auctions), true); - - // Seller approve ERC721TransferHelper - // vm.prank(address(seller)); - // token.setApprovalForAll(address(erc721TransferHelper), true); - - // Setup invariant targets - excludeContract(address(registrar)); - excludeContract(address(ZPFS)); - excludeContract(address(ZMM)); - excludeContract(address(erc721TransferHelper)); - - excludeContract(address(royaltyEngine)); - excludeContract(address(drop)); - excludeContract(address(weth)); - - excludeContract(address(seller)); - excludeContract(address(sellerFundsRecipient)); - excludeContract(address(operator)); - excludeContract(address(finder)); - excludeContract(address(royaltyRecipient)); - - excludeContract(address(bidder1)); - excludeContract(address(bidder2)); - excludeContract(address(bidder3)); - excludeContract(address(bidder4)); - excludeContract(address(bidder5)); - excludeContract(address(bidder6)); - excludeContract(address(bidder7)); - excludeContract(address(bidder8)); - excludeContract(address(bidder9)); - excludeContract(address(bidder10)); - excludeContract(address(bidder12)); - excludeContract(address(bidder13)); - excludeContract(address(bidder14)); - excludeContract(address(bidder15)); - - targetContract(address(auctions)); - - // TODO x setup invariant target senders (for actor-based invariant testing) - - // Setup one auction - vm.prank(address(seller)); - auctions.createAuction({ - _tokenContract: address(drop), - _minimumViableRevenue: 1 ether, - _sellerFundsRecipient: address(sellerFundsRecipient), - _startTime: block.timestamp, - _bidPhaseDuration: 3 days, - _revealPhaseDuration: 2 days, - _settlePhaseDuration: 1 days - }); - } - - // function invariant_true_eq_true() public { - // assertTrue(true); - // } - - // function invariant_auctionTotalBalance_lt_1ether() public { - // ( - // address sellerStored, - // uint256 minimumRevenue, - // address sellerFundsRecipientStored, - // uint256 startTime, - // uint256 endOfBidPhase, - // uint256 endOfRevealPhase, - // uint256 endOfSettlePhase, - // uint96 totalBalance - // ) = auctions.auctionForDrop(address(drop)); - - // assertLt(totalBalance, 1 ether); - // } -} +// // SPDX-License-Identifier: GPL-3.0 +// pragma solidity 0.8.10; + +// import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; + +// import {ERC721Drop} from "../../../modules/VariableSupplyAuction/temp-MockERC721Drop.sol"; +// import {Zorb} from "../../utils/users/Zorb.sol"; +// import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; +// import {ZoraModuleManager} from "../../../ZoraModuleManager.sol"; +// import {ZoraProtocolFeeSettings} from "../../../auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol"; +// import {ERC20TransferHelper} from "../../../transferHelpers/ERC20TransferHelper.sol"; +// import {ERC721TransferHelper} from "../../../transferHelpers/ERC721TransferHelper.sol"; +// import {RoyaltyEngine} from "../../utils/modules/RoyaltyEngine.sol"; +// import {TestERC721} from "../../utils/tokens/TestERC721.sol"; +// import {WETH} from "../../utils/tokens/WETH.sol"; +// import {InvariantTest} from "../../utils/InvariantTest.sol"; +// import {VM} from "../../utils/VM.sol"; + +// /// @title VariableSupplyAuctionTest +// /// @notice Invariant Tests for Variable Supply Auctions +// contract VariableSupplyAuctionInvariantTest is InvariantTest { +// // + +// ZoraRegistrar internal registrar; +// ZoraProtocolFeeSettings internal ZPFS; +// ZoraModuleManager internal ZMM; +// // ERC20TransferHelper internal erc20TransferHelper; +// ERC721TransferHelper internal erc721TransferHelper; +// RoyaltyEngine internal royaltyEngine; + +// VariableSupplyAuction internal auctions; +// ERC721Drop internal drop; +// WETH internal weth; + +// Zorb internal seller; +// Zorb internal sellerFundsRecipient; +// Zorb internal operator; +// Zorb internal finder; +// Zorb internal royaltyRecipient; +// Zorb internal bidder1; +// Zorb internal bidder2; +// Zorb internal bidder3; +// Zorb internal bidder4; +// Zorb internal bidder5; +// Zorb internal bidder6; +// Zorb internal bidder7; +// Zorb internal bidder8; +// Zorb internal bidder9; +// Zorb internal bidder10; +// Zorb internal bidder11; +// Zorb internal bidder12; +// Zorb internal bidder13; +// Zorb internal bidder14; +// Zorb internal bidder15; + +// string internal constant salt1 = "setec astronomy"; +// string internal constant salt2 = "too many secrets"; +// string internal constant salt3 = "cray tomes on set"; +// string internal constant salt4 = "o no my tesseract"; +// string internal constant salt5 = "ye some contrast"; +// string internal constant salt6 = "a tron ecosystem"; +// string internal constant salt7 = "stonecasty rome"; +// string internal constant salt8 = "coy teamster son"; +// string internal constant salt9 = "cyanometer toss"; +// string internal constant salt10 = "cementatory sos"; +// string internal constant salt11 = "my cotoneasters"; +// string internal constant salt12 = "ny sec stateroom"; +// string internal constant salt13 = "oc attorney mess"; +// string internal constant salt14 = "my cots earstones"; +// string internal constant salt15 = "easternmost coy"; + +// function setUp() public { +// // Deploy V3 +// registrar = new ZoraRegistrar(); +// ZPFS = new ZoraProtocolFeeSettings(); +// ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); +// // erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); +// erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); + +// // Init V3 +// registrar.init(ZMM); +// ZPFS.init(address(ZMM), address(0)); + +// // Create users +// seller = new Zorb(address(ZMM)); +// sellerFundsRecipient = new Zorb(address(ZMM)); +// operator = new Zorb(address(ZMM)); +// bidder1 = new Zorb(address(ZMM)); +// bidder2 = new Zorb(address(ZMM)); +// bidder3 = new Zorb(address(ZMM)); +// bidder4 = new Zorb(address(ZMM)); +// bidder5 = new Zorb(address(ZMM)); +// bidder6 = new Zorb(address(ZMM)); +// bidder7 = new Zorb(address(ZMM)); +// bidder8 = new Zorb(address(ZMM)); +// bidder9 = new Zorb(address(ZMM)); +// bidder10 = new Zorb(address(ZMM)); +// bidder11 = new Zorb(address(ZMM)); +// bidder12 = new Zorb(address(ZMM)); +// bidder13 = new Zorb(address(ZMM)); +// bidder14 = new Zorb(address(ZMM)); +// bidder15 = new Zorb(address(ZMM)); +// finder = new Zorb(address(ZMM)); +// royaltyRecipient = new Zorb(address(ZMM)); + +// // Set balances +// vm.deal(address(seller), 100 ether); +// vm.deal(address(bidder1), 100 ether); +// vm.deal(address(bidder2), 100 ether); +// vm.deal(address(bidder3), 100 ether); +// vm.deal(address(bidder4), 100 ether); +// vm.deal(address(bidder5), 100 ether); +// vm.deal(address(bidder6), 100 ether); +// vm.deal(address(bidder7), 100 ether); +// vm.deal(address(bidder8), 100 ether); +// vm.deal(address(bidder9), 100 ether); +// vm.deal(address(bidder10), 100 ether); +// vm.deal(address(bidder11), 100 ether); +// vm.deal(address(bidder12), 100 ether); +// vm.deal(address(bidder13), 100 ether); +// vm.deal(address(bidder14), 100 ether); +// vm.deal(address(bidder15), 100 ether); + +// // Deploy mocks +// royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); +// drop = new ERC721Drop(); +// drop.initialize({ +// _contractName: "Test Mutant Ninja Turtles", +// _contractSymbol: "TMNT", +// _initialOwner: address(seller), +// _fundsRecipient: payable(sellerFundsRecipient), +// _editionSize: 1, +// _royaltyBPS: 1000 +// // _metadataRenderer: dummyRenderer, +// // _metadataRendererInit: "", +// // _salesConfig: IERC721Drop.SalesConfiguration({ +// // publicSaleStart: 0, +// // publicSaleEnd: 0, +// // presaleStart: 0, +// // presaleEnd: 0, +// // publicSalePrice: 0, +// // maxSalePurchasePerAddress: 0, +// // presaleMerkleRoot: bytes32(0) +// // }) +// }); +// weth = new WETH(); + +// // Deploy Variable Supply Auction module +// auctions = new VariableSupplyAuction(address(erc721TransferHelper), address(royaltyEngine), address(ZPFS), address(weth)); +// registrar.registerModule(address(auctions)); + +// // Grant auction minter role on drop contract +// vm.prank(address(seller)); +// drop.grantRole(drop.MINTER_ROLE(), address(auctions)); + +// // Users approve module +// seller.setApprovalForModule(address(auctions), true); +// bidder1.setApprovalForModule(address(auctions), true); +// bidder2.setApprovalForModule(address(auctions), true); +// bidder3.setApprovalForModule(address(auctions), true); +// bidder4.setApprovalForModule(address(auctions), true); +// bidder5.setApprovalForModule(address(auctions), true); +// bidder6.setApprovalForModule(address(auctions), true); +// bidder7.setApprovalForModule(address(auctions), true); +// bidder8.setApprovalForModule(address(auctions), true); +// bidder9.setApprovalForModule(address(auctions), true); +// bidder10.setApprovalForModule(address(auctions), true); +// bidder11.setApprovalForModule(address(auctions), true); +// bidder12.setApprovalForModule(address(auctions), true); +// bidder13.setApprovalForModule(address(auctions), true); +// bidder14.setApprovalForModule(address(auctions), true); +// bidder15.setApprovalForModule(address(auctions), true); + +// // Seller approve ERC721TransferHelper +// // vm.prank(address(seller)); +// // token.setApprovalForAll(address(erc721TransferHelper), true); + +// // Setup invariant targets +// excludeContract(address(registrar)); +// excludeContract(address(ZPFS)); +// excludeContract(address(ZMM)); +// excludeContract(address(erc721TransferHelper)); + +// excludeContract(address(royaltyEngine)); +// excludeContract(address(drop)); +// excludeContract(address(weth)); + +// excludeContract(address(seller)); +// excludeContract(address(sellerFundsRecipient)); +// excludeContract(address(operator)); +// excludeContract(address(finder)); +// excludeContract(address(royaltyRecipient)); + +// excludeContract(address(bidder1)); +// excludeContract(address(bidder2)); +// excludeContract(address(bidder3)); +// excludeContract(address(bidder4)); +// excludeContract(address(bidder5)); +// excludeContract(address(bidder6)); +// excludeContract(address(bidder7)); +// excludeContract(address(bidder8)); +// excludeContract(address(bidder9)); +// excludeContract(address(bidder10)); +// excludeContract(address(bidder12)); +// excludeContract(address(bidder13)); +// excludeContract(address(bidder14)); +// excludeContract(address(bidder15)); + +// targetContract(address(auctions)); + +// // TODO x setup invariant target senders (for actor-based invariant testing) + +// // Setup one auction +// vm.prank(address(seller)); +// auctions.createAuction({ +// _tokenContract: address(drop), +// _minimumViableRevenue: 1 ether, +// _sellerFundsRecipient: address(sellerFundsRecipient), +// _startTime: block.timestamp, +// _bidPhaseDuration: 3 days, +// _revealPhaseDuration: 2 days, +// _settlePhaseDuration: 1 days +// }); +// } + +// // function invariant_true_eq_true() public { +// // assertTrue(true); +// // } + +// // function invariant_auctionTotalBalance_lt_1ether() public { +// // ( +// // address sellerStored, +// // uint256 minimumRevenue, +// // address sellerFundsRecipientStored, +// // uint256 startTime, +// // uint256 endOfBidPhase, +// // uint256 endOfRevealPhase, +// // uint256 endOfSettlePhase, +// // uint96 totalBalance +// // ) = auctions.auctionForDrop(address(drop)); + +// // assertLt(totalBalance, 1 ether); +// // } +// } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index ff90df39..5d8f6b4e 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; import "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; -import {ERC721Drop} from "../../../modules/VariableSupplyAuction/temp-MockERC721Drop.sol"; +import "../../../modules/VariableSupplyAuction/temp-MockERC721Drop.sol"; import {Zorb} from "../../utils/users/Zorb.sol"; import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; @@ -35,6 +35,7 @@ contract VariableSupplyAuctionTest is Test { VariableSupplyAuction internal auctions; ERC721Drop internal drop; + DummyMetadataRenderer internal dummyRenderer; WETH internal weth; Zorb internal seller; @@ -132,24 +133,25 @@ contract VariableSupplyAuctionTest is Test { // Deploy mocks royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); drop = new ERC721Drop(); + dummyRenderer = new DummyMetadataRenderer(); drop.initialize({ _contractName: "Test Mutant Ninja Turtles", _contractSymbol: "TMNT", _initialOwner: address(seller), _fundsRecipient: payable(sellerFundsRecipient), - _editionSize: 1, // to be updated during settle phase - _royaltyBPS: 1000 - // _metadataRenderer: dummyRenderer, - // _metadataRendererInit: "", - // _salesConfig: IERC721Drop.SalesConfiguration({ - // publicSaleStart: 0, - // publicSaleEnd: 0, - // presaleStart: 0, - // presaleEnd: 0, - // publicSalePrice: 0, - // maxSalePurchasePerAddress: 0, - // presaleMerkleRoot: bytes32(0) - // }) + _editionSize: 1, // to be updated by seller during settle phase + _royaltyBPS: 1000, + _metadataRenderer: dummyRenderer, + _metadataRendererInit: "", + _salesConfig: ERC721Drop.SalesConfiguration({ + publicSaleStart: 0, + publicSaleEnd: 0, + presaleStart: 0, + presaleEnd: 0, + publicSalePrice: 0, + maxSalePurchasePerAddress: 0, + presaleMerkleRoot: bytes32(0) + }) }); weth = new WETH(); @@ -195,13 +197,13 @@ contract VariableSupplyAuctionTest is Test { assertEq(drop.getRoleMember(drop.MINTER_ROLE(), 0), address(auctions)); ( - // IMetadataRenderer renderer, + IMetadataRenderer renderer, uint64 editionSize, uint16 royaltyBPS, address payable fundsRecipient ) = drop.config(); - // assertEq(address(renderer), address(dummyRenderer)); + assertEq(address(renderer), address(dummyRenderer)); assertEq(editionSize, 1); assertEq(royaltyBPS, 1000); assertEq(fundsRecipient, payable(sellerFundsRecipient)); From c2b7bedcf546a4f19a5796e0c204cd215856cdde Mon Sep 17 00:00:00 2001 From: neodaoist Date: Fri, 28 Oct 2022 12:06:56 -0400 Subject: [PATCH 22/31] [draft] Add initial minimum viable revenue biz logic --- .../IVariableSupplyAuction.sol | 3 +- .../VariableSupplyAuction.sol | 122 +++++++------ .../temp-MockERC721Drop.sol | 3 + .../1-VariableSupplyAuction.jtbd | 2 +- .../2-VariableSupplyAuction.feature | 68 ++------ .../VariableSupplyAuction.t.sol | 161 +++++++++++++----- uml/VariableSupplyAuction/6-settleAuction.txt | 2 + 7 files changed, 216 insertions(+), 145 deletions(-) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index eedb26bf..88ac031b 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -7,7 +7,8 @@ pragma solidity 0.8.10; interface IVariableSupplyAuction { // - // TODO x add NatSpec here also + // TODO x add NatSpec here + /// function createAuction( address _tokenContract, diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 1094a49b..965fa272 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -179,7 +179,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // | ----------------------->| // | | // | ----. - // | | validate no bids placed yet + // | | validate no bids placed yet TODO x add validate minimum viable revenue check // | <---' // | | // | ----. @@ -429,14 +429,17 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param auction The metadata of the created auction event AuctionSettled(address indexed tokenContract, Auction auction); + /// TODO struct SettleOption { uint16 editionSize; uint96 revenue; } - uint96[] public settlePricePoints; + /// TODO + mapping(address => uint96[]) public _settlePricePointsForAuction; - mapping(uint96 => SettleOption) public settleMapping; + /// TODO + mapping(address => mapping(uint96 => SettleOption)) public _settleOptionsForPricePoint; /// @notice Calculate edition size and revenue for each possible price point /// @dev Cheaper on subsequent calls, after the initial call when @@ -445,37 +448,43 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @return A tuple of 3 arrays representing the settle options -- /// the possible price points at which to settle, along with the /// resulting edition sizes and amounts of revenue generated - function calculateSettleOptions(address _tokenContract) external returns (uint96[] memory, uint16[] memory, uint96[] memory) { + function calculateSettleOptions(address _tokenContract) public returns (uint96[] memory, uint16[] memory, uint96[] memory) { // TODO x gas optimization -- algorithm =P // TODO x gas optimization -- consider other, less storage-intensive options - // TODO x gas optimization -- don't redo if already calculated + Auction storage auction = auctionForDrop[_tokenContract]; address[] storage bidders = _revealedBiddersForDrop[_tokenContract]; - - for (uint256 i = 0; i < bidders.length; i++) { - address bidder = bidders[i]; - uint96 bidAmount = bidsForDrop[_tokenContract][bidder].revealedBidAmount; - SettleOption storage settleOption = settleMapping[bidAmount]; - - if (settleOption.editionSize == 0) { - settlePricePoints.push(bidAmount); - settleOption.editionSize = 1; + uint96[] storage settlePricePoints = _settlePricePointsForAuction[_tokenContract]; + mapping(uint96 => SettleOption) storage settleOptions = _settleOptionsForPricePoint[_tokenContract]; + + if (settlePricePoints.length == 0) { // only calculate and store once, otherwise just return from storage + for (uint256 i = 0; i < bidders.length; i++) { + address bidder = bidders[i]; + uint96 bidAmount = bidsForDrop[_tokenContract][bidder].revealedBidAmount; + SettleOption storage settleOption = settleOptions[bidAmount]; + + if (settleOption.editionSize == 0) { + settlePricePoints.push(bidAmount); + settleOption.editionSize = 1; + } } - } - for (uint256 j = 0; j < bidders.length; j++) { - address bidder = bidders[j]; - uint96 bidAmount = bidsForDrop[_tokenContract][bidder].revealedBidAmount; - - for (uint256 k = 0; k < settlePricePoints.length; k++) { - uint96 settlePricePoint = settlePricePoints[k]; - SettleOption storage settleOption = settleMapping[settlePricePoint]; - - if (bidAmount >= settlePricePoint) { - settleOption.editionSize++; - settleOption.revenue += settlePricePoint; + for (uint256 j = 0; j < settlePricePoints.length; j++) { + uint96 settlePricePoint = settlePricePoints[j]; + SettleOption storage settleOption = settleOptions[settlePricePoint]; + + for (uint256 k = 0; k < bidders.length; k++) { + address bidder = bidders[k]; + uint96 bidAmount = bidsForDrop[_tokenContract][bidder].revealedBidAmount; + + if (bidAmount >= settlePricePoint) { + settleOption.editionSize++; + settleOption.revenue += settlePricePoint; + } } + + settleOption.editionSize--; // because 1st bidder at this settle price was double counted } } @@ -485,12 +494,15 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa for (uint256 m = 0; m < settlePricePoints.length; m++) { uint96 settlePricePoint = settlePricePoints[m]; - SettleOption storage settleOption = settleMapping[settlePricePoint]; + SettleOption storage settleOption = settleOptions[settlePricePoint]; + + if (settleOption.revenue < auction.minimumViableRevenue) { + settleOption.revenue = 0; // zero out, because not viable settle option + } pricePoints[m] = settlePricePoint; - editionSizes[m] = settleOption.editionSize - 1; // because 1st bidder at this settle price was double counted - revenues[m] = settleOption.revenue; - + editionSizes[m] = settleOption.editionSize; + revenues[m] = settleOption.revenue; } return (pricePoints, editionSizes, revenues); @@ -502,8 +514,10 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa function settleAuction(address _tokenContract, uint96 _settlePricePoint) external nonReentrant { // TODO x checks - // TODO x decide how to store the fact that this auction is settled // TODO x gas optimizations + // TODO x look for more ways to consolidate business logic with calculateSettleOptions + // TODO x document pragmatic max edition size / winning bidders + // TODO x consider storing winningBidders during calculateSettleOptions // Get the auction Auction storage auction = auctionForDrop[_tokenContract]; @@ -511,40 +525,46 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Get the bidders who revealed in this auction address[] storage bidders = _revealedBiddersForDrop[_tokenContract]; - // Get the balances for this auction + if (_settlePricePointsForAuction[_tokenContract].length == 0) { + calculateSettleOptions(_tokenContract); + } + + // Get the settle option at this price point + SettleOption storage settleOption = _settleOptionsForPricePoint[_tokenContract][_settlePricePoint]; + + // Check that revenue meets minimum viable revenue + require(settleOption.revenue >= auction.minimumViableRevenue, "DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); + + // Store the current total balance and final auction details + auction.totalBalance -= settleOption.revenue; + auction.settledRevenue = settleOption.revenue; + auction.settledPricePoint = _settlePricePoint; + auction.settledEditionSize = settleOption.editionSize; + + // TODO x store the fact that an auction has been settled and clean up unneeded storage + + // Get the bids for this auction mapping(address => Bid) storage bids = bidsForDrop[_tokenContract]; - // TODO x consolidate business logic with calculateSettleOptions - // TODO x document pragmatic max edition size / winning bidders - // TODO x switch to dynamically sized array - // TODO x consider moving winningBidders into storage - // Loop through bids to determine edition size and winning bidders - uint16 editionSize; - address[] memory winningBidders = new address[](1000); + // Loop through bids to determine winning bidders and update bidder balances + uint256 index; + address[] memory winningBidders = new address[](auction.settledEditionSize); for (uint256 i = 0; i < bidders.length; i++) { // Cache the bidder address bidder = bidders[i]; // Check if bid qualifies - if (bidsForDrop[_tokenContract][bidder].revealedBidAmount >= _settlePricePoint) { + if (bids[bidder].revealedBidAmount >= _settlePricePoint) { // Mark winning bidder and increment edition size - winningBidders[editionSize++] = bidder; - - // Update final revenue - auction.settledRevenue += _settlePricePoint; + winningBidders[index++] = bidder; // Update their balance bids[bidder].bidderBalance -= _settlePricePoint; } } - // Store the current total balance and final auction details - auction.totalBalance -= auction.settledRevenue; - auction.settledPricePoint = _settlePricePoint; - auction.settledEditionSize = editionSize; - - // Update edition size - ERC721Drop(_tokenContract).setEditionSize(uint64(winningBidders.length)); + // Update edition size of drop + ERC721Drop(_tokenContract).setEditionSize(uint64(auction.settledEditionSize)); // Mint NFTs to winning bidders ERC721Drop(_tokenContract).adminMintAirdrop(winningBidders); diff --git a/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol index 3dc39a76..0ff381bd 100644 --- a/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol +++ b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol @@ -88,6 +88,8 @@ contract ERC721Drop { SalesConfiguration public salesConfig; + bytes public metadataRendererInit; + function initialize( string memory _contractName, string memory _contractSymbol, @@ -109,6 +111,7 @@ contract ERC721Drop { fundsRecipient: _fundsRecipient }); salesConfig = _salesConfig; + metadataRendererInit = _metadataRendererInit; // to silence warning } function adminMint(address to, uint256 quantity) public returns (uint256) { diff --git a/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd b/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd index f1f497b1..0e29af6e 100644 --- a/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd +++ b/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd @@ -5,7 +5,7 @@ Core Functional Job-to-be-done: To discover optimal price point and edition size Job Map: 1. (Define) Create digital product 2. (Prepare) Decide on drop parameters: - - Content -- HMW help seller preview their content to potential bidders? + - Content -- (HMW help seller preview their content to potential bidders?) - Metadata -- name, symbol, initial owner, royalty bips, funds recipient, metadata renderer, and sales config 3. (Prepare) Decide on auction parameters: - Money -- minimum viable revenue and seller funds recipient diff --git a/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature index d645f92c..42a6940b 100644 --- a/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature +++ b/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature @@ -13,7 +13,7 @@ Feature: Variable Supply Auctions Background: VSA creation and bidding Given Seller creates a Variable Supply Auction - And Seller and all Bidder account balances are 100 ETH + And Seller, Seller Funds Recipient, and all Bidder account balances are 100 ETH And The following sealed bids are placed | account | bid amount | sent value | | Bidder1 | 1 ETH | 1 ETH | @@ -29,6 +29,7 @@ Feature: Variable Supply Auctions | Bidder11 | 6 ETH | 6 ETH | | Bidder12 | 6 ETH | 9 ETH | | Bidder13 | 11 ETH | 12 ETH | + | Bidder14 | 2 ETH | 2 ETH | Scenario: Bidders reveal bids When All bids are revealed @@ -47,27 +48,13 @@ Feature: Variable Supply Auctions | Bidder11 | 6 ETH | | Bidder12 | 6 ETH | | Bidder13 | 11 ETH | + | Bidder14 | 2 ETH | Scenario: Seller settles VSA at 1 ETH When All bids are revealed And Seller settles auction at 1 ETH - Then The NFT contract should be an edition of 13 - And The account balances should be - | account | balance | - | Seller | 113 ETH | - | Bidder1 | 99 ETH | - | Bidder2 | 99 ETH | - | Bidder3 | 99 ETH | - | Bidder4 | 99 ETH | - | Bidder5 | 99 ETH | - | Bidder6 | 99 ETH | - | Bidder7 | 99 ETH | - | Bidder8 | 99 ETH | - | Bidder9 | 99 ETH | - | Bidder10 | 99 ETH | - | Bidder11 | 99 ETH | - | Bidder12 | 99 ETH | - | Bidder13 | 99 ETH | + Then The NFT contract should be an edition of 14 + And The Seller Funds Recipient account balance should be 114 ETH And The following accounts should own 1 NFT | account | | Bidder1 | @@ -83,6 +70,7 @@ Feature: Variable Supply Auctions | Bidder11 | | Bidder12 | | Bidder13 | + | Bidder14 | And The available refunds should be | account | available refund | | Bidder1 | 0 ETH | @@ -98,27 +86,13 @@ Feature: Variable Supply Auctions | Bidder11 | 5 ETH | | Bidder12 | 8 ETH | | Bidder13 | 11 ETH | + | Bidder14 | 1 ETH | Scenario: Seller settles VSA at 6 ETH When All bids are revealed And Seller settles auction at 6 ETH Then The NFT contract should be an edition of 3 - And The account balances should be - | account | balance | - | Seller | 118 ETH | - | Bidder1 | 100 ETH | - | Bidder2 | 100 ETH | - | Bidder3 | 1000 ETH | - | Bidder4 | 100 ETH | - | Bidder5 | 100 ETH | - | Bidder6 | 100 ETH | - | Bidder7 | 100 ETH | - | Bidder8 | 100 ETH | - | Bidder9 | 100 ETH | - | Bidder10 | 100 ETH | - | Bidder11 | 94 ETH | - | Bidder12 | 94 ETH | - | Bidder13 | 94 ETH | + And The Seller Funds Recipient account balance should be 118 ETH And The following accounts should own 1 NFT | Bidder11 | | Bidder12 | @@ -138,27 +112,13 @@ Feature: Variable Supply Auctions | Bidder11 | 0 ETH | | Bidder12 | 3 ETH | | Bidder13 | 6 ETH | + | Bidder14 | 2 ETH | Scenario: Seller settles VSA at 11 ETH When All bids are revealed And Seller settles auction at 11 ETH Then The NFT contract should be a 1 of 1 - And The account balances should be - | account | balance | - | Seller | 111 ETH | - | Bidder1 | 100 ETH | - | Bidder2 | 100 ETH | - | Bidder3 | 100 ETH | - | Bidder4 | 100 ETH | - | Bidder5 | 100 ETH | - | Bidder6 | 100 ETH | - | Bidder7 | 100 ETH | - | Bidder8 | 100 ETH | - | Bidder9 | 100 ETH | - | Bidder10 | 100 ETH | - | Bidder11 | 100 ETH | - | Bidder12 | 100 ETH | - | Bidder13 | 89 ETH | + And The Seller Funds Recipient account balance should be 111 ETH And The following accounts should own 1 NFT | Bidder13 | And The available refunds should be @@ -176,6 +136,12 @@ Feature: Variable Supply Auctions | Bidder11 | 6 ETH | | Bidder12 | 9 ETH | | Bidder13 | 1 ETH | + | Bidder14 | 2 ETH | + + Scenario: Seller cannot settle VSA at 2 ETH + When All bids are revealed + And Seller settles auction at 2 ETH + Then The Seller should receive a Does Not Meet Minimum Revenue error # TODO handle additional bid space bounding, beyond minimum viable revenue ## Seller sets maximum edition size commitment @@ -183,4 +149,4 @@ Feature: Variable Supply Auctions # TODO address failure to reveal sad paths # TODO address failure to settle sad paths # TODO consider Cleanup function to delete auction, once all refunds have been claimed -# TODO add Cucumber feature for bidder functionality +# TODO add Cucumber scenarios for bidder functionality diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 5d8f6b4e..8bef8166 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -218,7 +218,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumViableRevenue: 1 ether, + _minimumViableRevenue: 10 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: TIME0, _bidPhaseDuration: 3 days, @@ -243,7 +243,7 @@ contract VariableSupplyAuctionTest is Test { ) = auctions.auctionForDrop(address(drop)); assertEq(sellerStored, address(seller)); - assertEq(minimumViableRevenue, 1 ether); + assertEq(minimumViableRevenue, 10 ether); assertEq(sellerFundsRecipientStored, address(sellerFundsRecipient)); assertEq(startTime, uint32(block.timestamp)); assertEq(endOfBidPhase, uint32(block.timestamp + 3 days)); @@ -259,7 +259,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumViableRevenue: 1 ether, + _minimumViableRevenue: 10 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: 1 days, _bidPhaseDuration: 3 days, @@ -289,7 +289,7 @@ contract VariableSupplyAuctionTest is Test { function testEvent_createAuction() public { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), - minimumViableRevenue: 1 ether, + minimumViableRevenue: 10 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(TIME0), endOfBidPhase: uint32(TIME0 + 3 days), @@ -307,7 +307,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumViableRevenue: 1 ether, + _minimumViableRevenue: 10 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: TIME0, _bidPhaseDuration: 3 days, @@ -322,7 +322,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumViableRevenue: 1 ether, + _minimumViableRevenue: 10 ether, _sellerFundsRecipient: address(sellerFundsRecipient), _startTime: TIME0, _bidPhaseDuration: 3 days, @@ -337,7 +337,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), - _minimumViableRevenue: 1 ether, + _minimumViableRevenue: 10 ether, _sellerFundsRecipient: address(0), _startTime: TIME0, _bidPhaseDuration: 3 days, @@ -346,6 +346,8 @@ contract VariableSupplyAuctionTest is Test { }); } + // TODO add tests for multiple valid auctions at once + /*////////////////////////////////////////////////////////////// CANCEL AUCTION //////////////////////////////////////////////////////////////*/ @@ -387,7 +389,7 @@ contract VariableSupplyAuctionTest is Test { function testEvent_CancelAuction() public setupBasicAuction { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), - minimumViableRevenue: 1 ether, + minimumViableRevenue: 10 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(TIME0), endOfBidPhase: uint32(TIME0 + 3 days), @@ -424,6 +426,10 @@ contract VariableSupplyAuctionTest is Test { auctions.cancelAuction(address(drop)); } + // function test_CancelAuction_WhenMinimumViableRevenueNotMet() public { + // TODO x + // } + // TODO x update biz logic to allow one other case -- cancelling auctions // in settle phase that did not meet minimum viable revenue goal @@ -474,7 +480,7 @@ contract VariableSupplyAuctionTest is Test { function testEvent_PlaceBid_WhenSingle() public setupBasicAuction { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), - minimumViableRevenue: 1 ether, + minimumViableRevenue: 10 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(TIME0), endOfBidPhase: uint32(TIME0 + 3 days), @@ -497,7 +503,7 @@ contract VariableSupplyAuctionTest is Test { function testEvent_PlaceBid_WhenMultiple() public setupBasicAuction { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), - minimumViableRevenue: 1 ether, + minimumViableRevenue: 10 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(TIME0), endOfBidPhase: uint32(TIME0 + 3 days), @@ -656,7 +662,7 @@ contract VariableSupplyAuctionTest is Test { function testEvent_RevealBid_WhenSingle() public setupBasicAuction { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), - minimumViableRevenue: 1 ether, + minimumViableRevenue: 10 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(TIME0), endOfBidPhase: uint32(TIME0 + 3 days), @@ -684,7 +690,7 @@ contract VariableSupplyAuctionTest is Test { function testEvent_RevealBid_WhenMultiple() public setupBasicAuction { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), - minimumViableRevenue: 1 ether, + minimumViableRevenue: 10 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(TIME0), endOfBidPhase: uint32(TIME0 + 3 days), @@ -838,15 +844,20 @@ contract VariableSupplyAuctionTest is Test { Expected output: [Price Point] [Edition Size] [Revenue] - [1] [13] [13] + [1] [14] [14] + [2] [4] [0] [6] [3] [18] [11] [1] [11] + Note that revenue is set to 0 at price point 2 ether + because 8 ether would not meet minimum viable revenue + (and therefore this won't be a viable settle option) + */ assertEq(pricePoints[0], 1 ether); - assertEq(editionSizes[0], 13); - assertEq(revenues[0], 13 ether); + assertEq(editionSizes[0], 14); + assertEq(revenues[0], 14 ether); assertEq(pricePoints[1], 6 ether); assertEq(editionSizes[1], 3); @@ -855,11 +866,41 @@ contract VariableSupplyAuctionTest is Test { assertEq(pricePoints[2], 11 ether); assertEq(editionSizes[2], 1); assertEq(revenues[2], 11 ether); + + assertEq(pricePoints[3], 2 ether); + assertEq(editionSizes[3], 4); + assertEq(revenues[3], 0); + } + + // 1_794_872-1_691_644 = 103_228 more than once + // 1_703_719-1_691_757 = 11_962 more than once (with optimization) + + function testGas_AndIdempotent_CalculateSettleOptions_WhenCalledTwice() public setupBasicAuction throughRevealPhaseComplex { + auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOptions(address(drop)); + } + + // 1_899_651-1_794_851 = 104_800 more than twice + // 1_717_252-1_703_719 = 13_533 more than twice (with optimization) + + function testGas_AndIdempotent_CalculateSettleOptions_WhenCalledThrice() public setupBasicAuction throughRevealPhaseComplex { + auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOptions(address(drop)); + } + + // 1_730_788-1_717_253 = 13_535 more than thrice (with optimization) + + function testGas_AndIdempotent_CalculateSettleOptions_WhenCalledFrice() public setupBasicAuction throughRevealPhaseComplex { + auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOptions(address(drop)); } /* - Scenario for the following 3 settleAuction unit tests + Scenario for the following 4 settleAuction tests Given The following sealed bids are placed | account | bid amount | sent value | @@ -876,10 +917,12 @@ contract VariableSupplyAuctionTest is Test { | Bidder11 | 6 ETH | 6 ETH | | Bidder12 | 6 ETH | 9 ETH | | Bidder13 | 11 ETH | 12 ETH | + | Bidder14 | 2 ETH | 2 ETH | When The seller settles the auction Then The seller can choose one of the following edition sizes and revenue amounts | edition size | revenue generated | | 13 | 13 ether | + | 5 | 0 ether | | 3 | 18 ether | | 1 | 11 ether | @@ -904,6 +947,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(drop.balanceOf(address(bidder11)), 0); assertEq(drop.balanceOf(address(bidder12)), 0); assertEq(drop.balanceOf(address(bidder13)), 0); + assertEq(drop.balanceOf(address(bidder14)), 0); // seller funds recipient has 100 ether assertEq(address(sellerFundsRecipient).balance, 100 ether); @@ -921,7 +965,7 @@ contract VariableSupplyAuctionTest is Test { , , ) = auctions.auctionForDrop(address(drop)); - assertEq(totalBalance, 82 ether); + assertEq(totalBalance, 84 ether); // bidder auction balances each still full amount of sent ether (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); @@ -937,6 +981,7 @@ contract VariableSupplyAuctionTest is Test { (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); + (, uint96 bidderBalance14, ) = auctions.bidsForDrop(address(drop), address(bidder14)); assertEq(bidderBalance1, 1 ether); assertEq(bidderBalance2, 9 ether); assertEq(bidderBalance3, 8 ether); @@ -950,10 +995,11 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance11, 6 ether); assertEq(bidderBalance12, 9 ether); assertEq(bidderBalance13, 12 ether); + assertEq(bidderBalance14, 2 ether); } function test_SettleAuction_WhenSettlingAtLowPriceHighSupply() public setupBasicAuction throughRevealPhaseComplex { - _expectSettledAuctionEvent(82 ether, 1 ether, 13); + _expectSettledAuctionEvent(84 ether, 1 ether, 14); // When -- seller settles auction at price point of 1 ether vm.prank(address(seller)); @@ -961,8 +1007,8 @@ contract VariableSupplyAuctionTest is Test { // Then assert -- // 1) all bidders have 1 NFT - // 2) seller funds recipient has 113 ether - // 3) auction total balance is sent ether less settled revenue of 13 ether + // 2) seller funds recipient has 114 ether + // 3) auction total balance is sent ether less settled revenue of 14 ether // 4) auction settled revenue, price point, and edition size are correct // 5) bidder auction balances (available to withdraw) are their amount of // sent ether less the settled price point of 1 ether @@ -980,8 +1026,9 @@ contract VariableSupplyAuctionTest is Test { assertEq(drop.balanceOf(address(bidder11)), 1); assertEq(drop.balanceOf(address(bidder12)), 1); assertEq(drop.balanceOf(address(bidder13)), 1); + assertEq(drop.balanceOf(address(bidder14)), 1); - assertEq(address(sellerFundsRecipient).balance, 113 ether); + assertEq(address(sellerFundsRecipient).balance, 114 ether); ( , @@ -997,11 +1044,11 @@ contract VariableSupplyAuctionTest is Test { uint16 settledEditionSize ) = auctions.auctionForDrop(address(drop)); - assertEq(totalBalance, 82 ether - 13 ether); + assertEq(totalBalance, 84 ether - 14 ether); - assertEq(settledRevenue, 13 ether); + assertEq(settledRevenue, 14 ether); assertEq(settledPricePoint, 1 ether); - assertEq(settledEditionSize, 13); + assertEq(settledEditionSize, 14); (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); @@ -1016,6 +1063,7 @@ contract VariableSupplyAuctionTest is Test { (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); + (, uint96 bidderBalance14, ) = auctions.bidsForDrop(address(drop), address(bidder14)); assertEq(bidderBalance1, 0 ether); assertEq(bidderBalance2, 8 ether); assertEq(bidderBalance3, 7 ether); @@ -1029,10 +1077,11 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance11, 5 ether); assertEq(bidderBalance12, 8 ether); assertEq(bidderBalance13, 11 ether); + assertEq(bidderBalance14, 1 ether); } function test_SettleAuction_WhenSettlingAtMidPriceMidSupply() public setupBasicAuction throughRevealPhaseComplex { - _expectSettledAuctionEvent(82 ether, 6 ether, 3); + _expectSettledAuctionEvent(84 ether, 6 ether, 3); // When -- seller settles auction at price point of 6 ether vm.prank(address(seller)); @@ -1044,7 +1093,7 @@ contract VariableSupplyAuctionTest is Test { // 3) auction total balance is sent ether less settled revenue of 18 ether // 4) auction settled revenue, price point, and edition size are correct // 5) bidders 11–13 balance is their sent ether less settled price point of 6 ether - // 6) bidders 1–10 balances (available to withdraw) are their full sent ether + // 6) bidders 1–10 and 14 balances (available to withdraw) are their full sent ether assertEq(drop.balanceOf(address(bidder1)), 0); assertEq(drop.balanceOf(address(bidder2)), 0); @@ -1059,6 +1108,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(drop.balanceOf(address(bidder11)), 1); assertEq(drop.balanceOf(address(bidder12)), 1); assertEq(drop.balanceOf(address(bidder13)), 1); + assertEq(drop.balanceOf(address(bidder14)), 0); assertEq(address(sellerFundsRecipient).balance, 118 ether); @@ -1076,7 +1126,7 @@ contract VariableSupplyAuctionTest is Test { uint16 settledEditionSize ) = auctions.auctionForDrop(address(drop)); - assertEq(totalBalance, 82 ether - 18 ether); + assertEq(totalBalance, 84 ether - 18 ether); assertEq(settledRevenue, 18 ether); assertEq(settledPricePoint, 6 ether); @@ -1095,6 +1145,7 @@ contract VariableSupplyAuctionTest is Test { (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); + (, uint96 bidderBalance14, ) = auctions.bidsForDrop(address(drop), address(bidder14)); assertEq(bidderBalance1, 1 ether); assertEq(bidderBalance2, 9 ether); assertEq(bidderBalance3, 8 ether); @@ -1108,10 +1159,11 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance11, 0 ether); assertEq(bidderBalance12, 3 ether); assertEq(bidderBalance13, 6 ether); + assertEq(bidderBalance14, 2 ether); } function test_SettleAuction_WhenSettlingAtHighPriceLowSupply() public setupBasicAuction throughRevealPhaseComplex { - _expectSettledAuctionEvent(82 ether, 11 ether, 1); + _expectSettledAuctionEvent(84 ether, 11 ether, 1); // When -- seller settles auction at price point of 11 ether vm.prank(address(seller)); @@ -1123,7 +1175,7 @@ contract VariableSupplyAuctionTest is Test { // 3) auction total balance is sent ether less settled revenue of 11 ether // 4) auction settled revenue, price point, and edition size are correct // 5) bidder 13 auction balance is their sent ether less settled price point of 11 ether - // 6) bidders 1–12 auction balances (available to withdraw) are their full sent ether + // 6) bidders 1–12 and 14 balances (available to withdraw) are their full sent ether assertEq(drop.balanceOf(address(bidder1)), 0); assertEq(drop.balanceOf(address(bidder2)), 0); @@ -1138,6 +1190,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(drop.balanceOf(address(bidder11)), 0); assertEq(drop.balanceOf(address(bidder12)), 0); assertEq(drop.balanceOf(address(bidder13)), 1); + assertEq(drop.balanceOf(address(bidder14)), 0); assertEq(address(sellerFundsRecipient).balance, 111 ether); @@ -1155,7 +1208,7 @@ contract VariableSupplyAuctionTest is Test { uint16 settledEditionSize ) = auctions.auctionForDrop(address(drop)); - assertEq(totalBalance, 82 ether - 11 ether); + assertEq(totalBalance, 84 ether - 11 ether); assertEq(settledRevenue, 11 ether); assertEq(settledPricePoint, 11 ether); @@ -1174,6 +1227,7 @@ contract VariableSupplyAuctionTest is Test { (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); + (, uint96 bidderBalance14, ) = auctions.bidsForDrop(address(drop), address(bidder14)); assertEq(bidderBalance1, 1 ether); assertEq(bidderBalance2, 9 ether); assertEq(bidderBalance3, 8 ether); @@ -1187,6 +1241,14 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance11, 6 ether); assertEq(bidderBalance12, 9 ether); assertEq(bidderBalance13, 1 ether); + assertEq(bidderBalance14, 2 ether); + } + + function testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() public setupBasicAuction throughRevealPhaseComplex { + vm.expectRevert("DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 2 ether); } /*////////////////////////////////////////////////////////////// @@ -1199,7 +1261,7 @@ contract VariableSupplyAuctionTest is Test { CLAIM REFUND //////////////////////////////////////////////////////////////*/ - function test_CheckAvailableRefund() public setupBasicAuction { + function test_CheckAvailableRefund() public setupBasicAuctionWithLowMinimumViableRevenue { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); vm.prank(address(bidder2)); @@ -1231,7 +1293,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(auctions.checkAvailableRefund(address(drop)), 1 ether); // = 3 ether sent - 2 ether winning bid } - function test_ClaimRefund_WhenWinner() public setupBasicAuction { + function test_ClaimRefund_WhenWinner() public setupBasicAuctionWithLowMinimumViableRevenue { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); @@ -1258,7 +1320,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance, 0 ether); } - function test_ClaimRefund_WhenNotWinner() public setupBasicAuction { + function test_ClaimRefund_WhenNotWinner() public setupBasicAuctionWithLowMinimumViableRevenue { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); vm.prank(address(bidder2)); @@ -1289,7 +1351,7 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance, 0 ether); } - function testEvent_ClaimRefund() public setupBasicAuction { + function testEvent_ClaimRefund() public setupBasicAuctionWithLowMinimumViableRevenue { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), minimumViableRevenue: 1 ether, @@ -1324,12 +1386,9 @@ contract VariableSupplyAuctionTest is Test { auctions.claimRefund(address(drop)); } - function testRevert_ClaimRefund_WhenNoBidPlaced() public setupBasicAuction { + function testRevert_ClaimRefund_WhenNoBidPlaced() public setupBasicAuctionWithLowMinimumViableRevenue { vm.warp(TIME0 + 3 days + 2 days); - vm.prank(address(seller)); - auctions.settleAuction(address(drop), 1 ether); - vm.expectRevert("NO_REFUND_AVAILABLE"); vm.prank(address(bidder1)); @@ -1340,7 +1399,7 @@ contract VariableSupplyAuctionTest is Test { // // TODO x // } - function testRevert_ClaimRefund_WhenAlreadyClaimed() public setupBasicAuction { + function testRevert_ClaimRefund_WhenAlreadyClaimed() public setupBasicAuctionWithLowMinimumViableRevenue { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); @@ -1371,6 +1430,20 @@ contract VariableSupplyAuctionTest is Test { // TODO parameterize modifier pattern to support fuzzing modifier setupBasicAuction() { + vm.prank(address(seller)); + auctions.createAuction({ + _tokenContract: address(drop), + _minimumViableRevenue: 10 ether, + _sellerFundsRecipient: address(sellerFundsRecipient), + _startTime: TIME0, + _bidPhaseDuration: 3 days, + _revealPhaseDuration: 2 days, + _settlePhaseDuration: 1 days + }); + + _; + } + modifier setupBasicAuctionWithLowMinimumViableRevenue() { vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), @@ -1416,7 +1489,11 @@ contract VariableSupplyAuctionTest is Test { // 10 bids at 1 ether vm.prank(address(bidder13)); - auctions.placeBid{value: 12 ether}(address(drop), _genSealedBid(11 ether, salt13)); + auctions.placeBid{value: 12 ether}(address(drop), _genSealedBid(11 ether, salt13)); + + // 1 bid at 2 ether + vm.prank(address(bidder14)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(2 ether, salt14)); vm.warp(TIME0 + 3 days); @@ -1446,6 +1523,8 @@ contract VariableSupplyAuctionTest is Test { auctions.revealBid(address(drop), 6 ether, salt12); vm.prank(address(bidder13)); auctions.revealBid(address(drop), 11 ether, salt13); + vm.prank(address(bidder14)); + auctions.revealBid(address(drop), 2 ether, salt14); vm.warp(TIME0 + 3 days + 2 days); @@ -1458,7 +1537,7 @@ contract VariableSupplyAuctionTest is Test { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), - minimumViableRevenue: 1 ether, + minimumViableRevenue: 10 ether, sellerFundsRecipient: address(sellerFundsRecipient), startTime: uint32(TIME0), endOfBidPhase: uint32(TIME0 + 3 days), diff --git a/uml/VariableSupplyAuction/6-settleAuction.txt b/uml/VariableSupplyAuction/6-settleAuction.txt index c8fbcb38..25da67fa 100644 --- a/uml/VariableSupplyAuction/6-settleAuction.txt +++ b/uml/VariableSupplyAuction/6-settleAuction.txt @@ -5,6 +5,8 @@ participant ERC721Drop Seller -> VariableSupplyAuction : settleAuction() +TODO x update to consolidate biz logic and add minimum viable revenue + VariableSupplyAuction -> VariableSupplyAuction : validate auction is in settle phase VariableSupplyAuction -> VariableSupplyAuction : validate auction not settled yet VariableSupplyAuction -> VariableSupplyAuction : based on chosen price point, calculate and store final edition size and winning bidders From 6820637a1ad171c06751681b61b8c759fd2f342b Mon Sep 17 00:00:00 2001 From: neodaoist Date: Sat, 29 Oct 2022 11:14:09 -0400 Subject: [PATCH 23/31] [draft] Many an additional check --- .gas-snapshot | 107 ++-- .../VariableSupplyAuction.sol | 103 ++-- .../2-VariableSupplyAuction.feature | 1 + .../VariableSupplyAuction.t.sol | 464 ++++++++++++++++-- uml/VariableSupplyAuction/6-settleAuction.txt | 1 + 5 files changed, 570 insertions(+), 106 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 37c329e8..0060bfc6 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,47 +319,78 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85179) -VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 1161772) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295824) +VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85246) +VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 377468) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295847) VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160672) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 414762) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 215232) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 415137) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 215387) VariableSupplyAuctionTest:testEvent_createAuction() (gas: 98154) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91986) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159757) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99790) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 1159973) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 997805) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20647) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOptions_WhenCalledFrice() (gas: 1739559) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOptions_WhenCalledThrice() (gas: 1725869) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOptions_WhenCalledTwice() (gas: 1712135) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 92009) +VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionDoesNotExist() (gas: 16678) +VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionHasZeroRevealedBids() (gas: 98470) +VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionInBidPhase() (gas: 95581) +VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionInCleanupPhase() (gas: 96411) +VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionInRevealPhase() (gas: 96038) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenAuctionDoesNotExist() (gas: 27022) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159915) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseAndMinimumViableRevenueWasMet() (gas: 571929) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOptionsYet() (gas: 404890) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99788) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionDoesNotExist() (gas: 15398) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInBidPhase() (gas: 156313) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInRevealPhase() (gas: 210667) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInSettlePhase() (gas: 211198) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 376044) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionDoesNotExist() (gas: 20376) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInBidPhase() (gas: 159325) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInRevealPhase() (gas: 213615) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInSettlePhase() (gas: 214187) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 102230) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidRevealed() (gas: 421038) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20670) VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97479) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27546) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106941) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 107023) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166187) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102008) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159715) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160433) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102528) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 161083) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160743) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 161085) -VariableSupplyAuctionTest:test_CalculateSettleOptions() (gas: 1551261) -VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85032) -VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 1356199) -VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 1253683) -VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 1159031) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95144) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96335) -VariableSupplyAuctionTest:test_DropInitial() (gas: 31013) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 284017) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156151) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401351) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 210004) -VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1408249) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 2347313) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2602685) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 2389920) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27591) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInCleanupPhase() (gas: 107105) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106963) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 107024) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166232) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102030) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionDoesNotExist() (gas: 20922) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159869) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInCleanupPhase() (gas: 160667) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160588) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102725) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 161215) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160875) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 161218) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionDoesNotExist() (gas: 22689) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionHasZeroRevealedBids() (gas: 102537) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInBidPhase() (gas: 99670) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInCleanupPhase() (gas: 100414) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInRevealPhase() (gas: 100127) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtInvalidPricePoint() (gas: 1700684) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() (gas: 1698673) +VariableSupplyAuctionTest:test_CalculateSettleOptions() (gas: 1699916) +VariableSupplyAuctionTest:test_CancelAuction_WhenInSettlePhaseButMinimumViableRevenueNotMet() (gas: 519610) +VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85151) +VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 609745) +VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 501043) +VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 374617) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95188) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96380) +VariableSupplyAuctionTest:test_DropInitial() (gas: 35529) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 283995) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156129) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401704) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 210113) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1508951) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 1850125) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2129064) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 1893040) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 965fa272..f7691853 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -179,7 +179,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // | ----------------------->| // | | // | ----. - // | | validate no bids placed yet TODO x add validate minimum viable revenue check + // | | validate no bids placed yet // | <---' // | | // | ----. @@ -207,12 +207,31 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Get the auction for the specified drop Auction memory auction = auctionForDrop[_tokenContract]; - // Ensure that no bids have been placed in this auction yet - require(auction.totalBalance == 0, "CANNOT_CANCEL_AUCTION_WITH_BIDS"); + // Ensure the auction exists + require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); // Ensure the caller is the seller require(msg.sender == auction.seller, "ONLY_SELLER"); + // Ensure that no bids have been placed in this auction yet, or, if in + // settle phase, that no price points meet auction minimum viable revenue + if (block.timestamp >= auction.endOfRevealPhase && block.timestamp < auction.endOfSettlePhase) { + // Get the settle price points + uint96[] storage settlePricePoints = _settlePricePointsForAuction[_tokenContract]; + + // Ensure seller has first considered the settle price points before attempting to cancel + require(settlePricePoints.length > 0, "CANNOT_CANCEL_AUCTION_BEFORE_CALCULATING_SETTLE_OPTIONS"); + + // Ensure none of the price point options meet minimum viable revenue + for (uint256 i = 0; i < settlePricePoints.length; i++) { + SettleOption storage settleOption = _settleOptionsForPricePoint[_tokenContract][settlePricePoints[i]]; + + require(settleOption.revenue < auction.minimumViableRevenue, "CANNOT_CANCEL_AUCTION_WITH_VIABLE_PRICE_POINT"); + } + } else { + require(auction.totalBalance == 0, "CANNOT_CANCEL_AUCTION_WITH_BIDS"); + } + emit AuctionCanceled(_tokenContract, auction); // Remove the auction from storage @@ -269,13 +288,13 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Ensure the auction exists require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); - // Ensure the auction is still in bid phase + // Ensure the auction is in bid phase require(block.timestamp < auction.endOfBidPhase, "BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); // Ensure the bidder has not placed a bid in auction already require(bidsForDrop[_tokenContract][msg.sender].bidderBalance == 0, "ALREADY_PLACED_BID_IN_AUCTION"); - // Ensure the bid is valid and includes some ether + // Ensure the bid is valid require(msg.value > 0 ether, "VALID_BIDS_MUST_INCLUDE_ETHER"); // Update the total balance for auction @@ -342,7 +361,10 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; - // Ensure auction is in reveal phase + // Ensure the auction exists + require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + + // Ensure the auction is in reveal phase require(block.timestamp >= auction.endOfBidPhase && block.timestamp < auction.endOfRevealPhase, "REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); // Get the bid for the specified bidder @@ -442,19 +464,33 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa mapping(address => mapping(uint96 => SettleOption)) public _settleOptionsForPricePoint; /// @notice Calculate edition size and revenue for each possible price point - /// @dev Cheaper on subsequent calls, after the initial call when - /// calculations have been performed and saved + /// @dev Cheaper on subsequent calls but idempotent -- after the initial call when + /// calculations have been performed and stored, the settle options will not change. + /// Function visibility is public instead of external, to support settleAuction calling it. /// @param _tokenContract The address of the ERC-721 drop contract /// @return A tuple of 3 arrays representing the settle options -- /// the possible price points at which to settle, along with the /// resulting edition sizes and amounts of revenue generated function calculateSettleOptions(address _tokenContract) public returns (uint96[] memory, uint16[] memory, uint96[] memory) { - // TODO x gas optimization -- algorithm =P - // TODO x gas optimization -- consider other, less storage-intensive options + // TODO x improve algorithm =P + // TODO x gas optimizations -- consider other, less storage-intensive options + // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; + + // Ensure the auction exists + require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + + // Ensure the auction is in settle phase + require(block.timestamp >= auction.endOfRevealPhase && block.timestamp < auction.endOfSettlePhase, "SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + + // Get the revealed bidders for the auction address[] storage bidders = _revealedBiddersForDrop[_tokenContract]; + + // Ensure the auction has at least 1 revealed bid + require(bidders.length > 0, "NO_REVEALED_BIDS_TO_SETTLE_AUCTION"); + uint96[] storage settlePricePoints = _settlePricePointsForAuction[_tokenContract]; mapping(uint96 => SettleOption) storage settleOptions = _settleOptionsForPricePoint[_tokenContract]; @@ -513,26 +549,23 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param _settlePricePoint The price point at which to settle the auction function settleAuction(address _tokenContract, uint96 _settlePricePoint) external nonReentrant { - // TODO x checks // TODO x gas optimizations - // TODO x look for more ways to consolidate business logic with calculateSettleOptions // TODO x document pragmatic max edition size / winning bidders // TODO x consider storing winningBidders during calculateSettleOptions + // TODO x look for more ways to consolidate business logic with calculateSettleOptions // Get the auction Auction storage auction = auctionForDrop[_tokenContract]; - // Get the bidders who revealed in this auction - address[] storage bidders = _revealedBiddersForDrop[_tokenContract]; - + // Calculate settle options, if not done yet (also includes check for auction existence) if (_settlePricePointsForAuction[_tokenContract].length == 0) { calculateSettleOptions(_tokenContract); } // Get the settle option at this price point SettleOption storage settleOption = _settleOptionsForPricePoint[_tokenContract][_settlePricePoint]; - - // Check that revenue meets minimum viable revenue + + // Ensure that revenue meets minimum viable revenue require(settleOption.revenue >= auction.minimumViableRevenue, "DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); // Store the current total balance and final auction details @@ -541,11 +574,16 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa auction.settledPricePoint = _settlePricePoint; auction.settledEditionSize = settleOption.editionSize; - // TODO x store the fact that an auction has been settled and clean up unneeded storage + // TODO store the fact that an auction has been settled and (1) update endOfSettlePhase + // to enter cleanup phase immediately and (2) clean up unneeded storage (and allow + // bidders to claim refunds ASAP) // Get the bids for this auction mapping(address => Bid) storage bids = bidsForDrop[_tokenContract]; + // Get the bidders who revealed in this auction + address[] storage bidders = _revealedBiddersForDrop[_tokenContract]; + // Loop through bids to determine winning bidders and update bidder balances uint256 index; address[] memory winningBidders = new address[](auction.settledEditionSize); @@ -621,37 +659,46 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// bid amount; if not winner, the full amount of ether sent with your bid /// @param _tokenContract The address of the ERC-721 drop contract function checkAvailableRefund(address _tokenContract) external view returns (uint96) { - - // TODO x add checks, including cleanup phase - // TODO x consolidate with claimRefund + // Get the auction + Auction storage auction = auctionForDrop[_tokenContract]; - // Get the balance for the specified bidder - Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; + // Ensure the auction exists + require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + + // Ensure the auction is in cleanup phase + require(block.timestamp >= auction.endOfSettlePhase, "REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); - return bid.bidderBalance; + // Return the balance for the specified bidder + return bidsForDrop[_tokenContract][msg.sender].bidderBalance; } /// @notice Claim refund -- if winner, any additional ether sent above your /// bid amount; if not winner, the full amount of ether sent with your bid + /// @dev Some duplicated business logic between claimRefund and checkAvailableRefund, + /// to eliminate additional (warm) SLOADs and keep checkAvailableRefund user-friendly /// @param _tokenContract The address of the ERC-721 drop contract function claimRefund(address _tokenContract) external nonReentrant { // Get the auction Auction storage auction = auctionForDrop[_tokenContract]; - // TODO x add temporal checks + // Ensure the auction exists + require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + + // Ensure the auction is in cleanup phase + require(block.timestamp >= auction.endOfSettlePhase, "REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); // Get the balance for the specified bidder Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; uint96 bidderBalance = bid.bidderBalance; - // Ensure bidder has balance - require(bidderBalance > 0, "NO_REFUND_AVAILABLE"); + // Ensure bidder revealed a bid and has a leftover balance + require(bid.revealedBidAmount > 0 && bidderBalance > 0, "NO_REFUND_AVAILABLE"); // Clear bidder balance bid.bidderBalance = 0; // Transfer the bidder's available refund balance to the bidder - _handleOutgoingTransfer(msg.sender, bidderBalance, address(0), 50_000); + _handleOutgoingTransfer(msg.sender, bidderBalance, address(0), 50_000); emit RefundClaimed(_tokenContract, msg.sender, bidderBalance, auction); } diff --git a/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature index 42a6940b..bac60e57 100644 --- a/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature +++ b/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature @@ -9,6 +9,7 @@ Feature: Variable Supply Auctions - Bid Phase - Reveal Phase - Settle Phase + - Cleanup Phase - Completed / Cancelled Background: VSA creation and bidding diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 8bef8166..5f580650 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -17,10 +17,6 @@ import {TestERC721} from "../../utils/tokens/TestERC721.sol"; import {WETH} from "../../utils/tokens/WETH.sol"; import {VM} from "../../utils/VM.sol"; -// TODO x more temporal checks -// TODO x improve settle auction biz logic and storage -// TODO x review - /// @title VariableSupplyAuctionTest /// @notice Unit Tests for Variable Supply Auctions contract VariableSupplyAuctionTest is Test { @@ -181,6 +177,7 @@ contract VariableSupplyAuctionTest is Test { bidder14.setApprovalForModule(address(auctions), true); bidder15.setApprovalForModule(address(auctions), true); + // TODO determine pattern for seller approving auction house to set edition size + mint // Seller approve ERC721TransferHelper // vm.prank(address(seller)); // token.setApprovalForAll(address(erc721TransferHelper), true); @@ -286,6 +283,8 @@ contract VariableSupplyAuctionTest is Test { assertEq(endOfSettlePhase, 1 days + 3 days + 2 days + 1 days); } + // TODO add tests that exercise other actions for auctions that don't start instantly + function testEvent_createAuction() public { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), @@ -371,6 +370,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.cancelAuction(address(drop)); + // Then auction has been deleted ( sellerStored, , @@ -386,6 +386,51 @@ contract VariableSupplyAuctionTest is Test { assertEq(sellerStored, address(0)); } + function test_CancelAuction_WhenInSettlePhaseButMinimumViableRevenueNotMet() public setupBasicAuction { + bytes32 commitment1 = _genSealedBid(1 ether, salt1); + bytes32 commitment2 = _genSealedBid(2 ether, salt2); + bytes32 commitment3 = _genSealedBid(3 ether, salt3); + + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment1); + vm.prank(address(bidder2)); + auctions.placeBid{value: 3 ether}(address(drop), commitment2); + vm.prank(address(bidder3)); + auctions.placeBid{value: 5 ether}(address(drop), commitment3); + + vm.warp(TIME0 + 3 days); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + vm.prank(address(bidder2)); + auctions.revealBid(address(drop), 2 ether, salt2); + vm.prank(address(bidder3)); + auctions.revealBid(address(drop), 3 ether, salt3); + + vm.warp(TIME0 + 3 days + 2 days); + + vm.prank(address(seller)); + auctions.calculateSettleOptions(address(drop)); // first, consider the settle options + + vm.prank(address(seller)); + auctions.cancelAuction(address(drop)); + + // Then auction has been deleted + ( + address sellerStored, + , + , + , + , + , + , + , + , + , + ) = auctions.auctionForDrop(address(drop)); + assertEq(sellerStored, address(0)); + } + function testEvent_CancelAuction() public setupBasicAuction { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), @@ -408,6 +453,13 @@ contract VariableSupplyAuctionTest is Test { auctions.cancelAuction(address(drop)); } + function testRevert_CancelAuction_WhenAuctionDoesNotExist() public { + vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + + vm.prank(address(seller)); + auctions.cancelAuction(address(drop)); + } + function testRevert_CancelAuction_WhenNotSeller() public setupBasicAuction { vm.expectRevert("ONLY_SELLER"); @@ -415,6 +467,67 @@ contract VariableSupplyAuctionTest is Test { auctions.cancelAuction(address(drop)); } + function testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOptionsYet() public setupBasicAuction { + bytes32 commitment1 = _genSealedBid(10 ether, salt1); + bytes32 commitment2 = _genSealedBid(2 ether, salt2); + bytes32 commitment3 = _genSealedBid(3 ether, salt3); + + vm.prank(address(bidder1)); + auctions.placeBid{value: 10 ether}(address(drop), commitment1); + vm.prank(address(bidder2)); + auctions.placeBid{value: 3 ether}(address(drop), commitment2); + vm.prank(address(bidder3)); + auctions.placeBid{value: 5 ether}(address(drop), commitment3); + + vm.warp(TIME0 + 3 days); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 10 ether, salt1); + vm.prank(address(bidder2)); + auctions.revealBid(address(drop), 2 ether, salt2); + vm.prank(address(bidder3)); + auctions.revealBid(address(drop), 3 ether, salt3); + + vm.warp(TIME0 + 3 days + 2 days); + + vm.expectRevert("CANNOT_CANCEL_AUCTION_BEFORE_CALCULATING_SETTLE_OPTIONS"); + + vm.prank(address(seller)); + auctions.cancelAuction(address(drop)); + } + + function testRevert_CancelAuction_WhenInSettlePhaseAndMinimumViableRevenueWasMet() public setupBasicAuction { + bytes32 commitment1 = _genSealedBid(10 ether, salt1); + bytes32 commitment2 = _genSealedBid(2 ether, salt2); + bytes32 commitment3 = _genSealedBid(3 ether, salt3); + + vm.prank(address(bidder1)); + auctions.placeBid{value: 10 ether}(address(drop), commitment1); + vm.prank(address(bidder2)); + auctions.placeBid{value: 3 ether}(address(drop), commitment2); + vm.prank(address(bidder3)); + auctions.placeBid{value: 5 ether}(address(drop), commitment3); + + vm.warp(TIME0 + 3 days); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 10 ether, salt1); + vm.prank(address(bidder2)); + auctions.revealBid(address(drop), 2 ether, salt2); + vm.prank(address(bidder3)); + auctions.revealBid(address(drop), 3 ether, salt3); + + vm.warp(TIME0 + 3 days + 2 days); + + vm.prank(address(seller)); + auctions.calculateSettleOptions(address(drop)); + + vm.expectRevert("CANNOT_CANCEL_AUCTION_WITH_VIABLE_PRICE_POINT"); + + vm.prank(address(seller)); + auctions.cancelAuction(address(drop)); + } + function testRevert_CancelAuction_WhenBidAlreadyPlaced() public setupBasicAuction { bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); @@ -426,19 +539,10 @@ contract VariableSupplyAuctionTest is Test { auctions.cancelAuction(address(drop)); } - // function test_CancelAuction_WhenMinimumViableRevenueNotMet() public { - // TODO x - // } - - // TODO x update biz logic to allow one other case -- cancelling auctions - // in settle phase that did not meet minimum viable revenue goal - /*////////////////////////////////////////////////////////////// PLACE BID //////////////////////////////////////////////////////////////*/ - // TODO x add more assertions around new storage variables - function test_PlaceBid_WhenSingle() public setupBasicAuction { bytes32 commitment = _genSealedBid(1 ether, salt1); @@ -570,15 +674,15 @@ contract VariableSupplyAuctionTest is Test { auctions.placeBid{value: 1 ether}(address(drop), commitment); } - // TODO x once settleAuction is written - // function testRevert_PlaceBid_WhenAuctionIsCompleted() public setupBasicAuction { - - // } + function testRevert_PlaceBid_WhenAuctionInCleanupPhase() public setupBasicAuction { + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase - // TODO x once cancelAuction is written - // function testRevert_PlaceBid_WhenAuctionIsCancelled() public setupBasicAuction { - - // } + vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); + + bytes32 commitment = _genSealedBid(1 ether, salt1); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1 ether}(address(drop), commitment); + } function testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() public setupBasicAuction { bytes32 commitment = _genSealedBid(1 ether, salt1); @@ -599,7 +703,9 @@ contract VariableSupplyAuctionTest is Test { auctions.placeBid(address(drop), commitment); } - // TODO revisit -– may become relevant if we move minter role granting into an ERC721DropTransferHelper + // TODO revisit, may become relevant if we move role granting for + // edition sizing / minting into an ERC721DropTransferHelper + // function testRevert_PlaceBid_WhenSellerDidNotApproveModule() public setupBasicAuction { // seller.setApprovalForModule(address(auctions), false); @@ -715,7 +821,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days); - // We can assert all events at once, bc stored auction does not change + // We can assert all events without changing Auction struct, bc stored auction does not change vm.expectEmit(true, true, true, true); emit BidRevealed(address(drop), address(bidder1), 1 ether, auction); vm.expectEmit(true, true, true, true); @@ -731,6 +837,13 @@ contract VariableSupplyAuctionTest is Test { auctions.revealBid(address(drop), 3 ether, salt3); } + function testRevert_RevealBid_WhenAuctionDoesNotExist() public { + vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + + vm.prank(address(bidder1)); + auctions.revealBid(address (drop), 1 ether, salt1); + } + function testRevert_RevealBid_WhenAuctionInBidPhase() public setupBasicAuction { bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); @@ -747,7 +860,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); - vm.warp(TIME0 + 3 days + 2 days); + vm.warp(TIME0 + 3 days + 2 days); // settle phase vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); @@ -755,15 +868,18 @@ contract VariableSupplyAuctionTest is Test { auctions.revealBid(address (drop), 1 ether, salt1); } - // TODO x once settleAuction is written - // function testRevert_RevealBid_WhenAuctionIsCompleted() public setupBasicAuction { - - // } + function testRevert_RevealBid_WhenAuctionInCleanupPhase() public setupBasicAuction { + bytes32 commitment = _genSealedBid(1 ether, salt1); + vm.prank(address(bidder1)); + auctions.placeBid{value: 1.1 ether}(address(drop), commitment); - // TODO x once cancelAuction is written - // function testRevert_RevealBid_WhenAuctionIsCancelled() public setupBasicAuction { - - // } + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase + + vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); + + vm.prank(address(bidder1)); + auctions.revealBid(address (drop), 1 ether, salt1); + } function testRevert_RevealBid_WhenNoCommittedBid() public setupBasicAuction { vm.warp(TIME0 + 3 days); @@ -825,6 +941,55 @@ contract VariableSupplyAuctionTest is Test { SETTLE AUCTION //////////////////////////////////////////////////////////////*/ + /* + + Checks for each action + + Create Auction + - check there is no live auction for the drop contract already + - check the funds recipient is not zero address + + Cancel Auction + - check the auction exists + - check the caller is the seller + - check there are no bids placed yet + - OR, if in settle phase: + - check that seller has first considered the settle price points + - check that no settle price points meet minimum viable revenue + + Place Bid + - check the auction exists + - check the auction is in bid phase + - check the bidder has not placed a bid yet + - check the bid is valid + + Reveal Bid + - check the auction exists + - check the auction is in reveal phase + - check the bidder placed a bid in the auction + - check the revealed amount is not greater than sent ether + - check the revealed bid matches the sealed bid + + Calculate Settle Options + - check the auction exists + - check the auction is in settle phase + - check the auction has at least 1 revealed bid + + Settle Auction + - (includes checks from calling calculate settle options, either in this call or previously) + - check that price point is a valid settle option (exists and meets minimum viable revenue) + + Check Available Refund + - check the auction exists + - check the auction is in cleanup phase + + Claim Refund + - check the auction exists + - check the auction is in cleanup phase + - check the bidder has a leftover balance + + */ + function test_CalculateSettleOptions() public setupBasicAuction throughRevealPhaseComplex { (uint96[] memory pricePoints, uint16[] memory editionSizes, uint96[] memory revenues) = auctions.calculateSettleOptions(address(drop)); @@ -898,6 +1063,47 @@ contract VariableSupplyAuctionTest is Test { auctions.calculateSettleOptions(address(drop)); } + function testRevert_CalculateSettleOptions_WhenAuctionDoesNotExist() public { + vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + + vm.prank(address(seller)); + auctions.calculateSettleOptions(address(drop)); + } + + function testRevert_CalculateSettleOptions_WhenAuctionInBidPhase() public setupBasicAuction { + vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + + vm.prank(address(seller)); + auctions.calculateSettleOptions(address(drop)); + } + + function testRevert_CalculateSettleOptions_WhenAuctionInRevealPhase() public setupBasicAuction { + vm.warp(TIME0 + 3 days); // reveal phase + + vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + + vm.prank(address(seller)); + auctions.calculateSettleOptions(address(drop)); + } + + function testRevert_CalculateSettleOptions_WhenAuctionInCleanupPhase() public setupBasicAuction { + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase + + vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + + vm.prank(address(seller)); + auctions.calculateSettleOptions(address(drop)); + } + + function testRevert_CalculateSettleOptions_WhenAuctionHasZeroRevealedBids() public setupBasicAuction { + vm.warp(TIME0 + 3 days + 2 days); + + vm.expectRevert("NO_REVEALED_BIDS_TO_SETTLE_AUCTION"); + + vm.prank(address(seller)); + auctions.calculateSettleOptions(address(drop)); + } + /* Scenario for the following 4 settleAuction tests @@ -1244,11 +1450,59 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance14, 2 ether); } + function testRevert_SettleAuction_WhenAuctionDoesNotExist() public { + vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 1 ether); + } + + function testRevert_SettleAuction_WhenAuctionInBidPhase() public setupBasicAuction { + vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 2 ether); + } + + function testRevert_SettleAuction_WhenAuctionInRevealPhase() public setupBasicAuction { + vm.warp(TIME0 + 3 days); // reveal phase + + vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 2 ether); + } + + function testRevert_SettleAuction_WhenAuctionInCleanupPhase() public setupBasicAuction { + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase + + vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 2 ether); + } + + function testRevert_SettleAuction_WhenAuctionHasZeroRevealedBids() public setupBasicAuction { + vm.warp(TIME0 + 3 days + 2 days); + + vm.expectRevert("NO_REVEALED_BIDS_TO_SETTLE_AUCTION"); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 2 ether); + } + function testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() public setupBasicAuction throughRevealPhaseComplex { vm.expectRevert("DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); vm.prank(address(seller)); - auctions.settleAuction(address(drop), 2 ether); + auctions.settleAuction(address(drop), 2 ether); // does not meet minimum viable revenue + } + + function testRevert_SettleAuction_WhenSettlingAtInvalidPricePoint() public setupBasicAuction throughRevealPhaseComplex { + vm.expectRevert("DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 3 ether); // non-existent settle price point } /*////////////////////////////////////////////////////////////// @@ -1283,6 +1537,8 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.settleAuction(address(drop), 2 ether); + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase + vm.prank(address(bidder1)); assertEq(auctions.checkAvailableRefund(address(drop)), 2 ether); // = full amount sent, not winning bid @@ -1293,6 +1549,55 @@ contract VariableSupplyAuctionTest is Test { assertEq(auctions.checkAvailableRefund(address(drop)), 1 ether); // = 3 ether sent - 2 ether winning bid } + function testRevert_CheckAvailableRefund_WhenAuctionDoesNotExist() public { + vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + + vm.prank(address(bidder1)); + auctions.checkAvailableRefund(address(drop)); + } + + function testRevert_CheckAvailableRefund_WhenAuctionInBidPhase() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + + vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + + vm.prank(address(bidder1)); + auctions.checkAvailableRefund(address(drop)); + } + + function testRevert_CheckAvailableRefund_WhenAuctionInRevealPhase() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + + vm.warp(TIME0 + 3 days); // reveal phase + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + + vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + + vm.prank(address(bidder1)); + auctions.checkAvailableRefund(address(drop)); + } + + function testRevert_CheckAvailableRefund_WhenAuctionInSettlePhase() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + + vm.warp(TIME0 + 3 days); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + + vm.warp(TIME0 + 3 days + 2 days); // settle phase + + vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + + vm.prank(address(bidder1)); + auctions.checkAvailableRefund(address(drop)); + } + function test_ClaimRefund_WhenWinner() public setupBasicAuctionWithLowMinimumViableRevenue { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); @@ -1307,6 +1612,8 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.settleAuction(address(drop), 1 ether); + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase + // Precondition checks assertEq(address(bidder1).balance, 98 ether); (, uint96 bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); @@ -1338,6 +1645,8 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.settleAuction(address(drop), 2 ether); + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase + // Precondition checks assertEq(address(bidder1).balance, 98 ether); (, uint96 bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); @@ -1379,6 +1688,8 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.settleAuction(address(drop), 1 ether); + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase + vm.expectEmit(true, true, true, true); emit RefundClaimed(address(drop), address(bidder1), 1 ether, auction); @@ -1386,8 +1697,57 @@ contract VariableSupplyAuctionTest is Test { auctions.claimRefund(address(drop)); } + function testRevert_ClaimRefund_WhenAuctionDoesNotExist() public { + vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + } + + function testRevert_ClaimRefund_WhenAuctionInBidPhase() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + + vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + } + + function testRevert_ClaimRefund_WhenAuctionInRevealPhase() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + + vm.warp(TIME0 + 3 days); // reveal phase + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + + vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + } + + function testRevert_ClaimRefund_WhenAuctionInSettlePhase() public setupBasicAuction { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + + vm.warp(TIME0 + 3 days); + + vm.prank(address(bidder1)); + auctions.revealBid(address(drop), 1 ether, salt1); + + vm.warp(TIME0 + 3 days + 2 days); // settle phase + + vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + } + function testRevert_ClaimRefund_WhenNoBidPlaced() public setupBasicAuctionWithLowMinimumViableRevenue { - vm.warp(TIME0 + 3 days + 2 days); + vm.warp(TIME0 + 3 days + 2 days + 1 days); vm.expectRevert("NO_REFUND_AVAILABLE"); @@ -1395,9 +1755,31 @@ contract VariableSupplyAuctionTest is Test { auctions.claimRefund(address(drop)); } - // function testRevert_ClaimRefund_WhenNoBidRevealed() public setupBasicAuction { - // // TODO x - // } + function testRevert_ClaimRefund_WhenNoBidRevealed() public setupBasicAuctionWithLowMinimumViableRevenue { + vm.prank(address(bidder1)); + auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); + vm.prank(address(bidder2)); + auctions.placeBid{value: 1 ether}(address(drop), _genSealedBid(1 ether, salt2)); + + vm.warp(TIME0 + 3 days); + + // bidder1 never revealed =( + // note bidder2 must reveal in this test, otherwise seller can't settle when no revealed bids + vm.prank(address(bidder2)); + auctions.revealBid(address(drop), 1 ether, salt2); + + vm.warp(TIME0 + 3 days + 2 days); + + vm.prank(address(seller)); + auctions.settleAuction(address(drop), 1 ether); + + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase + + vm.expectRevert("NO_REFUND_AVAILABLE"); + + vm.prank(address(bidder1)); + auctions.claimRefund(address(drop)); + } function testRevert_ClaimRefund_WhenAlreadyClaimed() public setupBasicAuctionWithLowMinimumViableRevenue { vm.prank(address(bidder1)); @@ -1413,6 +1795,8 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.settleAuction(address(drop), 1 ether); + vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase + vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); @@ -1422,13 +1806,12 @@ contract VariableSupplyAuctionTest is Test { auctions.claimRefund(address(drop)); } - // TODO x add temporal sad paths - /*////////////////////////////////////////////////////////////// TEST HELPERS //////////////////////////////////////////////////////////////*/ // TODO parameterize modifier pattern to support fuzzing + modifier setupBasicAuction() { vm.prank(address(seller)); auctions.createAuction({ @@ -1553,7 +1936,8 @@ contract VariableSupplyAuctionTest is Test { emit AuctionSettled(address(drop), auction); } - // IDEA could this be moved onto the hyperstructure module for better bidder usability ?! + // IDEA could genSealedBid be moved onto the hyperstructure module for better bidder usability ?! + function _genSealedBid(uint256 _amount, string memory _salt) internal pure returns (bytes32) { return keccak256(abi.encodePacked(_amount, bytes(_salt))); } diff --git a/uml/VariableSupplyAuction/6-settleAuction.txt b/uml/VariableSupplyAuction/6-settleAuction.txt index 25da67fa..d8422964 100644 --- a/uml/VariableSupplyAuction/6-settleAuction.txt +++ b/uml/VariableSupplyAuction/6-settleAuction.txt @@ -5,6 +5,7 @@ participant ERC721Drop Seller -> VariableSupplyAuction : settleAuction() +TODO x review all UML diagrams TODO x update to consolidate biz logic and add minimum viable revenue VariableSupplyAuction -> VariableSupplyAuction : validate auction is in settle phase From 63feb241accba6063770362624c6bdcf622c22a1 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 31 Oct 2022 10:16:01 -0400 Subject: [PATCH 24/31] Add EIP-165 support; Clean up member ordering; Write more NatSpec --- .../IVariableSupplyAuction.sol | 2 +- .../VariableSupplyAuction.sol | 147 ++++++------ .../temp-MockERC721Drop.sol | 4 - .../VariableSupplyAuction.t.sol | 209 +++++++++--------- 4 files changed, 195 insertions(+), 167 deletions(-) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index 88ac031b..a767b275 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -30,7 +30,7 @@ interface IVariableSupplyAuction { function revealBid(address _tokenContract, uint256 _bidAmount, string calldata _salt) external; /// - function calculateSettleOptions(address _tokenContract) external returns (uint96[] memory, uint16[] memory, uint96[] memory); + function calculateSettleOutcomes(address _tokenContract) external returns (uint96[] memory, uint16[] memory, uint96[] memory); /// function settleAuction(address _tokenContract, uint96 _settlePricePoint) external; diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index f7691853..2133ddbd 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -28,7 +28,25 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa ) FeePayoutSupportV1(_royaltyEngine, _protocolFeeSettings, _weth, ERC721TransferHelper(_erc721TransferHelper).ZMM().registrar()) ModuleNamingSupportV1("Variable Supply Auction") - {} + { + // TODO consider other approaches that keep VSA module ignorant of + // ERC-721 Drop implementation specifics, e.g., wrapping in an + // ERC721DropHelper which could extend BaseTransferHelper + + // erc721TransferHelper = ERC721TransferHelper(_erc721TransferHelper); + } + + /*////////////////////////////////////////////////////////////// + EIP-165 + //////////////////////////////////////////////////////////////*/ + + /// @notice Implements EIP-165 for standard interface detection + /// @dev `0x01ffc9a7` is the IERC165 interface id + /// @param _interfaceId The identifier of a given interface + /// @return If the given interface is supported + function supportsInterface(bytes4 _interfaceId) external pure returns (bool) { + return _interfaceId == type(IVariableSupplyAuction).interfaceId || _interfaceId == 0x01ffc9a7; + } /*////////////////////////////////////////////////////////////// AUCTION STORAGE @@ -75,18 +93,36 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa uint96 revealedBidAmount; } + /// @notice A possible outcome if settling an auction at a given price point + /// @param editionSize The total number of NFTs which would be minted + /// @param revenue The amount of revenue which would be generated + struct SettleOutcome { + uint16 editionSize; + uint96 revenue; + } + + // TODO storage optimization + /// @notice The auction for a given ERC-721 drop contract, if one exists /// (only one auction per token contract is allowed at one time) - /// @dev ERC-721 token contract => Auction + /// @dev ERC-721 token contract address => Auction mapping(address => Auction) public auctionForDrop; /// @notice The bids which have been placed in a given Auction - /// @dev ERC-721 token contract => (bidder address => Bid) - mapping(address => mapping(address => Bid)) public bidsForDrop; + /// @dev ERC-721 token contract address => (bidder address => Bid) + mapping(address => mapping(address => Bid)) public bidsForAuction; /// @notice The addresses who have placed and revealed a bid in a given auction - /// @dev ERC-721 token contract => all bidders who have revealed their bid - mapping(address => address[]) internal _revealedBiddersForDrop; + /// @dev ERC-721 token contract address => all bidders who have revealed their bid + mapping(address => address[]) internal _revealedBiddersForAuction; + + /// @notice All possible price points at which to settle an auction, based on revealed bids + /// @dev ERC-721 token contract address => price point + mapping(address => uint96[]) public _settlePricePointsForAuction; + + /// @notice All possible outcomes at which to settle an auction, based on revealed bids + /// @dev ERC-721 token contract address => (price point => SettleOutcome) + mapping(address => mapping(uint96 => SettleOutcome)) public _settleOutcomesForPricePoint; /*////////////////////////////////////////////////////////////// CREATE AUCTION @@ -222,11 +258,11 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Ensure seller has first considered the settle price points before attempting to cancel require(settlePricePoints.length > 0, "CANNOT_CANCEL_AUCTION_BEFORE_CALCULATING_SETTLE_OPTIONS"); - // Ensure none of the price point options meet minimum viable revenue + // Ensure none of the price point outcomes meet minimum viable revenue for (uint256 i = 0; i < settlePricePoints.length; i++) { - SettleOption storage settleOption = _settleOptionsForPricePoint[_tokenContract][settlePricePoints[i]]; + SettleOutcome storage settleOutcome = _settleOutcomesForPricePoint[_tokenContract][settlePricePoints[i]]; - require(settleOption.revenue < auction.minimumViableRevenue, "CANNOT_CANCEL_AUCTION_WITH_VIABLE_PRICE_POINT"); + require(settleOutcome.revenue < auction.minimumViableRevenue, "CANNOT_CANCEL_AUCTION_WITH_VIABLE_PRICE_POINT"); } } else { require(auction.totalBalance == 0, "CANNOT_CANCEL_AUCTION_WITH_BIDS"); @@ -292,7 +328,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa require(block.timestamp < auction.endOfBidPhase, "BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); // Ensure the bidder has not placed a bid in auction already - require(bidsForDrop[_tokenContract][msg.sender].bidderBalance == 0, "ALREADY_PLACED_BID_IN_AUCTION"); + require(bidsForAuction[_tokenContract][msg.sender].bidderBalance == 0, "ALREADY_PLACED_BID_IN_AUCTION"); // Ensure the bid is valid require(msg.value > 0 ether, "VALID_BIDS_MUST_INCLUDE_ETHER"); @@ -301,7 +337,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa auction.totalBalance += uint96(msg.value); // Store the commitment hash and included ether amount - bidsForDrop[_tokenContract][msg.sender] = Bid({ + bidsForAuction[_tokenContract][msg.sender] = Bid({ commitmentHash: _commitmentHash, bidderBalance: uint96(msg.value), revealedBidAmount: 0 @@ -368,7 +404,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa require(block.timestamp >= auction.endOfBidPhase && block.timestamp < auction.endOfRevealPhase, "REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); // Get the bid for the specified bidder - Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; + Bid storage bid = bidsForAuction[_tokenContract][msg.sender]; // Ensure bidder placed bid in auction require(bid.bidderBalance > 0 ether, "NO_PLACED_BID_FOUND_FOR_ADDRESS"); @@ -380,7 +416,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa require(keccak256(abi.encodePacked(_bidAmount, bytes(_salt))) == bid.commitmentHash, "REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); // Store the bidder - _revealedBiddersForDrop[_tokenContract].push(msg.sender); + _revealedBiddersForAuction[_tokenContract].push(msg.sender); // Store the revealed bid amount uint96 bidAmount = uint96(_bidAmount); @@ -451,30 +487,17 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param auction The metadata of the created auction event AuctionSettled(address indexed tokenContract, Auction auction); - /// TODO - struct SettleOption { - uint16 editionSize; - uint96 revenue; - } - - /// TODO - mapping(address => uint96[]) public _settlePricePointsForAuction; - - /// TODO - mapping(address => mapping(uint96 => SettleOption)) public _settleOptionsForPricePoint; - /// @notice Calculate edition size and revenue for each possible price point /// @dev Cheaper on subsequent calls but idempotent -- after the initial call when - /// calculations have been performed and stored, the settle options will not change. + /// calculations have been performed and stored, the settle outcomes will not change. /// Function visibility is public instead of external, to support settleAuction calling it. /// @param _tokenContract The address of the ERC-721 drop contract - /// @return A tuple of 3 arrays representing the settle options -- + /// @return A tuple of 3 arrays representing the settle outcomes -- /// the possible price points at which to settle, along with the /// resulting edition sizes and amounts of revenue generated - function calculateSettleOptions(address _tokenContract) public returns (uint96[] memory, uint16[] memory, uint96[] memory) { + function calculateSettleOutcomes(address _tokenContract) public returns (uint96[] memory, uint16[] memory, uint96[] memory) { - // TODO x improve algorithm =P - // TODO x gas optimizations -- consider other, less storage-intensive options + // TODO improve algorithm =P // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; @@ -486,41 +509,41 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa require(block.timestamp >= auction.endOfRevealPhase && block.timestamp < auction.endOfSettlePhase, "SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); // Get the revealed bidders for the auction - address[] storage bidders = _revealedBiddersForDrop[_tokenContract]; + address[] storage bidders = _revealedBiddersForAuction[_tokenContract]; // Ensure the auction has at least 1 revealed bid require(bidders.length > 0, "NO_REVEALED_BIDS_TO_SETTLE_AUCTION"); uint96[] storage settlePricePoints = _settlePricePointsForAuction[_tokenContract]; - mapping(uint96 => SettleOption) storage settleOptions = _settleOptionsForPricePoint[_tokenContract]; + mapping(uint96 => SettleOutcome) storage settleOutcomes = _settleOutcomesForPricePoint[_tokenContract]; if (settlePricePoints.length == 0) { // only calculate and store once, otherwise just return from storage for (uint256 i = 0; i < bidders.length; i++) { address bidder = bidders[i]; - uint96 bidAmount = bidsForDrop[_tokenContract][bidder].revealedBidAmount; - SettleOption storage settleOption = settleOptions[bidAmount]; + uint96 bidAmount = bidsForAuction[_tokenContract][bidder].revealedBidAmount; + SettleOutcome storage settleOutcome = settleOutcomes[bidAmount]; - if (settleOption.editionSize == 0) { + if (settleOutcome.editionSize == 0) { settlePricePoints.push(bidAmount); - settleOption.editionSize = 1; + settleOutcome.editionSize = 1; } } for (uint256 j = 0; j < settlePricePoints.length; j++) { uint96 settlePricePoint = settlePricePoints[j]; - SettleOption storage settleOption = settleOptions[settlePricePoint]; + SettleOutcome storage settleOutcome = settleOutcomes[settlePricePoint]; for (uint256 k = 0; k < bidders.length; k++) { address bidder = bidders[k]; - uint96 bidAmount = bidsForDrop[_tokenContract][bidder].revealedBidAmount; + uint96 bidAmount = bidsForAuction[_tokenContract][bidder].revealedBidAmount; if (bidAmount >= settlePricePoint) { - settleOption.editionSize++; - settleOption.revenue += settlePricePoint; + settleOutcome.editionSize++; + settleOutcome.revenue += settlePricePoint; } } - settleOption.editionSize--; // because 1st bidder at this settle price was double counted + settleOutcome.editionSize--; // because 1st bidder at this settle price was double counted } } @@ -530,15 +553,15 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa for (uint256 m = 0; m < settlePricePoints.length; m++) { uint96 settlePricePoint = settlePricePoints[m]; - SettleOption storage settleOption = settleOptions[settlePricePoint]; + SettleOutcome storage settleOutcome = settleOutcomes[settlePricePoint]; - if (settleOption.revenue < auction.minimumViableRevenue) { - settleOption.revenue = 0; // zero out, because not viable settle option + if (settleOutcome.revenue < auction.minimumViableRevenue) { + settleOutcome.revenue = 0; // zero out, because not viable settle outcome } pricePoints[m] = settlePricePoint; - editionSizes[m] = settleOption.editionSize; - revenues[m] = settleOption.revenue; + editionSizes[m] = settleOutcome.editionSize; + revenues[m] = settleOutcome.revenue; } return (pricePoints, editionSizes, revenues); @@ -549,40 +572,40 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param _settlePricePoint The price point at which to settle the auction function settleAuction(address _tokenContract, uint96 _settlePricePoint) external nonReentrant { - // TODO x gas optimizations - // TODO x document pragmatic max edition size / winning bidders - // TODO x consider storing winningBidders during calculateSettleOptions - // TODO x look for more ways to consolidate business logic with calculateSettleOptions + // TODO gas optimization + // TODO document pragmatic max edition size / winning bidders + // TODO consider storing winningBidders during calculateSettleOutcomes + // TODO look for ways to consolidate business logic with calculateSettleOutcomes // Get the auction Auction storage auction = auctionForDrop[_tokenContract]; - // Calculate settle options, if not done yet (also includes check for auction existence) + // Calculate settle outcomes, if not done yet (also includes check for auction existence) if (_settlePricePointsForAuction[_tokenContract].length == 0) { - calculateSettleOptions(_tokenContract); + calculateSettleOutcomes(_tokenContract); } - // Get the settle option at this price point - SettleOption storage settleOption = _settleOptionsForPricePoint[_tokenContract][_settlePricePoint]; + // Get the settle outcome at this price point + SettleOutcome storage settleOutcome = _settleOutcomesForPricePoint[_tokenContract][_settlePricePoint]; // Ensure that revenue meets minimum viable revenue - require(settleOption.revenue >= auction.minimumViableRevenue, "DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); + require(settleOutcome.revenue >= auction.minimumViableRevenue, "DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); // Store the current total balance and final auction details - auction.totalBalance -= settleOption.revenue; - auction.settledRevenue = settleOption.revenue; + auction.totalBalance -= settleOutcome.revenue; + auction.settledRevenue = settleOutcome.revenue; auction.settledPricePoint = _settlePricePoint; - auction.settledEditionSize = settleOption.editionSize; + auction.settledEditionSize = settleOutcome.editionSize; // TODO store the fact that an auction has been settled and (1) update endOfSettlePhase // to enter cleanup phase immediately and (2) clean up unneeded storage (and allow // bidders to claim refunds ASAP) // Get the bids for this auction - mapping(address => Bid) storage bids = bidsForDrop[_tokenContract]; + mapping(address => Bid) storage bids = bidsForAuction[_tokenContract]; // Get the bidders who revealed in this auction - address[] storage bidders = _revealedBiddersForDrop[_tokenContract]; + address[] storage bidders = _revealedBiddersForAuction[_tokenContract]; // Loop through bids to determine winning bidders and update bidder balances uint256 index; @@ -669,7 +692,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa require(block.timestamp >= auction.endOfSettlePhase, "REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); // Return the balance for the specified bidder - return bidsForDrop[_tokenContract][msg.sender].bidderBalance; + return bidsForAuction[_tokenContract][msg.sender].bidderBalance; } /// @notice Claim refund -- if winner, any additional ether sent above your @@ -688,7 +711,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa require(block.timestamp >= auction.endOfSettlePhase, "REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); // Get the balance for the specified bidder - Bid storage bid = bidsForDrop[_tokenContract][msg.sender]; + Bid storage bid = bidsForAuction[_tokenContract][msg.sender]; uint96 bidderBalance = bid.bidderBalance; // Ensure bidder revealed a bid and has a leftover balance diff --git a/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol index 0ff381bd..10b79e73 100644 --- a/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol +++ b/contracts/modules/VariableSupplyAuction/temp-MockERC721Drop.sol @@ -5,10 +5,6 @@ pragma solidity 0.8.10; Temp Mock ERC721Drop interface //////////////////////////////////////////////////////////////*/ -// TODO consider other approaches that keep VSA module ignorant of ERC-721 Drop -// implementation specifics, including wrapping in a ERC721DropHelper or similar, -// which could extend BaseTransferHelper - // TODO improve mocking pattern for OZ AccessControlEnumerable // TODO use actual function for setting edition sizes post-initialization from Iain diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 5f580650..26cc2854 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -206,6 +206,15 @@ contract VariableSupplyAuctionTest is Test { assertEq(fundsRecipient, payable(sellerFundsRecipient)); } + /*////////////////////////////////////////////////////////////// + EIP-165 + //////////////////////////////////////////////////////////////*/ + + function test_SupportsInterface() public { + assertTrue(auctions.supportsInterface(0x01ffc9a7)); // EIP-165 + assertTrue(auctions.supportsInterface(type(IVariableSupplyAuction).interfaceId)); + } + /*////////////////////////////////////////////////////////////// CREATE AUCTION //////////////////////////////////////////////////////////////*/ @@ -410,7 +419,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days + 2 days); vm.prank(address(seller)); - auctions.calculateSettleOptions(address(drop)); // first, consider the settle options + auctions.calculateSettleOutcomes(address(drop)); // first, consider the settle outcomes vm.prank(address(seller)); auctions.cancelAuction(address(drop)); @@ -467,7 +476,7 @@ contract VariableSupplyAuctionTest is Test { auctions.cancelAuction(address(drop)); } - function testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOptionsYet() public setupBasicAuction { + function testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOutcomesYet() public setupBasicAuction { bytes32 commitment1 = _genSealedBid(10 ether, salt1); bytes32 commitment2 = _genSealedBid(2 ether, salt2); bytes32 commitment3 = _genSealedBid(3 ether, salt3); @@ -520,7 +529,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days + 2 days); vm.prank(address(seller)); - auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); vm.expectRevert("CANNOT_CANCEL_AUCTION_WITH_VIABLE_PRICE_POINT"); @@ -549,7 +558,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); - (bytes32 commitmentStored, uint96 bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (bytes32 commitmentStored, uint96 bidderBalance, ) = auctions.bidsForAuction(address(drop), address(bidder1)); assertEq(address(auctions).balance, 1 ether); assertEq(bidderBalance, 1 ether); @@ -568,9 +577,9 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder3)); auctions.placeBid{value: 3 ether}(address(drop), commitment3); - (bytes32 commitmentStored1, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); - (bytes32 commitmentStored2, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); - (bytes32 commitmentStored3, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); + (bytes32 commitmentStored1, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); + (bytes32 commitmentStored2, uint96 bidderBalance2, ) = auctions.bidsForAuction(address(drop), address(bidder2)); + (bytes32 commitmentStored3, uint96 bidderBalance3, ) = auctions.bidsForAuction(address(drop), address(bidder3)); assertEq(address(auctions).balance, 6 ether); assertEq(bidderBalance1, 1 ether); @@ -730,7 +739,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1 ether, salt1); - (,, uint256 bidAmount) = auctions.bidsForDrop(address(drop), address(bidder1)); + (,, uint256 bidAmount) = auctions.bidsForAuction(address(drop), address(bidder1)); assertEq(bidAmount, 1 ether); } @@ -756,9 +765,9 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder3)); auctions.revealBid(address(drop), 3 ether, salt3); - (,, uint256 bidAmount1) = auctions.bidsForDrop(address(drop), address(bidder1)); - (,, uint256 bidAmount2) = auctions.bidsForDrop(address(drop), address(bidder2)); - (,, uint256 bidAmount3) = auctions.bidsForDrop(address(drop), address(bidder3)); + (,, uint256 bidAmount1) = auctions.bidsForAuction(address(drop), address(bidder1)); + (,, uint256 bidAmount2) = auctions.bidsForAuction(address(drop), address(bidder2)); + (,, uint256 bidAmount3) = auctions.bidsForAuction(address(drop), address(bidder3)); assertEq(bidAmount1, 1 ether); assertEq(bidAmount2, 2 ether); @@ -970,14 +979,14 @@ contract VariableSupplyAuctionTest is Test { - check the revealed amount is not greater than sent ether - check the revealed bid matches the sealed bid - Calculate Settle Options + Calculate Settle Outcomes - check the auction exists - check the auction is in settle phase - check the auction has at least 1 revealed bid Settle Auction - - (includes checks from calling calculate settle options, either in this call or previously) - - check that price point is a valid settle option (exists and meets minimum viable revenue) + - (includes checks from calling calculate settle outcomes, either in this call or previously) + - check that price point is a valid settle outcome (exists and meets minimum viable revenue) Check Available Refund - check the auction exists @@ -990,15 +999,15 @@ contract VariableSupplyAuctionTest is Test { */ - function test_CalculateSettleOptions() public setupBasicAuction throughRevealPhaseComplex { + function test_CalculateSettleOutcomes() public setupBasicAuction throughRevealPhaseComplex { (uint96[] memory pricePoints, uint16[] memory editionSizes, uint96[] memory revenues) = - auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); assertEq(editionSizes.length, pricePoints.length); assertEq(revenues.length, pricePoints.length); // for (uint256 i = 0; i < pricePoints.length; i++) { - // emit log_string("Option -------------"); + // emit log_string("Outcome -------------"); // emit log_named_uint("Price point", pricePoints[i] / 1 ether); // emit log_named_uint("Edition size", editionSizes[i]); // emit log_named_uint("Revenue", revenues[i] / 1 ether); @@ -1016,7 +1025,7 @@ contract VariableSupplyAuctionTest is Test { Note that revenue is set to 0 at price point 2 ether because 8 ether would not meet minimum viable revenue - (and therefore this won't be a viable settle option) + (and therefore this won't be a viable settle outcome) */ @@ -1040,68 +1049,68 @@ contract VariableSupplyAuctionTest is Test { // 1_794_872-1_691_644 = 103_228 more than once // 1_703_719-1_691_757 = 11_962 more than once (with optimization) - function testGas_AndIdempotent_CalculateSettleOptions_WhenCalledTwice() public setupBasicAuction throughRevealPhaseComplex { - auctions.calculateSettleOptions(address(drop)); - auctions.calculateSettleOptions(address(drop)); + function testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledTwice() public setupBasicAuction throughRevealPhaseComplex { + auctions.calculateSettleOutcomes(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); } // 1_899_651-1_794_851 = 104_800 more than twice // 1_717_252-1_703_719 = 13_533 more than twice (with optimization) - function testGas_AndIdempotent_CalculateSettleOptions_WhenCalledThrice() public setupBasicAuction throughRevealPhaseComplex { - auctions.calculateSettleOptions(address(drop)); - auctions.calculateSettleOptions(address(drop)); - auctions.calculateSettleOptions(address(drop)); + function testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledThrice() public setupBasicAuction throughRevealPhaseComplex { + auctions.calculateSettleOutcomes(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); } // 1_730_788-1_717_253 = 13_535 more than thrice (with optimization) - function testGas_AndIdempotent_CalculateSettleOptions_WhenCalledFrice() public setupBasicAuction throughRevealPhaseComplex { - auctions.calculateSettleOptions(address(drop)); - auctions.calculateSettleOptions(address(drop)); - auctions.calculateSettleOptions(address(drop)); - auctions.calculateSettleOptions(address(drop)); + function testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledFrice() public setupBasicAuction throughRevealPhaseComplex { + auctions.calculateSettleOutcomes(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); } - function testRevert_CalculateSettleOptions_WhenAuctionDoesNotExist() public { + function testRevert_CalculateSettleOutcomes_WhenAuctionDoesNotExist() public { vm.expectRevert("AUCTION_DOES_NOT_EXIST"); vm.prank(address(seller)); - auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); } - function testRevert_CalculateSettleOptions_WhenAuctionInBidPhase() public setupBasicAuction { + function testRevert_CalculateSettleOutcomes_WhenAuctionInBidPhase() public setupBasicAuction { vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); vm.prank(address(seller)); - auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); } - function testRevert_CalculateSettleOptions_WhenAuctionInRevealPhase() public setupBasicAuction { + function testRevert_CalculateSettleOutcomes_WhenAuctionInRevealPhase() public setupBasicAuction { vm.warp(TIME0 + 3 days); // reveal phase vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); vm.prank(address(seller)); - auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); } - function testRevert_CalculateSettleOptions_WhenAuctionInCleanupPhase() public setupBasicAuction { + function testRevert_CalculateSettleOutcomes_WhenAuctionInCleanupPhase() public setupBasicAuction { vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); vm.prank(address(seller)); - auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); } - function testRevert_CalculateSettleOptions_WhenAuctionHasZeroRevealedBids() public setupBasicAuction { + function testRevert_CalculateSettleOutcomes_WhenAuctionHasZeroRevealedBids() public setupBasicAuction { vm.warp(TIME0 + 3 days + 2 days); vm.expectRevert("NO_REVEALED_BIDS_TO_SETTLE_AUCTION"); vm.prank(address(seller)); - auctions.calculateSettleOptions(address(drop)); + auctions.calculateSettleOutcomes(address(drop)); } /* @@ -1174,20 +1183,20 @@ contract VariableSupplyAuctionTest is Test { assertEq(totalBalance, 84 ether); // bidder auction balances each still full amount of sent ether - (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); - (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); - (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); - (, uint96 bidderBalance4, ) = auctions.bidsForDrop(address(drop), address(bidder4)); - (, uint96 bidderBalance5, ) = auctions.bidsForDrop(address(drop), address(bidder5)); - (, uint96 bidderBalance6, ) = auctions.bidsForDrop(address(drop), address(bidder6)); - (, uint96 bidderBalance7, ) = auctions.bidsForDrop(address(drop), address(bidder7)); - (, uint96 bidderBalance8, ) = auctions.bidsForDrop(address(drop), address(bidder8)); - (, uint96 bidderBalance9, ) = auctions.bidsForDrop(address(drop), address(bidder9)); - (, uint96 bidderBalance10, ) = auctions.bidsForDrop(address(drop), address(bidder10)); - (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); - (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); - (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); - (, uint96 bidderBalance14, ) = auctions.bidsForDrop(address(drop), address(bidder14)); + (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); + (, uint96 bidderBalance2, ) = auctions.bidsForAuction(address(drop), address(bidder2)); + (, uint96 bidderBalance3, ) = auctions.bidsForAuction(address(drop), address(bidder3)); + (, uint96 bidderBalance4, ) = auctions.bidsForAuction(address(drop), address(bidder4)); + (, uint96 bidderBalance5, ) = auctions.bidsForAuction(address(drop), address(bidder5)); + (, uint96 bidderBalance6, ) = auctions.bidsForAuction(address(drop), address(bidder6)); + (, uint96 bidderBalance7, ) = auctions.bidsForAuction(address(drop), address(bidder7)); + (, uint96 bidderBalance8, ) = auctions.bidsForAuction(address(drop), address(bidder8)); + (, uint96 bidderBalance9, ) = auctions.bidsForAuction(address(drop), address(bidder9)); + (, uint96 bidderBalance10, ) = auctions.bidsForAuction(address(drop), address(bidder10)); + (, uint96 bidderBalance11, ) = auctions.bidsForAuction(address(drop), address(bidder11)); + (, uint96 bidderBalance12, ) = auctions.bidsForAuction(address(drop), address(bidder12)); + (, uint96 bidderBalance13, ) = auctions.bidsForAuction(address(drop), address(bidder13)); + (, uint96 bidderBalance14, ) = auctions.bidsForAuction(address(drop), address(bidder14)); assertEq(bidderBalance1, 1 ether); assertEq(bidderBalance2, 9 ether); assertEq(bidderBalance3, 8 ether); @@ -1256,20 +1265,20 @@ contract VariableSupplyAuctionTest is Test { assertEq(settledPricePoint, 1 ether); assertEq(settledEditionSize, 14); - (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); - (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); - (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); - (, uint96 bidderBalance4, ) = auctions.bidsForDrop(address(drop), address(bidder4)); - (, uint96 bidderBalance5, ) = auctions.bidsForDrop(address(drop), address(bidder5)); - (, uint96 bidderBalance6, ) = auctions.bidsForDrop(address(drop), address(bidder6)); - (, uint96 bidderBalance7, ) = auctions.bidsForDrop(address(drop), address(bidder7)); - (, uint96 bidderBalance8, ) = auctions.bidsForDrop(address(drop), address(bidder8)); - (, uint96 bidderBalance9, ) = auctions.bidsForDrop(address(drop), address(bidder9)); - (, uint96 bidderBalance10, ) = auctions.bidsForDrop(address(drop), address(bidder10)); - (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); - (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); - (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); - (, uint96 bidderBalance14, ) = auctions.bidsForDrop(address(drop), address(bidder14)); + (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); + (, uint96 bidderBalance2, ) = auctions.bidsForAuction(address(drop), address(bidder2)); + (, uint96 bidderBalance3, ) = auctions.bidsForAuction(address(drop), address(bidder3)); + (, uint96 bidderBalance4, ) = auctions.bidsForAuction(address(drop), address(bidder4)); + (, uint96 bidderBalance5, ) = auctions.bidsForAuction(address(drop), address(bidder5)); + (, uint96 bidderBalance6, ) = auctions.bidsForAuction(address(drop), address(bidder6)); + (, uint96 bidderBalance7, ) = auctions.bidsForAuction(address(drop), address(bidder7)); + (, uint96 bidderBalance8, ) = auctions.bidsForAuction(address(drop), address(bidder8)); + (, uint96 bidderBalance9, ) = auctions.bidsForAuction(address(drop), address(bidder9)); + (, uint96 bidderBalance10, ) = auctions.bidsForAuction(address(drop), address(bidder10)); + (, uint96 bidderBalance11, ) = auctions.bidsForAuction(address(drop), address(bidder11)); + (, uint96 bidderBalance12, ) = auctions.bidsForAuction(address(drop), address(bidder12)); + (, uint96 bidderBalance13, ) = auctions.bidsForAuction(address(drop), address(bidder13)); + (, uint96 bidderBalance14, ) = auctions.bidsForAuction(address(drop), address(bidder14)); assertEq(bidderBalance1, 0 ether); assertEq(bidderBalance2, 8 ether); assertEq(bidderBalance3, 7 ether); @@ -1338,20 +1347,20 @@ contract VariableSupplyAuctionTest is Test { assertEq(settledPricePoint, 6 ether); assertEq(settledEditionSize, 3); - (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); - (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); - (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); - (, uint96 bidderBalance4, ) = auctions.bidsForDrop(address(drop), address(bidder4)); - (, uint96 bidderBalance5, ) = auctions.bidsForDrop(address(drop), address(bidder5)); - (, uint96 bidderBalance6, ) = auctions.bidsForDrop(address(drop), address(bidder6)); - (, uint96 bidderBalance7, ) = auctions.bidsForDrop(address(drop), address(bidder7)); - (, uint96 bidderBalance8, ) = auctions.bidsForDrop(address(drop), address(bidder8)); - (, uint96 bidderBalance9, ) = auctions.bidsForDrop(address(drop), address(bidder9)); - (, uint96 bidderBalance10, ) = auctions.bidsForDrop(address(drop), address(bidder10)); - (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); - (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); - (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); - (, uint96 bidderBalance14, ) = auctions.bidsForDrop(address(drop), address(bidder14)); + (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); + (, uint96 bidderBalance2, ) = auctions.bidsForAuction(address(drop), address(bidder2)); + (, uint96 bidderBalance3, ) = auctions.bidsForAuction(address(drop), address(bidder3)); + (, uint96 bidderBalance4, ) = auctions.bidsForAuction(address(drop), address(bidder4)); + (, uint96 bidderBalance5, ) = auctions.bidsForAuction(address(drop), address(bidder5)); + (, uint96 bidderBalance6, ) = auctions.bidsForAuction(address(drop), address(bidder6)); + (, uint96 bidderBalance7, ) = auctions.bidsForAuction(address(drop), address(bidder7)); + (, uint96 bidderBalance8, ) = auctions.bidsForAuction(address(drop), address(bidder8)); + (, uint96 bidderBalance9, ) = auctions.bidsForAuction(address(drop), address(bidder9)); + (, uint96 bidderBalance10, ) = auctions.bidsForAuction(address(drop), address(bidder10)); + (, uint96 bidderBalance11, ) = auctions.bidsForAuction(address(drop), address(bidder11)); + (, uint96 bidderBalance12, ) = auctions.bidsForAuction(address(drop), address(bidder12)); + (, uint96 bidderBalance13, ) = auctions.bidsForAuction(address(drop), address(bidder13)); + (, uint96 bidderBalance14, ) = auctions.bidsForAuction(address(drop), address(bidder14)); assertEq(bidderBalance1, 1 ether); assertEq(bidderBalance2, 9 ether); assertEq(bidderBalance3, 8 ether); @@ -1420,20 +1429,20 @@ contract VariableSupplyAuctionTest is Test { assertEq(settledPricePoint, 11 ether); assertEq(settledEditionSize, 1); - (, uint96 bidderBalance1, ) = auctions.bidsForDrop(address(drop), address(bidder1)); - (, uint96 bidderBalance2, ) = auctions.bidsForDrop(address(drop), address(bidder2)); - (, uint96 bidderBalance3, ) = auctions.bidsForDrop(address(drop), address(bidder3)); - (, uint96 bidderBalance4, ) = auctions.bidsForDrop(address(drop), address(bidder4)); - (, uint96 bidderBalance5, ) = auctions.bidsForDrop(address(drop), address(bidder5)); - (, uint96 bidderBalance6, ) = auctions.bidsForDrop(address(drop), address(bidder6)); - (, uint96 bidderBalance7, ) = auctions.bidsForDrop(address(drop), address(bidder7)); - (, uint96 bidderBalance8, ) = auctions.bidsForDrop(address(drop), address(bidder8)); - (, uint96 bidderBalance9, ) = auctions.bidsForDrop(address(drop), address(bidder9)); - (, uint96 bidderBalance10, ) = auctions.bidsForDrop(address(drop), address(bidder10)); - (, uint96 bidderBalance11, ) = auctions.bidsForDrop(address(drop), address(bidder11)); - (, uint96 bidderBalance12, ) = auctions.bidsForDrop(address(drop), address(bidder12)); - (, uint96 bidderBalance13, ) = auctions.bidsForDrop(address(drop), address(bidder13)); - (, uint96 bidderBalance14, ) = auctions.bidsForDrop(address(drop), address(bidder14)); + (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); + (, uint96 bidderBalance2, ) = auctions.bidsForAuction(address(drop), address(bidder2)); + (, uint96 bidderBalance3, ) = auctions.bidsForAuction(address(drop), address(bidder3)); + (, uint96 bidderBalance4, ) = auctions.bidsForAuction(address(drop), address(bidder4)); + (, uint96 bidderBalance5, ) = auctions.bidsForAuction(address(drop), address(bidder5)); + (, uint96 bidderBalance6, ) = auctions.bidsForAuction(address(drop), address(bidder6)); + (, uint96 bidderBalance7, ) = auctions.bidsForAuction(address(drop), address(bidder7)); + (, uint96 bidderBalance8, ) = auctions.bidsForAuction(address(drop), address(bidder8)); + (, uint96 bidderBalance9, ) = auctions.bidsForAuction(address(drop), address(bidder9)); + (, uint96 bidderBalance10, ) = auctions.bidsForAuction(address(drop), address(bidder10)); + (, uint96 bidderBalance11, ) = auctions.bidsForAuction(address(drop), address(bidder11)); + (, uint96 bidderBalance12, ) = auctions.bidsForAuction(address(drop), address(bidder12)); + (, uint96 bidderBalance13, ) = auctions.bidsForAuction(address(drop), address(bidder13)); + (, uint96 bidderBalance14, ) = auctions.bidsForAuction(address(drop), address(bidder14)); assertEq(bidderBalance1, 1 ether); assertEq(bidderBalance2, 9 ether); assertEq(bidderBalance3, 8 ether); @@ -1616,14 +1625,14 @@ contract VariableSupplyAuctionTest is Test { // Precondition checks assertEq(address(bidder1).balance, 98 ether); - (, uint96 bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (, uint96 bidderBalance, ) = auctions.bidsForAuction(address(drop), address(bidder1)); assertEq(bidderBalance, 1 ether); vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); assertEq(address(bidder1).balance, 99 ether); - (, bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (, bidderBalance, ) = auctions.bidsForAuction(address(drop), address(bidder1)); assertEq(bidderBalance, 0 ether); } @@ -1649,14 +1658,14 @@ contract VariableSupplyAuctionTest is Test { // Precondition checks assertEq(address(bidder1).balance, 98 ether); - (, uint96 bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (, uint96 bidderBalance, ) = auctions.bidsForAuction(address(drop), address(bidder1)); assertEq(bidderBalance, 2 ether); vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); assertEq(address(bidder1).balance, 100 ether); // claim their full amount of sent ether - (, bidderBalance, ) = auctions.bidsForDrop(address(drop), address(bidder1)); + (, bidderBalance, ) = auctions.bidsForAuction(address(drop), address(bidder1)); assertEq(bidderBalance, 0 ether); } From f0090ea1a82e69b0432ea0995f9cb4edf8618396 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 31 Oct 2022 10:29:15 -0400 Subject: [PATCH 25/31] Improve NatSpec and add to interface --- .../IVariableSupplyAuction.sol | 47 ++++++++++++++----- .../VariableSupplyAuction.sol | 17 ++++--- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index a767b275..f8f73c32 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -7,12 +7,19 @@ pragma solidity 0.8.10; interface IVariableSupplyAuction { // - // TODO x add NatSpec here - - /// + /// @notice Creates a variable supply auction + /// @param _tokenContract The address of the ERC-721 drop contract + /// @param _minimumViableRevenue The minimum revenue the seller aims to generate in this auction -- + /// they can settle the auction below this value, but they cannot _not_ settle if the revenue + /// generated by any price point + edition size combination would be at least this value + /// @param _sellerFundsRecipient The address to send funds to once the auction is complete + /// @param _startTime The Unix time that users can begin placing bids + /// @param _bidPhaseDuration The length of time of the bid phase in seconds + /// @param _revealPhaseDuration The length of time of the reveal phase in seconds + /// @param _settlePhaseDuration The length of time of the settle phase in seconds function createAuction( address _tokenContract, - uint256 _minimumRevenue, + uint256 _minimumViableRevenue, address _sellerFundsRecipient, uint256 _startTime, uint256 _bidPhaseDuration, @@ -20,24 +27,42 @@ interface IVariableSupplyAuction { uint256 _settlePhaseDuration ) external; - /// + /// @notice Cancels the auction for a given drop + /// @param _tokenContract The address of the ERC-721 drop contract function cancelAuction(address _tokenContract) external; - /// + /// @notice Places a bid in a variable supply auction + /// @param _tokenContract The address of the ERC-721 drop contract + /// @param _commitmentHash The sha256 hash of the sealed bid amount concatenated with + /// a salt string, both of which need to be included in the subsequent reveal bid tx function placeBid(address _tokenContract, bytes32 _commitmentHash) external payable; - /// + /// @notice Reveals a previously placed bid + /// @param _tokenContract The address of the ERC-721 drop contract + /// @param _bidAmount The true bid amount + /// @param _salt The string which was used, in combination with the true bid amount, + /// to generate the commitment hash sent with the original placed bid tx function revealBid(address _tokenContract, uint256 _bidAmount, string calldata _salt) external; - /// + /// @notice Calculate edition size and revenue for each possible price point + /// @param _tokenContract The address of the ERC-721 drop contract + /// @return A tuple of 3 arrays representing the settle outcomes -- + /// the possible price points at which to settle, along with the + /// resulting edition sizes and amounts of revenue generated function calculateSettleOutcomes(address _tokenContract) external returns (uint96[] memory, uint16[] memory, uint96[] memory); - /// + /// @notice Settle an auction at a given price point + /// @param _tokenContract The address of the ERC-721 drop contract + /// @param _settlePricePoint The price point at which to settle the auction function settleAuction(address _tokenContract, uint96 _settlePricePoint) external; - /// + /// @notice Check available refund -- if a winning bidder, any additional ether sent above + /// your bid amount; if not a winning bidder, the full amount of ether sent with your bid + /// @param _tokenContract The address of the ERC-721 drop contract function checkAvailableRefund(address _tokenContract) external view returns (uint96); - /// + /// @notice Claim refund -- if a winning bidder, any additional ether sent above your + /// bid amount; if not a winning bidder, the full amount of ether sent with your bid + /// @param _tokenContract The address of the ERC-721 drop contract function claimRefund(address _tokenContract) external; } diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 2133ddbd..ac8cc238 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -161,13 +161,13 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa event AuctionCreated(address indexed tokenContract, Auction auction); /// @notice Creates a variable supply auction - /// @dev A given ERC-721 drop contract can have only one live auction at any one time + /// @dev Note that a given ERC-721 drop contract can have only one live auction at a time. /// @param _tokenContract The address of the ERC-721 drop contract /// @param _minimumViableRevenue The minimum revenue the seller aims to generate in this auction -- /// they can settle the auction below this value, but they cannot _not_ settle if the revenue /// generated by any price point + edition size combination would be at least this value /// @param _sellerFundsRecipient The address to send funds to once the auction is complete - /// @param _startTime The time that users can begin placing bids + /// @param _startTime The Unix time that users can begin placing bids /// @param _bidPhaseDuration The length of time of the bid phase in seconds /// @param _revealPhaseDuration The length of time of the reveal phase in seconds /// @param _settlePhaseDuration The length of time of the settle phase in seconds @@ -238,6 +238,8 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa event AuctionCanceled(address indexed tokenContract, Auction auction); /// @notice Cancels the auction for a given drop + /// @dev An auction can only be cancelled in 2 cases -- (1) if no bids have been placed yet, + /// or (2) if the auction is in settle phase and no price points meet minimum viable revenue. /// @param _tokenContract The address of the ERC-721 drop contract function cancelAuction(address _tokenContract) external nonReentrant { // Get the auction for the specified drop @@ -576,6 +578,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // TODO document pragmatic max edition size / winning bidders // TODO consider storing winningBidders during calculateSettleOutcomes // TODO look for ways to consolidate business logic with calculateSettleOutcomes + // TODO allow sellers to settle at a price point below minimum viable revenue if they so choose // Get the auction Auction storage auction = auctionForDrop[_tokenContract]; @@ -678,9 +681,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param auction The metadata of the created auction event RefundClaimed(address indexed tokenContract, address indexed bidder, uint96 refundAmount, Auction auction); - /// @notice Check available refund -- if winner, any additional ether sent above your - /// bid amount; if not winner, the full amount of ether sent with your bid - /// @param _tokenContract The address of the ERC-721 drop contract + /// @notice Check available refund -- if a winning bidder, any additional ether sent above + /// your bid amount; if not a winning bidder, the full amount of ether sent with your bid + /// @param _tokenContract The address of the ERC-721 drop contract function checkAvailableRefund(address _tokenContract) external view returns (uint96) { // Get the auction Auction storage auction = auctionForDrop[_tokenContract]; @@ -695,8 +698,8 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa return bidsForAuction[_tokenContract][msg.sender].bidderBalance; } - /// @notice Claim refund -- if winner, any additional ether sent above your - /// bid amount; if not winner, the full amount of ether sent with your bid + /// @notice Claim refund -- if a winning bidder, any additional ether sent above your + /// bid amount; if not a winning bidder, the full amount of ether sent with your bid /// @dev Some duplicated business logic between claimRefund and checkAvailableRefund, /// to eliminate additional (warm) SLOADs and keep checkAvailableRefund user-friendly /// @param _tokenContract The address of the ERC-721 drop contract From 4c5748eb1b83839ff5ea629882bb6a489002e685 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 31 Oct 2022 11:24:49 -0400 Subject: [PATCH 26/31] Revise PlantUML diagrams --- .../VariableSupplyAuction.sol | 402 +++++++++--------- .../VariableSupplyAuction.t.sol | 26 +- .../1-createAuction.atxt | 52 +-- uml/VariableSupplyAuction/1-createAuction.txt | 2 +- .../2-cancelAuction.atxt | 52 +-- uml/VariableSupplyAuction/2-cancelAuction.txt | 2 +- uml/VariableSupplyAuction/3-placeBid.atxt | 52 +-- uml/VariableSupplyAuction/3-placeBid.txt | 2 +- uml/VariableSupplyAuction/4-revealBid.atxt | 56 ++- uml/VariableSupplyAuction/4-revealBid.txt | 3 +- .../5-calculateSettleOptions.atxt | 28 -- .../5-calculateSettleOptions.txt | 17 - .../5-calculateSettleOutcomes.txt | 0 .../6-settleAuction.atxt | 114 ++--- uml/VariableSupplyAuction/6-settleAuction.txt | 18 +- .../7-checkAvailableRefund.atxt | 22 - .../7-checkAvailableRefund.txt | 10 - uml/VariableSupplyAuction/8-claimRefund.atxt | 60 +-- uml/VariableSupplyAuction/8-claimRefund.txt | 2 +- 19 files changed, 431 insertions(+), 489 deletions(-) delete mode 100644 uml/VariableSupplyAuction/5-calculateSettleOptions.atxt delete mode 100644 uml/VariableSupplyAuction/5-calculateSettleOptions.txt create mode 100644 uml/VariableSupplyAuction/5-calculateSettleOutcomes.txt delete mode 100644 uml/VariableSupplyAuction/7-checkAvailableRefund.atxt diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index ac8cc238..76984f1b 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -73,8 +73,8 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa uint32 endOfRevealPhase; uint32 endOfSettlePhase; uint96 totalBalance; - uint96 settledRevenue; uint96 settledPricePoint; + uint96 settledRevenue; uint16 settledEditionSize; } @@ -128,32 +128,32 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa CREATE AUCTION //////////////////////////////////////////////////////////////*/ - // ,-. - // `-' - // /|\ - // | ,---------------------. - // / \ |VariableSupplyAuction| - // Seller `----------+----------' - // | createAuction() | - // | ----------------------->| - // | | - // | ----. - // | | calculate phase end times - // | <---' - // | | - // | ----. - // | | store auction metadata - // | <---' - // | | - // | ----. - // | | emit AuctionCreated() - // | <---' - // Seller ,----------+----------. - // ,-. |VariableSupplyAuction| - // `-' `---------------------' - // /|\ - // | - // / \ + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Seller `----------+----------' + // | createAuction() | + // | ----------------------->| + // | | + // | ----. + // | | validate no existing auction for drop yet + // | <---' + // | | + // | ----. + // | | store auction metadata + // | <---' + // | | + // | ----. + // | | emit AuctionCreated() + // | <---' + // Seller ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when an auction is created /// @param tokenContract The address of the ERC-721 drop contract @@ -205,32 +205,32 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa CANCEL AUCTION //////////////////////////////////////////////////////////////*/ - // ,-. - // `-' - // /|\ - // | ,---------------------. - // / \ |VariableSupplyAuction| - // Seller `----------+----------' - // | cancelAuction() | - // | ----------------------->| - // | | - // | ----. - // | | validate no bids placed yet - // | <---' - // | | - // | ----. - // | | emit AuctionCanceled() - // | <---' - // | | - // | ----. - // | | delete auction - // | <---' - // Seller ,----------+----------. - // ,-. |VariableSupplyAuction| - // `-' `---------------------' - // /|\ - // | - // / \ + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Seller `----------+----------' + // | cancelAuction() | + // | ----------------------->| + // | | + // | ----. + // | | validate no bids placed yet, or no settle price points meet minimum viable revenue + // | <---' + // | | + // | ----. + // | | emit AuctionCanceled() + // | <---' + // | | + // | ----. + // | | delete auction + // | <---' + // Seller ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when an auction is canceled /// @param tokenContract The address of the ERC-721 drop contract @@ -280,32 +280,32 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa PLACE BID //////////////////////////////////////////////////////////////*/ - // ,-. - // `-' - // /|\ - // | ,---------------------. - // / \ |VariableSupplyAuction| - // Bidder `----------+----------' - // | placeBid() | - // | ----------------------->| - // | | - // | ----. - // | | validate auction is in bid phase - // | <---' - // | | - // | ----. - // | | update auction balance and store sealed bid - // | <---' - // | | - // | ----. - // | | emit BidPlaced() - // | <---' - // Bidder ,----------+----------. - // ,-. |VariableSupplyAuction| - // `-' `---------------------' - // /|\ - // | - // / \ + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Bidder `----------+----------' + // | placeBid() | + // | ----------------------->| + // | | + // | ----. + // | | validate auction is in bid phase, and no bid placed yet + // | <---' + // | | + // | ----. + // | | update auction balance and store sealed bid + // | <---' + // | | + // | ----. + // | | emit BidPlaced() + // | <---' + // Bidder ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when a bid is placed /// @param tokenContract The address of the ERC-721 drop contract @@ -352,36 +352,32 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa REVEAL BID //////////////////////////////////////////////////////////////*/ - // ,-. - // `-' - // /|\ - // | ,---------------------. - // / \ |VariableSupplyAuction| - // Bidder `----------+----------' - // | revealBid() | - // | ----------------------->| - // | | - // | ----. - // | | validate auction is in reveal phase - // | <---' - // | | - // | ----. - // | | validate revealed bid matches sealed bid - // | <---' - // | | - // | ----. - // | | store revealed bid - // | <---' - // | | - // | ----. - // | | emit BidRevealed() - // | <---' - // Bidder ,----------+----------. - // ,-. |VariableSupplyAuction| - // `-' `---------------------' - // /|\ - // | - // / \ + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Bidder `----------+----------' + // | revealBid() | + // | ----------------------->| + // | | + // | ----. + // | | validate auction is in reveal phase, and revealed bid matches sealed bid + // | <---' + // | | + // | ----. + // | | store revealed bid + // | <---' + // | | + // | ----. + // | | emit BidRevealed() + // | <---' + // Bidder ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when a bid is revealed /// @param tokenContract The address of the ERC-721 drop contract @@ -431,58 +427,68 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa SETTLE AUCTION //////////////////////////////////////////////////////////////*/ - // ,-. - // `-' - // /|\ - // | ,---------------------. ,----------. - // / \ |VariableSupplyAuction| |ERC721Drop| - // Seller `----------+----------' `----+-----' - // | settleAuction() | | - // | ----------------------->| | - // | | | - // | ----. | - // | | validate auction is in settle phase | - // | <---' | - // | | | - // | ----. | - // | | validate auction not settled yet | - // | <---' | - // | | | - // | ----. - // | | based on chosen price point, calculate and store final edition size and winning bidders - // | <---' - // | | | - // | ----. | - // | | update bidder balances to final available refund amounts | - // | <---' | - // | | | - // | | setEditionSize() | - // | |-------------------------------------------------------------------------------------------> - // | | | - // | | |----. - // | | | | update drop edition size - // | | |<---' - // | | | - // | | adminMintAirdrop() | - // | |-------------------------------------------------------------------------------------------> - // | | | - // | | |----. - // | | | | mint NFT to winning bidders - // | | |<---' - // | | | - // | ----. | - // | | handle seller funds recipient payout | - // | <---' | - // | | | - // | ----. | - // | | emit AuctionSettled() | - // | <---' | - // Seller ,----------+----------. ,----+-----. - // ,-. |VariableSupplyAuction| |ERC721Drop| - // `-' `---------------------' `----------' - // /|\ - // | - // / \ + // ,-. + // `-' + // /|\ + // | ,---------------------. ,----------. + // / \ |VariableSupplyAuction| |ERC721Drop| + // Seller `----------+----------' `----+-----' + // | settleAuction() | | + // | --------------->| | + // | | | + // | | | + // |___________________________________________________________________________________________ | + // |! ALT / Not calculated yet? ! | + // |!_____/ | ! | + // |! ----. ! | + // |! | validate auction is in settle phase, and there are revealed bids ! | + // |! <---' ! | + // |! | ! | + // |! ----. ! | + // |! | calculate and store possible settle outcomes ! | + // |! <---' ! | + // |!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! | + // |!~[noop]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! | + // | | | + // | ----. | + // | | validate chosen price point meets minimum viable revenue | + // | <---' | + // | | | + // | ----. | + // | | store final price point, revenue, and edition size | + // | <---' | + // | | | + // | ----. + // | | determine winning bidders and update balances to final available refunds + // | <---' + // | | | + // | | setEditionSize() | + // | |----------------------------------------------------------------------------> + // | | | + // | | |----. + // | | | | update drop edition size + // | | |<---' + // | | | + // | | adminMintAirdrop() | + // | |----------------------------------------------------------------------------> + // | | | + // | | |----. + // | | | | mint NFT to winning bidders + // | | |<---' + // | | | + // | ----. | + // | | handle seller funds recipient payout | + // | <---' | + // | | | + // | ----. | + // | | emit AuctionSettled() | + // | <---' | + // Seller ,----------+----------. ,----+-----. + // ,-. |VariableSupplyAuction| |ERC721Drop| + // `-' `---------------------' `----------' + // /|\ + // | + // / \ /// @notice Emitted when an auction is settled /// @param tokenContract The address of the ERC-721 drop contract @@ -499,7 +505,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// resulting edition sizes and amounts of revenue generated function calculateSettleOutcomes(address _tokenContract) public returns (uint96[] memory, uint16[] memory, uint96[] memory) { - // TODO improve algorithm =P + // TODO x improve algorithm =P // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; @@ -596,13 +602,13 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Store the current total balance and final auction details auction.totalBalance -= settleOutcome.revenue; - auction.settledRevenue = settleOutcome.revenue; auction.settledPricePoint = _settlePricePoint; + auction.settledRevenue = settleOutcome.revenue; auction.settledEditionSize = settleOutcome.editionSize; - // TODO store the fact that an auction has been settled and (1) update endOfSettlePhase - // to enter cleanup phase immediately and (2) clean up unneeded storage (and allow - // bidders to claim refunds ASAP) + // TODO store the fact that an auction has been settled and (1) disallow settling + // again, (2) update endOfSettlePhase to enter cleanup phase immediately, and + // (3) clean up unneeded storage (and allow bidders to claim refunds ASAP) // Get the bids for this auction mapping(address => Bid) storage bids = bidsForAuction[_tokenContract]; @@ -643,36 +649,36 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa CLAIM REFUND //////////////////////////////////////////////////////////////*/ - // ,-. - // `-' - // /|\ - // | ,---------------------. - // / \ |VariableSupplyAuction| - // Bidder `----------+----------' - // | claimRefund() | - // | ----------------------->| - // | | - // | ----. - // | | validate auction is in cleanup phase - // | <---' - // | | - // | ----. - // | | clear bidder available refund - // | <---' - // | | - // | ----. - // | | handle bidder available refund payout - // | <---' - // | | - // | ----. - // | | emit RefundClaimed() - // | <---' - // Bidder ,----------+----------. - // ,-. |VariableSupplyAuction| - // `-' `---------------------' - // /|\ - // | - // / \ + // ,-. + // `-' + // /|\ + // | ,---------------------. + // / \ |VariableSupplyAuction| + // Bidder `----------+----------' + // | claimRefund() | + // | ----------------------->| + // | | + // | ----. + // | | validate auction is in cleanup phase, and bidder has an available refund + // | <---' + // | | + // | ----. + // | | clear bidder available refund + // | <---' + // | | + // | ----. + // | | handle bidder available refund payout + // | <---' + // | | + // | ----. + // | | emit RefundClaimed() + // | <---' + // Bidder ,----------+----------. + // ,-. |VariableSupplyAuction| + // `-' `---------------------' + // /|\ + // | + // / \ /// @notice Emitted when a refund is claimed /// @param tokenContract The address of the ERC-721 drop contract @@ -717,12 +723,14 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa Bid storage bid = bidsForAuction[_tokenContract][msg.sender]; uint96 bidderBalance = bid.bidderBalance; - // Ensure bidder revealed a bid and has a leftover balance + // Ensure bidder has a leftover balance require(bid.revealedBidAmount > 0 && bidderBalance > 0, "NO_REFUND_AVAILABLE"); // Clear bidder balance bid.bidderBalance = 0; + // TODO x update totalBalance and add test + // Transfer the bidder's available refund balance to the bidder _handleOutgoingTransfer(msg.sender, bidderBalance, address(0), 50_000); diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 26cc2854..37802a77 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -304,8 +304,8 @@ contract VariableSupplyAuctionTest is Test { endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 0, - settledRevenue: 0, settledPricePoint: 0, + settledRevenue: 0, settledEditionSize: 0 }); @@ -450,8 +450,8 @@ contract VariableSupplyAuctionTest is Test { endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 0, - settledRevenue: 0, settledPricePoint: 0, + settledRevenue: 0, settledEditionSize: 0 }); @@ -600,9 +600,9 @@ contract VariableSupplyAuctionTest is Test { endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, - settledRevenue: uint96(0), - settledPricePoint: uint96(0), - settledEditionSize: uint16(0) + settledPricePoint: 0, + settledRevenue: 0, + settledEditionSize: 0 }); vm.expectEmit(true, true, true, true); @@ -623,9 +623,9 @@ contract VariableSupplyAuctionTest is Test { endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, - settledRevenue: uint96(0), - settledPricePoint: uint96(0), - settledEditionSize: uint16(0) + settledPricePoint: 0, + settledRevenue: 0, + settledEditionSize: 0 }); bytes32 commitment1 = _genSealedBid(1 ether, salt1); @@ -784,9 +784,9 @@ contract VariableSupplyAuctionTest is Test { endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, - settledRevenue: uint96(0), - settledPricePoint: uint96(0), - settledEditionSize: uint16(0) + settledPricePoint: 0, + settledRevenue: 0, + settledEditionSize: 0 }); bytes32 commitment = _genSealedBid(1 ether, salt1); @@ -1679,8 +1679,8 @@ contract VariableSupplyAuctionTest is Test { endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, - settledRevenue: 1 ether, settledPricePoint: 1 ether, + settledRevenue: 1 ether, settledEditionSize: uint16(1) }); @@ -1936,8 +1936,8 @@ contract VariableSupplyAuctionTest is Test { endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: totalBalance, - settledRevenue: settledRevenue, settledPricePoint: _settledPricePoint, + settledRevenue: settledRevenue, settledEditionSize: _settledEditionSize }); diff --git a/uml/VariableSupplyAuction/1-createAuction.atxt b/uml/VariableSupplyAuction/1-createAuction.atxt index 32580d14..d5c2440d 100644 --- a/uml/VariableSupplyAuction/1-createAuction.atxt +++ b/uml/VariableSupplyAuction/1-createAuction.atxt @@ -1,26 +1,26 @@ - ,-. - `-' - /|\ - | ,---------------------. - / \ |VariableSupplyAuction| - Seller `----------+----------' - | createAuction() | - | ----------------------->| - | | - | ----. - | | calculate phase end times - | <---' - | | - | ----. - | | store auction metadata - | <---' - | | - | ----. - | | emit AuctionCreated() - | <---' - Seller ,----------+----------. - ,-. |VariableSupplyAuction| - `-' `---------------------' - /|\ - | - / \ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Seller `----------+----------' + | createAuction() | + | ----------------------->| + | | + | ----. + | | validate no existing auction for drop yet + | <---' + | | + | ----. + | | store auction metadata + | <---' + | | + | ----. + | | emit AuctionCreated() + | <---' + Seller ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/1-createAuction.txt b/uml/VariableSupplyAuction/1-createAuction.txt index ab9b09f5..0f119ab9 100644 --- a/uml/VariableSupplyAuction/1-createAuction.txt +++ b/uml/VariableSupplyAuction/1-createAuction.txt @@ -4,7 +4,7 @@ participant VariableSupplyAuction Seller -> VariableSupplyAuction : createAuction() -VariableSupplyAuction -> VariableSupplyAuction : calculate phase end times +VariableSupplyAuction -> VariableSupplyAuction : validate no existing auction for drop yet VariableSupplyAuction -> VariableSupplyAuction : store auction metadata VariableSupplyAuction -> VariableSupplyAuction : emit AuctionCreated() diff --git a/uml/VariableSupplyAuction/2-cancelAuction.atxt b/uml/VariableSupplyAuction/2-cancelAuction.atxt index 11e48fc8..dea3d42b 100644 --- a/uml/VariableSupplyAuction/2-cancelAuction.atxt +++ b/uml/VariableSupplyAuction/2-cancelAuction.atxt @@ -1,26 +1,26 @@ - ,-. - `-' - /|\ - | ,---------------------. - / \ |VariableSupplyAuction| - Seller `----------+----------' - | cancelAuction() | - | ----------------------->| - | | - | ----. - | | validate no bids placed yet - | <---' - | | - | ----. - | | emit AuctionCanceled() - | <---' - | | - | ----. - | | delete auction - | <---' - Seller ,----------+----------. - ,-. |VariableSupplyAuction| - `-' `---------------------' - /|\ - | - / \ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Seller `----------+----------' + | cancelAuction() | + | ----------------------->| + | | + | ----. + | | validate no bids placed yet, or no settle price points meet minimum viable revenue + | <---' + | | + | ----. + | | emit AuctionCanceled() + | <---' + | | + | ----. + | | delete auction + | <---' + Seller ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/2-cancelAuction.txt b/uml/VariableSupplyAuction/2-cancelAuction.txt index 017eff02..acb61b2e 100644 --- a/uml/VariableSupplyAuction/2-cancelAuction.txt +++ b/uml/VariableSupplyAuction/2-cancelAuction.txt @@ -4,7 +4,7 @@ participant VariableSupplyAuction Seller -> VariableSupplyAuction : cancelAuction() -VariableSupplyAuction -> VariableSupplyAuction : validate no bids placed yet +VariableSupplyAuction -> VariableSupplyAuction : validate no bids placed yet, or no settle price points meet minimum viable revenue VariableSupplyAuction -> VariableSupplyAuction : emit AuctionCanceled() VariableSupplyAuction -> VariableSupplyAuction : delete auction diff --git a/uml/VariableSupplyAuction/3-placeBid.atxt b/uml/VariableSupplyAuction/3-placeBid.atxt index 2cc997d4..2d8b7a18 100644 --- a/uml/VariableSupplyAuction/3-placeBid.atxt +++ b/uml/VariableSupplyAuction/3-placeBid.atxt @@ -1,26 +1,26 @@ - ,-. - `-' - /|\ - | ,---------------------. - / \ |VariableSupplyAuction| - Bidder `----------+----------' - | placeBid() | - | ----------------------->| - | | - | ----. - | | validate auction is in bid phase - | <---' - | | - | ----. - | | update auction balance and store sealed bid - | <---' - | | - | ----. - | | emit BidPlaced() - | <---' - Bidder ,----------+----------. - ,-. |VariableSupplyAuction| - `-' `---------------------' - /|\ - | - / \ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Bidder `----------+----------' + | placeBid() | + | ----------------------->| + | | + | ----. + | | validate auction is in bid phase, and no bid placed yet + | <---' + | | + | ----. + | | update auction balance and store sealed bid + | <---' + | | + | ----. + | | emit BidPlaced() + | <---' + Bidder ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/3-placeBid.txt b/uml/VariableSupplyAuction/3-placeBid.txt index b4ae7433..946e6577 100644 --- a/uml/VariableSupplyAuction/3-placeBid.txt +++ b/uml/VariableSupplyAuction/3-placeBid.txt @@ -4,7 +4,7 @@ participant VariableSupplyAuction Bidder -> VariableSupplyAuction : placeBid() -VariableSupplyAuction -> VariableSupplyAuction : validate auction is in bid phase +VariableSupplyAuction -> VariableSupplyAuction : validate auction is in bid phase, and no bid placed yet VariableSupplyAuction -> VariableSupplyAuction : update auction balance and store sealed bid VariableSupplyAuction -> VariableSupplyAuction : emit BidPlaced() diff --git a/uml/VariableSupplyAuction/4-revealBid.atxt b/uml/VariableSupplyAuction/4-revealBid.atxt index 41dd9d4e..ea686334 100644 --- a/uml/VariableSupplyAuction/4-revealBid.atxt +++ b/uml/VariableSupplyAuction/4-revealBid.atxt @@ -1,30 +1,26 @@ - ,-. - `-' - /|\ - | ,---------------------. - / \ |VariableSupplyAuction| - Bidder `----------+----------' - | revealBid() | - | ----------------------->| - | | - | ----. - | | validate auction is in reveal phase - | <---' - | | - | ----. - | | validate revealed bid matches sealed bid - | <---' - | | - | ----. - | | store revealed bid - | <---' - | | - | ----. - | | emit BidRevealed() - | <---' - Bidder ,----------+----------. - ,-. |VariableSupplyAuction| - `-' `---------------------' - /|\ - | - / \ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Bidder `----------+----------' + | revealBid() | + | ----------------------->| + | | + | ----. + | | validate auction is in reveal phase, and revealed bid matches sealed bid + | <---' + | | + | ----. + | | store revealed bid + | <---' + | | + | ----. + | | emit BidRevealed() + | <---' + Bidder ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/4-revealBid.txt b/uml/VariableSupplyAuction/4-revealBid.txt index 11d2fa66..4a31f729 100644 --- a/uml/VariableSupplyAuction/4-revealBid.txt +++ b/uml/VariableSupplyAuction/4-revealBid.txt @@ -4,8 +4,7 @@ participant VariableSupplyAuction Bidder -> VariableSupplyAuction : revealBid() -VariableSupplyAuction -> VariableSupplyAuction : validate auction is in reveal phase -VariableSupplyAuction -> VariableSupplyAuction : validate revealed bid matches sealed bid +VariableSupplyAuction -> VariableSupplyAuction : validate auction is in reveal phase, and revealed bid matches sealed bid VariableSupplyAuction -> VariableSupplyAuction : store revealed bid VariableSupplyAuction -> VariableSupplyAuction : emit BidRevealed() diff --git a/uml/VariableSupplyAuction/5-calculateSettleOptions.atxt b/uml/VariableSupplyAuction/5-calculateSettleOptions.atxt deleted file mode 100644 index 74df66f2..00000000 --- a/uml/VariableSupplyAuction/5-calculateSettleOptions.atxt +++ /dev/null @@ -1,28 +0,0 @@ - ,-. - `-' - /|\ - | ,---------------------. - / \ |VariableSupplyAuction| - Caller `----------+----------' - | calculateSettleOptions()| - | ------------------------> - | | - | | - | _________________________________________________________________ - | ! ALT / Not calculated yet? ! - | !_____/ | ! - | ! |----. ! - | ! | | calculate and store settle options ! - | ! |<---' ! - | !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! - | !~[noop]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! - | | - | |----. - | | | return stored settle options - | |<---' - Caller ,----------+----------. - ,-. |VariableSupplyAuction| - `-' `---------------------' - /|\ - | - / \ diff --git a/uml/VariableSupplyAuction/5-calculateSettleOptions.txt b/uml/VariableSupplyAuction/5-calculateSettleOptions.txt deleted file mode 100644 index 8aaa3253..00000000 --- a/uml/VariableSupplyAuction/5-calculateSettleOptions.txt +++ /dev/null @@ -1,17 +0,0 @@ -@startuml -actor Caller -participant VariableSupplyAuction - -Caller -> VariableSupplyAuction : calculateSettleOptions() - -alt Not calculated yet? - - VariableSupplyAuction -> VariableSupplyAuction : calculate and store settle options - -else noop - -end - - VariableSupplyAuction -> VariableSupplyAuction : return stored settle options - -@enduml \ No newline at end of file diff --git a/uml/VariableSupplyAuction/5-calculateSettleOutcomes.txt b/uml/VariableSupplyAuction/5-calculateSettleOutcomes.txt new file mode 100644 index 00000000..e69de29b diff --git a/uml/VariableSupplyAuction/6-settleAuction.atxt b/uml/VariableSupplyAuction/6-settleAuction.atxt index ca00ee6d..bbb86a4c 100644 --- a/uml/VariableSupplyAuction/6-settleAuction.atxt +++ b/uml/VariableSupplyAuction/6-settleAuction.atxt @@ -1,52 +1,62 @@ - ,-. - `-' - /|\ - | ,---------------------. ,----------. - / \ |VariableSupplyAuction| |ERC721Drop| - Seller `----------+----------' `----+-----' - | settleAuction() | | - | ----------------------->| | - | | | - | ----. | - | | validate auction is in settle phase | - | <---' | - | | | - | ----. | - | | validate auction not settled yet | - | <---' | - | | | - | ----. - | | based on chosen price point, calculate and store final edition size and winning bidders - | <---' - | | | - | ----. | - | | update bidder balances to final available refund amounts | - | <---' | - | | | - | | setEditionSize() | - | |-------------------------------------------------------------------------------------------> - | | | - | | |----. - | | | | update drop edition size - | | |<---' - | | | - | | adminMintAirdrop() | - | |-------------------------------------------------------------------------------------------> - | | | - | | |----. - | | | | mint NFT to winning bidders - | | |<---' - | | | - | ----. | - | | handle seller funds recipient payout | - | <---' | - | | | - | ----. | - | | emit AuctionSettled() | - | <---' | - Seller ,----------+----------. ,----+-----. - ,-. |VariableSupplyAuction| |ERC721Drop| - `-' `---------------------' `----------' - /|\ - | - / \ + ,-. + `-' + /|\ + | ,---------------------. ,----------. + / \ |VariableSupplyAuction| |ERC721Drop| + Seller `----------+----------' `----+-----' + | settleAuction() | | + | --------------->| | + | | | + | | | + |___________________________________________________________________________________________ | + |! ALT / Not calculated yet? ! | + |!_____/ | ! | + |! ----. ! | + |! | validate auction is in settle phase, and there are revealed bids ! | + |! <---' ! | + |! | ! | + |! ----. ! | + |! | calculate and store possible settle outcomes ! | + |! <---' ! | + |!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! | + |!~[noop]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~! | + | | | + | ----. | + | | validate chosen price point meets minimum viable revenue | + | <---' | + | | | + | ----. | + | | store final price point, revenue, and edition size | + | <---' | + | | | + | ----. + | | determine winning bidders and update balances to final available refunds + | <---' + | | | + | | setEditionSize() | + | |----------------------------------------------------------------------------> + | | | + | | |----. + | | | | update drop edition size + | | |<---' + | | | + | | adminMintAirdrop() | + | |----------------------------------------------------------------------------> + | | | + | | |----. + | | | | mint NFT to winning bidders + | | |<---' + | | | + | ----. | + | | handle seller funds recipient payout | + | <---' | + | | | + | ----. | + | | emit AuctionSettled() | + | <---' | + Seller ,----------+----------. ,----+-----. + ,-. |VariableSupplyAuction| |ERC721Drop| + `-' `---------------------' `----------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/6-settleAuction.txt b/uml/VariableSupplyAuction/6-settleAuction.txt index d8422964..8dde691f 100644 --- a/uml/VariableSupplyAuction/6-settleAuction.txt +++ b/uml/VariableSupplyAuction/6-settleAuction.txt @@ -2,16 +2,22 @@ actor Seller participant VariableSupplyAuction participant ERC721Drop +skinparam MaxAsciiMessageLength 10 Seller -> VariableSupplyAuction : settleAuction() -TODO x review all UML diagrams -TODO x update to consolidate biz logic and add minimum viable revenue +alt Not calculated yet? -VariableSupplyAuction -> VariableSupplyAuction : validate auction is in settle phase -VariableSupplyAuction -> VariableSupplyAuction : validate auction not settled yet -VariableSupplyAuction -> VariableSupplyAuction : based on chosen price point, calculate and store final edition size and winning bidders -VariableSupplyAuction -> VariableSupplyAuction : update bidder balances to final available refund amounts + VariableSupplyAuction -> VariableSupplyAuction : validate auction is in settle phase, and there are revealed bids + VariableSupplyAuction -> VariableSupplyAuction : calculate and store possible settle outcomes + +else noop + +end + +VariableSupplyAuction -> VariableSupplyAuction : validate chosen price point meets minimum viable revenue +VariableSupplyAuction -> VariableSupplyAuction : store final price point, revenue, and edition size +VariableSupplyAuction -> VariableSupplyAuction : determine winning bidders and update balances to final available refunds VariableSupplyAuction -> ERC721Drop : setEditionSize() ERC721Drop -> ERC721Drop : update drop edition size VariableSupplyAuction -> ERC721Drop : adminMintAirdrop() diff --git a/uml/VariableSupplyAuction/7-checkAvailableRefund.atxt b/uml/VariableSupplyAuction/7-checkAvailableRefund.atxt deleted file mode 100644 index bbfe1a59..00000000 --- a/uml/VariableSupplyAuction/7-checkAvailableRefund.atxt +++ /dev/null @@ -1,22 +0,0 @@ - ,-. - `-' - /|\ - | ,---------------------. - / \ |VariableSupplyAuction| - Bidder `----------+----------' - | checkAvailableRefund() | - | ----------------------->| - | | - | ----. - | | validate auction is in cleanup phase - | <---' - | | - | ----. - | | return stored available refund for bidder - | <---' - Bidder ,----------+----------. - ,-. |VariableSupplyAuction| - `-' `---------------------' - /|\ - | - / \ diff --git a/uml/VariableSupplyAuction/7-checkAvailableRefund.txt b/uml/VariableSupplyAuction/7-checkAvailableRefund.txt index d3661ff3..e69de29b 100644 --- a/uml/VariableSupplyAuction/7-checkAvailableRefund.txt +++ b/uml/VariableSupplyAuction/7-checkAvailableRefund.txt @@ -1,10 +0,0 @@ -@startuml -actor Bidder -participant VariableSupplyAuction - -Bidder -> VariableSupplyAuction : checkAvailableRefund() - -VariableSupplyAuction -> VariableSupplyAuction : validate auction is in cleanup phase -VariableSupplyAuction -> VariableSupplyAuction : return stored available refund for bidder - -@enduml \ No newline at end of file diff --git a/uml/VariableSupplyAuction/8-claimRefund.atxt b/uml/VariableSupplyAuction/8-claimRefund.atxt index ee74a432..4892dec7 100644 --- a/uml/VariableSupplyAuction/8-claimRefund.atxt +++ b/uml/VariableSupplyAuction/8-claimRefund.atxt @@ -1,30 +1,30 @@ - ,-. - `-' - /|\ - | ,---------------------. - / \ |VariableSupplyAuction| - Bidder `----------+----------' - | claimRefund() | - | ----------------------->| - | | - | ----. - | | validate auction is in cleanup phase - | <---' - | | - | ----. - | | clear bidder available refund - | <---' - | | - | ----. - | | handle bidder available refund payout - | <---' - | | - | ----. - | | emit RefundClaimed() - | <---' - Bidder ,----------+----------. - ,-. |VariableSupplyAuction| - `-' `---------------------' - /|\ - | - / \ + ,-. + `-' + /|\ + | ,---------------------. + / \ |VariableSupplyAuction| + Bidder `----------+----------' + | claimRefund() | + | ----------------------->| + | | + | ----. + | | validate auction is in cleanup phase, and bidder has an available refund + | <---' + | | + | ----. + | | clear bidder available refund + | <---' + | | + | ----. + | | handle bidder available refund payout + | <---' + | | + | ----. + | | emit RefundClaimed() + | <---' + Bidder ,----------+----------. + ,-. |VariableSupplyAuction| + `-' `---------------------' + /|\ + | + / \ diff --git a/uml/VariableSupplyAuction/8-claimRefund.txt b/uml/VariableSupplyAuction/8-claimRefund.txt index 69b811a4..eb949cf2 100644 --- a/uml/VariableSupplyAuction/8-claimRefund.txt +++ b/uml/VariableSupplyAuction/8-claimRefund.txt @@ -4,7 +4,7 @@ participant VariableSupplyAuction Bidder -> VariableSupplyAuction : claimRefund() -VariableSupplyAuction -> VariableSupplyAuction : validate auction is in cleanup phase +VariableSupplyAuction -> VariableSupplyAuction : validate auction is in cleanup phase, and bidder has an available refund VariableSupplyAuction -> VariableSupplyAuction : clear bidder available refund VariableSupplyAuction -> VariableSupplyAuction : handle bidder available refund payout VariableSupplyAuction -> VariableSupplyAuction : emit RefundClaimed() From 55002190781ba13ed3536659713bfd17a4cb3bef Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 31 Oct 2022 11:35:09 -0400 Subject: [PATCH 27/31] Fix auction refund accounting bug --- .gas-snapshot | 133 ++++++++-------- .../VariableSupplyAuction.sol | 13 +- .../VariableSupplyAuction.t.sol | 144 +++++++++++------- 3 files changed, 160 insertions(+), 130 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 0060bfc6..6759f6f0 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -320,77 +320,78 @@ ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85246) -VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 377468) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295847) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160672) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 415137) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 215387) -VariableSupplyAuctionTest:testEvent_createAuction() (gas: 98154) -VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOptions_WhenCalledFrice() (gas: 1739559) -VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOptions_WhenCalledThrice() (gas: 1725869) -VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOptions_WhenCalledTwice() (gas: 1712135) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 92009) -VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionDoesNotExist() (gas: 16678) -VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionHasZeroRevealedBids() (gas: 98470) -VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionInBidPhase() (gas: 95581) -VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionInCleanupPhase() (gas: 96411) -VariableSupplyAuctionTest:testRevert_CalculateSettleOptions_WhenAuctionInRevealPhase() (gas: 96038) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenAuctionDoesNotExist() (gas: 27022) +VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 377976) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295913) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160694) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 415225) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 215409) +VariableSupplyAuctionTest:testEvent_createAuction() (gas: 98176) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledFrice() (gas: 1739933) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledThrice() (gas: 1726155) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledTwice() (gas: 1712485) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 92031) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionDoesNotExist() (gas: 16636) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionHasZeroRevealedBids() (gas: 98492) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInBidPhase() (gas: 95615) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInCleanupPhase() (gas: 96377) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInRevealPhase() (gas: 96050) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenAuctionDoesNotExist() (gas: 27044) VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159915) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseAndMinimumViableRevenueWasMet() (gas: 571929) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOptionsYet() (gas: 404890) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99788) -VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionDoesNotExist() (gas: 15398) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseAndMinimumViableRevenueWasMet() (gas: 572029) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOutcomesYet() (gas: 405010) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99831) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionDoesNotExist() (gas: 15354) VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInBidPhase() (gas: 156313) VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInRevealPhase() (gas: 210667) -VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInSettlePhase() (gas: 211198) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 376044) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionDoesNotExist() (gas: 20376) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInBidPhase() (gas: 159325) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInRevealPhase() (gas: 213615) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInSettlePhase() (gas: 214187) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 102230) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidRevealed() (gas: 421038) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20670) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97479) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27591) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInCleanupPhase() (gas: 107105) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106963) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 107024) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166232) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102030) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionDoesNotExist() (gas: 20922) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159869) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInCleanupPhase() (gas: 160667) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160588) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102725) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 161215) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160875) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 161218) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionDoesNotExist() (gas: 22689) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInSettlePhase() (gas: 211199) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 376554) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionDoesNotExist() (gas: 20398) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInBidPhase() (gas: 159347) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInRevealPhase() (gas: 213660) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInSettlePhase() (gas: 214209) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 102252) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidRevealed() (gas: 421037) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20692) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97545) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27569) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInCleanupPhase() (gas: 107127) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106985) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 107045) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166276) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102052) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionDoesNotExist() (gas: 20966) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159913) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInCleanupPhase() (gas: 160755) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160609) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102727) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 161259) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160942) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 161261) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionDoesNotExist() (gas: 22644) VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionHasZeroRevealedBids() (gas: 102537) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInBidPhase() (gas: 99670) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInBidPhase() (gas: 99604) VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInCleanupPhase() (gas: 100414) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInRevealPhase() (gas: 100127) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtInvalidPricePoint() (gas: 1700684) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() (gas: 1698673) -VariableSupplyAuctionTest:test_CalculateSettleOptions() (gas: 1699916) -VariableSupplyAuctionTest:test_CancelAuction_WhenInSettlePhaseButMinimumViableRevenueNotMet() (gas: 519610) -VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85151) -VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 609745) -VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 501043) -VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 374617) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95188) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96380) -VariableSupplyAuctionTest:test_DropInitial() (gas: 35529) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 283995) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156129) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401704) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 210113) -VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1508951) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 1850125) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2129064) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 1893040) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInRevealPhase() (gas: 100083) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtInvalidPricePoint() (gas: 1700927) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() (gas: 1698937) +VariableSupplyAuctionTest:test_CalculateSettleOutcomes() (gas: 1700243) +VariableSupplyAuctionTest:test_CancelAuction_WhenInSettlePhaseButMinimumViableRevenueNotMet() (gas: 519721) +VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85170) +VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 609612) +VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 504619) +VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 378172) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95211) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96403) +VariableSupplyAuctionTest:test_DropInitial() (gas: 35573) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 284102) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156172) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401832) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 210222) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1509578) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 1850705) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2129689) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 1893642) +VariableSupplyAuctionTest:test_SupportsInterface() (gas: 6615) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) ERC1155TransferHelperTest:testRevert_UserMustApproveModuleToTransferBatch() (gas: 73598) diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 76984f1b..f1ab3d51 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -721,20 +721,19 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Get the balance for the specified bidder Bid storage bid = bidsForAuction[_tokenContract][msg.sender]; - uint96 bidderBalance = bid.bidderBalance; + uint96 availableRefund = bid.bidderBalance; // Ensure bidder has a leftover balance - require(bid.revealedBidAmount > 0 && bidderBalance > 0, "NO_REFUND_AVAILABLE"); + require(bid.revealedBidAmount > 0 && availableRefund > 0, "NO_REFUND_AVAILABLE"); - // Clear bidder balance + // Clear bidder balance and update auction total balance bid.bidderBalance = 0; - - // TODO x update totalBalance and add test + auction.totalBalance -= availableRefund; // Transfer the bidder's available refund balance to the bidder - _handleOutgoingTransfer(msg.sender, bidderBalance, address(0), 50_000); + _handleOutgoingTransfer(msg.sender, availableRefund, address(0), 50_000); - emit RefundClaimed(_tokenContract, msg.sender, bidderBalance, auction); + emit RefundClaimed(_tokenContract, msg.sender, availableRefund, auction); } /*////////////////////////////////////////////////////////////// diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 37802a77..68e6b7a5 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -950,55 +950,6 @@ contract VariableSupplyAuctionTest is Test { SETTLE AUCTION //////////////////////////////////////////////////////////////*/ - /* - - Checks for each action - - Create Auction - - check there is no live auction for the drop contract already - - check the funds recipient is not zero address - - Cancel Auction - - check the auction exists - - check the caller is the seller - - check there are no bids placed yet - - OR, if in settle phase: - - check that seller has first considered the settle price points - - check that no settle price points meet minimum viable revenue - - Place Bid - - check the auction exists - - check the auction is in bid phase - - check the bidder has not placed a bid yet - - check the bid is valid - - Reveal Bid - - check the auction exists - - check the auction is in reveal phase - - check the bidder placed a bid in the auction - - check the revealed amount is not greater than sent ether - - check the revealed bid matches the sealed bid - - Calculate Settle Outcomes - - check the auction exists - - check the auction is in settle phase - - check the auction has at least 1 revealed bid - - Settle Auction - - (includes checks from calling calculate settle outcomes, either in this call or previously) - - check that price point is a valid settle outcome (exists and meets minimum viable revenue) - - Check Available Refund - - check the auction exists - - check the auction is in cleanup phase - - Claim Refund - - check the auction exists - - check the auction is in cleanup phase - - check the bidder has a leftover balance - - */ - function test_CalculateSettleOutcomes() public setupBasicAuction throughRevealPhaseComplex { (uint96[] memory pricePoints, uint16[] memory editionSizes, uint96[] memory revenues) = auctions.calculateSettleOutcomes(address(drop)); @@ -1254,15 +1205,15 @@ contract VariableSupplyAuctionTest is Test { , , uint96 totalBalance, - uint96 settledRevenue, uint96 settledPricePoint, + uint96 settledRevenue, uint16 settledEditionSize ) = auctions.auctionForDrop(address(drop)); assertEq(totalBalance, 84 ether - 14 ether); - assertEq(settledRevenue, 14 ether); assertEq(settledPricePoint, 1 ether); + assertEq(settledRevenue, 14 ether); assertEq(settledEditionSize, 14); (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); @@ -1336,15 +1287,15 @@ contract VariableSupplyAuctionTest is Test { , , uint96 totalBalance, - uint96 settledRevenue, uint96 settledPricePoint, + uint96 settledRevenue, uint16 settledEditionSize ) = auctions.auctionForDrop(address(drop)); assertEq(totalBalance, 84 ether - 18 ether); - assertEq(settledRevenue, 18 ether); assertEq(settledPricePoint, 6 ether); + assertEq(settledRevenue, 18 ether); assertEq(settledEditionSize, 3); (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); @@ -1418,15 +1369,15 @@ contract VariableSupplyAuctionTest is Test { , , uint96 totalBalance, - uint96 settledRevenue, uint96 settledPricePoint, + uint96 settledRevenue, uint16 settledEditionSize ) = auctions.auctionForDrop(address(drop)); assertEq(totalBalance, 84 ether - 11 ether); - assertEq(settledRevenue, 11 ether); assertEq(settledPricePoint, 11 ether); + assertEq(settledRevenue, 11 ether); assertEq(settledEditionSize, 1); (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); @@ -1634,6 +1585,21 @@ contract VariableSupplyAuctionTest is Test { assertEq(address(bidder1).balance, 99 ether); (, bidderBalance, ) = auctions.bidsForAuction(address(drop), address(bidder1)); assertEq(bidderBalance, 0 ether); + + ( + , + , + , + , + , + , + , + uint96 totalBalance, + , + , + + ) = auctions.auctionForDrop(address(drop)); + assertEq(totalBalance, 0); // no balance remaining for auction } function test_ClaimRefund_WhenNotWinner() public setupBasicAuctionWithLowMinimumViableRevenue { @@ -1667,6 +1633,21 @@ contract VariableSupplyAuctionTest is Test { assertEq(address(bidder1).balance, 100 ether); // claim their full amount of sent ether (, bidderBalance, ) = auctions.bidsForAuction(address(drop), address(bidder1)); assertEq(bidderBalance, 0 ether); + + ( + , + , + , + , + , + , + , + uint96 totalBalance, + , + , + + ) = auctions.auctionForDrop(address(drop)); + assertEq(totalBalance, 0); // no balance remaining for auction } function testEvent_ClaimRefund() public setupBasicAuctionWithLowMinimumViableRevenue { @@ -1678,7 +1659,7 @@ contract VariableSupplyAuctionTest is Test { endOfBidPhase: uint32(TIME0 + 3 days), endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), - totalBalance: 1 ether, + totalBalance: 0, settledPricePoint: 1 ether, settledRevenue: 1 ether, settledEditionSize: uint16(1) @@ -1773,7 +1754,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days); // bidder1 never revealed =( - // note bidder2 must reveal in this test, otherwise seller can't settle when no revealed bids + // note bidder2 must reveal in this test, otherwise seller can't settle bc no revealed bids vm.prank(address(bidder2)); auctions.revealBid(address(drop), 1 ether, salt2); @@ -1819,6 +1800,55 @@ contract VariableSupplyAuctionTest is Test { TEST HELPERS //////////////////////////////////////////////////////////////*/ + /* + + Checks for each action + + Create Auction + - check there is no live auction for the drop contract already + - check the funds recipient is not zero address + + Cancel Auction + - check the auction exists + - check the caller is the seller + - check there are no bids placed yet + - OR, if in settle phase: + - check that seller has first considered the settle price points + - check that no settle price points meet minimum viable revenue + + Place Bid + - check the auction exists + - check the auction is in bid phase + - check the bidder has not placed a bid yet + - check the bid is valid + + Reveal Bid + - check the auction exists + - check the auction is in reveal phase + - check the bidder placed a bid in the auction + - check the revealed amount is not greater than sent ether + - check the revealed bid matches the sealed bid + + Calculate Settle Outcomes + - check the auction exists + - check the auction is in settle phase + - check the auction has at least 1 revealed bid + + Settle Auction + - (includes checks from calling calculate settle outcomes, either in this call or previously) + - check that price point is a valid settle outcome (exists and meets minimum viable revenue) + + Check Available Refund + - check the auction exists + - check the auction is in cleanup phase + + Claim Refund + - check the auction exists + - check the auction is in cleanup phase + - check the bidder has a leftover balance + + */ + // TODO parameterize modifier pattern to support fuzzing modifier setupBasicAuction() { From a676e25143c6cd1e68f77e3270a82e113cb9258a Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 31 Oct 2022 12:03:25 -0400 Subject: [PATCH 28/31] [draft] Cleanup specification; Improve Auction struct clarity --- .gas-snapshot | 128 +++++++++--------- .../VariableSupplyAuction.sol | 4 +- .../1-VariableSupplyAuction.jtbd | 17 --- .../3-VariableSupplyAuction.invariant | 6 - ....feature => VariableSupplyAuction.feature} | 48 +++++-- .../VariableSupplyAuction.t.sol | 54 ++++---- 6 files changed, 132 insertions(+), 125 deletions(-) delete mode 100644 contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd delete mode 100644 contracts/test/modules/VariableSupplyAuction/3-VariableSupplyAuction.invariant rename contracts/test/modules/VariableSupplyAuction/{2-VariableSupplyAuction.feature => VariableSupplyAuction.feature} (74%) diff --git a/.gas-snapshot b/.gas-snapshot index 6759f6f0..47a14f49 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,78 +319,78 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85246) -VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 377976) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295913) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160694) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 415225) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 215409) -VariableSupplyAuctionTest:testEvent_createAuction() (gas: 98176) -VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledFrice() (gas: 1739933) -VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledThrice() (gas: 1726155) -VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledTwice() (gas: 1712485) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 92031) +VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85222) +VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 377850) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295805) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160640) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 415036) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 215328) +VariableSupplyAuctionTest:testEvent_createAuction() (gas: 98149) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledFrice() (gas: 1739150) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledThrice() (gas: 1725372) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledTwice() (gas: 1711702) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 92004) VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionDoesNotExist() (gas: 16636) -VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionHasZeroRevealedBids() (gas: 98492) -VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInBidPhase() (gas: 95615) -VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInCleanupPhase() (gas: 96377) -VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInRevealPhase() (gas: 96050) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenAuctionDoesNotExist() (gas: 27044) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159915) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseAndMinimumViableRevenueWasMet() (gas: 572029) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOutcomesYet() (gas: 405010) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99831) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionHasZeroRevealedBids() (gas: 98465) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInBidPhase() (gas: 95588) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInCleanupPhase() (gas: 96350) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInRevealPhase() (gas: 96023) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenAuctionDoesNotExist() (gas: 27047) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159864) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseAndMinimumViableRevenueWasMet() (gas: 571843) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOutcomesYet() (gas: 404824) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99807) VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionDoesNotExist() (gas: 15354) -VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInBidPhase() (gas: 156313) -VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInRevealPhase() (gas: 210667) -VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInSettlePhase() (gas: 211199) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 376554) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInBidPhase() (gas: 156259) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInRevealPhase() (gas: 210586) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInSettlePhase() (gas: 211118) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 376446) VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionDoesNotExist() (gas: 20398) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInBidPhase() (gas: 159347) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInRevealPhase() (gas: 213660) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInSettlePhase() (gas: 214209) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 102252) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidRevealed() (gas: 421037) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInBidPhase() (gas: 159293) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInRevealPhase() (gas: 213579) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInSettlePhase() (gas: 214128) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 102225) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidRevealed() (gas: 420929) VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20692) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97545) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97518) VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27569) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInCleanupPhase() (gas: 107127) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106985) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 107045) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166276) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102052) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInCleanupPhase() (gas: 107100) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106958) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 107018) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166222) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102025) VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionDoesNotExist() (gas: 20966) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159913) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInCleanupPhase() (gas: 160755) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160609) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102727) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 161259) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160942) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 161261) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159859) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInCleanupPhase() (gas: 160701) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160555) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102700) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 161205) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160888) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 161207) VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionDoesNotExist() (gas: 22644) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionHasZeroRevealedBids() (gas: 102537) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInBidPhase() (gas: 99604) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInCleanupPhase() (gas: 100414) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInRevealPhase() (gas: 100083) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtInvalidPricePoint() (gas: 1700927) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() (gas: 1698937) -VariableSupplyAuctionTest:test_CalculateSettleOutcomes() (gas: 1700243) -VariableSupplyAuctionTest:test_CancelAuction_WhenInSettlePhaseButMinimumViableRevenueNotMet() (gas: 519721) -VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85170) -VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 609612) -VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 504619) -VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 378172) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95211) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96403) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionHasZeroRevealedBids() (gas: 102510) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInBidPhase() (gas: 99577) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInCleanupPhase() (gas: 100387) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInRevealPhase() (gas: 100056) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtInvalidPricePoint() (gas: 1700144) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() (gas: 1698154) +VariableSupplyAuctionTest:test_CalculateSettleOutcomes() (gas: 1699460) +VariableSupplyAuctionTest:test_CancelAuction_WhenInSettlePhaseButMinimumViableRevenueNotMet() (gas: 519514) +VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85122) +VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 609423) +VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 504442) +VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 378049) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95169) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96361) VariableSupplyAuctionTest:test_DropInitial() (gas: 35573) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 284102) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156172) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401832) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 210222) -VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1509578) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 1850705) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2129689) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 1893642) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 283994) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156118) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401643) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 210141) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1508780) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 1849907) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2128891) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 1892844) VariableSupplyAuctionTest:test_SupportsInterface() (gas: 6615) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index f1ab3d51..eaedbc88 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -61,9 +61,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// @param endOfRevealPhase The unix timestamp until which placed bids can be revealed /// @param endOfSettlePhase The unix timestamp until which the seller must settle the auction /// @param totalBalance The total balance of all sent ether for this auction - /// @param settledRevenue The total revenue generated by the drop /// @param settledPricePoint The chosen price point for the drop /// @param settledEditionSize The resulting edition size for the drop + /// @param settledRevenue The total revenue generated by the drop struct Auction { address seller; uint96 minimumViableRevenue; @@ -74,8 +74,8 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa uint32 endOfSettlePhase; uint96 totalBalance; uint96 settledPricePoint; - uint96 settledRevenue; uint16 settledEditionSize; + uint96 settledRevenue; } /// @notice A sealed bid diff --git a/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd b/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd deleted file mode 100644 index 0e29af6e..00000000 --- a/contracts/test/modules/VariableSupplyAuction/1-VariableSupplyAuction.jtbd +++ /dev/null @@ -1,17 +0,0 @@ -Job Executor: Creator - -Core Functional Job-to-be-done: To discover optimal price point and edition size when selling a digital product - -Job Map: -1. (Define) Create digital product -2. (Prepare) Decide on drop parameters: - - Content -- (HMW help seller preview their content to potential bidders?) - - Metadata -- name, symbol, initial owner, royalty bips, funds recipient, metadata renderer, and sales config -3. (Prepare) Decide on auction parameters: - - Money -- minimum viable revenue and seller funds recipient - - Time -- start time, bid phase duration, reveal phase duration, settle phase duration -4. (Confirm) Confirm auction parameters look good -5. (Execute) Create Variable Supply Auction -6. (Monitor) Review settle options based on revealed bids -7. (Modify) Settle auction at a given price point and edition size -8. (Conclude) Cleanup auction once all bidder refunds have been claimed diff --git a/contracts/test/modules/VariableSupplyAuction/3-VariableSupplyAuction.invariant b/contracts/test/modules/VariableSupplyAuction/3-VariableSupplyAuction.invariant deleted file mode 100644 index 0667b792..00000000 --- a/contracts/test/modules/VariableSupplyAuction/3-VariableSupplyAuction.invariant +++ /dev/null @@ -1,6 +0,0 @@ -# TODO run workshop for generating VSA invariants -Invariant 1: endOfBidPhase > startTime -Invariant 2: endOfRevealPhase > endOfBidPhase -Invariant 3: endOfSettlePhase > endOfRevealPhase -Invariant 4: totalBalance == Σ all bidder balances, while now <= endOfSettlePhase -Invariant 5: bidder balance >= revealedBidAmount, while now > endOfRevealPhase diff --git a/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature similarity index 74% rename from contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature rename to contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature index bac60e57..75aa74e0 100644 --- a/contracts/test/modules/VariableSupplyAuction/2-VariableSupplyAuction.feature +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature @@ -1,16 +1,45 @@ Feature: Variable Supply Auctions - As a creator - I want to run a Variable Supply Auction + ## User Story + + As a Creator, + I want to run a Variable Supply Auction, so that I can conduct price discovery and right-size the market when selling my work Auction phases: - - Created - - Bid Phase - - Reveal Phase - - Settle Phase - - Cleanup Phase - - Completed / Cancelled + - Created = when auction start time is in the future + - Bid Phase = when bidders can place sealed bids + - Reveal Phase = when bidders must reveal their true bid amounts + - Settle Phase = when seller must choose a price point at which to settle the auction + - Cleanup Phase = when bidders can claim available refunds + - Completed / Cancelled = when an auction has been settled and all available refunds claimed / when an auction has been cancelled + + ## Job-to-be-done + ## (how does the user story fit into the seller's broader workflow) + + Job Executor: Creator + + Core Functional Job-to-be-done: To discover optimal price point and edition size when selling a digital product + + Job Map: + 1. (Define) Create digital product + 2. (Prepare) Decide on drop parameters: + -- a. Content: _HMW help seller preview their content to potential bidders?_ + -- b. Metadata: name, symbol, initial owner, royalty bips, funds recipient, metadata renderer, and sales config + 3. (Prepare) Decide on auction parameters: + -- a. Money: minimum viable revenue and seller funds recipient + -- b. Time: start time, bid phase duration, reveal phase duration, settle phase duration + 4. (Confirm) Confirm drop and auction parameters look good + 5. (Execute) Create Variable Supply Auction + 6. (Monitor) Review possible settle outcomes based on revealed bids + 7. (Modify) Settle auction at a given price point, revenue, and edition size + 8. (Conclude) Share results of auction with fanbase + + ## Module Invariants + + Invariant 1: contract balance == Σ all auction totalBalances + Invariant 2: auction settledRevenue == auction settledPricePoint * auction settledEditionSize + Invariant 3: auction totalBalance == Σ all bidder balances, while now <= auction endOfRevealPhase Background: VSA creation and bidding Given Seller creates a Variable Supply Auction @@ -144,10 +173,11 @@ Feature: Variable Supply Auctions And Seller settles auction at 2 ETH Then The Seller should receive a Does Not Meet Minimum Revenue error +# TODO add Cucumber scenarios for bidder functionality # TODO handle additional bid space bounding, beyond minimum viable revenue ## Seller sets maximum edition size commitment ## Bidder sets maximum edition size interest # TODO address failure to reveal sad paths # TODO address failure to settle sad paths # TODO consider Cleanup function to delete auction, once all refunds have been claimed -# TODO add Cucumber scenarios for bidder functionality +# TODO run workshop for generating more VSA invariants diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 68e6b7a5..50fa38a0 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -305,8 +305,8 @@ contract VariableSupplyAuctionTest is Test { endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 0, settledPricePoint: 0, - settledRevenue: 0, - settledEditionSize: 0 + settledEditionSize: 0, + settledRevenue: 0 }); vm.expectEmit(false, false, false, false); @@ -451,8 +451,8 @@ contract VariableSupplyAuctionTest is Test { endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 0, settledPricePoint: 0, - settledRevenue: 0, - settledEditionSize: 0 + settledEditionSize: 0, + settledRevenue: 0 }); vm.expectEmit(true, true, true, true); @@ -601,8 +601,8 @@ contract VariableSupplyAuctionTest is Test { endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, settledPricePoint: 0, - settledRevenue: 0, - settledEditionSize: 0 + settledEditionSize: 0, + settledRevenue: 0 }); vm.expectEmit(true, true, true, true); @@ -624,8 +624,8 @@ contract VariableSupplyAuctionTest is Test { endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, settledPricePoint: 0, - settledRevenue: 0, - settledEditionSize: 0 + settledEditionSize: 0, + settledRevenue: 0 }); bytes32 commitment1 = _genSealedBid(1 ether, salt1); @@ -785,8 +785,8 @@ contract VariableSupplyAuctionTest is Test { endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 1 ether, settledPricePoint: 0, - settledRevenue: 0, - settledEditionSize: 0 + settledEditionSize: 0, + settledRevenue: 0 }); bytes32 commitment = _genSealedBid(1 ether, salt1); @@ -812,9 +812,9 @@ contract VariableSupplyAuctionTest is Test { endOfRevealPhase: uint32(TIME0 + 3 days + 2 days), endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 9 ether, - settledRevenue: uint96(0), - settledPricePoint: uint96(0), - settledEditionSize: uint16(0) + settledPricePoint: 0, + settledEditionSize: 0, + settledRevenue: 0 }); bytes32 commitment1 = _genSealedBid(1 ether, salt1); @@ -1206,15 +1206,15 @@ contract VariableSupplyAuctionTest is Test { , uint96 totalBalance, uint96 settledPricePoint, - uint96 settledRevenue, - uint16 settledEditionSize + uint16 settledEditionSize, + uint96 settledRevenue ) = auctions.auctionForDrop(address(drop)); assertEq(totalBalance, 84 ether - 14 ether); assertEq(settledPricePoint, 1 ether); - assertEq(settledRevenue, 14 ether); assertEq(settledEditionSize, 14); + assertEq(settledRevenue, 14 ether); (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); (, uint96 bidderBalance2, ) = auctions.bidsForAuction(address(drop), address(bidder2)); @@ -1288,15 +1288,15 @@ contract VariableSupplyAuctionTest is Test { , uint96 totalBalance, uint96 settledPricePoint, - uint96 settledRevenue, - uint16 settledEditionSize + uint16 settledEditionSize, + uint96 settledRevenue ) = auctions.auctionForDrop(address(drop)); assertEq(totalBalance, 84 ether - 18 ether); assertEq(settledPricePoint, 6 ether); - assertEq(settledRevenue, 18 ether); assertEq(settledEditionSize, 3); + assertEq(settledRevenue, 18 ether); (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); (, uint96 bidderBalance2, ) = auctions.bidsForAuction(address(drop), address(bidder2)); @@ -1370,15 +1370,15 @@ contract VariableSupplyAuctionTest is Test { , uint96 totalBalance, uint96 settledPricePoint, - uint96 settledRevenue, - uint16 settledEditionSize + uint16 settledEditionSize, + uint96 settledRevenue ) = auctions.auctionForDrop(address(drop)); assertEq(totalBalance, 84 ether - 11 ether); assertEq(settledPricePoint, 11 ether); - assertEq(settledRevenue, 11 ether); assertEq(settledEditionSize, 1); + assertEq(settledRevenue, 11 ether); (, uint96 bidderBalance1, ) = auctions.bidsForAuction(address(drop), address(bidder1)); (, uint96 bidderBalance2, ) = auctions.bidsForAuction(address(drop), address(bidder2)); @@ -1661,8 +1661,8 @@ contract VariableSupplyAuctionTest is Test { endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: 0, settledPricePoint: 1 ether, - settledRevenue: 1 ether, - settledEditionSize: uint16(1) + settledEditionSize: uint16(1), + settledRevenue: 1 ether }); vm.prank(address(bidder1)); @@ -1849,7 +1849,7 @@ contract VariableSupplyAuctionTest is Test { */ - // TODO parameterize modifier pattern to support fuzzing + // TODO parameterize modifier pattern to enable easier fuzzing modifier setupBasicAuction() { vm.prank(address(seller)); @@ -1967,8 +1967,8 @@ contract VariableSupplyAuctionTest is Test { endOfSettlePhase: uint32(TIME0 + 3 days + 2 days + 1 days), totalBalance: totalBalance, settledPricePoint: _settledPricePoint, - settledRevenue: settledRevenue, - settledEditionSize: _settledEditionSize + settledEditionSize: _settledEditionSize, + settledRevenue: settledRevenue }); vm.expectEmit(true, true, true, true); From 12bd2b981c2cf6861154fe4c5413c5fa21fbaf56 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 1 Nov 2022 09:52:22 -0400 Subject: [PATCH 29/31] General cleanup --- .../VariableSupplyAuction.sol | 23 +- .../VariableSupplyAuction.integration.t.sol | 276 +++++++++--------- .../VariableSupplyAuction.invariant.t.sol | 6 +- .../VariableSupplyAuction.t.sol | 19 +- 4 files changed, 170 insertions(+), 154 deletions(-) diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index eaedbc88..8d576f53 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -496,7 +496,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa event AuctionSettled(address indexed tokenContract, Auction auction); /// @notice Calculate edition size and revenue for each possible price point - /// @dev Cheaper on subsequent calls but idempotent -- after the initial call when + /// @dev Cheaper on subsequent calls and idempotent -- after the initial call when /// calculations have been performed and stored, the settle outcomes will not change. /// Function visibility is public instead of external, to support settleAuction calling it. /// @param _tokenContract The address of the ERC-721 drop contract @@ -505,7 +505,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa /// resulting edition sizes and amounts of revenue generated function calculateSettleOutcomes(address _tokenContract) public returns (uint96[] memory, uint16[] memory, uint96[] memory) { - // TODO x improve algorithm =P + // TODO we could pre-cache price points and/or edition sizes at reveal time, + // which could reduce complexity from O(n^2) to O(n) but shift some of the + // gas cost from seller to each bidder // Get the auction for the specified drop Auction storage auction = auctionForDrop[_tokenContract]; @@ -522,10 +524,13 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Ensure the auction has at least 1 revealed bid require(bidders.length > 0, "NO_REVEALED_BIDS_TO_SETTLE_AUCTION"); + // Get the settle outcome data structures uint96[] storage settlePricePoints = _settlePricePointsForAuction[_tokenContract]; mapping(uint96 => SettleOutcome) storage settleOutcomes = _settleOutcomesForPricePoint[_tokenContract]; - if (settlePricePoints.length == 0) { // only calculate and store once, otherwise just return from storage + // Only calculate and store once, otherwise just return from storage + if (settlePricePoints.length == 0) { + // Determine all possible price points for (uint256 i = 0; i < bidders.length; i++) { address bidder = bidders[i]; uint96 bidAmount = bidsForAuction[_tokenContract][bidder].revealedBidAmount; @@ -537,6 +542,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa } } + // Calculate edition size and revenue for each price point for (uint256 j = 0; j < settlePricePoints.length; j++) { uint96 settlePricePoint = settlePricePoints[j]; SettleOutcome storage settleOutcome = settleOutcomes[settlePricePoint]; @@ -551,10 +557,12 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa } } - settleOutcome.editionSize--; // because 1st bidder at this settle price was double counted + // Decrement edition size because 1st bidder at this price point was double counted + settleOutcome.editionSize--; } } + // Prepare the return values uint96[] memory pricePoints = new uint96[](settlePricePoints.length); uint16[] memory editionSizes = new uint16[](settlePricePoints.length); uint96[] memory revenues = new uint96[](settlePricePoints.length); @@ -563,8 +571,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa uint96 settlePricePoint = settlePricePoints[m]; SettleOutcome storage settleOutcome = settleOutcomes[settlePricePoint]; + // Zero out as not a viable settle outcome, because minimum viable revenue would be met if (settleOutcome.revenue < auction.minimumViableRevenue) { - settleOutcome.revenue = 0; // zero out, because not viable settle outcome + settleOutcome.revenue = 0; } pricePoints[m] = settlePricePoint; @@ -581,9 +590,9 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa function settleAuction(address _tokenContract, uint96 _settlePricePoint) external nonReentrant { // TODO gas optimization - // TODO document pragmatic max edition size / winning bidders + // TODO document pragmatic max edition size / number of bids // TODO consider storing winningBidders during calculateSettleOutcomes - // TODO look for ways to consolidate business logic with calculateSettleOutcomes + // TODO look for more ways to consolidate business logic with calculateSettleOutcomes // TODO allow sellers to settle at a price point below minimum viable revenue if they so choose // Get the auction diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol index 44f8fa79..0422f2b2 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.integration.t.sol @@ -1,136 +1,140 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.10; - -import {DSTest} from "ds-test/test.sol"; - -import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; -import {Zorb} from "../../utils/users/Zorb.sol"; -import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; -import {ZoraModuleManager} from "../../../ZoraModuleManager.sol"; -import {ZoraProtocolFeeSettings} from "../../../auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol"; -import {ERC20TransferHelper} from "../../../transferHelpers/ERC20TransferHelper.sol"; -import {ERC721TransferHelper} from "../../../transferHelpers/ERC721TransferHelper.sol"; -import {RoyaltyEngine} from "../../utils/modules/RoyaltyEngine.sol"; -import {TestERC721} from "../../utils/tokens/TestERC721.sol"; -import {WETH} from "../../utils/tokens/WETH.sol"; -import {VM} from "../../utils/VM.sol"; - -/// @title VariableSupplyAuctionIntegrationTest -/// @notice Integration Tests for Variable Supply Auctions -contract VariableSupplyAuctionIntegrationTest is DSTest { - // - VM internal vm; - - ZoraRegistrar internal registrar; - ZoraProtocolFeeSettings internal ZPFS; - ZoraModuleManager internal ZMM; - ERC20TransferHelper internal erc20TransferHelper; - ERC721TransferHelper internal erc721TransferHelper; - RoyaltyEngine internal royaltyEngine; - - VariableSupplyAuction internal auctions; - TestERC721 internal token; - WETH internal weth; - - Zorb internal seller; - Zorb internal sellerFundsRecipient; - Zorb internal finder; - Zorb internal royaltyRecipient; - Zorb internal bidder; - Zorb internal otherBidder; - Zorb internal protocolFeeRecipient; - - function setUp() public { - // Cheatcodes - vm = VM(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - // Deploy V3 - registrar = new ZoraRegistrar(); - ZPFS = new ZoraProtocolFeeSettings(); - ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); - erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); - erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); - - // Init V3 - registrar.init(ZMM); - ZPFS.init(address(ZMM), address(0)); - - // Create users - seller = new Zorb(address(ZMM)); - sellerFundsRecipient = new Zorb(address(ZMM)); - bidder = new Zorb(address(ZMM)); - otherBidder = new Zorb(address(ZMM)); - royaltyRecipient = new Zorb(address(ZMM)); - protocolFeeRecipient = new Zorb(address(ZMM)); - - // Deploy mocks - royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); - token = new TestERC721(); - weth = new WETH(); - - // Deploy Reserve Auction Core ETH - auctions = new VariableSupplyAuction(address(erc721TransferHelper), address(royaltyEngine), address(ZPFS), address(weth)); - registrar.registerModule(address(auctions)); - - // Set module fee - vm.prank(address(registrar)); - ZPFS.setFeeParams(address(auctions), address(protocolFeeRecipient), 1); - - // Set balances - vm.deal(address(seller), 100 ether); - vm.deal(address(bidder), 100 ether); - vm.deal(address(otherBidder), 100 ether); - - // Mint seller token - token.mint(address(seller), 1); - - // Bidder swap 50 ETH <> 50 WETH - vm.prank(address(bidder)); - weth.deposit{value: 50 ether}(); - - // otherBidder swap 50 ETH <> 50 WETH - vm.prank(address(otherBidder)); - weth.deposit{value: 50 ether}(); - - // Users approve ReserveAuction module - seller.setApprovalForModule(address(auctions), true); - bidder.setApprovalForModule(address(auctions), true); - otherBidder.setApprovalForModule(address(auctions), true); - - // Seller approve ERC721TransferHelper - vm.prank(address(seller)); - token.setApprovalForAll(address(erc721TransferHelper), true); - - // Bidder approve ERC20TransferHelper - vm.prank(address(bidder)); - weth.approve(address(erc20TransferHelper), 50 ether); - - // otherBidder approve ERC20TransferHelper - vm.prank(address(otherBidder)); - weth.approve(address(erc20TransferHelper), 50 ether); - } - - // function runETH() public { - // // - // } - - // function test_ETHIntegration() public { - // uint256 beforeSellerBalance = address(sellerFundsRecipient).balance; - // uint256 beforeBidderBalance = address(bidder).balance; - // uint256 beforeOtherBidderBalance = address(otherBidder).balance; - // uint256 beforeRoyaltyRecipientBalance = address(royaltyRecipient).balance; - // uint256 beforeProtocolFeeRecipient = address(protocolFeeRecipient).balance; - // address beforeTokenOwner = token.ownerOf(1); - - // runETH(); - - // uint256 afterSellerBalance = address(sellerFundsRecipient).balance; - // uint256 afterBidderBalance = address(bidder).balance; - // uint256 afterOtherBidderBalance = address(otherBidder).balance; - // uint256 afterRoyaltyRecipientBalance = address(royaltyRecipient).balance; - // uint256 afterProtocolFeeRecipient = address(protocolFeeRecipient).balance; - // address afterTokenOwner = token.ownerOf(1); - - // assertEq(beforeSellerBalance, afterSellerBalance); - // } -} +// TODO move large settleAuction unit tests here and simplify the test scenarios there +// +// // SPDX-License-Identifier: GPL-3.0 +// pragma solidity 0.8.10; + +// import {DSTest} from "ds-test/test.sol"; + +// import {VariableSupplyAuction} from "../../../modules/VariableSupplyAuction/VariableSupplyAuction.sol"; +// import {Zorb} from "../../utils/users/Zorb.sol"; +// import {ZoraRegistrar} from "../../utils/users/ZoraRegistrar.sol"; +// import {ZoraModuleManager} from "../../../ZoraModuleManager.sol"; +// import {ZoraProtocolFeeSettings} from "../../../auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol"; +// import {ERC20TransferHelper} from "../../../transferHelpers/ERC20TransferHelper.sol"; +// import {ERC721TransferHelper} from "../../../transferHelpers/ERC721TransferHelper.sol"; +// import {RoyaltyEngine} from "../../utils/modules/RoyaltyEngine.sol"; +// import {TestERC721} from "../../utils/tokens/TestERC721.sol"; +// import {WETH} from "../../utils/tokens/WETH.sol"; +// import {VM} from "../../utils/VM.sol"; + +// /// @title VariableSupplyAuctionIntegrationTest +// /// @notice Integration Tests for Variable Supply Auctions +// contract VariableSupplyAuctionIntegrationTest is DSTest { +// // +// VM internal vm; + +// ZoraRegistrar internal registrar; +// ZoraProtocolFeeSettings internal ZPFS; +// ZoraModuleManager internal ZMM; +// ERC20TransferHelper internal erc20TransferHelper; +// ERC721TransferHelper internal erc721TransferHelper; +// RoyaltyEngine internal royaltyEngine; + +// VariableSupplyAuction internal auctions; +// TestERC721 internal token; +// WETH internal weth; + +// Zorb internal seller; +// Zorb internal sellerFundsRecipient; +// Zorb internal finder; +// Zorb internal royaltyRecipient; +// Zorb internal bidder; +// Zorb internal otherBidder; +// Zorb internal protocolFeeRecipient; + +// function setUp() public { +// // Cheatcodes +// vm = VM(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// // Deploy V3 +// registrar = new ZoraRegistrar(); +// ZPFS = new ZoraProtocolFeeSettings(); +// ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); +// erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); +// erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); + +// // Init V3 +// registrar.init(ZMM); +// ZPFS.init(address(ZMM), address(0)); + +// // Create users +// seller = new Zorb(address(ZMM)); +// sellerFundsRecipient = new Zorb(address(ZMM)); +// bidder = new Zorb(address(ZMM)); +// otherBidder = new Zorb(address(ZMM)); +// royaltyRecipient = new Zorb(address(ZMM)); +// protocolFeeRecipient = new Zorb(address(ZMM)); + +// // Deploy mocks +// royaltyEngine = new RoyaltyEngine(address(royaltyRecipient)); +// token = new TestERC721(); +// weth = new WETH(); + +// // Deploy Reserve Auction Core ETH +// auctions = new VariableSupplyAuction(address(erc721TransferHelper), address(royaltyEngine), address(ZPFS), address(weth)); +// registrar.registerModule(address(auctions)); + +// // Set module fee +// vm.prank(address(registrar)); +// ZPFS.setFeeParams(address(auctions), address(protocolFeeRecipient), 1); + +// // Set balances +// vm.deal(address(seller), 100 ether); +// vm.deal(address(bidder), 100 ether); +// vm.deal(address(otherBidder), 100 ether); + +// // Mint seller token +// token.mint(address(seller), 1); + +// // Bidder swap 50 ETH <> 50 WETH +// vm.prank(address(bidder)); +// weth.deposit{value: 50 ether}(); + +// // otherBidder swap 50 ETH <> 50 WETH +// vm.prank(address(otherBidder)); +// weth.deposit{value: 50 ether}(); + +// // Users approve ReserveAuction module +// seller.setApprovalForModule(address(auctions), true); +// bidder.setApprovalForModule(address(auctions), true); +// otherBidder.setApprovalForModule(address(auctions), true); + +// // Seller approve ERC721TransferHelper +// vm.prank(address(seller)); +// token.setApprovalForAll(address(erc721TransferHelper), true); + +// // Bidder approve ERC20TransferHelper +// vm.prank(address(bidder)); +// weth.approve(address(erc20TransferHelper), 50 ether); + +// // otherBidder approve ERC20TransferHelper +// vm.prank(address(otherBidder)); +// weth.approve(address(erc20TransferHelper), 50 ether); +// } + +// + +// function runETH() public { +// // +// } + +// function test_ETHIntegration() public { +// uint256 beforeSellerBalance = address(sellerFundsRecipient).balance; +// uint256 beforeBidderBalance = address(bidder).balance; +// uint256 beforeOtherBidderBalance = address(otherBidder).balance; +// uint256 beforeRoyaltyRecipientBalance = address(royaltyRecipient).balance; +// uint256 beforeProtocolFeeRecipient = address(protocolFeeRecipient).balance; +// address beforeTokenOwner = token.ownerOf(1); + +// runETH(); + +// uint256 afterSellerBalance = address(sellerFundsRecipient).balance; +// uint256 afterBidderBalance = address(bidder).balance; +// uint256 afterOtherBidderBalance = address(otherBidder).balance; +// uint256 afterRoyaltyRecipientBalance = address(royaltyRecipient).balance; +// uint256 afterProtocolFeeRecipient = address(protocolFeeRecipient).balance; +// address afterTokenOwner = token.ownerOf(1); + +// assertEq(beforeSellerBalance, afterSellerBalance); +// } +// } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol index 01bcbbeb..a9a1608e 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.invariant.t.sol @@ -1,3 +1,5 @@ +// TODO setup actor-based target senders and implement remaining invariants +// // // SPDX-License-Identifier: GPL-3.0 // pragma solidity 0.8.10; @@ -208,7 +210,7 @@ // targetContract(address(auctions)); -// // TODO x setup invariant target senders (for actor-based invariant testing) +// // // Setup one auction // vm.prank(address(seller)); @@ -223,6 +225,8 @@ // }); // } +// + // // function invariant_true_eq_true() public { // // assertTrue(true); // // } diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index 50fa38a0..e4f89cb4 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -25,7 +25,6 @@ contract VariableSupplyAuctionTest is Test { ZoraRegistrar internal registrar; ZoraProtocolFeeSettings internal ZPFS; ZoraModuleManager internal ZMM; - // ERC20TransferHelper internal erc20TransferHelper; ERC721TransferHelper internal erc721TransferHelper; RoyaltyEngine internal royaltyEngine; @@ -71,14 +70,13 @@ contract VariableSupplyAuctionTest is Test { string internal constant salt14 = "my cots earstones"; string internal constant salt15 = "easternmost coy"; - uint32 internal constant TIME0 = 1_666_000_000; // now-ish + uint32 internal constant TIME0 = 1_666_000_000; // now-ish, Autumn 2022 function setUp() public { // Deploy V3 registrar = new ZoraRegistrar(); ZPFS = new ZoraProtocolFeeSettings(); ZMM = new ZoraModuleManager(address(registrar), address(ZPFS)); - // erc20TransferHelper = new ERC20TransferHelper(address(ZMM)); erc721TransferHelper = new ERC721TransferHelper(address(ZMM)); // Init V3 @@ -177,7 +175,8 @@ contract VariableSupplyAuctionTest is Test { bidder14.setApprovalForModule(address(auctions), true); bidder15.setApprovalForModule(address(auctions), true); - // TODO determine pattern for seller approving auction house to set edition size + mint + // TODO determine pattern for seller approving module to set edition size and mint + // Seller approve ERC721TransferHelper // vm.prank(address(seller)); // token.setApprovalForAll(address(erc721TransferHelper), true); @@ -220,7 +219,7 @@ contract VariableSupplyAuctionTest is Test { //////////////////////////////////////////////////////////////*/ function testGas_CreateAuction() public { - // Note this basic setup is applied to other tests via the setupBasicAuction modifier + // Note this same basic setup is applied to other tests via the setupBasicAuction modifier vm.prank(address(seller)); auctions.createAuction({ _tokenContract: address(drop), @@ -292,8 +291,6 @@ contract VariableSupplyAuctionTest is Test { assertEq(endOfSettlePhase, 1 days + 3 days + 2 days + 1 days); } - // TODO add tests that exercise other actions for auctions that don't start instantly - function testEvent_createAuction() public { VariableSupplyAuction.Auction memory auction = VariableSupplyAuction.Auction({ seller: address(seller), @@ -356,6 +353,8 @@ contract VariableSupplyAuctionTest is Test { // TODO add tests for multiple valid auctions at once + // TODO add tests that exercise other actions for auctions that don't start instantly + /*////////////////////////////////////////////////////////////// CANCEL AUCTION //////////////////////////////////////////////////////////////*/ @@ -1066,7 +1065,7 @@ contract VariableSupplyAuctionTest is Test { /* - Scenario for the following 4 settleAuction tests + Scenario for the following settleAuction tests Given The following sealed bids are placed | account | bid amount | sent value | @@ -1092,7 +1091,7 @@ contract VariableSupplyAuctionTest is Test { | 3 | 18 ether | | 1 | 11 ether | - Note settle auction tests use throughRevealPhaseComplex modifier for further test setup + Note settleAuction tests use throughRevealPhaseComplex modifier for further test setup */ @@ -1754,7 +1753,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days); // bidder1 never revealed =( - // note bidder2 must reveal in this test, otherwise seller can't settle bc no revealed bids + // Note bidder2 must reveal in this test, otherwise seller can't settle bc no revealed bids vm.prank(address(bidder2)); auctions.revealBid(address(drop), 1 ether, salt2); From bc27d17fa297583b4762054452291de956e2701e Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 1 Nov 2022 12:25:11 -0400 Subject: [PATCH 30/31] [feat] Variable Supply Auctions v0.1 --- .../VariableSupplyAuction/VariableSupplyAuction.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature index 75aa74e0..2fa9a306 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.feature @@ -17,9 +17,9 @@ Feature: Variable Supply Auctions ## Job-to-be-done ## (how does the user story fit into the seller's broader workflow) - Job Executor: Creator + Who's it for: Creator - Core Functional Job-to-be-done: To discover optimal price point and edition size when selling a digital product + What's it for: To discover optimal price point and edition size when selling a digital product Job Map: 1. (Define) Create digital product From d959c1c499499767412d45f0b4b78f2e622a3dab Mon Sep 17 00:00:00 2001 From: neodaoist Date: Thu, 3 Nov 2022 17:48:41 -0400 Subject: [PATCH 31/31] [refactor] :fuelpump: Replace require strings with custom errors --- .gas-snapshot | 140 +++++++++--------- .../IVariableSupplyAuction.sol | 65 ++++++++ .../VariableSupplyAuction.sol | 111 ++++++++++---- .../VariableSupplyAuction.t.sol | 102 +++++++------ 4 files changed, 273 insertions(+), 145 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 47a14f49..a0ef6b21 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -319,78 +319,78 @@ ReserveAuctionListingEthTest:test_SetReservePrice() (gas: 132205) ReserveAuctionListingEthTest:test_SettleAuction() (gas: 252417) ReserveAuctionListingEthTest:test_StoreTimeOfFirstBid() (gas: 188049) ReserveAuctionListingEthTest:test_TransferNFTIntoEscrow() (gas: 185942) -VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85222) -VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 377850) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295805) -VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160640) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 415036) -VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 215328) +VariableSupplyAuctionTest:testEvent_CancelAuction() (gas: 85256) +VariableSupplyAuctionTest:testEvent_ClaimRefund() (gas: 377984) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenMultiple() (gas: 295787) +VariableSupplyAuctionTest:testEvent_PlaceBid_WhenSingle() (gas: 160612) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenMultiple() (gas: 415062) +VariableSupplyAuctionTest:testEvent_RevealBid_WhenSingle() (gas: 215322) VariableSupplyAuctionTest:testEvent_createAuction() (gas: 98149) -VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledFrice() (gas: 1739150) -VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledThrice() (gas: 1725372) -VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledTwice() (gas: 1711702) -VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 92004) -VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionDoesNotExist() (gas: 16636) -VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionHasZeroRevealedBids() (gas: 98465) -VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInBidPhase() (gas: 95588) -VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInCleanupPhase() (gas: 96350) -VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInRevealPhase() (gas: 96023) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenAuctionDoesNotExist() (gas: 27047) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159864) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseAndMinimumViableRevenueWasMet() (gas: 571843) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOutcomesYet() (gas: 404824) -VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99807) -VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionDoesNotExist() (gas: 15354) -VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInBidPhase() (gas: 156259) -VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInRevealPhase() (gas: 210586) -VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInSettlePhase() (gas: 211118) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 376446) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionDoesNotExist() (gas: 20398) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInBidPhase() (gas: 159293) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInRevealPhase() (gas: 213579) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInSettlePhase() (gas: 214128) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 102225) -VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidRevealed() (gas: 420929) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20692) -VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97518) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27569) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInCleanupPhase() (gas: 107100) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106958) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 107018) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166222) -VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 102025) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionDoesNotExist() (gas: 20966) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159859) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInCleanupPhase() (gas: 160701) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160555) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102700) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 161205) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160888) -VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 161207) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionDoesNotExist() (gas: 22644) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionHasZeroRevealedBids() (gas: 102510) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInBidPhase() (gas: 99577) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInCleanupPhase() (gas: 100387) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInRevealPhase() (gas: 100056) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtInvalidPricePoint() (gas: 1700144) -VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() (gas: 1698154) -VariableSupplyAuctionTest:test_CalculateSettleOutcomes() (gas: 1699460) -VariableSupplyAuctionTest:test_CancelAuction_WhenInSettlePhaseButMinimumViableRevenueNotMet() (gas: 519514) -VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85122) -VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 609423) -VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 504442) -VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 378049) -VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95169) -VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96361) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledFrice() (gas: 1739044) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledThrice() (gas: 1725288) +VariableSupplyAuctionTest:testGas_AndIdempotent_CalculateSettleOutcomes_WhenCalledTwice() (gas: 1711596) +VariableSupplyAuctionTest:testGas_CreateAuction() (gas: 91982) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionDoesNotExist() (gas: 16540) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionHasZeroRevealedBids() (gas: 98336) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInBidPhase() (gas: 95434) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInCleanupPhase() (gas: 96221) +VariableSupplyAuctionTest:testRevert_CalculateSettleOutcomes_WhenAuctionInRevealPhase() (gas: 95914) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenAuctionDoesNotExist() (gas: 26945) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenBidAlreadyPlaced() (gas: 159785) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseAndMinimumViableRevenueWasMet() (gas: 571665) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenInSettlePhaseButHaveNotCalculatedSettleOutcomesYet() (gas: 404655) +VariableSupplyAuctionTest:testRevert_CancelAuction_WhenNotSeller() (gas: 99689) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionDoesNotExist() (gas: 15230) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInBidPhase() (gas: 156099) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInRevealPhase() (gas: 210451) +VariableSupplyAuctionTest:testRevert_CheckAvailableRefund_WhenAuctionInSettlePhase() (gas: 211027) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAlreadyClaimed() (gas: 376478) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionDoesNotExist() (gas: 20274) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInBidPhase() (gas: 159133) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInRevealPhase() (gas: 213422) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenAuctionInSettlePhase() (gas: 214016) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidPlaced() (gas: 102169) +VariableSupplyAuctionTest:testRevert_ClaimRefund_WhenNoBidRevealed() (gas: 420942) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() (gas: 20574) +VariableSupplyAuctionTest:testRevert_CreateAuction_WhenDropHasLiveAuction() (gas: 97400) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionDoesNotExist() (gas: 27517) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInCleanupPhase() (gas: 107035) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInRevealPhase() (gas: 106807) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenAuctionInSettlePhase() (gas: 106912) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenBidderAlreadyPlacedBid() (gas: 166120) +VariableSupplyAuctionTest:testRevert_PlaceBid_WhenNoEtherIncluded() (gas: 101901) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionDoesNotExist() (gas: 20848) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInBidPhase() (gas: 159744) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInCleanupPhase() (gas: 160544) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenAuctionInSettlePhase() (gas: 160420) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenNoCommittedBid() (gas: 102646) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedAmountDoesNotMatchSealedBid() (gas: 161096) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedBidGreaterThanSentEther() (gas: 160731) +VariableSupplyAuctionTest:testRevert_RevealBid_WhenRevealedSaltDoesNotMatchSealedBid() (gas: 161075) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionHasZeroRevealedBids() (gas: 102505) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInBidPhase() (gas: 99635) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInCleanupPhase() (gas: 100404) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenAuctionInRevealPhase() (gas: 100070) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenNotSeller() (gas: 1434679) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtInvalidPricePoint() (gas: 1700119) +VariableSupplyAuctionTest:testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() (gas: 1698109) +VariableSupplyAuctionTest:test_CalculateSettleOutcomes() (gas: 1699354) +VariableSupplyAuctionTest:test_CancelAuction_WhenInSettlePhaseButMinimumViableRevenueNotMet() (gas: 519519) +VariableSupplyAuctionTest:test_CancelAuction_WhenNoBidsPlacedYet() (gas: 85140) +VariableSupplyAuctionTest:test_CheckAvailableRefund() (gas: 609529) +VariableSupplyAuctionTest:test_ClaimRefund_WhenNotWinner() (gas: 504548) +VariableSupplyAuctionTest:test_ClaimRefund_WhenWinner() (gas: 378183) +VariableSupplyAuctionTest:test_CreateAuction_WhenFuture() (gas: 95147) +VariableSupplyAuctionTest:test_CreateAuction_WhenInstant() (gas: 96405) VariableSupplyAuctionTest:test_DropInitial() (gas: 35573) -VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 283994) -VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156118) -VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401643) -VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 210141) -VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1508780) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 1849907) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2128891) -VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 1892844) +VariableSupplyAuctionTest:test_PlaceBid_WhenMultiple() (gas: 283954) +VariableSupplyAuctionTest:test_PlaceBid_WhenSingle() (gas: 156112) +VariableSupplyAuctionTest:test_RevealBid_WhenMultiple() (gas: 401648) +VariableSupplyAuctionTest:test_RevealBid_WhenSingle() (gas: 210113) +VariableSupplyAuctionTest:test_SettleAuction_Preconditions() (gas: 1508738) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtHighPriceLowSupply() (gas: 1849947) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtLowPriceHighSupply() (gas: 2128931) +VariableSupplyAuctionTest:test_SettleAuction_WhenSettlingAtMidPriceMidSupply() (gas: 1892884) VariableSupplyAuctionTest:test_SupportsInterface() (gas: 6615) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferBatch() (gas: 81869) ERC1155TransferHelperTest:testFail_UserMustApproveTransferHelperToTransferSingle() (gas: 63978) diff --git a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol index f8f73c32..6fa65641 100644 --- a/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/IVariableSupplyAuction.sol @@ -7,6 +7,71 @@ pragma solidity 0.8.10; interface IVariableSupplyAuction { // + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /// @notice A given ERC-721 drop contract can have only 1 live auction at a time + error Auction_AlreadyLiveAuctionForDrop(); + + /// @notice Seller funds recipient cannot be zero address + error Auction_InvalidFundsRecipient(); + + /// @notice Auction does not exist + error Auction_AuctionDoesNotExist(); + + /// @notice Only seller can access this function + error Access_OnlySeller(); + + /// @notice Cannot cancel an auction with bids before settle phase + error Seller_CannotCancelAuctionWithBidsBeforeSettlePhase(); + + /// @notice Cannot cancel an auction during settle phase without first calculating outcomes + error Seller_CannotCancelAuctionDuringSettlePhaseWithoutCalculatingOutcomes(); + + /// @notice Cannot cancel an auction during settle phase with at least one viable price point + error Seller_CannotCancelAuctionWithViablePricePoint(); + + /// @notice Settling the auction only allowed during settle phase + error Seller_SettleAuctionOnlyAllowedDuringSettlePhase(); + + /// @notice Cannot settle an auction with no revealed bids + error Seller_CannotSettleWithNoRevealedBids(); + + /// @notice Cannot settle an auction at a price point that does not meet minimum viable revenue + error Seller_PricePointDoesNotMeetMinimumViableRevenue(); + + /// @notice Placing bids only allowed during bid phase + error Bidder_BidsOnlyAllowedDuringBidPhase(); + + /// @notice Cannot place more than 1 bid in any given auction + error Bidder_AlreadyPlacedBidInAuction(); + + /// @notice Valid bids must include some ether + error Bidder_BidsMustIncludeEther(); + + /// @notice Revealing bids only allowed during reveal phase + error Bidder_RevealsOnlyAllowedDuringRevealPhase(); + + /// @notice No bid placed by address in this auction + error Bidder_NoPlacedBidByAddressInThisAuction(); + + /// @notice Revealed bid cannot be greater than amount of ether sent with sealed bid + error Bidder_RevealedBidCannotBeGreaterThanEtherSentWithSealedBid(); + + /// @notice Revealed bid does not match sealed bid + error Bidder_RevealedBidDoesNotMatchSealedBid(); + + /// @notice Refunds only allowed during cleanup phase + error Bidder_RefundsOnlyAllowedDuringCleanupPhase(); + + /// @notice No refund available for this address in this auction + error Bidder_NoRefundAvailableForAuction(); + + /*////////////////////////////////////////////////////////////// + FUNCTIONS + //////////////////////////////////////////////////////////////*/ + /// @notice Creates a variable supply auction /// @param _tokenContract The address of the ERC-721 drop contract /// @param _minimumViableRevenue The minimum revenue the seller aims to generate in this auction -- diff --git a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol index 8d576f53..d1966f5d 100644 --- a/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol +++ b/contracts/modules/VariableSupplyAuction/VariableSupplyAuction.sol @@ -161,7 +161,7 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa event AuctionCreated(address indexed tokenContract, Auction auction); /// @notice Creates a variable supply auction - /// @dev Note that a given ERC-721 drop contract can have only one live auction at a time. + /// @dev Note that a given ERC-721 drop contract can have only 1 live auction at a time. /// @param _tokenContract The address of the ERC-721 drop contract /// @param _minimumViableRevenue The minimum revenue the seller aims to generate in this auction -- /// they can settle the auction below this value, but they cannot _not_ settle if the revenue @@ -181,10 +181,14 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa uint256 _settlePhaseDuration ) external nonReentrant { // Ensure the drop does not already have a live auction - require(auctionForDrop[_tokenContract].startTime == 0, "ONLY_ONE_LIVE_AUCTION_PER_DROP"); + if (auctionForDrop[_tokenContract].startTime > 0) { + revert Auction_AlreadyLiveAuctionForDrop(); + } // Ensure the funds recipient is specified - require(_sellerFundsRecipient != address(0), "INVALID_FUNDS_RECIPIENT"); + if (_sellerFundsRecipient == address(0)) { + revert Auction_InvalidFundsRecipient(); + } // Get the auction's storage pointer Auction storage auction = auctionForDrop[_tokenContract]; @@ -246,10 +250,14 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa Auction memory auction = auctionForDrop[_tokenContract]; // Ensure the auction exists - require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + if (auction.seller == address(0)) { + revert Auction_AuctionDoesNotExist(); + } // Ensure the caller is the seller - require(msg.sender == auction.seller, "ONLY_SELLER"); + if (msg.sender != auction.seller) { + revert Access_OnlySeller(); + } // Ensure that no bids have been placed in this auction yet, or, if in // settle phase, that no price points meet auction minimum viable revenue @@ -257,17 +265,23 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Get the settle price points uint96[] storage settlePricePoints = _settlePricePointsForAuction[_tokenContract]; - // Ensure seller has first considered the settle price points before attempting to cancel - require(settlePricePoints.length > 0, "CANNOT_CANCEL_AUCTION_BEFORE_CALCULATING_SETTLE_OPTIONS"); + // Ensure seller has first considered the possible settle outcomes before attempting to cancel + if (settlePricePoints.length == 0) { + revert Seller_CannotCancelAuctionDuringSettlePhaseWithoutCalculatingOutcomes(); + } // Ensure none of the price point outcomes meet minimum viable revenue for (uint256 i = 0; i < settlePricePoints.length; i++) { SettleOutcome storage settleOutcome = _settleOutcomesForPricePoint[_tokenContract][settlePricePoints[i]]; - require(settleOutcome.revenue < auction.minimumViableRevenue, "CANNOT_CANCEL_AUCTION_WITH_VIABLE_PRICE_POINT"); + if (settleOutcome.revenue >= auction.minimumViableRevenue) { + revert Seller_CannotCancelAuctionWithViablePricePoint(); + } } } else { - require(auction.totalBalance == 0, "CANNOT_CANCEL_AUCTION_WITH_BIDS"); + if (auction.totalBalance > 0) { + revert Seller_CannotCancelAuctionWithBidsBeforeSettlePhase(); + } } emit AuctionCanceled(_tokenContract, auction); @@ -324,16 +338,24 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa Auction storage auction = auctionForDrop[_tokenContract]; // Ensure the auction exists - require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + if (auction.seller == address(0)) { + revert Auction_AuctionDoesNotExist(); + } // Ensure the auction is in bid phase - require(block.timestamp < auction.endOfBidPhase, "BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); + if (block.timestamp >= auction.endOfBidPhase) { + revert Bidder_BidsOnlyAllowedDuringBidPhase(); + } // Ensure the bidder has not placed a bid in auction already - require(bidsForAuction[_tokenContract][msg.sender].bidderBalance == 0, "ALREADY_PLACED_BID_IN_AUCTION"); + if (bidsForAuction[_tokenContract][msg.sender].bidderBalance > 0) { + revert Bidder_AlreadyPlacedBidInAuction(); + } // Ensure the bid is valid - require(msg.value > 0 ether, "VALID_BIDS_MUST_INCLUDE_ETHER"); + if (msg.value == 0 ether) { + revert Bidder_BidsMustIncludeEther(); + } // Update the total balance for auction auction.totalBalance += uint96(msg.value); @@ -396,22 +418,32 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa Auction storage auction = auctionForDrop[_tokenContract]; // Ensure the auction exists - require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + if (auction.seller == address(0)) { + revert Auction_AuctionDoesNotExist(); + } // Ensure the auction is in reveal phase - require(block.timestamp >= auction.endOfBidPhase && block.timestamp < auction.endOfRevealPhase, "REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); + if (block.timestamp < auction.endOfBidPhase || block.timestamp >= auction.endOfRevealPhase) { + revert Bidder_RevealsOnlyAllowedDuringRevealPhase(); + } // Get the bid for the specified bidder Bid storage bid = bidsForAuction[_tokenContract][msg.sender]; // Ensure bidder placed bid in auction - require(bid.bidderBalance > 0 ether, "NO_PLACED_BID_FOUND_FOR_ADDRESS"); + if (bid.bidderBalance == 0) { + revert Bidder_NoPlacedBidByAddressInThisAuction(); + } // Ensure revealed bid amount is not greater than sent ether - require(_bidAmount <= bid.bidderBalance, "REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); + if (_bidAmount > bid.bidderBalance) { + revert Bidder_RevealedBidCannotBeGreaterThanEtherSentWithSealedBid(); + } // Ensure revealed bid matches sealed bid - require(keccak256(abi.encodePacked(_bidAmount, bytes(_salt))) == bid.commitmentHash, "REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); + if (keccak256(abi.encodePacked(_bidAmount, bytes(_salt))) != bid.commitmentHash) { + revert Bidder_RevealedBidDoesNotMatchSealedBid(); + } // Store the bidder _revealedBiddersForAuction[_tokenContract].push(msg.sender); @@ -513,16 +545,22 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa Auction storage auction = auctionForDrop[_tokenContract]; // Ensure the auction exists - require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + if (auction.seller == address(0)) { + revert Auction_AuctionDoesNotExist(); + } // Ensure the auction is in settle phase - require(block.timestamp >= auction.endOfRevealPhase && block.timestamp < auction.endOfSettlePhase, "SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + if (block.timestamp < auction.endOfRevealPhase || block.timestamp >= auction.endOfSettlePhase) { + revert Seller_SettleAuctionOnlyAllowedDuringSettlePhase(); + } // Get the revealed bidders for the auction address[] storage bidders = _revealedBiddersForAuction[_tokenContract]; // Ensure the auction has at least 1 revealed bid - require(bidders.length > 0, "NO_REVEALED_BIDS_TO_SETTLE_AUCTION"); + if (bidders.length == 0) { + revert Seller_CannotSettleWithNoRevealedBids(); + } // Get the settle outcome data structures uint96[] storage settlePricePoints = _settlePricePointsForAuction[_tokenContract]; @@ -598,6 +636,11 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Get the auction Auction storage auction = auctionForDrop[_tokenContract]; + // Ensure the caller is the seller + if (msg.sender != auction.seller) { + revert Access_OnlySeller(); + } + // Calculate settle outcomes, if not done yet (also includes check for auction existence) if (_settlePricePointsForAuction[_tokenContract].length == 0) { calculateSettleOutcomes(_tokenContract); @@ -606,8 +649,10 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa // Get the settle outcome at this price point SettleOutcome storage settleOutcome = _settleOutcomesForPricePoint[_tokenContract][_settlePricePoint]; - // Ensure that revenue meets minimum viable revenue - require(settleOutcome.revenue >= auction.minimumViableRevenue, "DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); + // Ensure that revenue meets minimum viable revenue + if (settleOutcome.revenue < auction.minimumViableRevenue) { + revert Seller_PricePointDoesNotMeetMinimumViableRevenue(); + } // Store the current total balance and final auction details auction.totalBalance -= settleOutcome.revenue; @@ -704,10 +749,14 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa Auction storage auction = auctionForDrop[_tokenContract]; // Ensure the auction exists - require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + if (auction.seller == address(0)) { + revert Auction_AuctionDoesNotExist(); + } // Ensure the auction is in cleanup phase - require(block.timestamp >= auction.endOfSettlePhase, "REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + if (block.timestamp < auction.endOfSettlePhase) { + revert Bidder_RefundsOnlyAllowedDuringCleanupPhase(); + } // Return the balance for the specified bidder return bidsForAuction[_tokenContract][msg.sender].bidderBalance; @@ -723,17 +772,23 @@ contract VariableSupplyAuction is IVariableSupplyAuction, ReentrancyGuard, FeePa Auction storage auction = auctionForDrop[_tokenContract]; // Ensure the auction exists - require(auction.seller != address(0), "AUCTION_DOES_NOT_EXIST"); + if (auction.seller == address(0)) { + revert Auction_AuctionDoesNotExist(); + } // Ensure the auction is in cleanup phase - require(block.timestamp >= auction.endOfSettlePhase, "REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + if (block.timestamp < auction.endOfSettlePhase) { + revert Bidder_RefundsOnlyAllowedDuringCleanupPhase(); + } // Get the balance for the specified bidder Bid storage bid = bidsForAuction[_tokenContract][msg.sender]; uint96 availableRefund = bid.bidderBalance; // Ensure bidder has a leftover balance - require(bid.revealedBidAmount > 0 && availableRefund > 0, "NO_REFUND_AVAILABLE"); + if (bid.revealedBidAmount == 0 || availableRefund == 0) { + revert Bidder_NoRefundAvailableForAuction(); + } // Clear bidder balance and update auction total balance bid.bidderBalance = 0; diff --git a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol index e4f89cb4..1db71505 100644 --- a/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol +++ b/contracts/test/modules/VariableSupplyAuction/VariableSupplyAuction.t.sol @@ -322,7 +322,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_CreateAuction_WhenDropHasLiveAuction() public setupBasicAuction { - vm.expectRevert("ONLY_ONE_LIVE_AUCTION_PER_DROP"); + vm.expectRevert(IVariableSupplyAuction.Auction_AlreadyLiveAuctionForDrop.selector); vm.prank(address(seller)); auctions.createAuction({ @@ -337,7 +337,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_CreateAuction_WhenDidNotSpecifySellerFundsRecipient() public { - vm.expectRevert("INVALID_FUNDS_RECIPIENT"); + vm.expectRevert(IVariableSupplyAuction.Auction_InvalidFundsRecipient.selector); vm.prank(address(seller)); auctions.createAuction({ @@ -462,14 +462,14 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_CancelAuction_WhenAuctionDoesNotExist() public { - vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + vm.expectRevert(IVariableSupplyAuction.Auction_AuctionDoesNotExist.selector); vm.prank(address(seller)); auctions.cancelAuction(address(drop)); } function testRevert_CancelAuction_WhenNotSeller() public setupBasicAuction { - vm.expectRevert("ONLY_SELLER"); + vm.expectRevert(IVariableSupplyAuction.Access_OnlySeller.selector); vm.prank(address(bidder1)); auctions.cancelAuction(address(drop)); @@ -498,7 +498,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days + 2 days); - vm.expectRevert("CANNOT_CANCEL_AUCTION_BEFORE_CALCULATING_SETTLE_OPTIONS"); + vm.expectRevert(IVariableSupplyAuction.Seller_CannotCancelAuctionDuringSettlePhaseWithoutCalculatingOutcomes.selector); vm.prank(address(seller)); auctions.cancelAuction(address(drop)); @@ -530,7 +530,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(seller)); auctions.calculateSettleOutcomes(address(drop)); - vm.expectRevert("CANNOT_CANCEL_AUCTION_WITH_VIABLE_PRICE_POINT"); + vm.expectRevert(IVariableSupplyAuction.Seller_CannotCancelAuctionWithViablePricePoint.selector); vm.prank(address(seller)); auctions.cancelAuction(address(drop)); @@ -541,7 +541,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); - vm.expectRevert("CANNOT_CANCEL_AUCTION_WITH_BIDS"); + vm.expectRevert(IVariableSupplyAuction.Seller_CannotCancelAuctionWithBidsBeforeSettlePhase.selector); vm.prank(address(seller)); auctions.cancelAuction(address(drop)); @@ -655,7 +655,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_PlaceBid_WhenAuctionDoesNotExist() public { - vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + vm.expectRevert(IVariableSupplyAuction.Auction_AuctionDoesNotExist.selector); bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); @@ -665,7 +665,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_PlaceBid_WhenAuctionInRevealPhase() public setupBasicAuction { vm.warp(TIME0 + 3 days); // reveal phase - vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_BidsOnlyAllowedDuringBidPhase.selector); bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); @@ -675,7 +675,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_PlaceBid_WhenAuctionInSettlePhase() public setupBasicAuction { vm.warp(TIME0 + 3 days + 2 days); // settle phase - vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_BidsOnlyAllowedDuringBidPhase.selector); bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); @@ -685,7 +685,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_PlaceBid_WhenAuctionInCleanupPhase() public setupBasicAuction { vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase - vm.expectRevert("BIDS_ONLY_ALLOWED_DURING_BID_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_BidsOnlyAllowedDuringBidPhase.selector); bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); @@ -697,14 +697,14 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); - vm.expectRevert("ALREADY_PLACED_BID_IN_AUCTION"); + vm.expectRevert(IVariableSupplyAuction.Bidder_AlreadyPlacedBidInAuction.selector); vm.prank(address(bidder1)); auctions.placeBid{value: 1 ether}(address(drop), commitment); } function testRevert_PlaceBid_WhenNoEtherIncluded() public setupBasicAuction { - vm.expectRevert("VALID_BIDS_MUST_INCLUDE_ETHER"); + vm.expectRevert(IVariableSupplyAuction.Bidder_BidsMustIncludeEther.selector); bytes32 commitment = _genSealedBid(1 ether, salt1); vm.prank(address(bidder1)); @@ -846,7 +846,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_RevealBid_WhenAuctionDoesNotExist() public { - vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + vm.expectRevert(IVariableSupplyAuction.Auction_AuctionDoesNotExist.selector); vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1 ether, salt1); @@ -857,7 +857,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 1.1 ether}(address(drop), commitment); - vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RevealsOnlyAllowedDuringRevealPhase.selector); vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1 ether, salt1); @@ -870,7 +870,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days + 2 days); // settle phase - vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RevealsOnlyAllowedDuringRevealPhase.selector); vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1 ether, salt1); @@ -883,7 +883,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase - vm.expectRevert("REVEALS_ONLY_ALLOWED_DURING_REVEAL_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RevealsOnlyAllowedDuringRevealPhase.selector); vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1 ether, salt1); @@ -892,7 +892,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_RevealBid_WhenNoCommittedBid() public setupBasicAuction { vm.warp(TIME0 + 3 days); - vm.expectRevert("NO_PLACED_BID_FOUND_FOR_ADDRESS"); + vm.expectRevert(IVariableSupplyAuction.Bidder_NoPlacedBidByAddressInThisAuction.selector); vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1.1 ether, salt1); @@ -907,7 +907,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days); - vm.expectRevert("REVEALED_BID_CANNOT_BE_GREATER_THAN_SENT_ETHER"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RevealedBidCannotBeGreaterThanEtherSentWithSealedBid.selector); vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1.1 ether, salt1); @@ -920,7 +920,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days); - vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RevealedBidDoesNotMatchSealedBid.selector); vm.prank(address(bidder1)); auctions.revealBid(address (drop), 0.9 ether, salt1); // wrong amount @@ -933,7 +933,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days); - vm.expectRevert("REVEALED_BID_DOES_NOT_MATCH_SEALED_BID"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RevealedBidDoesNotMatchSealedBid.selector); vm.prank(address(bidder1)); auctions.revealBid(address (drop), 1 ether, salt2); // wrong salt @@ -1023,14 +1023,14 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_CalculateSettleOutcomes_WhenAuctionDoesNotExist() public { - vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + vm.expectRevert(IVariableSupplyAuction.Auction_AuctionDoesNotExist.selector); vm.prank(address(seller)); auctions.calculateSettleOutcomes(address(drop)); } function testRevert_CalculateSettleOutcomes_WhenAuctionInBidPhase() public setupBasicAuction { - vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Seller_SettleAuctionOnlyAllowedDuringSettlePhase.selector); vm.prank(address(seller)); auctions.calculateSettleOutcomes(address(drop)); @@ -1039,7 +1039,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_CalculateSettleOutcomes_WhenAuctionInRevealPhase() public setupBasicAuction { vm.warp(TIME0 + 3 days); // reveal phase - vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Seller_SettleAuctionOnlyAllowedDuringSettlePhase.selector); vm.prank(address(seller)); auctions.calculateSettleOutcomes(address(drop)); @@ -1048,16 +1048,16 @@ contract VariableSupplyAuctionTest is Test { function testRevert_CalculateSettleOutcomes_WhenAuctionInCleanupPhase() public setupBasicAuction { vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase - vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Seller_SettleAuctionOnlyAllowedDuringSettlePhase.selector); vm.prank(address(seller)); auctions.calculateSettleOutcomes(address(drop)); } function testRevert_CalculateSettleOutcomes_WhenAuctionHasZeroRevealedBids() public setupBasicAuction { - vm.warp(TIME0 + 3 days + 2 days); + vm.warp(TIME0 + 3 days + 2 days); - vm.expectRevert("NO_REVEALED_BIDS_TO_SETTLE_AUCTION"); + vm.expectRevert(IVariableSupplyAuction.Seller_CannotSettleWithNoRevealedBids.selector); vm.prank(address(seller)); auctions.calculateSettleOutcomes(address(drop)); @@ -1409,15 +1409,23 @@ contract VariableSupplyAuctionTest is Test { assertEq(bidderBalance14, 2 ether); } - function testRevert_SettleAuction_WhenAuctionDoesNotExist() public { - vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + // TODO consider different ordering of checks + + // function testRevert_SettleAuction_WhenAuctionDoesNotExist() public { + // vm.expectRevert(IVariableSupplyAuction.Auction_AuctionDoesNotExist.selector); + + // vm.prank(address(seller)); + // auctions.settleAuction(address(drop), 1 ether); + // } + + function testRevert_SettleAuction_WhenNotSeller() public setupBasicAuction throughRevealPhaseComplex { + vm.expectRevert(IVariableSupplyAuction.Access_OnlySeller.selector); - vm.prank(address(seller)); auctions.settleAuction(address(drop), 1 ether); } function testRevert_SettleAuction_WhenAuctionInBidPhase() public setupBasicAuction { - vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Seller_SettleAuctionOnlyAllowedDuringSettlePhase.selector); vm.prank(address(seller)); auctions.settleAuction(address(drop), 2 ether); @@ -1426,7 +1434,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_SettleAuction_WhenAuctionInRevealPhase() public setupBasicAuction { vm.warp(TIME0 + 3 days); // reveal phase - vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Seller_SettleAuctionOnlyAllowedDuringSettlePhase.selector); vm.prank(address(seller)); auctions.settleAuction(address(drop), 2 ether); @@ -1435,7 +1443,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_SettleAuction_WhenAuctionInCleanupPhase() public setupBasicAuction { vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase - vm.expectRevert("SETTLE_ONLY_ALLOWED_DURING_SETTLE_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Seller_SettleAuctionOnlyAllowedDuringSettlePhase.selector); vm.prank(address(seller)); auctions.settleAuction(address(drop), 2 ether); @@ -1444,21 +1452,21 @@ contract VariableSupplyAuctionTest is Test { function testRevert_SettleAuction_WhenAuctionHasZeroRevealedBids() public setupBasicAuction { vm.warp(TIME0 + 3 days + 2 days); - vm.expectRevert("NO_REVEALED_BIDS_TO_SETTLE_AUCTION"); + vm.expectRevert(IVariableSupplyAuction.Seller_CannotSettleWithNoRevealedBids.selector); vm.prank(address(seller)); auctions.settleAuction(address(drop), 2 ether); } function testRevert_SettleAuction_WhenSettlingAtPricePointThatDoesNotMeetMinimumViableRevenue() public setupBasicAuction throughRevealPhaseComplex { - vm.expectRevert("DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); + vm.expectRevert(IVariableSupplyAuction.Seller_PricePointDoesNotMeetMinimumViableRevenue.selector); vm.prank(address(seller)); auctions.settleAuction(address(drop), 2 ether); // does not meet minimum viable revenue } function testRevert_SettleAuction_WhenSettlingAtInvalidPricePoint() public setupBasicAuction throughRevealPhaseComplex { - vm.expectRevert("DOES_NOT_MEET_MINIMUM_VIABLE_REVENUE"); + vm.expectRevert(IVariableSupplyAuction.Seller_PricePointDoesNotMeetMinimumViableRevenue.selector); vm.prank(address(seller)); auctions.settleAuction(address(drop), 3 ether); // non-existent settle price point @@ -1509,7 +1517,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_CheckAvailableRefund_WhenAuctionDoesNotExist() public { - vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + vm.expectRevert(IVariableSupplyAuction.Auction_AuctionDoesNotExist.selector); vm.prank(address(bidder1)); auctions.checkAvailableRefund(address(drop)); @@ -1519,7 +1527,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); - vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RefundsOnlyAllowedDuringCleanupPhase.selector); vm.prank(address(bidder1)); auctions.checkAvailableRefund(address(drop)); @@ -1534,7 +1542,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.revealBid(address(drop), 1 ether, salt1); - vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RefundsOnlyAllowedDuringCleanupPhase.selector); vm.prank(address(bidder1)); auctions.checkAvailableRefund(address(drop)); @@ -1551,7 +1559,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days + 2 days); // settle phase - vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RefundsOnlyAllowedDuringCleanupPhase.selector); vm.prank(address(bidder1)); auctions.checkAvailableRefund(address(drop)); @@ -1687,7 +1695,7 @@ contract VariableSupplyAuctionTest is Test { } function testRevert_ClaimRefund_WhenAuctionDoesNotExist() public { - vm.expectRevert("AUCTION_DOES_NOT_EXIST"); + vm.expectRevert(IVariableSupplyAuction.Auction_AuctionDoesNotExist.selector); vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); @@ -1697,7 +1705,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.placeBid{value: 2 ether}(address(drop), _genSealedBid(1 ether, salt1)); - vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RefundsOnlyAllowedDuringCleanupPhase.selector); vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); @@ -1712,7 +1720,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.revealBid(address(drop), 1 ether, salt1); - vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RefundsOnlyAllowedDuringCleanupPhase.selector); vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); @@ -1729,7 +1737,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days + 2 days); // settle phase - vm.expectRevert("REFUNDS_ONLY_ALLOWED_DURING_CLEANUP_PHASE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_RefundsOnlyAllowedDuringCleanupPhase.selector); vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); @@ -1738,7 +1746,7 @@ contract VariableSupplyAuctionTest is Test { function testRevert_ClaimRefund_WhenNoBidPlaced() public setupBasicAuctionWithLowMinimumViableRevenue { vm.warp(TIME0 + 3 days + 2 days + 1 days); - vm.expectRevert("NO_REFUND_AVAILABLE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_NoRefundAvailableForAuction.selector); vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); @@ -1764,7 +1772,7 @@ contract VariableSupplyAuctionTest is Test { vm.warp(TIME0 + 3 days + 2 days + 1 days); // cleanup phase - vm.expectRevert("NO_REFUND_AVAILABLE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_NoRefundAvailableForAuction.selector); vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); @@ -1789,7 +1797,7 @@ contract VariableSupplyAuctionTest is Test { vm.prank(address(bidder1)); auctions.claimRefund(address(drop)); - vm.expectRevert("NO_REFUND_AVAILABLE"); + vm.expectRevert(IVariableSupplyAuction.Bidder_NoRefundAvailableForAuction.selector); vm.prank(address(bidder1)); auctions.claimRefund(address(drop));