diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 125dd02c..db5c1adc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,8 +33,6 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly # Providing the SSH key to the checkout action does not work with our config. # It likely is because of the install script wiping the lib/ directory. diff --git a/deployments/.base-sepolia-1738161094.json b/deployments/.base-sepolia-1738161094.json new file mode 100644 index 00000000..c5eba581 --- /dev/null +++ b/deployments/.base-sepolia-1738161094.json @@ -0,0 +1,3 @@ +{ + "deployments.callbacks.BatchUniswapV3DirectToLiquidityWithAllocatedAllowlist": "0xEE4d06C1c96Ae19D1D77ac279881a639307E97E2" +} diff --git a/deployments/.base-v1.0.1.json b/deployments/.base-v1.0.1.json new file mode 100644 index 00000000..211b7c0f --- /dev/null +++ b/deployments/.base-v1.0.1.json @@ -0,0 +1,3 @@ +{ + "deployments.callbacks.BatchUniswapV3DirectToLiquidityWithAllocatedAllowlist": "0xEE25c5B81Ac47c97F4458c5b20916cE35B7638F7" +} diff --git a/script/deploy/Deploy.s.sol b/script/deploy/Deploy.s.sol index 3daf8570..2bb1cd32 100644 --- a/script/deploy/Deploy.s.sol +++ b/script/deploy/Deploy.s.sol @@ -22,6 +22,8 @@ import {GUniFactory} from "@g-uni-v1-core-0.9.9/GUniFactory.sol"; // Callbacks import {UniswapV2DirectToLiquidity} from "../../src/callbacks/liquidity/UniswapV2DTL.sol"; import {UniswapV3DirectToLiquidity} from "../../src/callbacks/liquidity/UniswapV3DTL.sol"; +import {UniswapV3DTLWithAllocatedAllowlist} from + "../../src/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist.sol"; import {CappedMerkleAllowlist} from "../../src/callbacks/allowlists/CappedMerkleAllowlist.sol"; import {MerkleAllowlist} from "../../src/callbacks/allowlists/MerkleAllowlist.sol"; import {TokenAllowlist} from "../../src/callbacks/allowlists/TokenAllowlist.sol"; @@ -286,11 +288,22 @@ contract Deploy is Script, WithDeploySequence, WithSalts { "UniswapV2Router.factory() does not match given Uniswap V2 factory address" ); + Callbacks.Permissions memory permissions = Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: false, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }); + // Get the salt bytes32 salt_ = _getSalt( sequenceName_, type(UniswapV2DirectToLiquidity).creationCode, - abi.encode(atomicAuctionHouse, uniswapV2Factory, uniswapV2Router) + abi.encode(atomicAuctionHouse, uniswapV2Factory, uniswapV2Router, permissions) ); // Revert if the salt is not set @@ -302,7 +315,7 @@ contract Deploy is Script, WithDeploySequence, WithSalts { vm.broadcast(); UniswapV2DirectToLiquidity cbAtomicUniswapV2Dtl = new UniswapV2DirectToLiquidity{ salt: salt_ - }(atomicAuctionHouse, uniswapV2Factory, uniswapV2Router); + }(atomicAuctionHouse, uniswapV2Factory, uniswapV2Router, permissions); console2.log(""); console2.log(" deployed at:", address(cbAtomicUniswapV2Dtl)); @@ -330,11 +343,22 @@ contract Deploy is Script, WithDeploySequence, WithSalts { "UniswapV2Router.factory() does not match given Uniswap V2 factory address" ); + Callbacks.Permissions memory permissions = Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: false, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }); + // Get the salt bytes32 salt_ = _getSalt( deploymentKey, type(UniswapV2DirectToLiquidity).creationCode, - abi.encode(batchAuctionHouse, uniswapV2Factory, uniswapV2Router) + abi.encode(batchAuctionHouse, uniswapV2Factory, uniswapV2Router, permissions) ); // Revert if the salt is not set @@ -345,7 +369,7 @@ contract Deploy is Script, WithDeploySequence, WithSalts { vm.broadcast(); UniswapV2DirectToLiquidity cbBatchUniswapV2Dtl = new UniswapV2DirectToLiquidity{salt: salt_}( - batchAuctionHouse, uniswapV2Factory, uniswapV2Router + batchAuctionHouse, uniswapV2Factory, uniswapV2Router, permissions ); console2.log(""); console2.log(" deployed at:", address(cbBatchUniswapV2Dtl)); @@ -375,11 +399,22 @@ contract Deploy is Script, WithDeploySequence, WithSalts { "GUniFactory.factory() does not match given Uniswap V3 factory address" ); + Callbacks.Permissions memory permissions = Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: false, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }); + // Get the salt bytes32 salt_ = _getSalt( deploymentKey, type(UniswapV3DirectToLiquidity).creationCode, - abi.encode(atomicAuctionHouse, uniswapV3Factory, gUniFactory) + abi.encode(atomicAuctionHouse, uniswapV3Factory, gUniFactory, permissions) ); // Revert if the salt is not set @@ -391,7 +426,7 @@ contract Deploy is Script, WithDeploySequence, WithSalts { vm.broadcast(); UniswapV3DirectToLiquidity cbAtomicUniswapV3Dtl = new UniswapV3DirectToLiquidity{ salt: salt_ - }(atomicAuctionHouse, uniswapV3Factory, gUniFactory); + }(atomicAuctionHouse, uniswapV3Factory, gUniFactory, permissions); console2.log(""); console2.log(" deployed at:", address(cbAtomicUniswapV3Dtl)); @@ -420,11 +455,22 @@ contract Deploy is Script, WithDeploySequence, WithSalts { "GUniFactory.factory() does not match given Uniswap V3 factory address" ); + Callbacks.Permissions memory permissions = Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: false, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }); + // Get the salt bytes32 salt_ = _getSalt( deploymentKey, type(UniswapV3DirectToLiquidity).creationCode, - abi.encode(batchAuctionHouse, uniswapV3Factory, gUniFactory) + abi.encode(batchAuctionHouse, uniswapV3Factory, gUniFactory, permissions) ); // Revert if the salt is not set @@ -435,7 +481,7 @@ contract Deploy is Script, WithDeploySequence, WithSalts { vm.broadcast(); UniswapV3DirectToLiquidity cbBatchUniswapV3Dtl = new UniswapV3DirectToLiquidity{salt: salt_}( - batchAuctionHouse, uniswapV3Factory, gUniFactory + batchAuctionHouse, uniswapV3Factory, gUniFactory, permissions ); console2.log(""); console2.log(" deployed at:", address(cbBatchUniswapV3Dtl)); @@ -443,6 +489,51 @@ contract Deploy is Script, WithDeploySequence, WithSalts { return (address(cbBatchUniswapV3Dtl), _PREFIX_CALLBACKS, deploymentKey); } + function deployBatchUniswapV3DirectToLiquidityWithAllocatedAllowlist( + string memory sequenceName_ + ) public returns (address, string memory, string memory) { + console2.log(""); + console2.log("Deploying UniswapV3DirectToLiquidityWithAllocatedAllowlist (Batch)"); + + // Get configuration variables + address batchAuctionHouse = _getAddressNotZero("deployments.BatchAuctionHouse"); + address uniswapV3Factory = _getEnvAddressOrOverride( + "constants.uniswapV3.factory", sequenceName_, "args.uniswapV3Factory" + ); + address gUniFactory = + _getEnvAddressOrOverride("constants.gUni.factory", sequenceName_, "args.gUniFactory"); + string memory deploymentKey = _getDeploymentKey(sequenceName_); + console2.log(" deploymentKey:", deploymentKey); + + // Check that the GUni factory and Uniswap V3 factory are consistent + require( + GUniFactory(gUniFactory).factory() == uniswapV3Factory, + "GUniFactory.factory() does not match given Uniswap V3 factory address" + ); + + // Get the salt + bytes32 salt_ = _getSalt( + deploymentKey, + type(UniswapV3DTLWithAllocatedAllowlist).creationCode, + abi.encode(batchAuctionHouse, uniswapV3Factory, gUniFactory) + ); + + // Revert if the salt is not set + require(salt_ != bytes32(0), "Salt not set"); + + // Deploy the module + console2.log(" salt:", vm.toString(salt_)); + + vm.broadcast(); + UniswapV3DTLWithAllocatedAllowlist cbBatchUniswapV3Dtl = new UniswapV3DTLWithAllocatedAllowlist{ + salt: salt_ + }(batchAuctionHouse, uniswapV3Factory, gUniFactory); + console2.log(""); + console2.log(" deployed at:", address(cbBatchUniswapV3Dtl)); + + return (address(cbBatchUniswapV3Dtl), _PREFIX_CALLBACKS, deploymentKey); + } + function deployAtomicCappedMerkleAllowlist( string memory sequenceName_ ) public returns (address, string memory, string memory) { diff --git a/script/deploy/sequences/uniswap-v3-allocated-dtl.json b/script/deploy/sequences/uniswap-v3-allocated-dtl.json new file mode 100644 index 00000000..c0258363 --- /dev/null +++ b/script/deploy/sequences/uniswap-v3-allocated-dtl.json @@ -0,0 +1,7 @@ +{ + "sequence": [ + { + "name": "BatchUniswapV3DirectToLiquidityWithAllocatedAllowlist" + } + ] +} diff --git a/script/env.json b/script/env.json index 02381313..2079a5df 100644 --- a/script/env.json +++ b/script/env.json @@ -68,7 +68,8 @@ "BatchMerkleAllowlist": "0x98c5c24eB3FFEFeCd1a666423978f7A030319A78", "BatchTokenAllowlist": "0x98a5d4827A57056d30df93C7Bd4Bc294cC6dC0b9", "BatchUniswapV2DirectToLiquidity": "0xE6F93df14cB554737A26acd2aB5fEf649921D7F2", - "BatchUniswapV3DirectToLiquidity": "0xE64d6e058dD5F76CCc8566c07b994090a24CCB75" + "BatchUniswapV3DirectToLiquidity": "0xE64d6e058dD5F76CCc8566c07b994090a24CCB75", + "BatchUniswapV3DirectToLiquidityWithAllocatedAllowlist": "0xEE25c5B81Ac47c97F4458c5b20916cE35B7638F7" } } }, @@ -92,7 +93,8 @@ "BatchMerkleAllowlist": "0x98d64E00D9d6550913E73C940Ff476Cf1723d834", "BatchTokenAllowlist": "0x9801e45362a2bb7C9F22486CC3F5cA9224e9CC55", "BatchUniswapV2DirectToLiquidity": "0xE6546c03B1b9DFC4238f0A2923FdefD5E4af7659", - "BatchUniswapV3DirectToLiquidity": "0xE68b21C071534781BC4c40E6BF1bCFC23638fF4B" + "BatchUniswapV3DirectToLiquidity": "0xE68b21C071534781BC4c40E6BF1bCFC23638fF4B", + "BatchUniswapV3DirectToLiquidityWithAllocatedAllowlist": "0xEE4d06C1c96Ae19D1D77ac279881a639307E97E2" } } }, diff --git a/script/salts/dtl-uniswap/UniswapDTLSalts.s.sol b/script/salts/dtl-uniswap/UniswapDTLSalts.s.sol index a2452eb9..a33e0a6e 100644 --- a/script/salts/dtl-uniswap/UniswapDTLSalts.s.sol +++ b/script/salts/dtl-uniswap/UniswapDTLSalts.s.sol @@ -9,6 +9,8 @@ import {WithDeploySequence} from "../../deploy/WithDeploySequence.s.sol"; // Uniswap import {UniswapV2DirectToLiquidity} from "../../../src/callbacks/liquidity/UniswapV2DTL.sol"; import {UniswapV3DirectToLiquidity} from "../../../src/callbacks/liquidity/UniswapV3DTL.sol"; +import {UniswapV3DTLWithAllocatedAllowlist} from + "../../../src/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist.sol"; contract UniswapDTLSalts is Script, WithDeploySequence, WithSalts { string internal constant _ADDRESS_PREFIX = "E6"; @@ -67,6 +69,17 @@ contract UniswapDTLSalts is Script, WithDeploySequence, WithSalts { _generateV3(sequenceName, auctionHouse, deploymentKey); } + // Batch Uniswap V3 with Allocated Allowlist + else if ( + keccak256(abi.encodePacked(sequenceName)) + == keccak256( + abi.encodePacked("BatchUniswapV3DirectToLiquidityWithAllocatedAllowlist") + ) + ) { + address auctionHouse = _envAddressNotZero("deployments.BatchAuctionHouse"); + + _generateV3WithAllocatedAllowlist(sequenceName, auctionHouse, deploymentKey); + } // Something else else { console2.log(" Skipping unknown sequence: %s", sequenceName); @@ -116,4 +129,26 @@ contract UniswapDTLSalts is Script, WithDeploySequence, WithSalts { ); _setSalt(bytecodePath, _ADDRESS_PREFIX, deploymentKey_, bytecodeHash); } + + function _generateV3WithAllocatedAllowlist( + string memory sequenceName_, + address auctionHouse_, + string memory deploymentKey_ + ) internal { + // Get input variables or overrides + address envUniswapV3Factory = _getEnvAddressOrOverride( + "constants.uniswapV3.factory", sequenceName_, "args.uniswapV3Factory" + ); + address envGUniFactory = + _getEnvAddressOrOverride("constants.gUni.factory", sequenceName_, "args.gUniFactory"); + + // Calculate salt for the UniswapV2DirectToLiquidity + bytes memory contractCode = type(UniswapV3DTLWithAllocatedAllowlist).creationCode; + (string memory bytecodePath, bytes32 bytecodeHash) = _writeBytecode( + deploymentKey_, + contractCode, + abi.encode(auctionHouse_, envUniswapV3Factory, envGUniFactory) + ); + _setSalt(bytecodePath, "EE", deploymentKey_, bytecodeHash); + } } diff --git a/script/salts/salts.json b/script/salts/salts.json index 3c5f5059..e48204b2 100644 --- a/script/salts/salts.json +++ b/script/salts/salts.json @@ -36,6 +36,11 @@ "BatchUniswapV2DirectToLiquidity": { "0x5425c9b9254dc9126002dd9f7b17b6ff643b79c5aed0ed2f116b70af3e163500": "0x262dcec529815acf06c81b7e0845ece78296140a248524da0cdd182fa8133ee9" }, + "BatchUniswapV3DirectToLiquidityWithAllocatedAllowlist": { + "0x181fdad91467a532ae9ebdc80915f54694efba1225f67606039c1b85f01fd483": "0x56fa0a987993fead1434db033954e96847b8fa412ba170eb7b3e3148dbf27e15", + "0x6daa621e22611091db5b051a7dbfb5c0f0ec3bf75454115e2fb4a9079c66289b": "0x3b013f20c0e68b196c5c4cd04e299b9faa4b2d71d25ac31bd70cc0d7a116e688", + "0xfa068f11166e923192737f1de5d84c5c9f17a2cb504fdff8ab2bae2703c1dc55": "0xef23d7a54ac5936b84aad715bec0f1b580f0b35fee8236ddcf010491cb621399" + }, "CappedMerkleAllowlist": { "0x0249dded8310d17581f166a526ea9ded65b81112ccac0ee8a144e9770d2b8432": "0xf5746fa34aeff9866dc5ec58712f7f47045aea15db39a0aabf4dadc9d35c8854", "0x0ae7777d88dd21a8b9ca3dd9212307295a83aed438fef9dad0b62d57fbaf1025": "0x1de5ae5b126bd2cee8eb4f083080f5c30baa692580cf823fa5f382a7bfc70ac5", @@ -116,13 +121,16 @@ "0x71369782a004f899a09f30d15d07aea6e386231640ec5a67bf3f0ab2a7a335fe": "0xd546a781322897bd5be8fe859ddca634489f023edcc804545f0dc039f046cc2c" }, "Test_UniswapV2DirectToLiquidity": { - "0xf2b190e690a84e7be857af56398803943b855c7d589c61a9bb7795689a1f4086": "0x1446d0144b25261ce97bd9dfb74f095481741349546de789031f8189b39b687f" + "0x820db70374c2ffd13fd66deb7afba776b3a403561c28ce10994cae4694e96ee0": "0xb8f5361234f91908bda276227b012d5bb03026757344a7af40459a72ab0722f4" }, "Test_UniswapV2Router": { "0x82202d1015ce048c53cd194a4407a02c152ee834d4b503f3dbd41d3799ee0dbb": "0x67226a40a6c0b7968e2cf7c37240a16ea7f3deb588c2421d67ce10cb4494de7e" }, + "Test_UniswapV3DTLWithAllocatedAllowlist": { + "0xbf5ebfb1c6601a4c83bbbe4701337ff6b3e558cb0221311247283d935a83a8fc": "0xa16fae553d108a500f1bb1c5a5b24764bd9e62d7b166452f6d624fabca71f8c5" + }, "Test_UniswapV3DirectToLiquidity": { - "0x74a1f48af0886eb246c7a5ccd3fddc432320f2a6eab09cdeb998e4ea545bf8b4": "0x8d2eb44a9a6a44de1d0e1ccb0b9999633aaaf2bcf2b7b6671ec32b357939e645" + "0xa7b0391bbf2ed03a6ff5ee2b487f9bd689c7c030e44ca4c25de20346bc766a92": "0x46f99bc00335e71bcbeaaa0f83a5aca76278fda55cc32647f208ac5eb334166b" }, "Test_UniswapV3Factory": { "0xa56c03532e32af77794962a9477657f2caf39ad7070587c208fbf10ad705cf85": "0x19b9379f4d7172b3d99ce2f2bef5f3be5de1a581ed516956adcdf2f887aed861" diff --git a/script/salts/test/TestSalts.s.sol b/script/salts/test/TestSalts.s.sol index 37b0ee8b..104345a7 100644 --- a/script/salts/test/TestSalts.s.sol +++ b/script/salts/test/TestSalts.s.sol @@ -34,6 +34,8 @@ import {BALwithCappedAllowlist} from "../../../src/callbacks/liquidity/BaselineV2/BALwithCappedAllowlist.sol"; import {BALwithTokenAllowlist} from "../../../src/callbacks/liquidity/BaselineV2/BALwithTokenAllowlist.sol"; +import {UniswapV3DTLWithAllocatedAllowlist} from + "../../../src/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist.sol"; contract TestSalts is Script, WithEnvironment, Permit2User, WithSalts, TestConstants { string internal constant _CAPPED_MERKLE_ALLOWLIST = "CappedMerkleAllowlist"; @@ -193,7 +195,21 @@ contract TestSalts is Script, WithEnvironment, Permit2User, WithSalts, TestConst } function generateUniswapV2DirectToLiquidity() public { - bytes memory args = abi.encode(_AUCTION_HOUSE, _UNISWAP_V2_FACTORY, _UNISWAP_V2_ROUTER); + bytes memory args = abi.encode( + _AUCTION_HOUSE, + _UNISWAP_V2_FACTORY, + _UNISWAP_V2_ROUTER, + Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: false, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }) + ); bytes memory contractCode = type(UniswapV2DirectToLiquidity).creationCode; (string memory bytecodePath, bytes32 bytecodeHash) = _writeBytecode("UniswapV2DirectToLiquidity", contractCode, args); @@ -201,13 +217,35 @@ contract TestSalts is Script, WithEnvironment, Permit2User, WithSalts, TestConst } function generateUniswapV3DirectToLiquidity() public { - bytes memory args = abi.encode(_AUCTION_HOUSE, _UNISWAP_V3_FACTORY, _GUNI_FACTORY); + bytes memory args = abi.encode( + _AUCTION_HOUSE, + _UNISWAP_V3_FACTORY, + _GUNI_FACTORY, + Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: false, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }) + ); bytes memory contractCode = type(UniswapV3DirectToLiquidity).creationCode; (string memory bytecodePath, bytes32 bytecodeHash) = _writeBytecode("UniswapV3DirectToLiquidity", contractCode, args); _setTestSalt(bytecodePath, "E6", "UniswapV3DirectToLiquidity", bytecodeHash); } + function generateUniswapV3DTLWithAllocatedAllowlist() public { + bytes memory args = abi.encode(_AUCTION_HOUSE, _UNISWAP_V3_FACTORY, _GUNI_FACTORY); + bytes memory contractCode = type(UniswapV3DTLWithAllocatedAllowlist).creationCode; + (string memory bytecodePath, bytes32 bytecodeHash) = + _writeBytecode("UniswapV3DTLWithAllocatedAllowlist", contractCode, args); + _setTestSalt(bytecodePath, "EE", "UniswapV3DTLWithAllocatedAllowlist", bytecodeHash); + } + function generateGUniFactory() public { // Generate a salt for a GUniFactory bytes memory args = abi.encode(_UNISWAP_V3_FACTORY); diff --git a/src/callbacks/liquidity/BaseDTL.sol b/src/callbacks/liquidity/BaseDTL.sol index 40689d7b..95d253a0 100644 --- a/src/callbacks/liquidity/BaseDTL.sol +++ b/src/callbacks/liquidity/BaseDTL.sol @@ -94,22 +94,9 @@ abstract contract BaseDirectToLiquidity is BaseCallback { // ========== CONSTRUCTOR ========== // constructor( - address auctionHouse_ - ) - BaseCallback( - auctionHouse_, - Callbacks.Permissions({ - onCreate: true, - onCancel: true, - onCurate: true, - onPurchase: false, - onBid: false, - onSettle: true, - receiveQuoteTokens: true, - sendBaseTokens: false - }) - ) - {} + address auctionHouse_, + Callbacks.Permissions memory permissions_ + ) BaseCallback(auctionHouse_, permissions_) {} // ========== CALLBACK FUNCTIONS ========== // @@ -232,7 +219,7 @@ abstract contract BaseDirectToLiquidity is BaseCallback { /// - The lot has already been completed /// /// @param lotId_ The lot ID - function _onCancel(uint96 lotId_, uint256, bool, bytes calldata) internal override { + function _onCancel(uint96 lotId_, uint256, bool, bytes calldata) internal virtual override { // Check that the lot is active if (!lotConfiguration[lotId_].active) { revert Callback_AlreadyComplete(); @@ -258,7 +245,7 @@ abstract contract BaseDirectToLiquidity is BaseCallback { uint256 curatorPayout_, bool, bytes calldata - ) internal override { + ) internal virtual override { // Check that the lot is active if (!lotConfiguration[lotId_].active) { revert Callback_AlreadyComplete(); @@ -278,14 +265,14 @@ abstract contract BaseDirectToLiquidity is BaseCallback { uint256, bool, bytes calldata - ) internal pure override { + ) internal virtual override { // Not implemented revert Callback_NotImplemented(); } /// @notice Callback for a bid /// @dev Not implemented - function _onBid(uint96, uint64, address, uint256, bytes calldata) internal pure override { + function _onBid(uint96, uint64, address, uint256, bytes calldata) internal virtual override { // Not implemented revert Callback_NotImplemented(); } diff --git a/src/callbacks/liquidity/IUniswapV3DTLWithAllocatedAllowlist.sol b/src/callbacks/liquidity/IUniswapV3DTLWithAllocatedAllowlist.sol new file mode 100644 index 00000000..2fafaa82 --- /dev/null +++ b/src/callbacks/liquidity/IUniswapV3DTLWithAllocatedAllowlist.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +interface IUniswapV3DTLWithAllocatedAllowlist { + // ========== ERRORS ========== // + + /// @notice Error message when the bid amount exceeds the limit assigned to a buyer + error Callback_ExceedsLimit(); + + /// @notice Error message when the callback state does not support the action + error Callback_InvalidState(); + + // ========== EVENTS ========== // + + /// @notice Emitted when the merkle root is set + event MerkleRootSet(uint96 lotId, bytes32 merkleRoot); + + // ========== ADMIN ========== // + + /// @notice Sets the merkle root for the allowlist + /// This function can be called by the seller to update the merkle root after `onCreate()`. + /// @dev This function can only be called by the seller + /// + /// @param lotId_ The lot ID + /// @param merkleRoot_ The merkle root + function setMerkleRoot(uint96 lotId_, bytes32 merkleRoot_) external; +} diff --git a/src/callbacks/liquidity/UniswapV2DTL.sol b/src/callbacks/liquidity/UniswapV2DTL.sol index 5ebaf33e..f0e8cead 100644 --- a/src/callbacks/liquidity/UniswapV2DTL.sol +++ b/src/callbacks/liquidity/UniswapV2DTL.sol @@ -13,6 +13,7 @@ import {IUniswapV2Router02} from "@uniswap-v2-periphery-1.0.1/interfaces/IUniswa // Callbacks import {BaseDirectToLiquidity} from "./BaseDTL.sol"; +import {Callbacks} from "@axis-core-1.0.4/lib/Callbacks.sol"; /// @title UniswapV2DirectToLiquidity /// @notice This Callback contract deposits the proceeds from a batch auction into a Uniswap V2 pool @@ -54,8 +55,9 @@ contract UniswapV2DirectToLiquidity is BaseDirectToLiquidity { constructor( address auctionHouse_, address uniswapV2Factory_, - address uniswapV2Router_ - ) BaseDirectToLiquidity(auctionHouse_) { + address uniswapV2Router_, + Callbacks.Permissions memory permissions_ + ) BaseDirectToLiquidity(auctionHouse_, permissions_) { if (uniswapV2Factory_ == address(0)) { revert Callback_Params_InvalidAddress(); } diff --git a/src/callbacks/liquidity/UniswapV3DTL.sol b/src/callbacks/liquidity/UniswapV3DTL.sol index 9ef7a4d8..57b18905 100644 --- a/src/callbacks/liquidity/UniswapV3DTL.sol +++ b/src/callbacks/liquidity/UniswapV3DTL.sol @@ -19,6 +19,7 @@ import {GUniPool} from "@g-uni-v1-core-0.9.9/GUniPool.sol"; // Callbacks import {BaseDirectToLiquidity} from "./BaseDTL.sol"; +import {Callbacks} from "@axis-core-1.0.4/lib/Callbacks.sol"; /// @title UniswapV3DirectToLiquidity /// @notice This Callback contract deposits the proceeds from a batch auction into a Uniswap V3 pool @@ -69,11 +70,22 @@ contract UniswapV3DirectToLiquidity is BaseDirectToLiquidity { // ========== CONSTRUCTOR ========== // + // Default permissions: + // onCreate: true + // onCancel: true + // onCurate: true + // onPurchase: false + // onBid: false + // onSettle: true + // receiveQuoteTokens: true + // sendBaseTokens: false + constructor( address auctionHouse_, address uniV3Factory_, - address gUniFactory_ - ) BaseDirectToLiquidity(auctionHouse_) { + address gUniFactory_, + Callbacks.Permissions memory permissions_ + ) BaseDirectToLiquidity(auctionHouse_, permissions_) { if (uniV3Factory_ == address(0)) { revert Callback_Params_InvalidAddress(); } diff --git a/src/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist.sol b/src/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist.sol new file mode 100644 index 00000000..4128f336 --- /dev/null +++ b/src/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {MerkleProof} from "@openzeppelin-contracts-4.9.2/utils/cryptography/MerkleProof.sol"; + +import {UniswapV3DirectToLiquidity} from "./UniswapV3DTL.sol"; +import {BaseDirectToLiquidity} from "./BaseDTL.sol"; +import {Callbacks} from "@axis-core-1.0.4/lib/Callbacks.sol"; +import {IUniswapV3DTLWithAllocatedAllowlist} from "./IUniswapV3DTLWithAllocatedAllowlist.sol"; + +/// @notice Allocated allowlist version of the Uniswap V3 Direct To Liquidity callback. +/// @notice This version allows for each address in the Merkle tree to have a per-address amount of quote tokens they can spend. +/// @dev The merkle tree is expected to have both an address and an amount of quote tokens they can spend in each leaf. +contract UniswapV3DTLWithAllocatedAllowlist is + IUniswapV3DTLWithAllocatedAllowlist, + UniswapV3DirectToLiquidity +{ + // ========== STATE VARIABLES ========== // + + /// @notice The seller address for each lot + mapping(uint96 => address) public lotSeller; + + /// @notice The root of the merkle tree that represents the allowlist + /// @dev The merkle tree should adhere to the format specified in the OpenZeppelin MerkleProof library at https://github.com/OpenZeppelin/merkle-tree + /// In particular, leaf values (such as `(address)` or `(address,uint256)`) should be double-hashed. + mapping(uint96 => bytes32) public lotMerkleRoot; + + /// @notice Tracks the cumulative amount spent by a buyer + mapping(uint96 => mapping(address => uint256)) public lotBuyerSpent; + + // ========== CONSTRUCTOR ========== // + + // PERMISSIONS + // onCreate: true + // onCancel: true + // onCurate: true + // onPurchase: false + // onBid: true + // onSettle: true + // receiveQuoteTokens: true + // sendBaseTokens: false + // Contract prefix should be: 11101110 = 0xEE + + constructor( + address auctionHouse_, + address uniV3Factory_, + address gUniFactory_ + ) + UniswapV3DirectToLiquidity( + auctionHouse_, + uniV3Factory_, + gUniFactory_, + Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: true, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }) + ) + {} + + // ========== CALLBACKS ========== // + + /// @inheritdoc BaseDirectToLiquidity + /// @dev This function performs the following: + /// - Stores the seller address + /// - Passes the remaining parameters to the UniswapV3DTL implementation + /// + /// Due to the way that the callback data is structured, the merkle root cannot be passed in as part of the callback data. Instead, the seller must call `setMerkleRoot()` after `onCreate()` to set the merkle root. + function __onCreate( + uint96 lotId_, + address seller_, + address baseToken_, + address quoteToken_, + uint256 capacity_, + bool prefund_, + bytes calldata callbackData_ + ) internal virtual override { + // Store the seller address + lotSeller[lotId_] = seller_; + + // Pass to the UniswapV3DTL implementation + super.__onCreate( + lotId_, seller_, baseToken_, quoteToken_, capacity_, prefund_, callbackData_ + ); + } + + /// @inheritdoc BaseDirectToLiquidity + /// @dev This function will revert if: + /// - The callback data is invalid + /// - The bid amount exceeds the allocated amount for the buyer + /// - The merkle root for the auction has not been set by the seller + /// + /// @param callbackData_ abi-encoded data: (bytes32[], uint256) representing the merkle proof and allocated amount + function _onBid( + uint96 lotId_, + uint64, + address buyer_, + uint256 amount_, + bytes calldata callbackData_ + ) internal override { + // Validate that the merkle root has been set + if (lotMerkleRoot[lotId_] == bytes32(0)) { + revert Callback_InvalidState(); + } + + // Validate that the buyer is allowed to participate + uint256 allocatedAmount = _canParticipate(lotId_, buyer_, callbackData_); + + // Validate that the buyer can buy the amount + _canBuy(lotId_, buyer_, amount_, allocatedAmount); + } + + // ========== INTERNAL FUNCTIONS ========== // + + /// @dev The buyer must provide the proof and their total allocated amount in the callback data for this to succeed. + function _canParticipate( + uint96 lotId_, + address buyer_, + bytes calldata callbackData_ + ) internal view returns (uint256) { + // Decode the merkle proof from the callback data + (bytes32[] memory proof, uint256 allocatedAmount) = + abi.decode(callbackData_, (bytes32[], uint256)); + + // Get the leaf for the buyer + bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(buyer_, allocatedAmount)))); + + // Validate the merkle proof + if (!MerkleProof.verify(proof, lotMerkleRoot[lotId_], leaf)) { + revert Callback_NotAuthorized(); + } + + // Return the allocated amount for the buyer + return allocatedAmount; + } + + function _canBuy( + uint96 lotId_, + address buyer_, + uint256 amount_, + uint256 allocatedAmount_ + ) internal { + // Check if the buyer has already spent their limit + if (lotBuyerSpent[lotId_][buyer_] + amount_ > allocatedAmount_) { + revert Callback_ExceedsLimit(); + } + + // Update the buyer spent amount + lotBuyerSpent[lotId_][buyer_] += amount_; + } + + // ========== ADMIN FUNCTIONS ========== // + + /// @inheritdoc IUniswapV3DTLWithAllocatedAllowlist + /// @dev This function performs the following: + /// - Performs validation + /// - Sets the merkle root + /// - Emits a MerkleRootSet event + /// + /// This function reverts if: + /// - The auction has not been registered + /// - The auction has been completed + /// - The caller is not the seller + function setMerkleRoot(uint96 lotId_, bytes32 merkleRoot_) external override { + DTLConfiguration memory lotConfig = lotConfiguration[lotId_]; + + // Validate that onCreate has been called for this lot + if (lotConfig.recipient == address(0)) { + revert Callback_InvalidState(); + } + + // Validate that the auction is active + if (lotConfig.active == false) { + revert Callback_AlreadyComplete(); + } + + // Validate that the caller is the seller + if (msg.sender != lotSeller[lotId_]) { + revert Callback_NotAuthorized(); + } + + lotMerkleRoot[lotId_] = merkleRoot_; + + emit MerkleRootSet(lotId_, merkleRoot_); + } +} diff --git a/test/callbacks/liquidity/UniswapV2DTL/UniswapV2DTLTest.sol b/test/callbacks/liquidity/UniswapV2DTL/UniswapV2DTLTest.sol index 87f3accb..1331f0b6 100644 --- a/test/callbacks/liquidity/UniswapV2DTL/UniswapV2DTLTest.sol +++ b/test/callbacks/liquidity/UniswapV2DTL/UniswapV2DTLTest.sol @@ -128,9 +128,21 @@ abstract contract UniswapV2DirectToLiquidityTest is Test, Permit2User, WithSalts } modifier givenCallbackIsCreated() { + Callbacks.Permissions memory permissions = Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: false, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }); + // Get the salt - bytes memory args = - abi.encode(address(_auctionHouse), address(_uniV2Factory), address(_uniV2Router)); + bytes memory args = abi.encode( + address(_auctionHouse), address(_uniV2Factory), address(_uniV2Router), permissions + ); bytes32 salt = _getTestSalt( "UniswapV2DirectToLiquidity", type(UniswapV2DirectToLiquidity).creationCode, args ); @@ -139,7 +151,7 @@ abstract contract UniswapV2DirectToLiquidityTest is Test, Permit2User, WithSalts // Source: https://github.com/foundry-rs/foundry/issues/6402 vm.startBroadcast(); _dtl = new UniswapV2DirectToLiquidity{salt: salt}( - address(_auctionHouse), address(_uniV2Factory), address(_uniV2Router) + address(_auctionHouse), address(_uniV2Factory), address(_uniV2Router), permissions ); vm.stopBroadcast(); diff --git a/test/callbacks/liquidity/UniswapV2DTL/onSettle.t.sol b/test/callbacks/liquidity/UniswapV2DTL/onSettle.t.sol index 63c535cd..1b480d07 100644 --- a/test/callbacks/liquidity/UniswapV2DTL/onSettle.t.sol +++ b/test/callbacks/liquidity/UniswapV2DTL/onSettle.t.sol @@ -343,645 +343,645 @@ contract UniswapV2DirectToLiquidityOnSettleTest is UniswapV2DirectToLiquidityTes _assertApprovals(); } - function test_givenDonation_givenAuctionPriceGreaterThanOne_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDifferentQuoteTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenQuoteTokenDecimals(17) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenLowQuoteTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenQuoteTokenDecimals(6) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 1e24); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenAuctionPriceGreaterThanOne_givenDifferentBaseTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenBaseTokenDecimals(17) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDifferentBaseTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenBaseTokenDecimals(17) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDecimalPrice_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenOnCreate - givenPoolIsCreated - setCallbackParameters(15e18, _REFUND) // 1.5e18 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDecimalPrice_givenDifferentQuoteTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenQuoteTokenDecimals(17) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(15e18, _REFUND) // 1.5e17 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDecimalPrice_givenLowQuoteTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenQuoteTokenDecimals(6) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(15e18, _REFUND) // 1.5e6 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 1e24); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDecimalPrice_givenDifferentBaseTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenBaseTokenDecimals(17) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(15e18, _REFUND) // 1.5e18 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenAuctionPriceOne_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_LOT_CAPACITY, 0) // Price = 1 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceOne_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_LOT_CAPACITY, 0) // Price = 1 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceOne_givenDifferentQuoteTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenQuoteTokenDecimals(17) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_LOT_CAPACITY, 0) - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceOne_givenLowQuoteTokenDecimals_reverts() - public - givenCallbackIsCreated - givenQuoteTokenDecimals(6) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_LOT_CAPACITY, 0) - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // This is a demonstration that a ridiculous quantity of quote tokens will cause - // the donation mitigation functionality to revert - uint256 donatedQuoteTokens = 1e24; - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Expect revert - vm.expectRevert("UniswapV2: K"); - - // Callback - _performOnSettle(); - } - - function test_givenDonation_givenSync_givenAuctionPriceOne_givenDifferentBaseTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenBaseTokenDecimals(17) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_LOT_CAPACITY, 0) - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenAuctionPriceLessThanOne_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS_PRICE_LESS_THAN_ONE, 0) // Price = 0.5 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceLessThanOne_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS_PRICE_LESS_THAN_ONE, 0) // Price = 0.5 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceLessThanOne_givenDifferentQuoteTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenQuoteTokenDecimals(17) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS_PRICE_LESS_THAN_ONE, 0) // Price = 0.5 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } - - function test_givenDonation_givenSync_givenAuctionPriceLessThanOne_givenDifferentBaseTokenDecimals_fuzz( - uint256 donatedQuoteTokens_ - ) - public - givenCallbackIsCreated - givenBaseTokenDecimals(17) - givenOnCreate - givenPoolIsCreated - setCallbackParameters(_PROCEEDS_PRICE_LESS_THAN_ONE, 0) // Price = 0.5 - givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) - givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) - givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) - { - // Donation amount could be more or less than the auction price - uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); - _quoteTokensDonated += donatedQuoteTokens; - - // Donate to the pool - _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); - - // Sync - _syncPool(); - - // Callback - _performOnSettle(); - - // Assertions - _assertLpTokenBalance(); - _assertLpUnderlyingBalances(); - _assertVestingTokenBalance(); - _assertQuoteTokenBalance(); - _assertBaseTokenBalance(); - _assertApprovals(); - } + // function test_givenDonation_givenAuctionPriceGreaterThanOne_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDifferentQuoteTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenQuoteTokenDecimals(17) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenLowQuoteTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenQuoteTokenDecimals(6) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 1e24); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenAuctionPriceGreaterThanOne_givenDifferentBaseTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenBaseTokenDecimals(17) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDifferentBaseTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenBaseTokenDecimals(17) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS, _REFUND) // Price is 2 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDecimalPrice_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(15e18, _REFUND) // 1.5e18 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDecimalPrice_givenDifferentQuoteTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenQuoteTokenDecimals(17) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(15e18, _REFUND) // 1.5e17 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDecimalPrice_givenLowQuoteTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenQuoteTokenDecimals(6) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(15e18, _REFUND) // 1.5e6 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 1e24); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceGreaterThanOne_givenDecimalPrice_givenDifferentBaseTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenBaseTokenDecimals(17) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(15e18, _REFUND) // 1.5e18 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenAuctionPriceOne_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_LOT_CAPACITY, 0) // Price = 1 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceOne_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_LOT_CAPACITY, 0) // Price = 1 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceOne_givenDifferentQuoteTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenQuoteTokenDecimals(17) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_LOT_CAPACITY, 0) + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceOne_givenLowQuoteTokenDecimals_reverts() + // public + // givenCallbackIsCreated + // givenQuoteTokenDecimals(6) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_LOT_CAPACITY, 0) + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // This is a demonstration that a ridiculous quantity of quote tokens will cause + // // the donation mitigation functionality to revert + // uint256 donatedQuoteTokens = 1e24; + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Expect revert + // vm.expectRevert("UniswapV2: K"); + + // // Callback + // _performOnSettle(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceOne_givenDifferentBaseTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenBaseTokenDecimals(17) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_LOT_CAPACITY, 0) + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenAuctionPriceLessThanOne_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS_PRICE_LESS_THAN_ONE, 0) // Price = 0.5 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceLessThanOne_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS_PRICE_LESS_THAN_ONE, 0) // Price = 0.5 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceLessThanOne_givenDifferentQuoteTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenQuoteTokenDecimals(17) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS_PRICE_LESS_THAN_ONE, 0) // Price = 0.5 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e17); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } + + // function test_givenDonation_givenSync_givenAuctionPriceLessThanOne_givenDifferentBaseTokenDecimals_fuzz( + // uint256 donatedQuoteTokens_ + // ) + // public + // givenCallbackIsCreated + // givenBaseTokenDecimals(17) + // givenOnCreate + // givenPoolIsCreated + // setCallbackParameters(_PROCEEDS_PRICE_LESS_THAN_ONE, 0) // Price = 0.5 + // givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + // givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + // givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + // { + // // Donation amount could be more or less than the auction price + // uint256 donatedQuoteTokens = bound(donatedQuoteTokens_, 1, 3e18); + // _quoteTokensDonated += donatedQuoteTokens; + + // // Donate to the pool + // _quoteToken.mint(address(_getUniswapV2Pool()), donatedQuoteTokens); + + // // Sync + // _syncPool(); + + // // Callback + // _performOnSettle(); + + // // Assertions + // _assertLpTokenBalance(); + // _assertLpUnderlyingBalances(); + // _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); + // _assertBaseTokenBalance(); + // _assertApprovals(); + // } function test_givenPoolPercent_fuzz( uint24 percent_ diff --git a/test/callbacks/liquidity/UniswapV3DTL/UniswapV3DTLTest.sol b/test/callbacks/liquidity/UniswapV3DTL/UniswapV3DTLTest.sol index eb2c0561..fbba333f 100644 --- a/test/callbacks/liquidity/UniswapV3DTL/UniswapV3DTLTest.sol +++ b/test/callbacks/liquidity/UniswapV3DTL/UniswapV3DTLTest.sol @@ -139,9 +139,21 @@ abstract contract UniswapV3DirectToLiquidityTest is Test, Permit2User, WithSalts } modifier givenCallbackIsCreated() { + Callbacks.Permissions memory permissions = Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: false, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }); + // Get the salt - bytes memory args = - abi.encode(address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory)); + bytes memory args = abi.encode( + address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory), permissions + ); bytes32 salt = _getTestSalt( "UniswapV3DirectToLiquidity", type(UniswapV3DirectToLiquidity).creationCode, args ); @@ -150,7 +162,7 @@ abstract contract UniswapV3DirectToLiquidityTest is Test, Permit2User, WithSalts // Source: https://github.com/foundry-rs/foundry/issues/6402 vm.startBroadcast(); _dtl = new UniswapV3DirectToLiquidity{salt: salt}( - address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory) + address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory), permissions ); vm.stopBroadcast(); diff --git a/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/UniswapV3DTLWithAllocatedAllowlistTest.sol b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/UniswapV3DTLWithAllocatedAllowlistTest.sol new file mode 100644 index 00000000..6b6b053c --- /dev/null +++ b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/UniswapV3DTLWithAllocatedAllowlistTest.sol @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Test} from "@forge-std-1.9.1/Test.sol"; +import {Callbacks} from "@axis-core-1.0.4/lib/Callbacks.sol"; +import {Permit2User} from "@axis-core-1.0.4-test/lib/permit2/Permit2User.sol"; + +import {IAuction} from "@axis-core-1.0.4/interfaces/modules/IAuction.sol"; +import {IAuctionHouse} from "@axis-core-1.0.4/interfaces/IAuctionHouse.sol"; +import {BatchAuctionHouse} from "@axis-core-1.0.4/BatchAuctionHouse.sol"; + +import {GUniFactory} from "@g-uni-v1-core-0.9.9/GUniFactory.sol"; +import {GUniPool} from "@g-uni-v1-core-0.9.9/GUniPool.sol"; +import {IUniswapV3Factory} from + "@uniswap-v3-core-1.0.1-solc-0.8-simulate/interfaces/IUniswapV3Factory.sol"; + +import {UniswapV3Factory} from "../../../lib/uniswap-v3/UniswapV3Factory.sol"; + +import {BaseDirectToLiquidity} from "../../../../src/callbacks/liquidity/BaseDTL.sol"; +import {UniswapV3DirectToLiquidity} from "../../../../src/callbacks/liquidity/UniswapV3DTL.sol"; +import {UniswapV3DTLWithAllocatedAllowlist} from + "../../../../src/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist.sol"; +import {LinearVesting} from "@axis-core-1.0.4/modules/derivatives/LinearVesting.sol"; +import {MockBatchAuctionModule} from + "@axis-core-1.0.4-test/modules/Auction/MockBatchAuctionModule.sol"; + +import {keycodeFromVeecode, toKeycode} from "@axis-core-1.0.4/modules/Keycode.sol"; + +import {MockERC20} from "@solmate-6.8.0/test/utils/mocks/MockERC20.sol"; + +import {WithSalts} from "../../../lib/WithSalts.sol"; +import {console2} from "@forge-std-1.9.1/console2.sol"; +import {TestConstants} from "../../../Constants.sol"; + +// solhint-disable max-states-count + +abstract contract UniswapV3DirectToLiquidityWithAllocatedAllowlistTest is + Test, + Permit2User, + WithSalts, + TestConstants +{ + using Callbacks for UniswapV3DirectToLiquidity; + + address internal constant _PROTOCOL = address(0x3); + address internal constant _BUYER = address(0x4); + address internal constant _NOT_SELLER = address(0x20); + + uint96 internal constant _LOT_CAPACITY = 10e18; + + uint48 internal constant _START = 1_000_000; + uint48 internal constant _DURATION = 1 days; + uint48 internal constant _AUCTION_START = _START + 1; + uint48 internal constant _AUCTION_CONCLUSION = _AUCTION_START + _DURATION; + + // Values: + // 0x0000000000000000000000000000000000000004, 5e18 + // 0x0000000000000000000000000000000000000020, 0 + bytes32 internal constant _MERKLE_ROOT = + 0x0fdc3942d9af344db31ff2e80c06bc4e558dc967ca5b4d421d741870f5ea40df; + bytes32[] internal _merkleProof; + uint256 internal constant _BUYER_ALLOCATED_AMOUNT = 5e18; + + uint96 internal _lotId = 1; + + BatchAuctionHouse internal _auctionHouse; + UniswapV3DTLWithAllocatedAllowlist internal _dtl; + address internal _dtlAddress; + IUniswapV3Factory internal _uniV3Factory; + GUniFactory internal _gUniFactory; + LinearVesting internal _linearVesting; + MockBatchAuctionModule internal _batchAuctionModule; + + MockERC20 internal _quoteToken; + MockERC20 internal _baseToken; + + uint96 internal _proceeds; + uint96 internal _refund; + uint24 internal _maxSlippage = 1; // 0.01% + + // Inputs + uint24 internal _poolFee = 500; + UniswapV3DirectToLiquidity.UniswapV3OnCreateParams internal _uniswapV3CreateParams = + UniswapV3DirectToLiquidity.UniswapV3OnCreateParams({ + poolFee: _poolFee, + maxSlippage: 1 // 0.01%, to handle rounding errors + }); + BaseDirectToLiquidity.OnCreateParams internal _dtlCreateParams = BaseDirectToLiquidity + .OnCreateParams({ + poolPercent: 100e2, + vestingStart: 0, + vestingExpiry: 0, + recipient: _SELLER, + implParams: abi.encode(_uniswapV3CreateParams) + }); + + function setUp() public { + // Set reasonable timestamp + vm.warp(_START); + + // Create an AuctionHouse at a deterministic address, since it is used as input to callbacks + BatchAuctionHouse auctionHouse = new BatchAuctionHouse(_OWNER, _PROTOCOL, _permit2Address); + _auctionHouse = BatchAuctionHouse(_AUCTION_HOUSE); + vm.etch(address(_auctionHouse), address(auctionHouse).code); + vm.store(address(_auctionHouse), bytes32(uint256(0)), bytes32(abi.encode(_OWNER))); // Owner + vm.store(address(_auctionHouse), bytes32(uint256(6)), bytes32(abi.encode(1))); // Reentrancy + vm.store(address(_auctionHouse), bytes32(uint256(10)), bytes32(abi.encode(_PROTOCOL))); // Protocol + + // Create a UniswapV3Factory at a deterministic address + vm.startBroadcast(_CREATE2_DEPLOYER); + bytes32 uniswapV3Salt = + _getTestSalt("UniswapV3Factory", type(UniswapV3Factory).creationCode, abi.encode()); + _uniV3Factory = new UniswapV3Factory{salt: uniswapV3Salt}(); + vm.stopBroadcast(); + if (address(_uniV3Factory) != _UNISWAP_V3_FACTORY) { + console2.log("UniswapV3Factory address: ", address(_uniV3Factory)); + revert("UniswapV3Factory address mismatch"); + } + + // Create a GUniFactory at a deterministic address + vm.startBroadcast(_CREATE2_DEPLOYER); + bytes32 gUniFactorySalt = _getTestSalt( + "GUniFactory", type(GUniFactory).creationCode, abi.encode(address(_uniV3Factory)) + ); + _gUniFactory = new GUniFactory{salt: gUniFactorySalt}(address(_uniV3Factory)); + vm.stopBroadcast(); + if (address(_gUniFactory) != _GUNI_FACTORY) { + console2.log("GUniFactory address: ", address(_gUniFactory)); + revert("GUniFactory address mismatch"); + } + + // Initialize the GUniFactory + address payable gelatoAddress = payable(address(0x10)); + GUniPool poolImplementation = new GUniPool(gelatoAddress); + _gUniFactory.initialize(address(poolImplementation), address(0), address(this)); + + _linearVesting = new LinearVesting(address(_auctionHouse)); + _batchAuctionModule = new MockBatchAuctionModule(address(_auctionHouse)); + + // Install a mock batch auction module + vm.prank(_OWNER); + _auctionHouse.installModule(_batchAuctionModule); + + _quoteToken = new MockERC20("Quote Token", "QT", 18); + _baseToken = new MockERC20("Base Token", "BT", 18); + + _merkleProof.push( + bytes32(0x2eac7b0cadd960cd4457012a5e232aa3532d9365ba6df63c1b5a9c7846f77760) + ); // Corresponds to _BUYER + } + + // ========== MODIFIERS ========== // + + modifier givenLinearVestingModuleIsInstalled() { + vm.prank(_OWNER); + _auctionHouse.installModule(_linearVesting); + _; + } + + modifier givenCallbackIsCreated() { + // Get the salt + bytes memory args = + abi.encode(address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory)); + bytes32 salt = _getTestSalt( + "UniswapV3DTLWithAllocatedAllowlist", + type(UniswapV3DTLWithAllocatedAllowlist).creationCode, + args + ); + + // Required for CREATE2 address to work correctly. doesn't do anything in a test + // Source: https://github.com/foundry-rs/foundry/issues/6402 + vm.startBroadcast(); + _dtl = new UniswapV3DTLWithAllocatedAllowlist{salt: salt}( + address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory) + ); + vm.stopBroadcast(); + + _dtlAddress = address(_dtl); + _; + } + + modifier givenAddressHasQuoteTokenBalance(address address_, uint256 amount_) { + _quoteToken.mint(address_, amount_); + _; + } + + modifier givenAddressHasBaseTokenBalance(address address_, uint256 amount_) { + _baseToken.mint(address_, amount_); + _; + } + + modifier givenAddressHasQuoteTokenAllowance(address owner_, address spender_, uint256 amount_) { + vm.prank(owner_); + _quoteToken.approve(spender_, amount_); + _; + } + + modifier givenAddressHasBaseTokenAllowance(address owner_, address spender_, uint256 amount_) { + vm.prank(owner_); + _baseToken.approve(spender_, amount_); + _; + } + + function _createLot(address seller_, bytes memory err_) internal returns (uint96 lotId) { + // Mint and approve the capacity to the owner + _baseToken.mint(seller_, _LOT_CAPACITY); + vm.prank(seller_); + _baseToken.approve(address(_auctionHouse), _LOT_CAPACITY); + + // Prep the lot arguments + IAuctionHouse.RoutingParams memory routingParams = IAuctionHouse.RoutingParams({ + auctionType: keycodeFromVeecode(_batchAuctionModule.VEECODE()), + baseToken: address(_baseToken), + quoteToken: address(_quoteToken), + referrerFee: 0, // No referrer fee + curator: address(0), + callbacks: _dtl, + callbackData: abi.encode(_dtlCreateParams), + derivativeType: toKeycode(""), + derivativeParams: abi.encode(""), + wrapDerivative: false + }); + + IAuction.AuctionParams memory auctionParams = IAuction.AuctionParams({ + start: _AUCTION_START, + duration: _DURATION, + capacityInQuote: false, + capacity: _LOT_CAPACITY, + implParams: abi.encode("") + }); + + if (err_.length > 0) { + vm.expectRevert(err_); + } + + // Create a new lot + vm.prank(seller_); + return _auctionHouse.auction(routingParams, auctionParams, ""); + } + + function _createLot( + address seller_ + ) internal returns (uint96 lotId) { + return _createLot(seller_, ""); + } + + modifier givenOnCreate() { + _lotId = _createLot(_SELLER); + _; + } + + function _performOnCreate( + address seller_ + ) internal { + vm.prank(address(_auctionHouse)); + _dtl.onCreate( + _lotId, + seller_, + address(_baseToken), + address(_quoteToken), + _LOT_CAPACITY, + false, + abi.encode(_dtlCreateParams) + ); + } + + function _performOnCreate() internal { + _performOnCreate(_SELLER); + } + + function _performOnCurate( + uint96 curatorPayout_ + ) internal { + vm.prank(address(_auctionHouse)); + _dtl.onCurate(_lotId, curatorPayout_, false, abi.encode("")); + } + + modifier givenOnCurate( + uint96 curatorPayout_ + ) { + _performOnCurate(curatorPayout_); + _; + } + + function _performOnCancel(uint96 lotId_, uint256 refundAmount_) internal { + vm.prank(address(_auctionHouse)); + _dtl.onCancel(lotId_, refundAmount_, false, abi.encode("")); + } + + function _performOnCancel() internal { + _performOnCancel(_lotId, 0); + } + + function _performOnSettle( + uint96 lotId_ + ) internal { + vm.prank(address(_auctionHouse)); + _dtl.onSettle(lotId_, _proceeds, _refund, abi.encode("")); + } + + function _performOnSettle() internal { + _performOnSettle(_lotId); + } + + function _setPoolPercent( + uint24 percent_ + ) internal { + _dtlCreateParams.poolPercent = percent_; + } + + modifier givenPoolPercent( + uint24 percent_ + ) { + _setPoolPercent(percent_); + _; + } + + modifier givenPoolFee( + uint24 fee_ + ) { + _uniswapV3CreateParams.poolFee = fee_; + + // Update the callback data + _dtlCreateParams.implParams = abi.encode(_uniswapV3CreateParams); + _; + } + + function _setMaxSlippage( + uint24 maxSlippage_ + ) internal { + _uniswapV3CreateParams.maxSlippage = maxSlippage_; + + // Update the callback data + _dtlCreateParams.implParams = abi.encode(_uniswapV3CreateParams); + } + + modifier givenMaxSlippage( + uint24 maxSlippage_ + ) { + _setMaxSlippage(maxSlippage_); + _; + } + + modifier givenVestingStart( + uint48 start_ + ) { + _dtlCreateParams.vestingStart = start_; + _; + } + + modifier givenVestingExpiry( + uint48 end_ + ) { + _dtlCreateParams.vestingExpiry = end_; + _; + } + + modifier whenRecipientIsNotSeller() { + _dtlCreateParams.recipient = _NOT_SELLER; + _; + } + + modifier givenMerkleRootIsSet() { + vm.prank(_SELLER); + _dtl.setMerkleRoot(_lotId, _MERKLE_ROOT); + _; + } + + // ========== FUNCTIONS ========== // + + function _getDTLConfiguration( + uint96 lotId_ + ) internal view returns (BaseDirectToLiquidity.DTLConfiguration memory) { + ( + address recipient_, + uint256 lotCapacity_, + uint256 lotCuratorPayout_, + uint24 poolPercent_, + uint48 vestingStart_, + uint48 vestingExpiry_, + LinearVesting linearVestingModule_, + bool active_, + bytes memory implParams_ + ) = _dtl.lotConfiguration(lotId_); + + return BaseDirectToLiquidity.DTLConfiguration({ + recipient: recipient_, + lotCapacity: lotCapacity_, + lotCuratorPayout: lotCuratorPayout_, + poolPercent: poolPercent_, + vestingStart: vestingStart_, + vestingExpiry: vestingExpiry_, + linearVestingModule: linearVestingModule_, + active: active_, + implParams: implParams_ + }); + } +} diff --git a/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onBid.t.sol b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onBid.t.sol new file mode 100644 index 00000000..f4eeb2d5 --- /dev/null +++ b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onBid.t.sol @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {UniswapV3DirectToLiquidityWithAllocatedAllowlistTest} from + "./UniswapV3DTLWithAllocatedAllowlistTest.sol"; + +import {BaseCallback} from "@axis-core-1.0.4/bases/BaseCallback.sol"; +import {UniswapV3DTLWithAllocatedAllowlist} from + "../../../../src/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist.sol"; +import {IUniswapV3DTLWithAllocatedAllowlist} from + "../../../../src/callbacks/liquidity/IUniswapV3DTLWithAllocatedAllowlist.sol"; + +contract UniswapV3DTLWithAllocatedAllowlistOnBidTest is + UniswapV3DirectToLiquidityWithAllocatedAllowlistTest +{ + // Use the @openzeppelin/merkle-tree package or the scripts in axis-utils to generate the merkle tree + + // Values: + // 0x0000000000000000000000000000000000000004, 5e18 + // 0x0000000000000000000000000000000000000020, 0 + bytes32 internal constant _BUYER_MERKLE_PROOF = + 0x2eac7b0cadd960cd4457012a5e232aa3532d9365ba6df63c1b5a9c7846f77760; + bytes32 internal constant _NOT_SELLER_MERKLE_PROOF = + 0xe0a73973cd60d8cbabb978d1f3c983065148b388619b9176d3d30e47c16d4fd5; + + bytes32[] internal _proof; + uint256 internal _allocatedAmount; + + uint64 internal constant _BID_ID = 1; + + // ========== MODIFIER ========== // + + modifier givenMerkleProof( + bytes32 merkleProof_ + ) { + bytes32[] memory proof = new bytes32[](1); + proof[0] = merkleProof_; + + _proof = proof; + _; + } + + modifier givenMerkleAllocatedAmount( + uint256 allocatedAmount_ + ) { + _allocatedAmount = allocatedAmount_; + _; + } + + function _onBid( + uint256 bidAmount_ + ) internal { + // Call the callback + vm.prank(address(_auctionHouse)); + _dtl.onBid(_lotId, _BID_ID, _BUYER, bidAmount_, abi.encode(_proof, _allocatedAmount)); + } + + // ========== TESTS ========== // + + // when the merkle root is not set + // [X] it reverts + // when the allowlist parameters are in an incorrect format + // [X] it reverts + // when the merkle proof is invalid + // [X] it reverts + // when the buyer is not in the merkle tree + // [X] it reverts + // when the buyer has already spent their limit + // [X] it reverts + // when the buyer has a 0 limit + // [X] it reverts + // when the buyer has not made a bid + // when the bid amount is over the buyer's limit + // [X] it reverts + // [X] it updates the spent amount with the bid amount + // when the bid amount is over the remaining limit + // [X] it reverts + // [X] it updates the spent amount with the bid amount + + function test_merkleRootNotSet_reverts() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleProof(_BUYER_MERKLE_PROOF) + givenMerkleAllocatedAmount(5e18) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + IUniswapV3DTLWithAllocatedAllowlist.Callback_InvalidState.selector + ); + vm.expectRevert(err); + + // Call the callback + _onBid(5e18); + } + + function test_parametersInvalid_reverts() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleRootIsSet + { + // Expect revert + vm.expectRevert(); + + // Call the callback with an invalid parameter format + vm.prank(address(_auctionHouse)); + _dtl.onBid(_lotId, _BID_ID, _BUYER, 5e18, abi.encode(uint256(20), bytes("something"))); + } + + function test_merkleProofInvalid_reverts() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleRootIsSet + givenMerkleProof(_NOT_SELLER_MERKLE_PROOF) + givenMerkleAllocatedAmount(5e18) // Amount is different to what is in the merkle tree + { + // Expect revert + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_NotAuthorized.selector); + vm.expectRevert(err); + + // Call the callback with an invalid merkle proof + _onBid(5e18); + } + + function test_buyerNotInMerkleTree_reverts() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleRootIsSet + givenMerkleProof(_BUYER_MERKLE_PROOF) + givenMerkleAllocatedAmount(5e18) + { + // Expect revert + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_NotAuthorized.selector); + vm.expectRevert(err); + + // Call the callback + vm.prank(address(_auctionHouse)); + _dtl.onBid(_lotId, _BID_ID, address(0x55), 5e18, abi.encode(_proof, _allocatedAmount)); + } + + function test_buyerLimitSpent_reverts() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleRootIsSet + givenMerkleProof(_BUYER_MERKLE_PROOF) + givenMerkleAllocatedAmount(5e18) + { + // Spend the allocation + _onBid(5e18); + + // Expect revert + bytes memory err = abi.encodeWithSelector( + IUniswapV3DTLWithAllocatedAllowlist.Callback_ExceedsLimit.selector + ); + vm.expectRevert(err); + + // Call the callback again + _onBid(1e18); + } + + function test_buyerZeroLimit_reverts() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleRootIsSet + givenMerkleProof(_NOT_SELLER_MERKLE_PROOF) + givenMerkleAllocatedAmount(0) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + IUniswapV3DTLWithAllocatedAllowlist.Callback_ExceedsLimit.selector + ); + vm.expectRevert(err); + + // Call the callback + vm.prank(address(_auctionHouse)); + _dtl.onBid(_lotId, _BID_ID, _NOT_SELLER, 5e18, abi.encode(_proof, _allocatedAmount)); + } + + function test_noBids_aboveLimit_reverts() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleRootIsSet + givenMerkleProof(_BUYER_MERKLE_PROOF) + givenMerkleAllocatedAmount(5e18) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + IUniswapV3DTLWithAllocatedAllowlist.Callback_ExceedsLimit.selector + ); + vm.expectRevert(err); + + // Call the callback + _onBid(6e18); + } + + function test_noBids_belowLimit() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleRootIsSet + givenMerkleProof(_BUYER_MERKLE_PROOF) + givenMerkleAllocatedAmount(5e18) + { + // Call the callback + _onBid(4e18); + + // Check the buyer spent amount + assertEq( + UniswapV3DTLWithAllocatedAllowlist(address(_dtl)).lotBuyerSpent(_lotId, _BUYER), + 4e18, + "buyer spent" + ); + } + + function test_remainingLimit_aboveLimit_reverts() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleRootIsSet + givenMerkleProof(_BUYER_MERKLE_PROOF) + givenMerkleAllocatedAmount(5e18) + { + // Spend the allocation + _onBid(4e18); + + // Expect revert + bytes memory err = abi.encodeWithSelector( + IUniswapV3DTLWithAllocatedAllowlist.Callback_ExceedsLimit.selector + ); + vm.expectRevert(err); + + // Call the callback again + _onBid(2e18); + } + + function test_remainingLimit_belowLimit() + public + givenCallbackIsCreated + givenOnCreate + givenMerkleRootIsSet + givenMerkleProof(_BUYER_MERKLE_PROOF) + givenMerkleAllocatedAmount(5e18) + { + // Spend the allocation + _onBid(4e18); + + // Call the callback + _onBid(1e18); + + // Check the buyer spent amount + assertEq( + UniswapV3DTLWithAllocatedAllowlist(address(_dtl)).lotBuyerSpent(_lotId, _BUYER), + 5e18, + "buyer spent" + ); + } +} diff --git a/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onCancel.t.sol b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onCancel.t.sol new file mode 100644 index 00000000..20b5347f --- /dev/null +++ b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onCancel.t.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {UniswapV3DirectToLiquidityWithAllocatedAllowlistTest} from + "./UniswapV3DTLWithAllocatedAllowlistTest.sol"; + +import {BaseCallback} from "@axis-core-1.0.4/bases/BaseCallback.sol"; +import {BaseDirectToLiquidity} from "../../../../src/callbacks/liquidity/BaseDTL.sol"; + +contract UniswapV3DTLWithAllocatedAllowlistOnCancelTest is + UniswapV3DirectToLiquidityWithAllocatedAllowlistTest +{ + uint96 internal constant _REFUND_AMOUNT = 2e18; + + // ============ Modifiers ============ // + + // ============ Tests ============ // + + // [X] given the onCancel callback has already been called + // [X] when onSettle is called + // [X] it reverts + // [X] when onCancel is called + // [X] it reverts + // [X] when onCurate is called + // [X] it reverts + // [X] when onCreate is called + // [X] it reverts + // [X] when the lot has not been registered + // [X] it reverts + // [X] when multiple lots are created + // [X] it marks the correct lot as inactive + // [X] it marks the lot as inactive + + function test_whenLotNotRegistered_reverts() public givenCallbackIsCreated { + // Expect revert + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_NotAuthorized.selector); + vm.expectRevert(err); + + // Call the function + _performOnCancel(); + } + + function test_success() public givenCallbackIsCreated givenOnCreate { + // Call the function + _performOnCancel(); + + // Check the values + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.active, false, "active"); + + // Check the balances + assertEq(_baseToken.balanceOf(_dtlAddress), 0, "base token balance"); + assertEq(_baseToken.balanceOf(_SELLER), 0, "seller base token balance"); + assertEq(_baseToken.balanceOf(_NOT_SELLER), 0, "not seller base token balance"); + } + + function test_success_multiple() public givenCallbackIsCreated givenOnCreate { + uint96 lotIdOne = _lotId; + + // Create a second lot and cancel it + uint96 lotIdTwo = _createLot(_NOT_SELLER); + _performOnCancel(lotIdTwo, _REFUND_AMOUNT); + + // Check the values + BaseDirectToLiquidity.DTLConfiguration memory configurationOne = + _getDTLConfiguration(lotIdOne); + assertEq(configurationOne.active, true, "lot one: active"); + + BaseDirectToLiquidity.DTLConfiguration memory configurationTwo = + _getDTLConfiguration(lotIdTwo); + assertEq(configurationTwo.active, false, "lot two: active"); + + // Check the balances + assertEq(_baseToken.balanceOf(_dtlAddress), 0, "base token balance"); + assertEq(_baseToken.balanceOf(_SELLER), 0, "seller base token balance"); + assertEq(_baseToken.balanceOf(_NOT_SELLER), 0, "not seller base token balance"); + } + + function test_auctionCancelled_onCreate_reverts() public givenCallbackIsCreated givenOnCreate { + // Call the function + _performOnCancel(); + + // Expect revert + // BaseCallback determines if the lot has already been registered + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_InvalidParams.selector); + vm.expectRevert(err); + + _performOnCreate(); + } + + function test_auctionCancelled_onCurate_reverts() public givenCallbackIsCreated givenOnCreate { + // Call the function + _performOnCancel(); + + // Expect revert + // BaseDirectToLiquidity determines if the lot has already been completed + bytes memory err = + abi.encodeWithSelector(BaseDirectToLiquidity.Callback_AlreadyComplete.selector); + vm.expectRevert(err); + + _performOnCurate(0); + } + + function test_auctionCancelled_onCancel_reverts() public givenCallbackIsCreated givenOnCreate { + // Call the function + _performOnCancel(); + + // Expect revert + // BaseDirectToLiquidity determines if the lot has already been completed + bytes memory err = + abi.encodeWithSelector(BaseDirectToLiquidity.Callback_AlreadyComplete.selector); + vm.expectRevert(err); + + _performOnCancel(); + } + + function test_auctionCancelled_onSettle_reverts() public givenCallbackIsCreated givenOnCreate { + // Call the function + _performOnCancel(); + + // Expect revert + // BaseDirectToLiquidity determines if the lot has already been completed + bytes memory err = + abi.encodeWithSelector(BaseDirectToLiquidity.Callback_AlreadyComplete.selector); + vm.expectRevert(err); + + _performOnSettle(); + } +} diff --git a/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onCreate.t.sol b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onCreate.t.sol new file mode 100644 index 00000000..7c798665 --- /dev/null +++ b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onCreate.t.sol @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {UniswapV3DirectToLiquidityWithAllocatedAllowlistTest} from + "./UniswapV3DTLWithAllocatedAllowlistTest.sol"; + +import {BaseCallback} from "@axis-core-1.0.4/bases/BaseCallback.sol"; +import {BaseDirectToLiquidity} from "../../../../src/callbacks/liquidity/BaseDTL.sol"; +import {UniswapV3DirectToLiquidity} from "../../../../src/callbacks/liquidity/UniswapV3DTL.sol"; + +contract UniswapV3DTLWithAllocatedAllowlistOnCreateTest is + UniswapV3DirectToLiquidityWithAllocatedAllowlistTest +{ + // ============ Modifiers ============ // + + // ============ Assertions ============ // + + function _expectTransferFrom() internal { + vm.expectRevert("TRANSFER_FROM_FAILED"); + } + + function _expectInvalidParams() internal { + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_InvalidParams.selector); + vm.expectRevert(err); + } + + function _expectNotAuthorized() internal { + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_NotAuthorized.selector); + vm.expectRevert(err); + } + + function _assertBaseTokenBalances() internal view { + assertEq(_baseToken.balanceOf(_SELLER), 0, "seller balance"); + assertEq(_baseToken.balanceOf(_NOT_SELLER), 0, "not seller balance"); + assertEq(_baseToken.balanceOf(_dtlAddress), 0, "dtl balance"); + } + + // ============ Tests ============ // + + // [X] when the callback data is incorrect + // [X] it reverts + // [X] when the callback is not called by the auction house + // [X] it reverts + // [X] when the lot has already been registered + // [X] it reverts + // [X] when the proceeds utilisation is 0 + // [X] it reverts + // [X] when the proceeds utilisation is greater than 100% + // [X] it reverts + // [X] when the implParams is not the correct length + // [X] it reverts + // [X] when the max slippage is between 0 and 100% + // [X] it succeeds + // [X] when the max slippage is greater than 100% + // [X] it reverts + // [X] given the pool fee is not enabled + // [X] it reverts + // [X] given uniswap v3 pool already exists + // [X] it succeeds + // [X] when the start and expiry timestamps are the same + // [X] it reverts + // [X] when the start timestamp is after the expiry timestamp + // [X] it reverts + // [X] when the start timestamp is before the current timestamp + // [X] it succeeds + // [X] when the expiry timestamp is before the current timestamp + // [X] it reverts + // [X] when the start timestamp and expiry timestamp are specified + // [X] given the linear vesting module is not installed + // [X] it reverts + // [X] given the vesting start timestamp is before the auction conclusion + // [X] it reverts + // [X] it records the address of the linear vesting module + // [X] when the recipient is the zero address + // [X] it reverts + // [X] when the recipient is not the seller + // [X] it records the recipient + // [X] when multiple lots are created + // [X] it registers each lot + // [ ] it registers the seller of the lot + // [X] it registers the lot + // [ ] it registers the seller of the lot + + function test_whenCallbackDataIsIncorrect_reverts() public givenCallbackIsCreated { + // Expect revert + vm.expectRevert(); + + vm.prank(address(_auctionHouse)); + _dtl.onCreate( + _lotId, + _SELLER, + address(_baseToken), + address(_quoteToken), + _LOT_CAPACITY, + false, + abi.encode(uint256(10)) + ); + } + + function test_whenCallbackIsNotCalledByAuctionHouse_reverts() public givenCallbackIsCreated { + _expectNotAuthorized(); + + _dtl.onCreate( + _lotId, + _SELLER, + address(_baseToken), + address(_quoteToken), + _LOT_CAPACITY, + false, + abi.encode(_dtlCreateParams) + ); + } + + function test_whenLotHasAlreadyBeenRegistered_reverts() public givenCallbackIsCreated { + _performOnCreate(); + + _expectInvalidParams(); + + _performOnCreate(); + } + + function test_poolPercent_whenBelowBounds_reverts( + uint24 poolPercent_ + ) public givenCallbackIsCreated { + uint24 poolPercent = uint24(bound(poolPercent_, 0, 10e2 - 1)); + + // Set pool percent + _setPoolPercent(poolPercent); + + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_Params_PercentOutOfBounds.selector, + poolPercent, + 10e2, + 100e2 + ); + vm.expectRevert(err); + + _performOnCreate(); + } + + function test_poolPercent_whenAboveBounds_reverts( + uint24 poolPercent_ + ) public givenCallbackIsCreated { + uint24 poolPercent = uint24(bound(poolPercent_, 100e2 + 1, type(uint24).max)); + + // Set pool percent + _setPoolPercent(poolPercent); + + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_Params_PercentOutOfBounds.selector, + poolPercent, + 10e2, + 100e2 + ); + vm.expectRevert(err); + + _performOnCreate(); + } + + function test_poolPercent_fuzz( + uint24 poolPercent_ + ) public givenCallbackIsCreated { + uint24 poolPercent = uint24(bound(poolPercent_, 10e2, 100e2)); + + _setPoolPercent(poolPercent); + + _performOnCreate(); + + // Assert values + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.poolPercent, poolPercent, "poolPercent"); + } + + function test_paramsIncorrectLength_reverts() public givenCallbackIsCreated { + // Set the implParams to an incorrect length + _dtlCreateParams.implParams = abi.encode(uint256(10)); + + // Expect revert + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_InvalidParams.selector); + vm.expectRevert(err); + + _performOnCreate(); + } + + function test_maxSlippageGreaterThan100Percent_reverts( + uint24 maxSlippage_ + ) public givenCallbackIsCreated { + uint24 maxSlippage = uint24(bound(maxSlippage_, 100e2 + 1, type(uint24).max)); + _setMaxSlippage(maxSlippage); + + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_Params_PercentOutOfBounds.selector, maxSlippage, 0, 100e2 + ); + vm.expectRevert(err); + + _performOnCreate(); + } + + function test_givenPoolFeeIsNotEnabled_reverts() + public + givenCallbackIsCreated + givenPoolFee(0) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + UniswapV3DirectToLiquidity.Callback_Params_PoolFeeNotEnabled.selector + ); + vm.expectRevert(err); + + _performOnCreate(); + } + + function test_givenUniswapV3PoolAlreadyExists() + public + givenCallbackIsCreated + givenPoolFee(500) + { + // Create the pool + _uniV3Factory.createPool(address(_baseToken), address(_quoteToken), 500); + + // Perform the callback + _performOnCreate(); + + // Assert that the callback was successful + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.active, true, "active"); + } + + function test_whenStartAndExpiryTimestampsAreTheSame_reverts() + public + givenCallbackIsCreated + givenLinearVestingModuleIsInstalled + givenVestingStart(_AUCTION_CONCLUSION + 1) + givenVestingExpiry(_AUCTION_CONCLUSION + 1) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_Params_InvalidVestingParams.selector + ); + + _createLot(address(_SELLER), err); + } + + function test_whenStartTimestampIsAfterExpiryTimestamp_reverts() + public + givenCallbackIsCreated + givenLinearVestingModuleIsInstalled + givenVestingStart(_AUCTION_CONCLUSION + 2) + givenVestingExpiry(_AUCTION_CONCLUSION + 1) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_Params_InvalidVestingParams.selector + ); + + _createLot(address(_SELLER), err); + } + + function test_whenStartTimestampIsBeforeCurrentTimestamp_reverts() + public + givenCallbackIsCreated + givenLinearVestingModuleIsInstalled + givenVestingStart(_START - 1) + givenVestingExpiry(_START + 1) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_Params_InvalidVestingParams.selector + ); + + _createLot(address(_SELLER), err); + } + + function test_whenExpiryTimestampIsBeforeCurrentTimestamp_reverts() + public + givenCallbackIsCreated + givenLinearVestingModuleIsInstalled + givenVestingStart(_AUCTION_CONCLUSION + 1) + givenVestingExpiry(_AUCTION_CONCLUSION - 1) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_Params_InvalidVestingParams.selector + ); + + _createLot(address(_SELLER), err); + } + + function test_whenVestingSpecified_givenLinearVestingModuleNotInstalled_reverts() + public + givenCallbackIsCreated + givenVestingStart(_AUCTION_CONCLUSION + 1) + givenVestingExpiry(_AUCTION_CONCLUSION + 2) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_LinearVestingModuleNotFound.selector + ); + + _createLot(address(_SELLER), err); + } + + function test_whenVestingSpecified_whenStartTimestampIsBeforeAuctionConclusion_reverts() + public + givenCallbackIsCreated + givenLinearVestingModuleIsInstalled + givenVestingStart(_AUCTION_CONCLUSION - 1) + givenVestingExpiry(_AUCTION_CONCLUSION + 2) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_Params_InvalidVestingParams.selector + ); + + _createLot(address(_SELLER), err); + } + + function test_whenVestingSpecified_whenVestingStartTimestampIsOnAuctionConclusion() + public + givenCallbackIsCreated + givenLinearVestingModuleIsInstalled + givenVestingStart(_AUCTION_CONCLUSION) + givenVestingExpiry(_AUCTION_CONCLUSION + 2) + { + _lotId = _createLot(address(_SELLER)); + + // Assert values + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.vestingStart, _AUCTION_CONCLUSION, "vestingStart"); + assertEq(configuration.vestingExpiry, _AUCTION_CONCLUSION + 2, "vestingExpiry"); + assertEq( + address(configuration.linearVestingModule), + address(_linearVesting), + "linearVestingModule" + ); + + // Assert balances + _assertBaseTokenBalances(); + } + + function test_whenVestingSpecified() + public + givenCallbackIsCreated + givenLinearVestingModuleIsInstalled + givenVestingStart(_AUCTION_CONCLUSION + 1) + givenVestingExpiry(_AUCTION_CONCLUSION + 2) + { + _lotId = _createLot(address(_SELLER)); + + // Assert values + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.vestingStart, _AUCTION_CONCLUSION + 1, "vestingStart"); + assertEq(configuration.vestingExpiry, _AUCTION_CONCLUSION + 2, "vestingExpiry"); + assertEq( + address(configuration.linearVestingModule), + address(_linearVesting), + "linearVestingModule" + ); + + // Assert balances + _assertBaseTokenBalances(); + } + + function test_whenRecipientIsZeroAddress_reverts() public givenCallbackIsCreated { + _dtlCreateParams.recipient = address(0); + + // Expect revert + bytes memory err = + abi.encodeWithSelector(BaseDirectToLiquidity.Callback_Params_InvalidAddress.selector); + vm.expectRevert(err); + + _performOnCreate(); + } + + function test_whenRecipientIsNotSeller_succeeds() + public + givenCallbackIsCreated + whenRecipientIsNotSeller + { + _performOnCreate(); + + // Assert values + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.recipient, _NOT_SELLER, "recipient"); + + // Assert balances + _assertBaseTokenBalances(); + } + + function test_succeeds() public givenCallbackIsCreated { + _performOnCreate(); + + // Assert values + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.recipient, _SELLER, "recipient"); + assertEq(configuration.lotCapacity, _LOT_CAPACITY, "lotCapacity"); + assertEq(configuration.lotCuratorPayout, 0, "lotCuratorPayout"); + assertEq(configuration.poolPercent, _dtlCreateParams.poolPercent, "poolPercent"); + assertEq(configuration.vestingStart, 0, "vestingStart"); + assertEq(configuration.vestingExpiry, 0, "vestingExpiry"); + assertEq(address(configuration.linearVestingModule), address(0), "linearVestingModule"); + assertEq(configuration.active, true, "active"); + + (uint24 configurationPoolFee) = abi.decode(configuration.implParams, (uint24)); + assertEq(configurationPoolFee, _poolFee, "poolFee"); + assertEq(configuration.implParams, _dtlCreateParams.implParams, "implParams"); + + UniswapV3DirectToLiquidity.UniswapV3OnCreateParams memory uniswapV3CreateParams = abi.decode( + configuration.implParams, (UniswapV3DirectToLiquidity.UniswapV3OnCreateParams) + ); + assertEq(uniswapV3CreateParams.poolFee, _uniswapV3CreateParams.poolFee, "poolFee"); + assertEq( + uniswapV3CreateParams.maxSlippage, _uniswapV3CreateParams.maxSlippage, "maxSlippage" + ); + + // Assert balances + _assertBaseTokenBalances(); + } + + function test_succeeds_multiple() public givenCallbackIsCreated { + // Lot one + _performOnCreate(); + + // Lot two + _dtlCreateParams.recipient = _NOT_SELLER; + _lotId = 2; + _performOnCreate(_NOT_SELLER); + + // Assert values + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.recipient, _NOT_SELLER, "recipient"); + assertEq(configuration.lotCapacity, _LOT_CAPACITY, "lotCapacity"); + assertEq(configuration.lotCuratorPayout, 0, "lotCuratorPayout"); + assertEq(configuration.poolPercent, _dtlCreateParams.poolPercent, "poolPercent"); + assertEq(configuration.vestingStart, 0, "vestingStart"); + assertEq(configuration.vestingExpiry, 0, "vestingExpiry"); + assertEq(address(configuration.linearVestingModule), address(0), "linearVestingModule"); + assertEq(configuration.active, true, "active"); + assertEq(configuration.implParams, _dtlCreateParams.implParams, "implParams"); + + UniswapV3DirectToLiquidity.UniswapV3OnCreateParams memory uniswapV3CreateParams = abi.decode( + configuration.implParams, (UniswapV3DirectToLiquidity.UniswapV3OnCreateParams) + ); + assertEq(uniswapV3CreateParams.poolFee, _uniswapV3CreateParams.poolFee, "poolFee"); + assertEq( + uniswapV3CreateParams.maxSlippage, _uniswapV3CreateParams.maxSlippage, "maxSlippage" + ); + + // Assert balances + _assertBaseTokenBalances(); + } + + function test_maxSlippage_fuzz( + uint24 maxSlippage_ + ) public givenCallbackIsCreated { + uint24 maxSlippage = uint24(bound(maxSlippage_, 0, 100e2)); + _setMaxSlippage(maxSlippage); + + _performOnCreate(); + + // Assert values + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.implParams, _dtlCreateParams.implParams, "implParams"); + + UniswapV3DirectToLiquidity.UniswapV3OnCreateParams memory uniswapV3CreateParams = abi.decode( + configuration.implParams, (UniswapV3DirectToLiquidity.UniswapV3OnCreateParams) + ); + assertEq(uniswapV3CreateParams.maxSlippage, maxSlippage, "maxSlippage"); + } +} diff --git a/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onCurate.t.sol b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onCurate.t.sol new file mode 100644 index 00000000..7a89d1c8 --- /dev/null +++ b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onCurate.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {UniswapV3DirectToLiquidityWithAllocatedAllowlistTest} from + "./UniswapV3DTLWithAllocatedAllowlistTest.sol"; + +import {BaseCallback} from "@axis-core-1.0.4/bases/BaseCallback.sol"; +import {BaseDirectToLiquidity} from "../../../../src/callbacks/liquidity/BaseDTL.sol"; + +contract UniswapV3DTLWithAllocatedAllowlistOnCurateTest is + UniswapV3DirectToLiquidityWithAllocatedAllowlistTest +{ + uint96 internal constant _PAYOUT_AMOUNT = 1e18; + + // ============ Modifiers ============ // + + function _performCallback( + uint96 lotId_ + ) internal { + vm.prank(address(_auctionHouse)); + _dtl.onCurate(lotId_, _PAYOUT_AMOUNT, false, abi.encode("")); + } + + // ============ Tests ============ // + + // [X] when the lot has not been registered + // [X] it reverts + // [X] when multiple lots are created + // [X] it marks the correct lot as inactive + // [X] it registers the curator payout + + function test_whenLotNotRegistered_reverts() public givenCallbackIsCreated { + // Expect revert + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_NotAuthorized.selector); + vm.expectRevert(err); + + // Call the function + _performCallback(_lotId); + } + + function test_success() public givenCallbackIsCreated givenOnCreate { + // Call the function + _performCallback(_lotId); + + // Check the values + BaseDirectToLiquidity.DTLConfiguration memory configuration = _getDTLConfiguration(_lotId); + assertEq(configuration.lotCuratorPayout, _PAYOUT_AMOUNT, "lotCuratorPayout"); + + // Check the balances + assertEq(_baseToken.balanceOf(_dtlAddress), 0, "base token balance"); + assertEq(_baseToken.balanceOf(_SELLER), 0, "seller base token balance"); + assertEq(_baseToken.balanceOf(_NOT_SELLER), 0, "not seller base token balance"); + assertEq( + _baseToken.balanceOf(address(_auctionHouse)), + _LOT_CAPACITY, + "auction house base token balance" + ); + } + + function test_success_multiple() public givenCallbackIsCreated givenOnCreate { + uint96 lotIdOne = _lotId; + + // Create a second lot + uint96 lotIdTwo = _createLot(_NOT_SELLER); + + // Call the function + _performCallback(lotIdTwo); + + // Check the values + BaseDirectToLiquidity.DTLConfiguration memory configurationOne = + _getDTLConfiguration(lotIdOne); + assertEq(configurationOne.lotCuratorPayout, 0, "lot one: lotCuratorPayout"); + + BaseDirectToLiquidity.DTLConfiguration memory configurationTwo = + _getDTLConfiguration(lotIdTwo); + assertEq(configurationTwo.lotCuratorPayout, _PAYOUT_AMOUNT, "lot two: lotCuratorPayout"); + + // Check the balances + assertEq(_baseToken.balanceOf(_dtlAddress), 0, "base token balance"); + assertEq(_baseToken.balanceOf(_SELLER), 0, "seller base token balance"); + assertEq(_baseToken.balanceOf(_NOT_SELLER), 0, "not seller base token balance"); + assertEq( + _baseToken.balanceOf(address(_auctionHouse)), + _LOT_CAPACITY * 2, + "auction house base token balance" + ); + } +} diff --git a/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onSettle.t.sol b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onSettle.t.sol new file mode 100644 index 00000000..b12c077c --- /dev/null +++ b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/onSettle.t.sol @@ -0,0 +1,1013 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {UniswapV3DirectToLiquidityWithAllocatedAllowlistTest} from + "./UniswapV3DTLWithAllocatedAllowlistTest.sol"; + +// Libraries +import {FixedPointMathLib} from "@solmate-6.8.0/utils/FixedPointMathLib.sol"; +import {ERC20} from "@solmate-6.8.0/tokens/ERC20.sol"; + +// Uniswap +import {IUniswapV3Pool} from + "@uniswap-v3-core-1.0.1-solc-0.8-simulate/interfaces/IUniswapV3Pool.sol"; +import {SqrtPriceMath} from "../../../../src/lib/uniswap-v3/SqrtPriceMath.sol"; +import {TickMath} from "@uniswap-v3-core-1.0.1-solc-0.8-simulate/libraries/TickMath.sol"; + +// G-UNI +import {GUniPool} from "@g-uni-v1-core-0.9.9/GUniPool.sol"; + +// AuctionHouse +import {ILinearVesting} from "@axis-core-1.0.4/interfaces/modules/derivatives/ILinearVesting.sol"; +import {BaseDirectToLiquidity} from "../../../../src/callbacks/liquidity/BaseDTL.sol"; +import {UniswapV3DirectToLiquidity} from "../../../../src/callbacks/liquidity/UniswapV3DTL.sol"; +import {BaseCallback} from "@axis-core-1.0.4/bases/BaseCallback.sol"; + +import {console2} from "@forge-std-1.9.1/console2.sol"; + +contract UniswapV3DTLWithAllocatedAllowlistOnSettleTest is + UniswapV3DirectToLiquidityWithAllocatedAllowlistTest +{ + uint96 internal constant _PROCEEDS = 20e18; + uint96 internal constant _REFUND = 0; + + uint96 internal _capacityUtilised; + uint96 internal _quoteTokensToDeposit; + uint96 internal _baseTokensToDeposit; + uint96 internal _curatorPayout; + uint256 internal _additionalQuoteTokensMinted; + + uint160 internal constant _SQRT_PRICE_X96_OVERRIDE = 125_270_724_187_523_965_593_206_000_000; // Different to what is normally calculated + + /// @dev Set via `setCallbackParameters` modifier + uint160 internal _sqrtPriceX96; + + // ========== Internal functions ========== // + + function _getGUniPool() internal view returns (GUniPool) { + // Get the pools deployed by the DTL callback + address[] memory pools = _gUniFactory.getPools(_dtlAddress); + + return GUniPool(pools[0]); + } + + function _getVestingTokenId() internal view returns (uint256) { + // Get the pools deployed by the DTL callback + address pool = address(_getGUniPool()); + + return _linearVesting.computeId( + pool, + abi.encode( + ILinearVesting.VestingParams({ + start: _dtlCreateParams.vestingStart, + expiry: _dtlCreateParams.vestingExpiry + }) + ) + ); + } + + // ========== Assertions ========== // + + function _assertPoolState( + uint160 sqrtPriceX96_ + ) internal view { + // Get the pool + IUniswapV3Pool pool = _getPool(); + + (uint160 sqrtPriceX96,,,,,,) = pool.slot0(); + assertEq(sqrtPriceX96, sqrtPriceX96_, "pool sqrt price"); + } + + function _assertLpTokenBalance() internal view { + // Get the pools deployed by the DTL callback + GUniPool pool = _getGUniPool(); + + uint256 sellerExpectedBalance; + uint256 linearVestingExpectedBalance; + // Only has a balance if not vesting + if (_dtlCreateParams.vestingStart == 0) { + sellerExpectedBalance = pool.totalSupply(); + } else { + linearVestingExpectedBalance = pool.totalSupply(); + } + + assertEq( + pool.balanceOf(_SELLER), + _dtlCreateParams.recipient == _SELLER ? sellerExpectedBalance : 0, + "seller: LP token balance" + ); + assertEq( + pool.balanceOf(_NOT_SELLER), + _dtlCreateParams.recipient == _NOT_SELLER ? sellerExpectedBalance : 0, + "not seller: LP token balance" + ); + assertEq( + pool.balanceOf(address(_linearVesting)), + linearVestingExpectedBalance, + "linear vesting: LP token balance" + ); + } + + function _assertVestingTokenBalance() internal { + // Exit if not vesting + if (_dtlCreateParams.vestingStart == 0) { + return; + } + + // Get the pools deployed by the DTL callback + address pool = address(_getGUniPool()); + + // Get the wrapped address + (, address wrappedVestingTokenAddress) = _linearVesting.deploy( + pool, + abi.encode( + ILinearVesting.VestingParams({ + start: _dtlCreateParams.vestingStart, + expiry: _dtlCreateParams.vestingExpiry + }) + ), + true + ); + ERC20 wrappedVestingToken = ERC20(wrappedVestingTokenAddress); + uint256 sellerExpectedBalance = wrappedVestingToken.totalSupply(); + + assertEq( + wrappedVestingToken.balanceOf(_SELLER), + _dtlCreateParams.recipient == _SELLER ? sellerExpectedBalance : 0, + "seller: vesting token balance" + ); + assertEq( + wrappedVestingToken.balanceOf(_NOT_SELLER), + _dtlCreateParams.recipient == _NOT_SELLER ? sellerExpectedBalance : 0, + "not seller: vesting token balance" + ); + } + + function _assertQuoteTokenBalance() internal view { + assertEq(_quoteToken.balanceOf(_dtlAddress), 0, "DTL: quote token balance"); + + uint256 nonPoolProceeds = _proceeds - _quoteTokensToDeposit; + assertApproxEqAbs( + _quoteToken.balanceOf(_NOT_SELLER), + _dtlCreateParams.recipient == _NOT_SELLER ? nonPoolProceeds : 0, + ( + _dtlCreateParams.recipient == _NOT_SELLER + ? _uniswapV3CreateParams.maxSlippage * _quoteTokensToDeposit / 100e2 + : 0 + ) + 2, // Rounding errors + "not seller: quote token balance" + ); + assertApproxEqAbs( + _quoteToken.balanceOf(_SELLER), + _dtlCreateParams.recipient == _SELLER ? nonPoolProceeds : 0, + ( + _dtlCreateParams.recipient == _NOT_SELLER + ? _uniswapV3CreateParams.maxSlippage * _quoteTokensToDeposit / 100e2 + : 0 + ) + 2, // Rounding errors + "seller: quote token balance" + ); + } + + function _assertBaseTokenBalance() internal view { + assertEq(_baseToken.balanceOf(_dtlAddress), 0, "DTL: base token balance"); + } + + function _assertApprovals() internal view { + // Ensure there are no dangling approvals + assertEq( + _quoteToken.allowance(_dtlAddress, address(_getGUniPool())), + 0, + "DTL: quote token allowance" + ); + assertEq( + _baseToken.allowance(_dtlAddress, address(_getGUniPool())), + 0, + "DTL: base token allowance" + ); + } + + // ========== Modifiers ========== // + + function _createPool() internal returns (address) { + (address token0, address token1) = address(_baseToken) < address(_quoteToken) + ? (address(_baseToken), address(_quoteToken)) + : (address(_quoteToken), address(_baseToken)); + + return _uniV3Factory.createPool(token0, token1, _poolFee); + } + + function _initializePool(address pool_, uint160 sqrtPriceX96_) internal { + IUniswapV3Pool(pool_).initialize(sqrtPriceX96_); + } + + modifier givenPoolIsCreated() { + _createPool(); + _; + } + + modifier givenPoolIsCreatedAndInitialized( + uint160 sqrtPriceX96_ + ) { + address pool = _createPool(); + _initializePool(pool, sqrtPriceX96_); + _; + } + + function _calculateSqrtPriceX96( + uint256 quoteTokenAmount_, + uint256 baseTokenAmount_ + ) internal view returns (uint160) { + return SqrtPriceMath.getSqrtPriceX96( + address(_quoteToken), address(_baseToken), quoteTokenAmount_, baseTokenAmount_ + ); + } + + modifier setCallbackParameters(uint96 proceeds_, uint96 refund_) { + _proceeds = proceeds_; + _refund = refund_; + + // Calculate the capacity utilised + // Any unspent curator payout is included in the refund + // However, curator payouts are linear to the capacity utilised + // Calculate the percent utilisation + uint96 capacityUtilisationPercent = 100e2 + - uint96(FixedPointMathLib.mulDivDown(_refund, 100e2, _LOT_CAPACITY + _curatorPayout)); + _capacityUtilised = _LOT_CAPACITY * capacityUtilisationPercent / 100e2; + + // The proceeds utilisation percent scales the quote tokens and base tokens linearly + _quoteTokensToDeposit = _proceeds * _dtlCreateParams.poolPercent / 100e2; + _baseTokensToDeposit = _capacityUtilised * _dtlCreateParams.poolPercent / 100e2; + + _sqrtPriceX96 = _calculateSqrtPriceX96(_quoteTokensToDeposit, _baseTokensToDeposit); + _; + } + + modifier givenUnboundedPoolPercent( + uint24 percent_ + ) { + // Bound the percent + uint24 percent = uint24(bound(percent_, 10e2, 100e2)); + + // Set the value on the DTL + _dtlCreateParams.poolPercent = percent; + _; + } + + modifier givenUnboundedOnCurate( + uint96 curationPayout_ + ) { + // Bound the value + _curatorPayout = uint96(bound(curationPayout_, 1e17, _LOT_CAPACITY)); + + // Call the onCurate callback + _performOnCurate(_curatorPayout); + _; + } + + modifier whenRefundIsBounded( + uint96 refund_ + ) { + // Bound the refund + _refund = uint96(bound(refund_, 1e17, 5e18)); + _; + } + + modifier givenPoolHasDepositLowerPrice() { + _sqrtPriceX96 = _calculateSqrtPriceX96(_PROCEEDS / 2, _LOT_CAPACITY); + _; + } + + modifier givenPoolHasDepositHigherPrice() { + _sqrtPriceX96 = _calculateSqrtPriceX96(_PROCEEDS * 2, _LOT_CAPACITY); + _; + } + + modifier givenPoolHasDepositMuchHigherPrice() { + _sqrtPriceX96 = _calculateSqrtPriceX96(_PROCEEDS * 10, _LOT_CAPACITY); + _; + } + + function _getPool() internal view returns (IUniswapV3Pool) { + (address token0, address token1) = address(_baseToken) < address(_quoteToken) + ? (address(_baseToken), address(_quoteToken)) + : (address(_quoteToken), address(_baseToken)); + return IUniswapV3Pool(_uniV3Factory.getPool(token0, token1, _poolFee)); + } + + // ========== Tests ========== // + + // [X] given the onSettle callback has already been called + // [X] when onSettle is called + // [X] it reverts + // [X] when onCancel is called + // [X] it reverts + // [X] when onCreate is called + // [X] it reverts + // [X] when onCurate is called + // [X] it reverts + // [X] given the pool is created + // [X] it initializes the pool + // [X] given the pool is created and initialized + // [X] it succeeds + // [X] given there is liquidity in the pool at a higher tick + // [X] it adjusts the pool price + // [X] given there is liquidity in the pool at a lower tick + // [X] it adjusts the pool price + // [X] given the proceeds utilisation percent is set + // [X] it calculates the deposit amount correctly + // [X] given curation is enabled + // [X] the utilisation percent considers this + // [X] when the refund amount changes + // [X] the utilisation percent considers this + // [X] given minting pool tokens utilises less than the available amount of base tokens + // [X] the excess base tokens are returned + // [X] given minting pool tokens utilises less than the available amount of quote tokens + // [X] the excess quote tokens are returned + // [X] given the send base tokens flag is false + // [X] it transfers the base tokens from the seller + // [X] given vesting is enabled + // [X] given the recipient is not the seller + // [X] it mints the vesting tokens to the seller + // [X] it mints the vesting tokens to the seller + // [X] given the recipient is not the seller + // [X] it mints the LP token to the recipient + // [X] when multiple lots are created + // [X] it performs actions on the correct pool + // [X] it creates and initializes the pool, creates a pool token, deposits into the pool token, transfers the LP token to the seller and transfers any excess back to the seller + + function test_givenPoolIsCreated() + public + givenCallbackIsCreated + givenOnCreate + givenPoolIsCreated + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_givenPoolPercent_fuzz( + uint24 percent_ + ) + public + givenCallbackIsCreated + givenUnboundedPoolPercent(percent_) + whenRecipientIsNotSeller + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_givenCurationPayout_fuzz( + uint96 curationPayout_ + ) + public + givenCallbackIsCreated + givenOnCreate + givenUnboundedOnCurate(curationPayout_) + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_givenPoolPercent_givenCurationPayout_fuzz( + uint24 percent_, + uint96 curationPayout_ + ) + public + givenCallbackIsCreated + givenUnboundedPoolPercent(percent_) + givenOnCreate + givenUnboundedOnCurate(curationPayout_) + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _baseTokensToDeposit) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _baseTokensToDeposit) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_whenRefund_fuzz( + uint96 refund_ + ) + public + givenCallbackIsCreated + givenOnCreate + whenRefundIsBounded(refund_) + setCallbackParameters(_PROCEEDS, _refund) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _baseTokensToDeposit) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _baseTokensToDeposit) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_givenPoolHasDepositWithLowerPrice() + public + givenCallbackIsCreated + givenMaxSlippage(200) // 2% + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenPoolHasDepositLowerPrice + givenPoolIsCreatedAndInitialized(_sqrtPriceX96) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _baseTokensToDeposit) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _baseTokensToDeposit) + { + _performOnSettle(); + + _assertPoolState(_calculateSqrtPriceX96(_PROCEEDS, _LOT_CAPACITY)); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + // TODO need to add a case where the price is more than 2x the target price and there is too much liquidity to sell through + // the result should be the pool is initialized at a higher price than the target price, but with balanced liquidity + function test_givenPoolHasDepositWithHigherPrice() + public + givenCallbackIsCreated + givenMaxSlippage(200) // 2% + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenPoolHasDepositHigherPrice + givenPoolIsCreatedAndInitialized(_sqrtPriceX96) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _baseTokensToDeposit) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _baseTokensToDeposit) + { + _performOnSettle(); + + _assertPoolState(_calculateSqrtPriceX96(_PROCEEDS, _LOT_CAPACITY)); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_lessThanMaxSlippage() + public + givenCallbackIsCreated + givenMaxSlippage(1) // 0.01% + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _baseTokensToDeposit) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _baseTokensToDeposit) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_greaterThanMaxSlippage_reverts() + public + givenCallbackIsCreated + givenMaxSlippage(0) // 0% + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _baseTokensToDeposit) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _baseTokensToDeposit) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + UniswapV3DirectToLiquidity.Callback_Slippage.selector, + address(_quoteToken), + 19_999_999_999_999_999_999, // Hardcoded + _quoteTokensToDeposit + ); + vm.expectRevert(err); + + _performOnSettle(); + } + + function test_givenVesting() + public + givenLinearVestingModuleIsInstalled + givenCallbackIsCreated + givenVestingStart(_AUCTION_CONCLUSION + 1) + givenVestingExpiry(_AUCTION_CONCLUSION + 2) + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _baseTokensToDeposit) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _baseTokensToDeposit) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_givenVesting_whenRecipientIsNotSeller() + public + givenLinearVestingModuleIsInstalled + givenCallbackIsCreated + givenVestingStart(_AUCTION_CONCLUSION + 1) + givenVestingExpiry(_AUCTION_CONCLUSION + 2) + whenRecipientIsNotSeller + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _baseTokensToDeposit) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _baseTokensToDeposit) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_givenVesting_redemption() + public + givenLinearVestingModuleIsInstalled + givenCallbackIsCreated + givenVestingStart(_AUCTION_CONCLUSION + 1) + givenVestingExpiry(_AUCTION_CONCLUSION + 2) + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _baseTokensToDeposit) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _baseTokensToDeposit) + { + _performOnSettle(); + + // Warp to the end of the vesting period + vm.warp(_AUCTION_CONCLUSION + 3); + + // Check that there is a vested token balance + uint256 tokenId = _getVestingTokenId(); + uint256 redeemable = _linearVesting.redeemable(_SELLER, tokenId); + assertGt(redeemable, 0, "redeemable"); + + // Redeem the vesting tokens + vm.prank(_SELLER); + _linearVesting.redeemMax(tokenId); + + // Assert that the LP token has been transferred to the seller + GUniPool pool = _getGUniPool(); + assertEq(pool.balanceOf(_SELLER), pool.totalSupply(), "seller: LP token balance"); + } + + function test_withdrawLpToken() + public + givenCallbackIsCreated + givenOnCreate + givenPoolIsCreated + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + // Get the pools deployed by the DTL callback + address[] memory pools = _gUniFactory.getPools(_dtlAddress); + assertEq(pools.length, 1, "pools length"); + GUniPool pool = GUniPool(pools[0]); + + IUniswapV3Pool uniPool = _getPool(); + + // Withdraw the LP token + uint256 sellerBalance = pool.balanceOf(_SELLER); + vm.prank(_SELLER); + pool.burn(sellerBalance, _SELLER); + + // Check the balances + assertEq(pool.balanceOf(_SELLER), 0, "seller: LP token balance"); + assertEq(_quoteToken.balanceOf(_SELLER), _proceeds - 1, "seller: quote token balance"); + assertEq(_baseToken.balanceOf(_SELLER), _capacityUtilised - 1, "seller: base token balance"); + assertEq(_quoteToken.balanceOf(pools[0]), 0, "pool: quote token balance"); + assertEq(_baseToken.balanceOf(pools[0]), 0, "pool: base token balance"); + assertEq(_quoteToken.balanceOf(_dtlAddress), 0, "DTL: quote token balance"); + assertEq(_baseToken.balanceOf(_dtlAddress), 0, "DTL: base token balance"); + // There is a rounding error when burning the LP token, which leaves dust in the pool + assertEq(_quoteToken.balanceOf(address(uniPool)), 1, "uni pool: quote token balance"); + assertEq(_baseToken.balanceOf(address(uniPool)), 1, "uni pool: base token balance"); + } + + function test_givenInsufficientBaseTokenBalance_reverts() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised - 1) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + BaseDirectToLiquidity.Callback_InsufficientBalance.selector, + address(_baseToken), + _SELLER, + _baseTokensToDeposit, + _baseTokensToDeposit - 1 + ); + vm.expectRevert(err); + + _performOnSettle(); + } + + function test_givenInsufficientBaseTokenAllowance_reverts() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised - 1) + { + // Expect revert + vm.expectRevert("TRANSFER_FROM_FAILED"); + + _performOnSettle(); + } + + function test_success() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_success_multiple() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_NOT_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_NOT_SELLER, _dtlAddress, _capacityUtilised) + { + // Create second lot + uint96 lotIdTwo = _createLot(_NOT_SELLER); + + _performOnSettle(lotIdTwo); + + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_whenRecipientIsNotSeller() + public + givenCallbackIsCreated + whenRecipientIsNotSeller + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + _assertPoolState(_sqrtPriceX96); + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_auctionCompleted_onCreate_reverts() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + // Expect revert + // BaseCallback determines if the lot has already been registered + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_InvalidParams.selector); + vm.expectRevert(err); + + // Try to call onCreate again + _performOnCreate(); + } + + function test_auctionCompleted_onCurate_reverts() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + // Expect revert + // BaseDirectToLiquidity determines if the lot has already been completed + bytes memory err = + abi.encodeWithSelector(BaseDirectToLiquidity.Callback_AlreadyComplete.selector); + vm.expectRevert(err); + + // Try to call onCurate + _performOnCurate(_curatorPayout); + } + + function test_auctionCompleted_onCancel_reverts() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + // Expect revert + // BaseDirectToLiquidity determines if the lot has already been completed + bytes memory err = + abi.encodeWithSelector(BaseDirectToLiquidity.Callback_AlreadyComplete.selector); + vm.expectRevert(err); + + // Try to call onCancel + _performOnCancel(); + } + + function test_auctionCompleted_onSettle_reverts() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + { + _performOnSettle(); + + // Expect revert + // BaseDirectToLiquidity determines if the lot has already been completed + bytes memory err = + abi.encodeWithSelector(BaseDirectToLiquidity.Callback_AlreadyComplete.selector); + vm.expectRevert(err); + + // Try to call onSettle + _performOnSettle(); + } + + function uniswapV3MintCallback(uint256, uint256 amount1Owed, bytes calldata) external { + console2.log("Minting additional quote tokens", amount1Owed); + _additionalQuoteTokensMinted += amount1Owed; + + // Transfer the quote tokens + _quoteToken.mint(msg.sender, amount1Owed); + } + + function _mintPosition(int24 tickLower_, int24 tickUpper_) internal { + // Using PoC: https://github.com/GuardianAudits/axis-1/pull/4/files + IUniswapV3Pool pool = _getPool(); + + pool.mint(address(this), tickLower_, tickUpper_, 1e18, ""); + } + + function uniswapV3SwapCallback(int256, int256, bytes memory) external pure { + return; + } + + function _swap( + uint160 sqrtPrice_ + ) internal { + IUniswapV3Pool pool = _getPool(); + + pool.swap(address(this), true, 1, sqrtPrice_, ""); + } + + function test_existingReservesAtHigherPoolTick() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + givenPoolIsCreatedAndInitialized(_sqrtPriceX96) + { + // Assert the pool price + int24 poolTick; + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 6931, "pool tick after mint"); // Original active tick + + // Swap at a tick higher than the anchor range + IUniswapV3Pool pool = _getPool(); + pool.swap(address(this), false, 1, TickMath.getSqrtRatioAtTick(60_000), ""); + + // Assert that the pool tick has moved higher + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 60_000, "pool tick after swap"); + + // Provide reserve tokens to the pool at a tick higher than the original active tick and lower than the new active tick + _mintPosition(7200, 7200 + _getPool().tickSpacing()); + + // Perform callback + _performOnSettle(); + + // Assert that the pool tick has corrected + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 6931, "pool tick after settlement"); // Ends up rounded to the tick spacing + + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + // _assertQuoteTokenBalance(); // Difficult to calculate the exact balance, given the swaps + // _assertBaseTokenBalance(); // Difficult to calculate the exact balance, given the swaps + _assertApprovals(); + } + + function test_existingReservesAtHigherPoolTick_noLiquidity() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + givenPoolIsCreatedAndInitialized(_sqrtPriceX96) + { + // Assert the pool price + int24 poolTick; + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 6931, "pool tick after mint"); // Original active tick + + // Swap at a tick higher than the active tick + IUniswapV3Pool pool = _getPool(); + pool.swap(address(this), false, 1, TickMath.getSqrtRatioAtTick(60_000), ""); + + // Assert that the pool tick has moved higher + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 60_000, "pool tick after swap"); + + // Do not mint any liquidity above the previous active tick + + // Perform callback + _performOnSettle(); + + // Assert that the pool tick has corrected + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 6931, "pool tick after settlement"); // Ends up rounded to the tick spacing + + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_existingReservesAtLowerPoolTick() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + givenPoolIsCreatedAndInitialized(_sqrtPriceX96) + { + // Provide reserve tokens to the pool at a lower tick + _mintPosition(-60_000 - _getPool().tickSpacing(), -60_000); + + // Assert the pool price + int24 poolTick; + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 6931, "pool tick after mint"); // Original active tick + + // Swap at a tick lower than the active tick + _swap(TickMath.getSqrtRatioAtTick(-60_000)); + + // Assert that the pool price has moved lower + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, -60_001, "pool tick after swap"); + + // Perform callback + _performOnSettle(); + + // Assert that the pool tick has corrected + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 6931, "pool tick after settlement"); // Ends up rounded to the tick spacing + + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } + + function test_existingReservesAtLowerPoolTick_noLiquidity() + public + givenCallbackIsCreated + givenOnCreate + setCallbackParameters(_PROCEEDS, _REFUND) + givenAddressHasQuoteTokenBalance(_dtlAddress, _proceeds) + givenAddressHasBaseTokenBalance(_SELLER, _capacityUtilised) + givenAddressHasBaseTokenAllowance(_SELLER, _dtlAddress, _capacityUtilised) + givenPoolIsCreatedAndInitialized(_sqrtPriceX96) + { + // Don't mint any liquidity + + // Assert the pool price + int24 poolTick; + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 6931, "pool tick after mint"); // Original active tick + + // Swap at a tick lower than the active tick + _swap(TickMath.getSqrtRatioAtTick(-60_000)); + + // Assert that the pool price has moved lower + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, -60_000, "pool tick after swap"); + + // Perform callback + _performOnSettle(); + + // Assert that the pool tick has corrected + (, poolTick,,,,,) = _getPool().slot0(); + assertEq(poolTick, 6931, "pool tick after settlement"); // Ends up rounded to the tick spacing + + _assertLpTokenBalance(); + _assertVestingTokenBalance(); + _assertQuoteTokenBalance(); + _assertBaseTokenBalance(); + _assertApprovals(); + } +} diff --git a/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/setMerkleRoot.t.sol b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/setMerkleRoot.t.sol new file mode 100644 index 00000000..11bf61cb --- /dev/null +++ b/test/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist/setMerkleRoot.t.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {UniswapV3DirectToLiquidityWithAllocatedAllowlistTest} from + "./UniswapV3DTLWithAllocatedAllowlistTest.sol"; +import {UniswapV3DTLWithAllocatedAllowlist} from + "src/callbacks/liquidity/UniswapV3DTLWithAllocatedAllowlist.sol"; +import {BaseCallback} from "@axis-core-1.0.4/bases/BaseCallback.sol"; +import {BaseDirectToLiquidity} from "src/callbacks/liquidity/BaseDTL.sol"; +import {IUniswapV3DTLWithAllocatedAllowlist} from + "src/callbacks/liquidity/IUniswapV3DTLWithAllocatedAllowlist.sol"; + +contract UniswapV3DTLWithAllocatedAllowlistSetMerkleRootTest is + UniswapV3DirectToLiquidityWithAllocatedAllowlistTest +{ + event MerkleRootSet(uint96 lotId, bytes32 merkleRoot); + + // when the auction has not been registered + // [X] it reverts + // when the caller is not the seller + // [X] it reverts + // when the auction has been completed + // [ X] it reverts + // [X] the merkle root is updated and an event is emitted + + function test_auctionNotRegistered_reverts() public givenCallbackIsCreated { + // Expect revert + bytes memory err = abi.encodeWithSelector( + IUniswapV3DTLWithAllocatedAllowlist.Callback_InvalidState.selector + ); + vm.expectRevert(err); + + // Call the callback + vm.prank(_SELLER); + _dtl.setMerkleRoot(_lotId, _MERKLE_ROOT); + } + + function test_callerIsNotSeller_reverts() public givenCallbackIsCreated givenOnCreate { + // Expect revert + bytes memory err = abi.encodeWithSelector(BaseCallback.Callback_NotAuthorized.selector); + vm.expectRevert(err); + + // Call the callback + vm.prank(address(_auctionHouse)); + _dtl.setMerkleRoot(_lotId, _MERKLE_ROOT); + } + + function test_auctionCompleted_reverts() public givenCallbackIsCreated givenOnCreate { + _performOnCancel(); + + // Expect revert + bytes memory err = + abi.encodeWithSelector(BaseDirectToLiquidity.Callback_AlreadyComplete.selector); + vm.expectRevert(err); + + // Call the callback + vm.prank(_SELLER); + _dtl.setMerkleRoot(_lotId, _MERKLE_ROOT); + } + + function test_success() public givenCallbackIsCreated givenOnCreate { + // Expect event + vm.expectEmit(true, true, true, true); + emit MerkleRootSet(_lotId, _MERKLE_ROOT); + + // Call the callback + vm.prank(_SELLER); + _dtl.setMerkleRoot(_lotId, _MERKLE_ROOT); + + // Assert the merkle root is updated + assertEq(_dtl.lotMerkleRoot(_lotId), _MERKLE_ROOT); + } +} diff --git a/test/invariant/Setup.sol b/test/invariant/Setup.sol index c1b62db3..cbd6206a 100644 --- a/test/invariant/Setup.sol +++ b/test/invariant/Setup.sol @@ -195,9 +195,22 @@ abstract contract Setup is Test, Permit2User, WithSalts, TestConstants { _quoteToken = new MockERC20("Quote Token", "QT", 18); _baseToken = new MockERC20("Base Token", "BT", 18); + Callbacks.Permissions memory permissions = Callbacks.Permissions({ + onCreate: true, + onCancel: true, + onCurate: true, + onPurchase: false, + onBid: false, + onSettle: true, + receiveQuoteTokens: true, + sendBaseTokens: false + }); + bytes memory constructorArgs = abi.encodePacked( type(UniswapV2DirectToLiquidity).creationCode, - abi.encode(address(_auctionHouse), address(_uniV2Factory), address(_uniV2Router)) + abi.encode( + address(_auctionHouse), address(_uniV2Factory), address(_uniV2Router), permissions + ) ); string[] memory uniswapV2Inputs = new string[](7); @@ -213,7 +226,7 @@ abstract contract Setup is Test, Permit2User, WithSalts, TestConstants { bytes32 uniswapV2Salt = abi.decode(uniswapV2Res, (bytes32)); _dtlV2 = new UniswapV2DirectToLiquidity{salt: uniswapV2Salt}( - address(_auctionHouse), address(_uniV2Factory), address(_uniV2Router) + address(_auctionHouse), address(_uniV2Factory), address(_uniV2Router), permissions ); _dtlV2Address = address(_dtlV2); @@ -232,7 +245,9 @@ abstract contract Setup is Test, Permit2User, WithSalts, TestConstants { bytes memory v3SaltArgs = abi.encodePacked( type(UniswapV3DirectToLiquidity).creationCode, - abi.encode(address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory)) + abi.encode( + address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory), permissions + ) ); string[] memory uniswapV3Inputs = new string[](7); @@ -248,7 +263,7 @@ abstract contract Setup is Test, Permit2User, WithSalts, TestConstants { bytes32 uniswapV3Salt = abi.decode(uniswapV3Res, (bytes32)); _dtlV3 = new UniswapV3DirectToLiquidity{salt: uniswapV3Salt}( - address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory) + address(_auctionHouse), address(_uniV3Factory), address(_gUniFactory), permissions ); _dtlV3Address = address(_dtlV3);