From 2df6a18cd9de06a9a6032e2d842754b0de584396 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 30 Apr 2024 15:54:56 -0500 Subject: [PATCH 01/69] forge install: prb-math v4.0.2 --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 81318a919..14141220e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = https://github.com/transmissions11/solmate [submodule "lib/prb-math"] path = lib/prb-math - url = https://github.com/PaulRBerg/prb-math + url = https://github.com/Oighty/prb-math [submodule "lib/uniswap-v3-periphery"] path = lib/uniswap-v3-periphery url = https://github.com/uniswap/v3-periphery From aa3fa817e9dd7d8018154631f9226feb82ec712f Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 30 Apr 2024 19:54:02 -0500 Subject: [PATCH 02/69] git: use modified prb-math --- lib/prb-math | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prb-math b/lib/prb-math index 9dc06519f..16419e568 160000 --- a/lib/prb-math +++ b/lib/prb-math @@ -1 +1 @@ -Subproject commit 9dc06519f3b9f1659fec7d396da634fe690f660c +Subproject commit 16419e5686504e15f973ebe73c3a75bd618d6ca4 From f8fa62f8ec041999d141a8c427a9de6352566b0d Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 30 Apr 2024 20:35:41 -0500 Subject: [PATCH 03/69] chore: update install script --- install.sh | 2 +- lib/prb-math | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index d24d2f687..18290b87d 100755 --- a/install.sh +++ b/install.sh @@ -21,7 +21,7 @@ cd lib/forge-std/ && git checkout v1.7.1 && cd ../.. echo "" echo "prb-math" -cd lib/prb-math/ && git checkout v4.0.2 && cd ../.. +cd lib/prb-math/ && git checkout main && cd ../.. echo "" echo "solady" diff --git a/lib/prb-math b/lib/prb-math index 16419e568..dd984cbde 160000 --- a/lib/prb-math +++ b/lib/prb-math @@ -1 +1 @@ -Subproject commit 16419e5686504e15f973ebe73c3a75bd618d6ca4 +Subproject commit dd984cbde943d8371cb6fe7c58e9be1af945aefb From 60253533b21d506a57d749481672fc81e0cc453c Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 30 Apr 2024 20:35:54 -0500 Subject: [PATCH 04/69] feat: initial GDA implementation --- src/modules/auctions/GDA.sol | 279 +++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 src/modules/auctions/GDA.sol diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol new file mode 100644 index 000000000..95c9936ed --- /dev/null +++ b/src/modules/auctions/GDA.sol @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: BSL-1.1 +pragma solidity 0.8.19; + +// Interfaces +import {IAtomicAuction} from "src/interfaces/IAtomicAuction.sol"; + +// Protocol dependencies +import {Module} from "src/modules/Modules.sol"; +import {AuctionModule} from "src/modules/Auction.sol"; +import {Veecode, toVeecode} from "src/modules/Modules.sol"; +import {AtomicAuctionModule} from "src/modules/auctions/AtomicAuctionModule.sol"; + +// External libraries +import {UD60x18, ud, convert, ZERO, UNIT, uUNIT} from "lib/prb-math/src/UD60x18.sol"; +import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol"; + +/// @notice Continuous Gradual Dutch Auction (GDA) module with exponential decay and a minimum price. +contract GradualDutchAuction is AtomicAuctionModule { + using FixedPointMathLib for uint256; + + /// @notice Auction pricing data + struct AuctionData { + uint256 equilibriumPrice; // price at which the auction is balanced + uint256 minimumPrice; // minimum price for the auction + uint48 lastAuctionStart; + UD60x18 decayConstant; // speed at which the price decays, as UD60x18. + UD60x18 emissionsRate; // number of tokens released per second, as UD60x18. Calculated as capacity / duration. + } + + struct GDAParams { + uint256 equilibriumPrice; + uint256 minimumPrice; + UD60x18 decayConstant; + } + + // ========== STATE VARIABLES ========== // + + mapping(uint256 id => AuctionData data) public auctionData; + + // ========== SETUP ========== // + + constructor(address auctionHouse_) AuctionModule(auctionHouse_) { + // TODO think about appropriate minimum for auction duration + minAuctionDuration = 1 days; + } + + /// @inheritdoc Module + function VEECODE() public pure override returns (Veecode) { + return toVeecode("01GDA"); + } + + // ========== AUCTION ========== // + + function _auction(uint96 lotId_, Lot memory lot_, bytes memory params_) internal override { + // Decode implementation parameters + GDAParams memory params = abi.decode(params_, (GDAParams)); + + // Validate parameters + // Equilibrium Price must not be zero and greater than minimum price (which can be zero) + if (params.equilibriumPrice == 0 || params.equilibriumPrice <= params.minimumPrice) { + revert Auction_InvalidParams(); + } + + // Capacity must be in base token + if (lot_.capacityInQuote) revert Auction_InvalidParams(); + + // Minimum price can be zero, but the equations default back to the basic GDA implementation + + // Validate the decay constant + // Cannot be zero + // TODO do we need to set tighter bounds on this? + if (params.decayConstant == ZERO) revert Auction_InvalidParams(); + + // Calculate emissions rate + UD60x18 duration = convert(uint256(lot_.conclusion - lot_.start)); + UD60x18 emissionsRate = + ud(lot_.capacity.fullMulDiv(uUNIT, 10 ** lot_.baseTokenDecimals)).div(duration); + + // Store auction data + AuctionData storage data = auctionData[lotId_]; + data.equilibriumPrice = params.equilibriumPrice; + data.minimumPrice = params.minimumPrice; + data.decayConstant = params.decayConstant; + data.emissionsRate = emissionsRate; + } + + // Do not need to do anything extra here + function _cancelAuction(uint96 lotId_) internal override {} + + // ========== PURCHASE ========== // + + function _purchase( + uint96 lotId_, + uint256 amount_, + bytes calldata + ) internal override returns (uint256 payout, bytes memory auctionOutput) { + // Calculate the payout and emissions + uint48 secondsOfEmissions; + (payout, secondsOfEmissions) = _payoutAndEmissionsFor(lotId_, amount_); + + // Update last auction start with seconds of emissions + // Do not have to check that too many seconds have passed here + // since payout is checked against capacity in the top-level function + auctionData[lotId_].lastAuctionStart += secondsOfEmissions; + + // Return the payout and emissions + return (payout, bytes("")); + } + + // ========== VIEW FUNCTIONS ========== // + + // For Continuos GDAs with exponential decay, the price of a given token t seconds after being emitted is: + // q(t) = (q0 - qm) * e^(-k*t) + qm + // where k is the decay constant, q0 is the initial price, and qm is the minimum price + // Integrating this function from the last auction start time for a particular number of tokens, + // gives the multiplier for the token price to determine amount of quote tokens required to purchase: + // Q(T) = ((q0 - qm) * (e^(k*P) - 1)) / ke^(k*T) + (qm * P) / r + // where T is the time since the last auction start, P is the number of payout tokens to purchase, + // and r is the emissions rate (number of tokens released per second). + // + // If qm is 0, then the equation simplifies to: + // q(t) = q0 * e^(-k*t) + // Integrating this function from the last auction start time for a particular number of tokens, + // gives the multiplier for the token price to determine amount of quote tokens required to purchase: + // Q(T) = (q0 * (e^(k*P) - 1)) / ke^(k*T) + // where T is the time since the last auction start, P is the number of payout tokens to purchase. + function priceFor(uint96 lotId_, uint256 payout_) public view override returns (uint256) { + Lot memory lot = lotData[lotId_]; + AuctionData memory auction = auctionData[lotId_]; + + // Convert payout to UD60x18. We scale first to 18 decimals from the payout token decimals + uint256 baseTokenScale = 10 ** lot.baseTokenDecimals; + UD60x18 payout = ud(payout_.mulDiv(uUNIT, baseTokenScale)); + + // Calculate time since last auction start + UD60x18 timeSinceLastAuctionStart = + convert(block.timestamp - uint256(auction.lastAuctionStart)); + + // Subtract the minimum price from the equilibrium price + // In the auction creation, we checked that the equilibrium price is greater than the minimum price + // Scale the result to 18 decimals, set as numerator factor 1 + uint256 quoteTokenScale = 10 ** lot.quoteTokenDecimals; + UD60x18 num1 = + ud((auction.equilibriumPrice - auction.minimumPrice).fullMulDiv(uUNIT, quoteTokenScale)); + + // Calculate the second numerator factor + UD60x18 num2 = auction.decayConstant.mul(payout).exp().sub(UNIT); + + // Calculate the denominator + UD60x18 denominator = + auction.decayConstant.mul(timeSinceLastAuctionStart).exp().mul(auction.decayConstant); + + // Calculate first term + UD60x18 result = num1.mul(num2).div(denominator); + + // If minimum price is zero, then the first term is the result, otherwise we add the second term + if (auction.minimumPrice > 0) { + UD60x18 minPrice = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + result = result + minPrice.mul(payout).div(auction.emissionsRate); + } + + // Scale price back to quote token decimals + uint256 amount = result.intoUint256().fullMulDiv(quoteTokenScale, uUNIT); + + // Check that amount in or payout do not exceed remaining capacity + if (lot.capacityInQuote ? amount > lot.capacity : payout_ > lot.capacity) { + revert Auction_InsufficientCapacity(); + } + + return amount; + } + + function payoutFor(uint96 lotId_, uint256 amount_) public view override returns (uint256) { + // Calculate the payout and emissions + uint256 payout; + (payout,) = _payoutAndEmissionsFor(lotId_, amount_); + + // Check that payout does not exceed remaining capacity + if (payout > lotData[lotId_].capacity) { + revert Auction_InsufficientCapacity(); + } + + return payout; + } + + // Two cases: + // + // 1. Minimum price is zero + // P = (r * ln((Q * k * e^(k*T) / q0) + 1)) / k + // where P is the number of payout tokens, Q is the number of quote tokens, + // r is the emissions rate, k is the decay constant, q0 is the equilibrium price of the auction, + // and T is the time since the last auction start + // + // 2. Minimum price is not zero + // P = (r * (k * Q / qm + C - W(C e^(k * Q / qm + C)))) / k + // where P is the number of payout tokens, Q is the number of quote tokens, + // r is the emissions rate, k is the decay constant, qm is the minimum price of the auction, + // q0 is the equilibrium price of the auction, T is the time since the last auction start, + // C = (q0 - qm)/(qm * e^(k * T)), and W is the Lambert-W function (productLn). + function _payoutAndEmissionsFor( + uint96 lotId_, + uint256 amount_ + ) internal view returns (uint256, uint48) { + Lot memory lot = lotData[lotId_]; + AuctionData memory auction = auctionData[lotId_]; + + // Get quote token scale and convert equilibrium price to 18 decimals + uint256 quoteTokenScale = 10 ** lot.quoteTokenDecimals; + UD60x18 q0 = ud(auction.equilibriumPrice.fullMulDiv(uUNIT, quoteTokenScale)); + + // Scale amount to 18 decimals + UD60x18 amount = ud(amount_.mulDiv(uUNIT, quoteTokenScale)); + + // Factors are calculated in a certain order to avoid precision loss + UD60x18 payout; + if (auction.minimumPrice == 0) { + // Calculate the exponential factor + UD60x18 ekt = auction.decayConstant.mul( + convert(block.timestamp - uint256(auction.lastAuctionStart)) + ).exp(); + + // Calculate the logarithm + // Operand is guaranteed to be >= 1, so the result is positive + UD60x18 logFactor = amount.mul(auction.decayConstant).mul(ekt).div(q0).add(UNIT).ln(); + + // Calculate the payout + payout = auction.emissionsRate.mul(logFactor).div(auction.decayConstant); + } else { + // Convert minimum price to 18 decimals + UD60x18 qm = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + + // Calculate first term: (k * Q) / qm + UD60x18 f = auction.decayConstant.mul(amount).div(qm); + + // Calculate second term aka C: (q0 - qm)/(qm * e^(k * T)) + UD60x18 c = q0.sub(qm).div( + auction.decayConstant.mul( + convert(block.timestamp - uint256(auction.lastAuctionStart)) + ).exp().mul(qm) + ); + + // Calculate the third term: W(C e^(k * Q / qm + C)) + UD60x18 w = c.add(f).exp().mul(c).productLn(); + + // Calculate payout + // The intermediate term (f + c - w) cannot underflow because + // firstTerm + c - thirdTerm >= 0 for all amounts >= 0. + // + // Proof: + // 1. k > 0, Q >= 0, qm > 0 => f >= 0 + // 2. q0 > qm, qm > 0, k >= 0, T >= 0 => c >= 0 + // 3. f + c = W((f + c) * e^(f + c)) + // 4. 1 & 2 => f + c >= 0, f + c >= f, f + c >= c + // 5. W(x) is monotonically increasing for x >= 0 + // 6. 4 & 5 => W((f + c) * e^(f + c)) >= W(c * e^(f + c)) + // 7. 3 & 6 => f + c >= W(c * e^(f + c)) + // QED + payout = auction.emissionsRate.mul(f.add(c).sub(w)).div(auction.decayConstant); + } + + // Calculate seconds of emissions from payout + // TODO need to think about overflows on this cast + // emissionsRate has a floor based on the minimum auction duration + uint48 secondsOfEmissions = uint48(payout.div(auction.emissionsRate).intoUint256()); + + // Scale payout to payout token decimals and return + return (payout.intoUint256().mulDiv(10 ** lot.baseTokenDecimals, uUNIT), secondsOfEmissions); + } + + function maxPayout(uint96 lotId_) external view override returns (uint256) { + // The max payout is the remaining capacity of the lot + return lotData[lotId_].capacity; + } + + function maxAmountAccepted(uint96 lotId_) external view override returns (uint256) { + // The max amount accepted is the price to purchase the remaining capacity of the lot + return priceFor(lotId_, lotData[lotId_].capacity); + } +} From bf170a78a6e3546e2c06048f9cd4f509d96d40dd Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 30 Apr 2024 21:18:19 -0500 Subject: [PATCH 05/69] fix: minor GDA updates and add'l TODOs --- src/modules/auctions/GDA.sol | 38 ++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 95c9936ed..90fbb7d39 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -114,7 +114,7 @@ contract GradualDutchAuction is AtomicAuctionModule { // where k is the decay constant, q0 is the initial price, and qm is the minimum price // Integrating this function from the last auction start time for a particular number of tokens, // gives the multiplier for the token price to determine amount of quote tokens required to purchase: - // Q(T) = ((q0 - qm) * (e^(k*P) - 1)) / ke^(k*T) + (qm * P) / r + // Q(T) = ((q0 - qm) * (e^((k*P)/r) - 1)) / ke^(k*T) + (qm * P) / r // where T is the time since the last auction start, P is the number of payout tokens to purchase, // and r is the emissions rate (number of tokens released per second). // @@ -122,17 +122,23 @@ contract GradualDutchAuction is AtomicAuctionModule { // q(t) = q0 * e^(-k*t) // Integrating this function from the last auction start time for a particular number of tokens, // gives the multiplier for the token price to determine amount of quote tokens required to purchase: - // Q(T) = (q0 * (e^(k*P) - 1)) / ke^(k*T) + // Q(T) = (q0 * (e^((k*P)/r) - 1)) / ke^(k*T) // where T is the time since the last auction start, P is the number of payout tokens to purchase. function priceFor(uint96 lotId_, uint256 payout_) public view override returns (uint256) { Lot memory lot = lotData[lotId_]; AuctionData memory auction = auctionData[lotId_]; + // Check that payout does not exceed remaining capacity + if (payout_ > lot.capacity) { + revert Auction_InsufficientCapacity(); + } + // Convert payout to UD60x18. We scale first to 18 decimals from the payout token decimals uint256 baseTokenScale = 10 ** lot.baseTokenDecimals; UD60x18 payout = ud(payout_.mulDiv(uUNIT, baseTokenScale)); // Calculate time since last auction start + // TODO handle case where lastAuctionStart is greater than block.timestamp UD60x18 timeSinceLastAuctionStart = convert(block.timestamp - uint256(auction.lastAuctionStart)); @@ -143,10 +149,10 @@ contract GradualDutchAuction is AtomicAuctionModule { UD60x18 num1 = ud((auction.equilibriumPrice - auction.minimumPrice).fullMulDiv(uUNIT, quoteTokenScale)); - // Calculate the second numerator factor - UD60x18 num2 = auction.decayConstant.mul(payout).exp().sub(UNIT); + // Calculate the second numerator factor: e^((k*P)/r) - 1 + UD60x18 num2 = auction.decayConstant.mul(payout).div(auction.emissionsRate).exp().sub(UNIT); - // Calculate the denominator + // Calculate the denominator: ke^(k*T) UD60x18 denominator = auction.decayConstant.mul(timeSinceLastAuctionStart).exp().mul(auction.decayConstant); @@ -162,11 +168,6 @@ contract GradualDutchAuction is AtomicAuctionModule { // Scale price back to quote token decimals uint256 amount = result.intoUint256().fullMulDiv(quoteTokenScale, uUNIT); - // Check that amount in or payout do not exceed remaining capacity - if (lot.capacityInQuote ? amount > lot.capacity : payout_ > lot.capacity) { - revert Auction_InsufficientCapacity(); - } - return amount; } @@ -215,6 +216,8 @@ contract GradualDutchAuction is AtomicAuctionModule { UD60x18 payout; if (auction.minimumPrice == 0) { // Calculate the exponential factor + // TODO lastAuctionStart may be greater than block.timestamp if the auction is ahead of schedule + // Need to handle this case UD60x18 ekt = auction.decayConstant.mul( convert(block.timestamp - uint256(auction.lastAuctionStart)) ).exp(); @@ -226,6 +229,11 @@ contract GradualDutchAuction is AtomicAuctionModule { // Calculate the payout payout = auction.emissionsRate.mul(logFactor).div(auction.decayConstant); } else { + // TODO think about refactoring to avoid precision loss + + // TODO do we check if amount divided by minimum price is greater than capacity? + // May help with some overflow situations below. + // Convert minimum price to 18 decimals UD60x18 qm = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); @@ -233,6 +241,8 @@ contract GradualDutchAuction is AtomicAuctionModule { UD60x18 f = auction.decayConstant.mul(amount).div(qm); // Calculate second term aka C: (q0 - qm)/(qm * e^(k * T)) + // TODO lastAuctionStart may be greater than block.timestamp if the auction is ahead of schedule + // Need to handle this case UD60x18 c = q0.sub(qm).div( auction.decayConstant.mul( convert(block.timestamp - uint256(auction.lastAuctionStart)) @@ -248,7 +258,7 @@ contract GradualDutchAuction is AtomicAuctionModule { // // Proof: // 1. k > 0, Q >= 0, qm > 0 => f >= 0 - // 2. q0 > qm, qm > 0, k >= 0, T >= 0 => c >= 0 + // 2. q0 > qm, qm > 0 => c >= 0 // 3. f + c = W((f + c) * e^(f + c)) // 4. 1 & 2 => f + c >= 0, f + c >= f, f + c >= c // 5. W(x) is monotonically increasing for x >= 0 @@ -260,7 +270,11 @@ contract GradualDutchAuction is AtomicAuctionModule { // Calculate seconds of emissions from payout // TODO need to think about overflows on this cast - // emissionsRate has a floor based on the minimum auction duration + // emissionsRate has a max based on the minimum auction duration + // another way to arrange the equations is that s = (f + c - w) / k + // it can be a precursor to the payout calculation + // however, this doesn't solve the overflow problem + // my hunch is that T being less than auction duration may be helpful in determining a bound uint48 secondsOfEmissions = uint48(payout.div(auction.emissionsRate).intoUint256()); // Scale payout to payout token decimals and return From ef82640575900f8a72c2686151e5c3af8acbf833 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 30 Apr 2024 21:22:45 -0500 Subject: [PATCH 06/69] fix: change gda timestamps to be uint256 --- src/modules/auctions/GDA.sol | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 90fbb7d39..2ac237574 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -22,7 +22,7 @@ contract GradualDutchAuction is AtomicAuctionModule { struct AuctionData { uint256 equilibriumPrice; // price at which the auction is balanced uint256 minimumPrice; // minimum price for the auction - uint48 lastAuctionStart; + uint256 lastAuctionStart; // time that the last un-purchased auction started, may be in the future UD60x18 decayConstant; // speed at which the price decays, as UD60x18. UD60x18 emissionsRate; // number of tokens released per second, as UD60x18. Calculated as capacity / duration. } @@ -71,6 +71,8 @@ contract GradualDutchAuction is AtomicAuctionModule { // TODO do we need to set tighter bounds on this? if (params.decayConstant == ZERO) revert Auction_InvalidParams(); + // TODO other validation checks? + // Calculate emissions rate UD60x18 duration = convert(uint256(lot_.conclusion - lot_.start)); UD60x18 emissionsRate = @@ -82,6 +84,7 @@ contract GradualDutchAuction is AtomicAuctionModule { data.minimumPrice = params.minimumPrice; data.decayConstant = params.decayConstant; data.emissionsRate = emissionsRate; + data.lastAuctionStart = uint256(lot_.start); } // Do not need to do anything extra here @@ -95,7 +98,7 @@ contract GradualDutchAuction is AtomicAuctionModule { bytes calldata ) internal override returns (uint256 payout, bytes memory auctionOutput) { // Calculate the payout and emissions - uint48 secondsOfEmissions; + uint256 secondsOfEmissions; (payout, secondsOfEmissions) = _payoutAndEmissionsFor(lotId_, amount_); // Update last auction start with seconds of emissions @@ -201,7 +204,7 @@ contract GradualDutchAuction is AtomicAuctionModule { function _payoutAndEmissionsFor( uint96 lotId_, uint256 amount_ - ) internal view returns (uint256, uint48) { + ) internal view returns (uint256, uint256) { Lot memory lot = lotData[lotId_]; AuctionData memory auction = auctionData[lotId_]; @@ -218,9 +221,8 @@ contract GradualDutchAuction is AtomicAuctionModule { // Calculate the exponential factor // TODO lastAuctionStart may be greater than block.timestamp if the auction is ahead of schedule // Need to handle this case - UD60x18 ekt = auction.decayConstant.mul( - convert(block.timestamp - uint256(auction.lastAuctionStart)) - ).exp(); + UD60x18 ekt = + auction.decayConstant.mul(convert(block.timestamp - auction.lastAuctionStart)).exp(); // Calculate the logarithm // Operand is guaranteed to be >= 1, so the result is positive @@ -244,9 +246,8 @@ contract GradualDutchAuction is AtomicAuctionModule { // TODO lastAuctionStart may be greater than block.timestamp if the auction is ahead of schedule // Need to handle this case UD60x18 c = q0.sub(qm).div( - auction.decayConstant.mul( - convert(block.timestamp - uint256(auction.lastAuctionStart)) - ).exp().mul(qm) + auction.decayConstant.mul(convert(block.timestamp - auction.lastAuctionStart)).exp() + .mul(qm) ); // Calculate the third term: W(C e^(k * Q / qm + C)) @@ -269,13 +270,7 @@ contract GradualDutchAuction is AtomicAuctionModule { } // Calculate seconds of emissions from payout - // TODO need to think about overflows on this cast - // emissionsRate has a max based on the minimum auction duration - // another way to arrange the equations is that s = (f + c - w) / k - // it can be a precursor to the payout calculation - // however, this doesn't solve the overflow problem - // my hunch is that T being less than auction duration may be helpful in determining a bound - uint48 secondsOfEmissions = uint48(payout.div(auction.emissionsRate).intoUint256()); + uint256 secondsOfEmissions = payout.div(auction.emissionsRate).intoUint256(); // Scale payout to payout token decimals and return return (payout.intoUint256().mulDiv(10 ** lot.baseTokenDecimals, uUNIT), secondsOfEmissions); From 43ddff18c41f2eb25435d97561a71c29ef1773d3 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Wed, 1 May 2024 10:20:07 +0400 Subject: [PATCH 07/69] Remove prb-math submodule --- .gitmodules | 3 --- lib/prb-math | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/prb-math diff --git a/.gitmodules b/.gitmodules index 14141220e..398c9ce37 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate -[submodule "lib/prb-math"] - path = lib/prb-math - url = https://github.com/Oighty/prb-math [submodule "lib/uniswap-v3-periphery"] path = lib/uniswap-v3-periphery url = https://github.com/uniswap/v3-periphery diff --git a/lib/prb-math b/lib/prb-math deleted file mode 160000 index dd984cbde..000000000 --- a/lib/prb-math +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dd984cbde943d8371cb6fe7c58e9be1af945aefb From 7208c4b7d23ab2be4a7b9624519de351847be8b8 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Wed, 1 May 2024 10:25:18 +0400 Subject: [PATCH 08/69] Re-add prb-math --- .gitmodules | 3 +++ lib/prb-math | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/prb-math diff --git a/.gitmodules b/.gitmodules index 398c9ce37..9dcdc7d3f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "lib/uniswap-v2-core"] path = lib/uniswap-v2-core url = https://github.com/uniswap/v2-core +[submodule "lib/prb-math"] + path = lib/prb-math + url = https://github.com/Oighty/prb-math diff --git a/lib/prb-math b/lib/prb-math new file mode 160000 index 000000000..dd984cbde --- /dev/null +++ b/lib/prb-math @@ -0,0 +1 @@ +Subproject commit dd984cbde943d8371cb6fe7c58e9be1af945aefb From 3c75f34db69ea09b2976a34a673f6ac680e7bb14 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Wed, 1 May 2024 10:50:08 +0400 Subject: [PATCH 09/69] If the deployed address of a required contract (UniV2/V3 factory, GUni factory) changes, log the address and revert. Also consolidate the addresses used in tests and salt generation into one contract. --- script/salts/TestSaltConstants.sol | 15 +++++++ script/salts/TestSalts.s.sol | 14 ++---- script/salts/salts.json | 44 +++++++++---------- test/AtomicAuctionHouse/AuctionHouseTest.sol | 6 +-- test/BatchAuctionHouse/AuctionHouseTest.sol | 6 +-- .../CappedMerkleAllowlistAtomic.t.sol | 6 +-- .../CappedMerkleAllowlistBatch.t.sol | 6 +-- .../UniswapV2DTL/UniswapV2DTLTest.sol | 12 ++--- .../UniswapV3DTL/UniswapV3DTLTest.sol | 15 ++++--- 9 files changed, 68 insertions(+), 56 deletions(-) create mode 100644 script/salts/TestSaltConstants.sol diff --git a/script/salts/TestSaltConstants.sol b/script/salts/TestSaltConstants.sol new file mode 100644 index 000000000..f19fd1076 --- /dev/null +++ b/script/salts/TestSaltConstants.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +abstract contract TestSaltConstants { + address internal constant _OWNER = address(0x1); + address internal constant _AUCTION_HOUSE = address(0x000000000000000000000000000000000000000A); + + address internal constant _UNISWAP_V2_FACTORY = + address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); + address internal constant _UNISWAP_V2_ROUTER = + address(0x1EBC400fd43aC56937d4e14B8495B0f021e7c876); + address internal constant _UNISWAP_V3_FACTORY = + address(0xE70b554757175BAb9eD3245C7f2b387ef09889Bd); + address internal constant _GUNI_FACTORY = address(0x6c4f6A2E6b9AFB0406919979cE3570741BCb866A); +} diff --git a/script/salts/TestSalts.s.sol b/script/salts/TestSalts.s.sol index cb224a142..09d89b4fb 100644 --- a/script/salts/TestSalts.s.sol +++ b/script/salts/TestSalts.s.sol @@ -7,23 +7,15 @@ import {WithEnvironment} from "script/deploy/WithEnvironment.s.sol"; import {Permit2User} from "test/lib/permit2/Permit2User.sol"; import {WithSalts} from "script/salts/WithSalts.s.sol"; +import {TestSaltConstants} from "script/salts/TestSaltConstants.sol"; import {MockCallback} from "test/callbacks/MockCallback.sol"; import {Callbacks} from "src/lib/Callbacks.sol"; import {CappedMerkleAllowlist} from "src/callbacks/allowlists/CappedMerkleAllowlist.sol"; import {UniswapV2DirectToLiquidity} from "src/callbacks/liquidity/UniswapV2DTL.sol"; import {UniswapV3DirectToLiquidity} from "src/callbacks/liquidity/UniswapV3DTL.sol"; -contract TestSalts is Script, WithEnvironment, Permit2User, WithSalts { - // TODO shift into abstract contract that tests also inherit from - address internal constant _OWNER = address(0x1); - address internal constant _AUCTION_HOUSE = address(0x000000000000000000000000000000000000000A); - address internal constant _UNISWAP_V2_FACTORY = - address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); - address internal constant _UNISWAP_V2_ROUTER = - address(0x584A2a1F5eCdCDcB6c0616cd280a7Db89239872B); - address internal constant _UNISWAP_V3_FACTORY = - address(0x5573DCDCc96692D24938F4E440d99EFC3d7EDA04); - address internal constant _GUNI_FACTORY = address(0xe6b8030b2fd30ea9198D3F39AEeD4f448A2704f0); +contract TestSalts is Script, WithEnvironment, Permit2User, WithSalts, TestSaltConstants { + // See TestSaltConstants for UniV2 and UniV3 factory and router addresses string internal constant _MOCK_CALLBACK = "MockCallback"; string internal constant _CAPPED_MERKLE_ALLOWLIST = "CappedMerkleAllowlist"; diff --git a/script/salts/salts.json b/script/salts/salts.json index d94516819..ed4f4e353 100644 --- a/script/salts/salts.json +++ b/script/salts/salts.json @@ -12,33 +12,33 @@ "0xb0ffc6f92c2cb79c8186b0ab12b62ca54c6058b7d4ef7132ae4ef9e9e44cb25e": "0x261929eb8d9c1b58179fe9eea857377f09d5118b0043c0324bdaa08ec8dc7d0e" }, "Test_CappedMerkleAllowlist": { - "0x949cc973758fec263c28001984b366da5a91b8c2808ff8a54d0a15d553fbb119": "0x044bdf804a73e80f856d07cd8cb99998bb81bfb74f22b7b0e717af8daaa34178", - "0xfd52ac3d9067d2a4bdcfa15682ed088ccf4d5745f11144f12855affdb373c101": "0xc938a69c79a466dd8bf9a79c608f94f12bb3ac4db9d553d80f60897c3bac1d98" + "0x7bc65722fd1bb5078612d7cf193c76eed38e4ee74d3eca1c64069fe208273bb8": "0xdd1697b2635ef96d4ef154a273cd602e85c9dd1bce8947257df399a149558cd1", + "0xd987f98da18ebf5a2cd0222a4a580f45dadac95efb1b198e81c30ce5caffda9c": "0x57a9ddca97366bca653e74e78ec8db3fbecf1d51224fb81ddcfa799dc7aed921" }, "Test_MockCallback": { - "0x18f82a7c75cf0ebf30437dcc4fdab9c53229b580cb6f796de5ad63f31d1fe781": "0x48a3b8ee88809e89fde7e8b87ac2b3e5f2417f852a5bccc48d95c62be8f4200d", - "0x397b1cd983db931f7cd8152144faef94e4f37009916614a6fb0d0993742d2809": "0xc6422c18f5b7daed88381a4b2df3432da5c509f37a3943390b846d9bc370721e", - "0x5d4118a8eb5d15379c61f75be47abd706181c2208cb2454cdc3e605084d03c54": "0xd0ea7ce565d5a97556ca0ac10c20faf98fc4464cb334547777fffa386e9ca225", - "0x5f8fb39b0ff047ec8766bbed926632bb0b19f198e496b085ba1f7670c7d7e696": "0x7a0cbdba9126f4a0ef7e1c4c61269a2dcbdc4cc52d861d0242374fb27a0d9ffd", - "0x63c4720322d8c03280c3f37a079a6a4079854e69a3738778683dfd45dcd5ad35": "0xed1a5d0f40a776138a9fac5ad76cacd42ca37467e31700582c2778822a30cb53", - "0x65f38c951a7916a53122f70dfbef497015ecbae3174e5d23cdbd46d5578800c8": "0xae698423ecbc134afb114fd6b9978fc3f3fd48823a5c9f589fba6d7a075642b0", - "0x7e8812ebede0deaa732aaf72590c9a692d4eae0363d683685eba14da577132ee": "0x7dea2636097620f864985e9857671fe26fe549357cb8eac6f94a8871323aa084", - "0x8d5e7621b69fc644d724f1f56d5367216fc2fd4b98d4a54489693241adeaf056": "0xd1a8ad5939559461b51da8e75f43ec503d673bc678273bc7186ee3bcd42f4703", - "0x94086d51fe11dcc1ccee87b113295f491f0e600105403e704bf0f45450df835a": "0x364f111bd05caaeb872ed993b3453caf93b9cf15d8282db30a0dc500483136bd", - "0x9a6fcb38980db23372d6854263eba6c59456333cc38ccf01dc26844fd33b5325": "0xc9a510e0735abfd5e73600042f4918c7eb300b7fb5051204bf200d69133f6ddc", - "0xb92f554865429a8d0184d5ff16d07c12223697ef50213997606ceb79ceb0a0a6": "0x5c2c173aeabb5598d99c2c5a119a3836afb20b5ae7159c3b3ecf82027722ba98", - "0xbd8cca8085ce24688555ad61b21c871e2f5facf0bf0f225e1ba93542cc5f321a": "0x90ca01076b8cc36e4dfce87d3c0b348dd4d630ead2f93be70d841aeec17ae214", - "0xdf30a21d92771c1352f68fb5faecc29f072e20ac2878ff01b55115022a0b8766": "0xffd712c84d8e1e461688d2f113c839d4e9878a73295a2a8acc77329469180703", - "0xedfa86537de5eec103867f46a3969f9949d7dabf636b71eb0d640f440fd939be": "0x65425915bcd995a0493cc69d6d06795ba82a31bce5b10a8963d2d445eda9419c", - "0xf14768b5de74bcea48b76c2614bfa814c4d3b083a79fd05bae557a6b008b45e5": "0xbdb7e5d7e1d33afdb26f245ff39e83904d20ebc9bae692a3928dc217e04a0848", - "0xf7fee737515be357584cd1a05596ff069679dfa74140e63279d8bbc1ebfa9cc7": "0x473fb9780868c8f1092a630a852be26b1abd5fd207ef24c97b5b0b2c65f63068", - "0xfa50dbc4d777e2435ab02f6cb1caa32b168fd8540aab6d267ecea0cb2d6100da": "0xbebff5b7cc42c77c4785b2543f059f7c55072573bdbd0223e296165c175a27e3", - "0xff6827164c72081a53911ce72ef276ed510de6d3e37d431bee13d29692118c99": "0xd78ff389899f7a6ef65c948df7d395b356530463f0a0a51d897a2fb9b5da0f58" + "0x010059e01cd59ea6a09e6e448b116698eaac50fe6488e429c5c95d16900bfe23": "0x0805d22f4d54bf7d6d24abcd42585c542ed568bd1c2608d1c5873a3bcb32be06", + "0x010da737ba914196f5eed88baa4cf0fad7d2567c2bf7d0fcba7c3e0a9d713588": "0x482298d9794bcee87293216806dcf9f3b283b92676017fa0504848a4c00cc843", + "0x17231a2e8d2f29e077e1f77a916404c058bdc54260465828dfc759b558214c45": "0x9c6cc4d681b02f35f71949e1bda922c4a88c1db30cd45b9346e34ca5ea5113c1", + "0x185866d60623ccc475c2998188e22c28610082a70b93783a7607ea38d1c47a2f": "0xa69e61ae0ccc31eecc314c20dcd284aab7fc6f6c28437b6efc4ab255a702bebf", + "0x25659263337ab5820af263af89b733766a9b07a1ed477017611877a41fe9f40c": "0x037a4b2644f98c4e7bf711c8e07c1476a94326f4bcdaf496c1899948c3098b7c", + "0x347cff54c0205748ac7b84c151c68b6a03c3df54a0e78e4cace3426ea84e8cff": "0x0acf1683120d596180c705168eb5413ce93d9d46038ab37f367344abc3ecb810", + "0x3d953f594621e5bc32bc4c359b4682016df661d5e1217c652430c73ccec4dda2": "0xed2205bfef0b17d27335df5f1819436e8637b34a9a218fe1da60b3e0af8b8d0e", + "0x4573b0b224e03abb28d67541fb1a1380e05862b4971517f6268a1a472cecc953": "0xda5c761fe6a1e41a0d589781c440e2f99666ce7625c028db92dd63c457e117ee", + "0x47186a1aff5f048bd03f8b40fb67d362c0d532a76a668196fbd392c7be6a5560": "0x381560595712b30e4bbd5f0f63199efb0462e711142daef33f34aba5a7e76d6f", + "0x4f7bd7eeb39337e41bf2cd855f93e694c02c5efb3f8a103744f3d74d6607f550": "0x758a93358832579087c0ff4b75f6c5b461afbcc0e4991a74277f66d7f1c56a5a", + "0x50ed73984fbb2d58833cb80ea4a8003cd7dc6921da44b38149f86728bbbc3e02": "0x3e43414e606a5f4b001b482f3c750d22a2bdf356c51e698f421cc1717e04ecb1", + "0x58fc404596b5e83aa7223763ae37c2fb02f919da5d706a61254a2b1a08c6c52e": "0x15f194509aaca4684690cd42de711331c1d002f1a51933005ad5f4996c1906c9", + "0x7a9b46b7be5f68effb8b2fb7ed79bd082142e3b857facbfddfc9b116656b8a09": "0xaf5fc5997f493939c400d48d4edc71ca57d639314961b1f8bf06f033160a8ced", + "0x81dd101d067decf7d788bf54e325585bc267181d039a09cc11a223ba4b02cd1b": "0x3e92aa610f0bba892154b5587cba893816994da5ee0d778777ab459d52556f42", + "0x92f0deaaaaefdb94ae71237dd641bbccb0e695d7452424308aae0d4398fb1fbc": "0x2576bfb7788d443ac477240478190bb43255b7e2b144ff7c0712b84c22e1a865", + "0x9a7a4b879167fc272b53cd92456c3fdf46552b4fedc9c6e62c1d76e3bc0033cf": "0xdecb5d5b0d74db8087f2f0015d8408612becf61e0d39019c8d21974fd1b93702", + "0x9e02852077b32aa4af8fbbd7666f31b11e03524b04d91e39f0b32ab40fd05d35": "0xb9e3facb474ac4304700e73cdf6c060b73beb4b2db744c6cce271026ab687ab7", + "0xb63fb2f46de21fcddba622d7689826a803de501bd5cc2067d54144b03fe8417a": "0x2ce1dce0e644719fe7c3d81e44218393b4e335c551e981bbec694d09dd942d38" }, "Test_UniswapV2DirectToLiquidity": { - "0x7505be1d8be83a023eb4b3e01f58a2d05d32d9331bddc26429bfc2fc05eb797a": "0x1b3dd4c4355b948ab11983168afce27bdb29cd27d92fa7d8e3e27c8c68f52265" + "0x7c575ff0fa47b0ee7c5761b34dc014b863742a6ed3f8806f058cb37240b7baae": "0xbed726ac0e9f0ac126170bb7d77d9d7f4275592a7af6d7285d58c51e15a9eaec" }, "Test_UniswapV3DirectToLiquidity": { - "0x37dd55257564edfcc63a9f8fa76bbc43dd1da7e16e1b09e55fc30071078494e2": "0xe8c5923535cc6746549b0116385ab310ac914aa0cc19e6992d1dfb490e0e0371" + "0x4325a6d5cf49789fad91233f281461dded98130a4d3eaef80f102d9831461059": "0x68202c10c6a7668f451dcc76293b5fc810b681183f6f32004e480dd2a47dd0c7" } } diff --git a/test/AtomicAuctionHouse/AuctionHouseTest.sol b/test/AtomicAuctionHouse/AuctionHouseTest.sol index 99727a22d..7dd123008 100644 --- a/test/AtomicAuctionHouse/AuctionHouseTest.sol +++ b/test/AtomicAuctionHouse/AuctionHouseTest.sol @@ -33,8 +33,9 @@ import {AuctionModule} from "src/modules/Auction.sol"; import {Veecode, toKeycode, keycodeFromVeecode, Keycode} from "src/modules/Keycode.sol"; import {WithSalts} from "test/lib/WithSalts.sol"; +import {TestSaltConstants} from "script/salts/TestSaltConstants.sol"; -abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts { +abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSaltConstants { MockFeeOnTransferERC20 internal _baseToken; MockFeeOnTransferERC20 internal _quoteToken; @@ -53,7 +54,6 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts { uint256 internal constant _BASE_SCALE = 1e18; - address internal constant _OWNER = address(0x1); address internal constant _SELLER = address(0x2); address internal constant _PROTOCOL = address(0x3); address internal constant _CURATOR = address(0x4); @@ -105,7 +105,7 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts { // Create an AtomicAuctionHouse at a deterministic address, since it is used as input to callbacks AtomicAuctionHouse auctionHouse = new AtomicAuctionHouse(_OWNER, _PROTOCOL, _permit2Address); - _auctionHouse = AtomicAuctionHouse(address(0x000000000000000000000000000000000000000A)); + _auctionHouse = AtomicAuctionHouse(_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 diff --git a/test/BatchAuctionHouse/AuctionHouseTest.sol b/test/BatchAuctionHouse/AuctionHouseTest.sol index d57e6dfb1..17d4511fb 100644 --- a/test/BatchAuctionHouse/AuctionHouseTest.sol +++ b/test/BatchAuctionHouse/AuctionHouseTest.sol @@ -33,8 +33,9 @@ import {AuctionHouse} from "src/bases/AuctionHouse.sol"; import {Veecode, toKeycode, keycodeFromVeecode, Keycode} from "src/modules/Keycode.sol"; import {WithSalts} from "test/lib/WithSalts.sol"; +import {TestSaltConstants} from "script/salts/TestSaltConstants.sol"; -abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts { +abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSaltConstants { MockFeeOnTransferERC20 internal _baseToken; MockFeeOnTransferERC20 internal _quoteToken; @@ -52,7 +53,6 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts { uint256 internal constant _BASE_SCALE = 1e18; - address internal constant _OWNER = address(0x1); address internal constant _SELLER = address(0x2); address internal constant _PROTOCOL = address(0x3); address internal constant _CURATOR = address(0x4); @@ -105,7 +105,7 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts { // Create a BatchAuctionHouse at a deterministic address, since it is used as input to callbacks BatchAuctionHouse auctionHouse = new BatchAuctionHouse(_OWNER, _PROTOCOL, _permit2Address); - _auctionHouse = BatchAuctionHouse(address(0x000000000000000000000000000000000000000A)); + _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 diff --git a/test/callbacks/CappedMerkleAllowlistAtomic.t.sol b/test/callbacks/CappedMerkleAllowlistAtomic.t.sol index 5c346e715..14ccf49f0 100644 --- a/test/callbacks/CappedMerkleAllowlistAtomic.t.sol +++ b/test/callbacks/CappedMerkleAllowlistAtomic.t.sol @@ -12,11 +12,11 @@ import {BaseCallback} from "src/callbacks/BaseCallback.sol"; import {CappedMerkleAllowlist} from "src/callbacks/allowlists/CappedMerkleAllowlist.sol"; import {WithSalts} from "test/lib/WithSalts.sol"; +import {TestSaltConstants} from "script/salts/TestSaltConstants.sol"; -contract CappedMerkleAllowlistAtomicTest is Test, Permit2User, WithSalts { +contract CappedMerkleAllowlistAtomicTest is Test, Permit2User, WithSalts, TestSaltConstants { using Callbacks for CappedMerkleAllowlist; - address internal constant _OWNER = address(0x1); address internal constant _SELLER = address(0x2); address internal constant _PROTOCOL = address(0x3); address internal constant _BUYER = address(0x4); @@ -43,7 +43,7 @@ contract CappedMerkleAllowlistAtomicTest is Test, Permit2User, WithSalts { function setUp() public { // Create an AuctionHouse at a deterministic address, since it is used as input to callbacks AtomicAuctionHouse auctionHouse = new AtomicAuctionHouse(_OWNER, _PROTOCOL, _permit2Address); - _auctionHouse = AtomicAuctionHouse(address(0x000000000000000000000000000000000000000A)); + _auctionHouse = AtomicAuctionHouse(_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 diff --git a/test/callbacks/CappedMerkleAllowlistBatch.t.sol b/test/callbacks/CappedMerkleAllowlistBatch.t.sol index e8729270d..250ce9371 100644 --- a/test/callbacks/CappedMerkleAllowlistBatch.t.sol +++ b/test/callbacks/CappedMerkleAllowlistBatch.t.sol @@ -12,11 +12,11 @@ import {BaseCallback} from "src/callbacks/BaseCallback.sol"; import {CappedMerkleAllowlist} from "src/callbacks/allowlists/CappedMerkleAllowlist.sol"; import {WithSalts} from "test/lib/WithSalts.sol"; +import {TestSaltConstants} from "script/salts/TestSaltConstants.sol"; -contract CappedMerkleAllowlistBatchTest is Test, Permit2User, WithSalts { +contract CappedMerkleAllowlistBatchTest is Test, Permit2User, WithSalts, TestSaltConstants { using Callbacks for CappedMerkleAllowlist; - address internal constant _OWNER = address(0x1); address internal constant _SELLER = address(0x2); address internal constant _PROTOCOL = address(0x3); address internal constant _BUYER = address(0x4); @@ -43,7 +43,7 @@ contract CappedMerkleAllowlistBatchTest is Test, Permit2User, WithSalts { function setUp() public { // Create an AuctionHouse at a deterministic address, since it is used as input to callbacks BatchAuctionHouse auctionHouse = new BatchAuctionHouse(_OWNER, _PROTOCOL, _permit2Address); - _auctionHouse = BatchAuctionHouse(address(0x000000000000000000000000000000000000000A)); + _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 diff --git a/test/callbacks/UniswapV2DTL/UniswapV2DTLTest.sol b/test/callbacks/UniswapV2DTL/UniswapV2DTLTest.sol index 43440d90b..a95ed3e33 100644 --- a/test/callbacks/UniswapV2DTL/UniswapV2DTLTest.sol +++ b/test/callbacks/UniswapV2DTL/UniswapV2DTLTest.sol @@ -26,18 +26,15 @@ import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {WithSalts} from "test/lib/WithSalts.sol"; import {console2} from "forge-std/console2.sol"; +import {TestSaltConstants} from "script/salts/TestSaltConstants.sol"; -abstract contract UniswapV2DirectToLiquidityTest is Test, Permit2User, WithSalts { +abstract contract UniswapV2DirectToLiquidityTest is Test, Permit2User, WithSalts, TestSaltConstants { using Callbacks for UniswapV2DirectToLiquidity; - address internal constant _OWNER = address(0x1); address internal constant _SELLER = address(0x2); address internal constant _PROTOCOL = address(0x3); address internal constant _BUYER = address(0x4); address internal constant _NOT_SELLER = address(0x20); - address internal constant _AUCTION_HOUSE = address(0x000000000000000000000000000000000000000A); - address internal constant _UNISWAP_V2_FACTORY = - address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); uint96 internal constant _LOT_CAPACITY = 10e18; @@ -88,7 +85,10 @@ abstract contract UniswapV2DirectToLiquidityTest is Test, Permit2User, WithSalts _uniV2Router = new UniswapV2Router02{ salt: bytes32(0x035ba535d735a8e92093764ec05c30d49ab56cfd0d3da306185ab02b1fcac4f4) }(address(_uniV2Factory), address(0)); - console2.log("UniswapV2Router address: {}", address(_uniV2Router)); // 0x584A2a1F5eCdCDcB6c0616cd280a7Db89239872B + if (address(_uniV2Router) != _UNISWAP_V2_ROUTER) { + console2.log("UniswapV2Router address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", address(_uniV2Router)); + revert("UniswapV2Router address has changed. See logs."); + } _linearVesting = new LinearVesting(address(_auctionHouse)); _batchAuctionModule = new MockBatchAuctionModule(address(_auctionHouse)); diff --git a/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol b/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol index 9b4a9dd71..6fb7c1bc0 100644 --- a/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol +++ b/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol @@ -26,16 +26,15 @@ import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {WithSalts} from "test/lib/WithSalts.sol"; import {console2} from "forge-std/console2.sol"; +import {TestSaltConstants} from "script/salts/TestSaltConstants.sol"; -abstract contract UniswapV3DirectToLiquidityTest is Test, Permit2User, WithSalts { +abstract contract UniswapV3DirectToLiquidityTest is Test, Permit2User, WithSalts, TestSaltConstants { using Callbacks for UniswapV3DirectToLiquidity; - address internal constant _OWNER = address(0x1); address internal constant _SELLER = address(0x2); address internal constant _PROTOCOL = address(0x3); address internal constant _BUYER = address(0x4); address internal constant _NOT_SELLER = address(0x20); - address internal constant _AUCTION_HOUSE = address(0x000000000000000000000000000000000000000A); uint96 internal constant _LOT_CAPACITY = 10e18; @@ -81,13 +80,19 @@ abstract contract UniswapV3DirectToLiquidityTest is Test, Permit2User, WithSalts _uniV3Factory = new UniswapV3Factory{ salt: bytes32(0xbc65534283bdbbac4a95a3fb1933af63d55135566688dd54d1c55a626b1bc366) }(); - console2.log("UniswapV3Factory address: {}", address(_uniV3Factory)); // 0x5573DCDCc96692D24938F4E440d99EFC3d7EDA04 + if (address(_uniV3Factory) != _UNISWAP_V3_FACTORY) { + console2.log("UniswapV3Factory address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", address(_uniV3Factory)); + revert("UniswapV3Factory address has changed. See logs."); + } // Create a GUniFactory at a deterministic address _gUniFactory = new GUniFactory{ salt: bytes32(0x31d4bb3a2cd73df799deceac86fa252d040e24c2ea206f4172d74f72cfa34e4b) }(address(_uniV3Factory)); - console2.log("GUniFactory address: {}", address(_gUniFactory)); // 0xe6b8030b2fd30ea9198D3F39AEeD4f448A2704f0 + if (address(_gUniFactory) != _GUNI_FACTORY) { + console2.log("GUniFactory address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", address(_gUniFactory)); + revert("UniswapV2Router address has changed. See logs."); + } // Initialize the GUniFactory address payable gelatoAddress = payable(address(0x10)); From 19bd8565e01b1af9c8d828888d4b62efc3de2b39 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Wed, 1 May 2024 10:50:32 +0400 Subject: [PATCH 10/69] chore: linting --- .../callbacks/UniswapV2DTL/UniswapV2DTLTest.sol | 12 ++++++++++-- .../callbacks/UniswapV3DTL/UniswapV3DTLTest.sol | 17 ++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/test/callbacks/UniswapV2DTL/UniswapV2DTLTest.sol b/test/callbacks/UniswapV2DTL/UniswapV2DTLTest.sol index a95ed3e33..827e91023 100644 --- a/test/callbacks/UniswapV2DTL/UniswapV2DTLTest.sol +++ b/test/callbacks/UniswapV2DTL/UniswapV2DTLTest.sol @@ -28,7 +28,12 @@ import {WithSalts} from "test/lib/WithSalts.sol"; import {console2} from "forge-std/console2.sol"; import {TestSaltConstants} from "script/salts/TestSaltConstants.sol"; -abstract contract UniswapV2DirectToLiquidityTest is Test, Permit2User, WithSalts, TestSaltConstants { +abstract contract UniswapV2DirectToLiquidityTest is + Test, + Permit2User, + WithSalts, + TestSaltConstants +{ using Callbacks for UniswapV2DirectToLiquidity; address internal constant _SELLER = address(0x2); @@ -86,7 +91,10 @@ abstract contract UniswapV2DirectToLiquidityTest is Test, Permit2User, WithSalts salt: bytes32(0x035ba535d735a8e92093764ec05c30d49ab56cfd0d3da306185ab02b1fcac4f4) }(address(_uniV2Factory), address(0)); if (address(_uniV2Router) != _UNISWAP_V2_ROUTER) { - console2.log("UniswapV2Router address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", address(_uniV2Router)); + console2.log( + "UniswapV2Router address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", + address(_uniV2Router) + ); revert("UniswapV2Router address has changed. See logs."); } diff --git a/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol b/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol index 6fb7c1bc0..2b2794ab5 100644 --- a/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol +++ b/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol @@ -28,7 +28,12 @@ import {WithSalts} from "test/lib/WithSalts.sol"; import {console2} from "forge-std/console2.sol"; import {TestSaltConstants} from "script/salts/TestSaltConstants.sol"; -abstract contract UniswapV3DirectToLiquidityTest is Test, Permit2User, WithSalts, TestSaltConstants { +abstract contract UniswapV3DirectToLiquidityTest is + Test, + Permit2User, + WithSalts, + TestSaltConstants +{ using Callbacks for UniswapV3DirectToLiquidity; address internal constant _SELLER = address(0x2); @@ -81,7 +86,10 @@ abstract contract UniswapV3DirectToLiquidityTest is Test, Permit2User, WithSalts salt: bytes32(0xbc65534283bdbbac4a95a3fb1933af63d55135566688dd54d1c55a626b1bc366) }(); if (address(_uniV3Factory) != _UNISWAP_V3_FACTORY) { - console2.log("UniswapV3Factory address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", address(_uniV3Factory)); + console2.log( + "UniswapV3Factory address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", + address(_uniV3Factory) + ); revert("UniswapV3Factory address has changed. See logs."); } @@ -90,7 +98,10 @@ abstract contract UniswapV3DirectToLiquidityTest is Test, Permit2User, WithSalts salt: bytes32(0x31d4bb3a2cd73df799deceac86fa252d040e24c2ea206f4172d74f72cfa34e4b) }(address(_uniV3Factory)); if (address(_gUniFactory) != _GUNI_FACTORY) { - console2.log("GUniFactory address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", address(_gUniFactory)); + console2.log( + "GUniFactory address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", + address(_gUniFactory) + ); revert("UniswapV2Router address has changed. See logs."); } From bb3b8deee2a35600fc15bcfb6eab01658b35b32f Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Wed, 1 May 2024 10:59:48 +0400 Subject: [PATCH 11/69] Add forge test config into foundry.toml --- README.md | 8 ++++++++ foundry.toml | 2 ++ package.json | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 787594ae7..bc1b0d0a2 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,14 @@ Then, the test suite can be run with: pnpm run test ``` +Or: + +```shell +forge test +``` + +This command usually performs faster than the `pnpm` command. + ### Format Combines `forge fmt` and `solhint` diff --git a/foundry.toml b/foundry.toml index fb59ee307..1ab0eb8ef 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,6 +6,8 @@ fs_permissions = [{access = "read-write", path = "./bytecode/"}, {access = "read ffi = true solc_version = "0.8.19" evm_version = "paris" +verbosity = 3 +no-match-test = "optimal" [fuzz] runs = 1024 diff --git a/package.json b/package.json index 35b102ce3..eb3cfd86e 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "solhint:all": "solhint --fix --config ./.solhint.json 'src/**/*.sol' 'test/**/*.sol' 'script/**/*.sol'", "solhint:check": "solhint --config ./.solhint.json 'src/**/*.sol'", "solhint": "solhint --fix --config ./.solhint.json 'src/**/*.sol'", - "test": "forge test --nmt optimal -vvv" + "test": "forge test" }, "keywords": [], "author": "", From 81ab2bc4c0580123f99caed00c4b5764047fcfc5 Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 1 May 2024 08:58:06 -0500 Subject: [PATCH 12/69] feat: handle positive and negative T in GDA --- src/modules/auctions/GDA.sol | 90 ++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 29 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 2ac237574..a410570d6 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -140,27 +140,37 @@ contract GradualDutchAuction is AtomicAuctionModule { uint256 baseTokenScale = 10 ** lot.baseTokenDecimals; UD60x18 payout = ud(payout_.mulDiv(uUNIT, baseTokenScale)); - // Calculate time since last auction start - // TODO handle case where lastAuctionStart is greater than block.timestamp - UD60x18 timeSinceLastAuctionStart = - convert(block.timestamp - uint256(auction.lastAuctionStart)); - - // Subtract the minimum price from the equilibrium price + // Calculate the first numerator factor: (q0 - qm) // In the auction creation, we checked that the equilibrium price is greater than the minimum price - // Scale the result to 18 decimals, set as numerator factor 1 + // Scale the result to 18 decimals uint256 quoteTokenScale = 10 ** lot.quoteTokenDecimals; - UD60x18 num1 = + UD60x18 priceDiff = ud((auction.equilibriumPrice - auction.minimumPrice).fullMulDiv(uUNIT, quoteTokenScale)); // Calculate the second numerator factor: e^((k*P)/r) - 1 - UD60x18 num2 = auction.decayConstant.mul(payout).div(auction.emissionsRate).exp().sub(UNIT); + UD60x18 ekpr = auction.decayConstant.mul(payout).div(auction.emissionsRate).exp().sub(UNIT); + + // Handle cases of T being positive or negative + UD60x18 result; + if (block.timestamp >= auction.lastAuctionStart) { + // T is positive + // Calculate the denominator: ke^(k*T) + UD60x18 kekt = auction.decayConstant.mul( + convert(block.timestamp - auction.lastAuctionStart) + ).exp().mul(auction.decayConstant); + + // Calculate the first term in the formula + result = priceDiff.mul(ekpr).div(kekt); + } else { + // T is negative: flip the e^(k * T) term to the numerator - // Calculate the denominator: ke^(k*T) - UD60x18 denominator = - auction.decayConstant.mul(timeSinceLastAuctionStart).exp().mul(auction.decayConstant); + // Calculate the exponential: e^(k*T) + UD60x18 ekt = + auction.decayConstant.mul(convert(auction.lastAuctionStart - block.timestamp)).exp(); - // Calculate first term - UD60x18 result = num1.mul(num2).div(denominator); + // Calculate the first term in the formula + result = priceDiff.mul(ekpr).mul(ekt).div(auction.decayConstant); + } // If minimum price is zero, then the first term is the result, otherwise we add the second term if (auction.minimumPrice > 0) { @@ -218,15 +228,29 @@ contract GradualDutchAuction is AtomicAuctionModule { // Factors are calculated in a certain order to avoid precision loss UD60x18 payout; if (auction.minimumPrice == 0) { - // Calculate the exponential factor - // TODO lastAuctionStart may be greater than block.timestamp if the auction is ahead of schedule - // Need to handle this case - UD60x18 ekt = - auction.decayConstant.mul(convert(block.timestamp - auction.lastAuctionStart)).exp(); - - // Calculate the logarithm - // Operand is guaranteed to be >= 1, so the result is positive - UD60x18 logFactor = amount.mul(auction.decayConstant).mul(ekt).div(q0).add(UNIT).ln(); + UD60x18 logFactor; + if (block.timestamp >= auction.lastAuctionStart) { + // T is positive + // Calculate the exponential factor + UD60x18 ekt = auction.decayConstant.mul( + convert(block.timestamp - auction.lastAuctionStart) + ).exp(); + + // Calculate the logarithm + // Operand is guaranteed to be >= 1, so the result is positive + logFactor = amount.mul(auction.decayConstant).mul(ekt).div(q0).add(UNIT).ln(); + } else { + // T is negative: flip the e^(k * T) term to the denominator + + // Calculate the exponential factor + UD60x18 ekt = auction.decayConstant.mul( + convert(auction.lastAuctionStart - block.timestamp) + ).exp(); + + // Calculate the logarithm + // Operand is guaranteed to be >= 1, so the result is positive + logFactor = amount.mul(auction.decayConstant).div(ekt.mul(q0)).add(UNIT).ln(); + } // Calculate the payout payout = auction.emissionsRate.mul(logFactor).div(auction.decayConstant); @@ -243,12 +267,20 @@ contract GradualDutchAuction is AtomicAuctionModule { UD60x18 f = auction.decayConstant.mul(amount).div(qm); // Calculate second term aka C: (q0 - qm)/(qm * e^(k * T)) - // TODO lastAuctionStart may be greater than block.timestamp if the auction is ahead of schedule - // Need to handle this case - UD60x18 c = q0.sub(qm).div( - auction.decayConstant.mul(convert(block.timestamp - auction.lastAuctionStart)).exp() - .mul(qm) - ); + UD60x18 c; + if (block.timestamp >= auction.lastAuctionStart) { + // T is positive + c = q0.sub(qm).div( + auction.decayConstant.mul(convert(block.timestamp - auction.lastAuctionStart)) + .exp().mul(qm) + ); + } else { + // T is negative: flip the e^(k * T) term to the numerator + c = q0.sub(qm).mul( + auction.decayConstant.mul(convert(auction.lastAuctionStart - block.timestamp)) + .exp() + ).div(qm); + } // Calculate the third term: W(C e^(k * Q / qm + C)) UD60x18 w = c.add(f).exp().mul(c).productLn(); From ad504e9e50c912fb22be86246f76b5126be2bd15 Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 1 May 2024 11:19:41 -0500 Subject: [PATCH 13/69] refactor: simplify gda inputs and add some bounds --- src/modules/auctions/GDA.sol | 82 ++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index a410570d6..22b166dab 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: BSL-1.1 pragma solidity 0.8.19; -// Interfaces -import {IAtomicAuction} from "src/interfaces/IAtomicAuction.sol"; - // Protocol dependencies import {Module} from "src/modules/Modules.sol"; import {AuctionModule} from "src/modules/Auction.sol"; @@ -30,11 +27,32 @@ contract GradualDutchAuction is AtomicAuctionModule { struct GDAParams { uint256 equilibriumPrice; uint256 minimumPrice; - UD60x18 decayConstant; + uint256 decayPercentFirstPeriod; // target decay percent over the first decay period of an auction (steepest part of the curve) + uint256 decayPeriod; // period over which the target decay percent is reached } // ========== STATE VARIABLES ========== // + // Decay percent over the first period must be at most 99% and at least 1% + // We use 18 decimals so we don't have to convert it to use as a UD60x18 + uint256 internal constant MIN_DECAY_PERCENT = 1e16; // 1% + uint256 internal constant MAX_DECAY_PERCENT = 99e16; // 99% + + // Decay period must be greater than or equal to 1 hour and less than or equal to 1 week + // A minimum of 1 hour means that the maximum value for the decay constant is determined by: + // MAX_LN_OUTPUT = 135_999146549453176925 + // MAX_LN_OUTPUT / 3600 = 0_037777540708181438 + // A maximum of 1 week means that the minimum value for the decay constant is determined by: + // MIN_LN_OUTPUT = ln(1/0.99) = 0_010050335853501441 + // MIN_LN_OUTPUT / 604800 = 0_000000016617618805 + // We use these bounds to prove that various calculations won't overflow below + // TODO: implement the above + // TODO should we be able to update the min and max periods? + uint48 internal constant MIN_DECAY_PERIOD = 1 hours; + uint48 internal constant MAX_DECAY_PERIOD = 1 weeks; + UD60x18 internal constant MAX_DECAY_CONSTANT = UD60x18(37_777_540_708_181_438); + UD60x18 internal constant MIN_DECAY_CONSTANT = UD60x18(16_617_618_805); + mapping(uint256 id => AuctionData data) public auctionData; // ========== SETUP ========== // @@ -66,10 +84,37 @@ contract GradualDutchAuction is AtomicAuctionModule { // Minimum price can be zero, but the equations default back to the basic GDA implementation - // Validate the decay constant - // Cannot be zero - // TODO do we need to set tighter bounds on this? - if (params.decayConstant == ZERO) revert Auction_InvalidParams(); + // Validate the decay parameters and calculate the decay constant + // k = ln((q0 - qm) / (q1 - qm)) / dp + // require q0 > q1 > qm + // q1 = q0 * (1 - d1) + // => 100% > d1 > 0% + if (params.decayPercentFirstPeriod >= uUNIT || params.decayPercentFirstPeriod < uUNIT / 100) + { + revert Auction_InvalidParams(); + } + + // Decay period must be between the set bounds + if (params.decayPeriod < MIN_DECAY_PERIOD || params.decayPeriod > MAX_DECAY_PERIOD) { + revert Auction_InvalidParams(); + } + + UD60x18 decayConstant; + { + uint256 quoteTokenScale = 10 ** lot_.quoteTokenDecimals; + UD60x18 q0 = ud(params.equilibriumPrice.fullMulDiv(uUNIT, quoteTokenScale)); + UD60x18 q1 = q0.mul(UNIT - ud(params.decayPercentFirstPeriod)).div(UNIT); + UD60x18 qm = ud(params.minimumPrice.fullMulDiv(uUNIT, quoteTokenScale)); + + // Check that q0 > q1 > qm + // This ensures that the operand for the logarithm is positive + if (q0 <= q1 || q1 <= qm) { + revert Auction_InvalidParams(); + } + + // Calculate the decay constant + decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(params.decayPeriod)); + } // TODO other validation checks? @@ -82,7 +127,7 @@ contract GradualDutchAuction is AtomicAuctionModule { AuctionData storage data = auctionData[lotId_]; data.equilibriumPrice = params.equilibriumPrice; data.minimumPrice = params.minimumPrice; - data.decayConstant = params.decayConstant; + data.decayConstant = decayConstant; data.emissionsRate = emissionsRate; data.lastAuctionStart = uint256(lot_.start); } @@ -140,7 +185,7 @@ contract GradualDutchAuction is AtomicAuctionModule { uint256 baseTokenScale = 10 ** lot.baseTokenDecimals; UD60x18 payout = ud(payout_.mulDiv(uUNIT, baseTokenScale)); - // Calculate the first numerator factor: (q0 - qm) + // Calculate the first numerator factor: (q0 - qm), if qm is zero, this is q0 // In the auction creation, we checked that the equilibrium price is greater than the minimum price // Scale the result to 18 decimals uint256 quoteTokenScale = 10 ** lot.quoteTokenDecimals; @@ -257,11 +302,20 @@ contract GradualDutchAuction is AtomicAuctionModule { } else { // TODO think about refactoring to avoid precision loss - // TODO do we check if amount divided by minimum price is greater than capacity? - // May help with some overflow situations below. + { + // Check that the amount / minPrice is not greater than the max payout (i.e. remaining capacity) + uint256 minPrice = auction.minimumPrice; + uint256 payoutAtMinPrice = FixedPointMathLib.fullMulDiv( + amount_, 10 ** lotData[lotId_].baseTokenDecimals, minPrice + ); + if (payoutAtMinPrice > maxPayout(lotId_)) { + revert Auction_InsufficientCapacity(); + } + } // Convert minimum price to 18 decimals - UD60x18 qm = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + // Can't overflow because quoteTokenScale <= uUNIT + UD60x18 qm = ud(auction.minimumPrice.fullMulDiv(uUNIT, quoteTokenScale)); // Calculate first term: (k * Q) / qm UD60x18 f = auction.decayConstant.mul(amount).div(qm); @@ -308,7 +362,7 @@ contract GradualDutchAuction is AtomicAuctionModule { return (payout.intoUint256().mulDiv(10 ** lot.baseTokenDecimals, uUNIT), secondsOfEmissions); } - function maxPayout(uint96 lotId_) external view override returns (uint256) { + function maxPayout(uint96 lotId_) public view override returns (uint256) { // The max payout is the remaining capacity of the lot return lotData[lotId_].capacity; } From e140c162080c6cd9b641fa7acf3494b8f7562ada Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 1 May 2024 20:48:59 -0500 Subject: [PATCH 14/69] fix: compile error --- src/modules/auctions/GDA.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 22b166dab..ab8c162b5 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -50,8 +50,8 @@ contract GradualDutchAuction is AtomicAuctionModule { // TODO should we be able to update the min and max periods? uint48 internal constant MIN_DECAY_PERIOD = 1 hours; uint48 internal constant MAX_DECAY_PERIOD = 1 weeks; - UD60x18 internal constant MAX_DECAY_CONSTANT = UD60x18(37_777_540_708_181_438); - UD60x18 internal constant MIN_DECAY_CONSTANT = UD60x18(16_617_618_805); + UD60x18 internal constant MAX_DECAY_CONSTANT = UD60x18.wrap(uint256(37_777_540_708_181_438)); + UD60x18 internal constant MIN_DECAY_CONSTANT = UD60x18.wrap(uint256(16_617_618_805)); mapping(uint256 id => AuctionData data) public auctionData; From c71aeb223e96eb3ea78d4a5c70cc1057ac984ec8 Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 2 May 2024 13:20:00 -0500 Subject: [PATCH 15/69] fix: use constant bounds for GDA and rename target --- src/modules/auctions/GDA.sol | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index ab8c162b5..4fd550cd1 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -27,7 +27,7 @@ contract GradualDutchAuction is AtomicAuctionModule { struct GDAParams { uint256 equilibriumPrice; uint256 minimumPrice; - uint256 decayPercentFirstPeriod; // target decay percent over the first decay period of an auction (steepest part of the curve) + uint256 decayTarget; // target decay percent over the first decay period of an auction (steepest part of the curve) uint256 decayPeriod; // period over which the target decay percent is reached } @@ -88,9 +88,7 @@ contract GradualDutchAuction is AtomicAuctionModule { // k = ln((q0 - qm) / (q1 - qm)) / dp // require q0 > q1 > qm // q1 = q0 * (1 - d1) - // => 100% > d1 > 0% - if (params.decayPercentFirstPeriod >= uUNIT || params.decayPercentFirstPeriod < uUNIT / 100) - { + if (params.decayTarget > MAX_DECAY_PERCENT || params.decayTarget < MIN_DECAY_PERCENT) { revert Auction_InvalidParams(); } @@ -103,7 +101,7 @@ contract GradualDutchAuction is AtomicAuctionModule { { uint256 quoteTokenScale = 10 ** lot_.quoteTokenDecimals; UD60x18 q0 = ud(params.equilibriumPrice.fullMulDiv(uUNIT, quoteTokenScale)); - UD60x18 q1 = q0.mul(UNIT - ud(params.decayPercentFirstPeriod)).div(UNIT); + UD60x18 q1 = q0.mul(UNIT - ud(params.decayTarget)).div(UNIT); UD60x18 qm = ud(params.minimumPrice.fullMulDiv(uUNIT, quoteTokenScale)); // Check that q0 > q1 > qm From b4c65a5f810422d560af76fc3fd52e74f5d454f1 Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 2 May 2024 14:33:34 -0500 Subject: [PATCH 16/69] fix: convert times to fixed point days in GDA and add bounds --- src/modules/auctions/GDA.sol | 84 +++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 4fd550cd1..b66416986 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -8,7 +8,9 @@ import {Veecode, toVeecode} from "src/modules/Modules.sol"; import {AtomicAuctionModule} from "src/modules/auctions/AtomicAuctionModule.sol"; // External libraries -import {UD60x18, ud, convert, ZERO, UNIT, uUNIT} from "lib/prb-math/src/UD60x18.sol"; +import { + UD60x18, ud, convert, ZERO, UNIT, uUNIT, EXP_MAX_INPUT +} from "lib/prb-math/src/UD60x18.sol"; import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol"; /// @notice Continuous Gradual Dutch Auction (GDA) module with exponential decay and a minimum price. @@ -21,7 +23,7 @@ contract GradualDutchAuction is AtomicAuctionModule { uint256 minimumPrice; // minimum price for the auction uint256 lastAuctionStart; // time that the last un-purchased auction started, may be in the future UD60x18 decayConstant; // speed at which the price decays, as UD60x18. - UD60x18 emissionsRate; // number of tokens released per second, as UD60x18. Calculated as capacity / duration. + UD60x18 emissionsRate; // number of tokens released per day, as UD60x18. Calculated as capacity / duration. } struct GDAParams { @@ -32,26 +34,34 @@ contract GradualDutchAuction is AtomicAuctionModule { } // ========== STATE VARIABLES ========== // + // TODO instead of converting all times to fixed point days, we could just use seconds and not convert to 1e18. It + // It could problems with the decay constant being too large though + UD60x18 internal constant ONE_DAY = UD60x18.wrap(1 days); - // Decay percent over the first period must be at most 99% and at least 1% + // Decay target over the first period must fit within these bounds // We use 18 decimals so we don't have to convert it to use as a UD60x18 - uint256 internal constant MIN_DECAY_PERCENT = 1e16; // 1% - uint256 internal constant MAX_DECAY_PERCENT = 99e16; // 99% - - // Decay period must be greater than or equal to 1 hour and less than or equal to 1 week - // A minimum of 1 hour means that the maximum value for the decay constant is determined by: - // MAX_LN_OUTPUT = 135_999146549453176925 - // MAX_LN_OUTPUT / 3600 = 0_037777540708181438 - // A maximum of 1 week means that the minimum value for the decay constant is determined by: + uint256 internal constant MIN_DECAY_TARGET = 1e16; // 1% + uint256 internal constant MAX_DECAY_TARGET = 50e16; // 50% + + // Decay period must be greater than or equal to 1 day and less than or equal to 1 week + // A minimum value of q1 = q0 * 0.01 and a min period of 1 day means: + // MAX_LN_OUTPUT = ln(1/0.5) = 0_693147180559945309 + // MAX_LN_OUTPUT / 1 = 0_693147180559945309 + // A maximum value of q1 = q0 * 0.99 and a max period of 7 days means: // MIN_LN_OUTPUT = ln(1/0.99) = 0_010050335853501441 - // MIN_LN_OUTPUT / 604800 = 0_000000016617618805 + // MIN_LN_OUTPUT / 7 = 0_001435762264785920 + // We use these bounds to prove that various calculations won't overflow below // TODO: implement the above // TODO should we be able to update the min and max periods? - uint48 internal constant MIN_DECAY_PERIOD = 1 hours; + uint48 internal constant MIN_DECAY_PERIOD = 1 days; uint48 internal constant MAX_DECAY_PERIOD = 1 weeks; - UD60x18 internal constant MAX_DECAY_CONSTANT = UD60x18.wrap(uint256(37_777_540_708_181_438)); - UD60x18 internal constant MIN_DECAY_CONSTANT = UD60x18.wrap(uint256(16_617_618_805)); + UD60x18 internal constant MAX_DECAY_CONSTANT = UD60x18.wrap(uint256(693_147_180_559_945_309)); + UD60x18 internal constant MIN_DECAY_CONSTANT = UD60x18.wrap(uint256(1_435_762_264_785_920)); + + // These bounds imply a max auction duration of 192 days to avoid overflow of some of the exponential calculations + // TODO dynamically calculate based on params? this isn't hard and fast if the decay constant is less than the max + UD60x18 internal constant MAX_AUCTION_DURATION = UD60x18.wrap(192e18); mapping(uint256 id => AuctionData data) public auctionData; @@ -80,6 +90,7 @@ contract GradualDutchAuction is AtomicAuctionModule { } // Capacity must be in base token + // TODO can we allow capacity in quote token? Mostly effects emissions rate if (lot_.capacityInQuote) revert Auction_InvalidParams(); // Minimum price can be zero, but the equations default back to the basic GDA implementation @@ -88,7 +99,7 @@ contract GradualDutchAuction is AtomicAuctionModule { // k = ln((q0 - qm) / (q1 - qm)) / dp // require q0 > q1 > qm // q1 = q0 * (1 - d1) - if (params.decayTarget > MAX_DECAY_PERCENT || params.decayTarget < MIN_DECAY_PERCENT) { + if (params.decayTarget > MAX_DECAY_TARGET || params.decayTarget < MIN_DECAY_TARGET) { revert Auction_InvalidParams(); } @@ -116,8 +127,17 @@ contract GradualDutchAuction is AtomicAuctionModule { // TODO other validation checks? - // Calculate emissions rate - UD60x18 duration = convert(uint256(lot_.conclusion - lot_.start)); + // Calculate duration of the auction in days + UD60x18 duration = convert(uint256(lot_.conclusion - lot_.start)).div(ONE_DAY); + + // The duration must be less than the max exponential input divided by the decay constant + // in order for the exponential operations to not overflow. The lowest this value can be + // is 192 days if the decay constant is at it's maximum value + if (duration > EXP_MAX_INPUT.div(decayConstant)) { + revert Auction_InvalidParams(); + } + + // Calculate emissions rate as number of tokens released per day UD60x18 emissionsRate = ud(lot_.capacity.fullMulDiv(uUNIT, 10 ** lot_.baseTokenDecimals)).div(duration); @@ -155,7 +175,7 @@ contract GradualDutchAuction is AtomicAuctionModule { // ========== VIEW FUNCTIONS ========== // - // For Continuos GDAs with exponential decay, the price of a given token t seconds after being emitted is: + // For Continuous GDAs with exponential decay, the price of a given token t seconds after being emitted is: // q(t) = (q0 - qm) * e^(-k*t) + qm // where k is the decay constant, q0 is the initial price, and qm is the minimum price // Integrating this function from the last auction start time for a particular number of tokens, @@ -191,6 +211,7 @@ contract GradualDutchAuction is AtomicAuctionModule { ud((auction.equilibriumPrice - auction.minimumPrice).fullMulDiv(uUNIT, quoteTokenScale)); // Calculate the second numerator factor: e^((k*P)/r) - 1 + // This cannot exceed the max exponential input due to the bounds imbosed on auction creation UD60x18 ekpr = auction.decayConstant.mul(payout).div(auction.emissionsRate).exp().sub(UNIT); // Handle cases of T being positive or negative @@ -198,8 +219,10 @@ contract GradualDutchAuction is AtomicAuctionModule { if (block.timestamp >= auction.lastAuctionStart) { // T is positive // Calculate the denominator: ke^(k*T) + // This cannot exceed the max exponential input due to the bounds imbosed on auction creation + // Current time - last auction start is guaranteed to be < duration. If not, the auction is over. UD60x18 kekt = auction.decayConstant.mul( - convert(block.timestamp - auction.lastAuctionStart) + convert(block.timestamp - auction.lastAuctionStart).div(ONE_DAY) ).exp().mul(auction.decayConstant); // Calculate the first term in the formula @@ -208,8 +231,11 @@ contract GradualDutchAuction is AtomicAuctionModule { // T is negative: flip the e^(k * T) term to the numerator // Calculate the exponential: e^(k*T) - UD60x18 ekt = - auction.decayConstant.mul(convert(auction.lastAuctionStart - block.timestamp)).exp(); + // This cannot exceed the max exponential input due to the bounds imbosed on auction creation + // last auction start - current time is guaranteed to be < duration. If not, the auction is over. + UD60x18 ekt = auction.decayConstant.mul( + convert(auction.lastAuctionStart - block.timestamp).div(ONE_DAY) + ).exp(); // Calculate the first term in the formula result = priceDiff.mul(ekpr).mul(ekt).div(auction.decayConstant); @@ -266,7 +292,7 @@ contract GradualDutchAuction is AtomicAuctionModule { UD60x18 q0 = ud(auction.equilibriumPrice.fullMulDiv(uUNIT, quoteTokenScale)); // Scale amount to 18 decimals - UD60x18 amount = ud(amount_.mulDiv(uUNIT, quoteTokenScale)); + UD60x18 amount = ud(amount_.fullMulDiv(uUNIT, quoteTokenScale)); // Factors are calculated in a certain order to avoid precision loss UD60x18 payout; @@ -275,8 +301,10 @@ contract GradualDutchAuction is AtomicAuctionModule { if (block.timestamp >= auction.lastAuctionStart) { // T is positive // Calculate the exponential factor + // This cannot exceed the max exponential input due to the bounds imbosed on auction creation + // Current time - last auction start is guaranteed to be < duration. If not, the auction is over. UD60x18 ekt = auction.decayConstant.mul( - convert(block.timestamp - auction.lastAuctionStart) + convert(block.timestamp - auction.lastAuctionStart).div(ONE_DAY) ).exp(); // Calculate the logarithm @@ -286,6 +314,8 @@ contract GradualDutchAuction is AtomicAuctionModule { // T is negative: flip the e^(k * T) term to the denominator // Calculate the exponential factor + // This cannot exceed the max exponential input due to the bounds imbosed on auction creation + // last auction start - current time is guaranteed to be < duration. If not, the auction is over. UD60x18 ekt = auction.decayConstant.mul( convert(auction.lastAuctionStart - block.timestamp) ).exp(); @@ -322,12 +352,18 @@ contract GradualDutchAuction is AtomicAuctionModule { UD60x18 c; if (block.timestamp >= auction.lastAuctionStart) { // T is positive + // This cannot exceed the max exponential input due to the bounds imbosed on auction creation + // Current time - last auction start is guaranteed to be < duration. If not, the auction is over. + // TODO determine bounds to prevent overflow on multiplication and division c = q0.sub(qm).div( auction.decayConstant.mul(convert(block.timestamp - auction.lastAuctionStart)) .exp().mul(qm) ); } else { // T is negative: flip the e^(k * T) term to the numerator + // This cannot exceed the max exponential input due to the bounds imbosed on auction creation + // last auction start - current time is guaranteed to be < duration. If not, the auction is over. + // TODO determine bounds to prevent overflow on multiplication and division c = q0.sub(qm).mul( auction.decayConstant.mul(convert(auction.lastAuctionStart - block.timestamp)) .exp() From 79375d6f64f4febb1e8a431f46451b38bef07e2a Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 2 May 2024 14:38:28 -0500 Subject: [PATCH 17/69] chore: remove previously added constant and clarify docs --- src/modules/auctions/GDA.sol | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index b66416986..17f74a5af 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -43,6 +43,12 @@ contract GradualDutchAuction is AtomicAuctionModule { uint256 internal constant MIN_DECAY_TARGET = 1e16; // 1% uint256 internal constant MAX_DECAY_TARGET = 50e16; // 50% + // Bounds for the decay period, which establishes the bounds for the decay constant + // If a you want a longer or shorter period for the target, you can find another point on the curve that is in this range + // and calculate the decay target for that point as your input + uint48 internal constant MIN_DECAY_PERIOD = 1 days; + uint48 internal constant MAX_DECAY_PERIOD = 1 weeks; + // Decay period must be greater than or equal to 1 day and less than or equal to 1 week // A minimum value of q1 = q0 * 0.01 and a min period of 1 day means: // MAX_LN_OUTPUT = ln(1/0.5) = 0_693147180559945309 @@ -51,17 +57,8 @@ contract GradualDutchAuction is AtomicAuctionModule { // MIN_LN_OUTPUT = ln(1/0.99) = 0_010050335853501441 // MIN_LN_OUTPUT / 7 = 0_001435762264785920 - // We use these bounds to prove that various calculations won't overflow below - // TODO: implement the above - // TODO should we be able to update the min and max periods? - uint48 internal constant MIN_DECAY_PERIOD = 1 days; - uint48 internal constant MAX_DECAY_PERIOD = 1 weeks; - UD60x18 internal constant MAX_DECAY_CONSTANT = UD60x18.wrap(uint256(693_147_180_559_945_309)); - UD60x18 internal constant MIN_DECAY_CONSTANT = UD60x18.wrap(uint256(1_435_762_264_785_920)); - - // These bounds imply a max auction duration of 192 days to avoid overflow of some of the exponential calculations - // TODO dynamically calculate based on params? this isn't hard and fast if the decay constant is less than the max - UD60x18 internal constant MAX_AUCTION_DURATION = UD60x18.wrap(192e18); + // UD60x18 internal constant MAX_DECAY_CONSTANT = UD60x18.wrap(uint256(693_147_180_559_945_309)); + // UD60x18 internal constant MIN_DECAY_CONSTANT = UD60x18.wrap(uint256(1_435_762_264_785_920)); mapping(uint256 id => AuctionData data) public auctionData; From fe594b3cbff1ed5a6580e7172c6a6849f3016d93 Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 2 May 2024 14:49:33 -0500 Subject: [PATCH 18/69] chore: switch out solady for prb-math in GDA --- src/modules/auctions/GDA.sol | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 17f74a5af..1ad2e04e7 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -11,11 +11,12 @@ import {AtomicAuctionModule} from "src/modules/auctions/AtomicAuctionModule.sol" import { UD60x18, ud, convert, ZERO, UNIT, uUNIT, EXP_MAX_INPUT } from "lib/prb-math/src/UD60x18.sol"; +import "lib/prb-math/src/Common.sol" as PRBMath; import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol"; /// @notice Continuous Gradual Dutch Auction (GDA) module with exponential decay and a minimum price. contract GradualDutchAuction is AtomicAuctionModule { - using FixedPointMathLib for uint256; + using {PRBMath.mulDiv} for uint256; /// @notice Auction pricing data struct AuctionData { @@ -108,9 +109,9 @@ contract GradualDutchAuction is AtomicAuctionModule { UD60x18 decayConstant; { uint256 quoteTokenScale = 10 ** lot_.quoteTokenDecimals; - UD60x18 q0 = ud(params.equilibriumPrice.fullMulDiv(uUNIT, quoteTokenScale)); + UD60x18 q0 = ud(params.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); UD60x18 q1 = q0.mul(UNIT - ud(params.decayTarget)).div(UNIT); - UD60x18 qm = ud(params.minimumPrice.fullMulDiv(uUNIT, quoteTokenScale)); + UD60x18 qm = ud(params.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); // Check that q0 > q1 > qm // This ensures that the operand for the logarithm is positive @@ -136,7 +137,7 @@ contract GradualDutchAuction is AtomicAuctionModule { // Calculate emissions rate as number of tokens released per day UD60x18 emissionsRate = - ud(lot_.capacity.fullMulDiv(uUNIT, 10 ** lot_.baseTokenDecimals)).div(duration); + ud(lot_.capacity.mulDiv(uUNIT, 10 ** lot_.baseTokenDecimals)).div(duration); // Store auction data AuctionData storage data = auctionData[lotId_]; @@ -205,10 +206,13 @@ contract GradualDutchAuction is AtomicAuctionModule { // Scale the result to 18 decimals uint256 quoteTokenScale = 10 ** lot.quoteTokenDecimals; UD60x18 priceDiff = - ud((auction.equilibriumPrice - auction.minimumPrice).fullMulDiv(uUNIT, quoteTokenScale)); + ud((auction.equilibriumPrice - auction.minimumPrice).mulDiv(uUNIT, quoteTokenScale)); // Calculate the second numerator factor: e^((k*P)/r) - 1 // This cannot exceed the max exponential input due to the bounds imbosed on auction creation + // emissions rate = initial capacity / duration + // payout must be less then or equal to initial capacity + // therefore, the resulting exponent is at most decay constant * duration UD60x18 ekpr = auction.decayConstant.mul(payout).div(auction.emissionsRate).exp().sub(UNIT); // Handle cases of T being positive or negative @@ -245,7 +249,7 @@ contract GradualDutchAuction is AtomicAuctionModule { } // Scale price back to quote token decimals - uint256 amount = result.intoUint256().fullMulDiv(quoteTokenScale, uUNIT); + uint256 amount = result.intoUint256().mulDiv(quoteTokenScale, uUNIT); return amount; } @@ -286,10 +290,10 @@ contract GradualDutchAuction is AtomicAuctionModule { // Get quote token scale and convert equilibrium price to 18 decimals uint256 quoteTokenScale = 10 ** lot.quoteTokenDecimals; - UD60x18 q0 = ud(auction.equilibriumPrice.fullMulDiv(uUNIT, quoteTokenScale)); + UD60x18 q0 = ud(auction.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); // Scale amount to 18 decimals - UD60x18 amount = ud(amount_.fullMulDiv(uUNIT, quoteTokenScale)); + UD60x18 amount = ud(amount_.mulDiv(uUNIT, quoteTokenScale)); // Factors are calculated in a certain order to avoid precision loss UD60x18 payout; @@ -330,7 +334,7 @@ contract GradualDutchAuction is AtomicAuctionModule { { // Check that the amount / minPrice is not greater than the max payout (i.e. remaining capacity) uint256 minPrice = auction.minimumPrice; - uint256 payoutAtMinPrice = FixedPointMathLib.fullMulDiv( + uint256 payoutAtMinPrice = FixedPointMathLib.mulDiv( amount_, 10 ** lotData[lotId_].baseTokenDecimals, minPrice ); if (payoutAtMinPrice > maxPayout(lotId_)) { @@ -340,7 +344,7 @@ contract GradualDutchAuction is AtomicAuctionModule { // Convert minimum price to 18 decimals // Can't overflow because quoteTokenScale <= uUNIT - UD60x18 qm = ud(auction.minimumPrice.fullMulDiv(uUNIT, quoteTokenScale)); + UD60x18 qm = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); // Calculate first term: (k * Q) / qm UD60x18 f = auction.decayConstant.mul(amount).div(qm); From 57c69e7c2e966880846ea76db3d87f5da2a16235 Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 8 May 2024 16:12:37 -0500 Subject: [PATCH 19/69] feat: avoid overflows and clean-up TODOs --- src/modules/auctions/GDA.sol | 41 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 1ad2e04e7..a5212934a 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -8,9 +8,7 @@ import {Veecode, toVeecode} from "src/modules/Modules.sol"; import {AtomicAuctionModule} from "src/modules/auctions/AtomicAuctionModule.sol"; // External libraries -import { - UD60x18, ud, convert, ZERO, UNIT, uUNIT, EXP_MAX_INPUT -} from "lib/prb-math/src/UD60x18.sol"; +import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol"; @@ -20,24 +18,23 @@ contract GradualDutchAuction is AtomicAuctionModule { /// @notice Auction pricing data struct AuctionData { - uint256 equilibriumPrice; // price at which the auction is balanced - uint256 minimumPrice; // minimum price for the auction + uint256 equilibriumPrice; // initial price of one base token, where capacity and time are balanced + uint256 minimumPrice; // minimum price for one base token uint256 lastAuctionStart; // time that the last un-purchased auction started, may be in the future UD60x18 decayConstant; // speed at which the price decays, as UD60x18. UD60x18 emissionsRate; // number of tokens released per day, as UD60x18. Calculated as capacity / duration. } struct GDAParams { - uint256 equilibriumPrice; - uint256 minimumPrice; + uint256 equilibriumPrice; // initial price of one base token, where capacity and time are balanced + uint256 minimumPrice; // minimum price for one base token uint256 decayTarget; // target decay percent over the first decay period of an auction (steepest part of the curve) uint256 decayPeriod; // period over which the target decay percent is reached } // ========== STATE VARIABLES ========== // - // TODO instead of converting all times to fixed point days, we could just use seconds and not convert to 1e18. It - // It could problems with the decay constant being too large though - UD60x18 internal constant ONE_DAY = UD60x18.wrap(1 days); + /* solhint-disable private-vars-leading-underscore */ + UD60x18 internal constant ONE_DAY = UD60x18.wrap(1 days * uUNIT); // Decay target over the first period must fit within these bounds // We use 18 decimals so we don't have to convert it to use as a UD60x18 @@ -58,16 +55,15 @@ contract GradualDutchAuction is AtomicAuctionModule { // MIN_LN_OUTPUT = ln(1/0.99) = 0_010050335853501441 // MIN_LN_OUTPUT / 7 = 0_001435762264785920 - // UD60x18 internal constant MAX_DECAY_CONSTANT = UD60x18.wrap(uint256(693_147_180_559_945_309)); - // UD60x18 internal constant MIN_DECAY_CONSTANT = UD60x18.wrap(uint256(1_435_762_264_785_920)); + /* solhint-enable private-vars-leading-underscore */ mapping(uint256 id => AuctionData data) public auctionData; // ========== SETUP ========== // constructor(address auctionHouse_) AuctionModule(auctionHouse_) { - // TODO think about appropriate minimum for auction duration - minAuctionDuration = 1 days; + // Initially setting the minimum GDA duration to 1 hour + minAuctionDuration = 1 hours; } /// @inheritdoc Module @@ -88,7 +84,6 @@ contract GradualDutchAuction is AtomicAuctionModule { } // Capacity must be in base token - // TODO can we allow capacity in quote token? Mostly effects emissions rate if (lot_.capacityInQuote) revert Auction_InvalidParams(); // Minimum price can be zero, but the equations default back to the basic GDA implementation @@ -298,6 +293,7 @@ contract GradualDutchAuction is AtomicAuctionModule { // Factors are calculated in a certain order to avoid precision loss UD60x18 payout; if (auction.minimumPrice == 0) { + // Auction does not have a minimum price UD60x18 logFactor; if (block.timestamp >= auction.lastAuctionStart) { // T is positive @@ -329,8 +325,7 @@ contract GradualDutchAuction is AtomicAuctionModule { // Calculate the payout payout = auction.emissionsRate.mul(logFactor).div(auction.decayConstant); } else { - // TODO think about refactoring to avoid precision loss - + // Auction has a minimum price { // Check that the amount / minPrice is not greater than the max payout (i.e. remaining capacity) uint256 minPrice = auction.minimumPrice; @@ -355,20 +350,20 @@ contract GradualDutchAuction is AtomicAuctionModule { // T is positive // This cannot exceed the max exponential input due to the bounds imbosed on auction creation // Current time - last auction start is guaranteed to be < duration. If not, the auction is over. - // TODO determine bounds to prevent overflow on multiplication and division - c = q0.sub(qm).div( + // We have to divide twice to avoid multipling the exponential result by qm, which could overflow. + c = q0.sub(qm).div(qm).div( auction.decayConstant.mul(convert(block.timestamp - auction.lastAuctionStart)) - .exp().mul(qm) + .exp() ); } else { // T is negative: flip the e^(k * T) term to the numerator // This cannot exceed the max exponential input due to the bounds imbosed on auction creation // last auction start - current time is guaranteed to be < duration. If not, the auction is over. - // TODO determine bounds to prevent overflow on multiplication and division - c = q0.sub(qm).mul( + // We divide before multiplying here to avoid reduce the odds of an intermediate result overflowing. + c = q0.sub(qm).div(qm).mul( auction.decayConstant.mul(convert(auction.lastAuctionStart - block.timestamp)) .exp() - ).div(qm); + ); } // Calculate the third term: W(C e^(k * Q / qm + C)) From c916291cacff11bcb0b0667ba07d10122c9f8082 Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 15 May 2024 16:47:07 -0500 Subject: [PATCH 20/69] chore: only one math lib in GDA --- src/modules/auctions/GDA.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index a5212934a..d3661df76 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -10,7 +10,6 @@ import {AtomicAuctionModule} from "src/modules/auctions/AtomicAuctionModule.sol" // External libraries import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; -import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol"; /// @notice Continuous Gradual Dutch Auction (GDA) module with exponential decay and a minimum price. contract GradualDutchAuction is AtomicAuctionModule { @@ -329,9 +328,8 @@ contract GradualDutchAuction is AtomicAuctionModule { { // Check that the amount / minPrice is not greater than the max payout (i.e. remaining capacity) uint256 minPrice = auction.minimumPrice; - uint256 payoutAtMinPrice = FixedPointMathLib.mulDiv( - amount_, 10 ** lotData[lotId_].baseTokenDecimals, minPrice - ); + uint256 payoutAtMinPrice = + amount_.mulDiv(10 ** lotData[lotId_].baseTokenDecimals, minPrice); if (payoutAtMinPrice > maxPayout(lotId_)) { revert Auction_InsufficientCapacity(); } From 68bf9fafeb596f58971e1ab8f797b8f720c1404a Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 15 May 2024 16:48:38 -0500 Subject: [PATCH 21/69] test: add GDA test base contract --- test/modules/auctions/GDA/GDATest.sol | 218 ++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 test/modules/auctions/GDA/GDATest.sol diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol new file mode 100644 index 000000000..be7527fe8 --- /dev/null +++ b/test/modules/auctions/GDA/GDATest.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +// Libraries +import {Test} from "forge-std/Test.sol"; +import {UD60x18} from "lib/prb-math/src/UD60x18.sol"; +import "lib/prb-math/src/Common.sol" as PRBMath; + +// Mocks +import {Permit2User} from "test/lib/permit2/Permit2User.sol"; + +// Modules +import {AtomicAuctionHouse} from "src/AtomicAuctionHouse.sol"; +import {IAuction} from "src/interfaces/IAuction.sol"; +import {GradualDutchAuction} from "src/modules/auctions/GDA.sol"; + +abstract contract GdaTest is Test, Permit2User { + using {PRBMath.mulDiv} for uint256; + + uint256 internal constant _BASE_SCALE = 1e18; + + address internal constant _PROTOCOL = address(0x2); + address internal constant _BIDDER = address(0x3); + address internal constant _REFERRER = address(0x4); + + uint256 internal constant _LOT_CAPACITY = 10e18; + uint48 internal constant _DURATION = 1 days; + uint256 internal constant _INITIAL_PRICE = 5e18; + uint256 internal constant _MIN_PRICE = 2e18; + uint256 internal constant _DECAY_TARGET = 10e16; // 10% + uint256 internal constant _DECAY_PERIOD = 1 days; + + AtomicAuctionHouse internal _auctionHouse; + GradualDutchAuction internal _module; + + // Input parameters (modified by modifiers) + uint48 internal _start; + uint96 internal _lotId = type(uint96).max; + IAuction.AuctionParams internal _auctionParams; + GradualDutchAuction.GDAParams internal _gdaParams; + + uint8 internal _quoteTokenDecimals = 18; + uint8 internal _baseTokenDecimals = 18; + + function setUp() external { + vm.warp(1_000_000); + + _auctionHouse = new AtomicAuctionHouse(address(this), _PROTOCOL, _permit2Address); + _module = new GradualDutchAuction(address(_auctionHouse)); + + _start = uint48(block.timestamp) + 1; + + _gdaParams = GradualDutchAuction.GDAParams({ + equilibriumPrice: _INITIAL_PRICE, + minimumPrice: _MIN_PRICE, + decayTarget: _DECAY_TARGET, + decayPeriod: _DECAY_PERIOD + }); + + _auctionParams = IAuction.AuctionParams({ + start: _start, + duration: _DURATION, + capacityInQuote: false, + capacity: _LOT_CAPACITY, + implParams: abi.encode(_gdaParams) + }); + } + + // ========== MODIFIERS ========== // + + function _setQuoteTokenDecimals(uint8 decimals_) internal { + _quoteTokenDecimals = decimals_; + + _gdaParams.equilibriumPrice = _scaleQuoteTokenAmount(_INITIAL_PRICE); + _gdaParams.minimumPrice = _scaleQuoteTokenAmount(_MIN_PRICE); + + _auctionParams.implParams = abi.encode(_gdaParams); + } + + modifier givenQuoteTokenDecimals(uint8 decimals_) { + _setQuoteTokenDecimals(decimals_); + _; + } + + function _setBaseTokenDecimals(uint8 decimals_) internal { + _baseTokenDecimals = decimals_; + + _auctionParams.capacity = _scaleBaseTokenAmount(_LOT_CAPACITY); + } + + modifier givenBaseTokenDecimals(uint8 decimals_) { + _setBaseTokenDecimals(decimals_); + _; + } + + modifier givenLotCapacity(uint256 capacity_) { + _auctionParams.capacity = capacity_; + _; + } + + modifier givenStartTimestamp(uint48 start_) { + _auctionParams.start = start_; + _; + } + + modifier givenDuration(uint48 duration_) { + _auctionParams.duration = duration_; + _; + } + + modifier givenCapacityInQuote() { + _auctionParams.capacityInQuote = true; + _; + } + + function _createAuctionLot() internal { + vm.prank(address(_auctionHouse)); + _module.auction(_lotId, _auctionParams, _quoteTokenDecimals, _baseTokenDecimals); + } + + modifier givenLotIsCreated() { + _createAuctionLot(); + _; + } + + modifier givenEquilibriumPrice(uint256 price_) { + _gdaParams.equilibriumPrice = price_; + _auctionParams.implParams = abi.encode(_gdaParams); + _; + } + + modifier givenMinPrice(uint256 minPrice_) { + _gdaParams.minimumPrice = minPrice_; + _auctionParams.implParams = abi.encode(_gdaParams); + _; + } + + function givenDecayTarget(uint256 decayTarget_) internal { + _gdaParams.decayTarget = decayTarget_; + _auctionParams.implParams = abi.encode(_gdaParams); + } + + modifier givenDecayPeriod(uint256 decayPeriod_) { + _gdaParams.decayPeriod = decayPeriod_; + _auctionParams.implParams = abi.encode(_gdaParams); + _; + } + + function _concludeLot() internal { + vm.warp(_start + _DURATION + 1); + } + + modifier givenLotHasConcluded() { + _concludeLot(); + _; + } + + modifier givenLotHasStarted() { + vm.warp(_start + 1); + _; + } + + function _cancelAuctionLot() internal { + vm.prank(address(_auctionHouse)); + _module.cancelAuction(_lotId); + } + + modifier givenLotIsCancelled() { + _cancelAuctionLot(); + _; + } + + function _createPurchase(uint256 amount_, uint256 minAmountOut_) internal { + vm.prank(address(_auctionHouse)); + _module.purchase(_lotId, amount_, abi.encode(minAmountOut_)); + } + + modifier givenPurchase(uint256 amount_, uint256 minAmountOut_) { + _createPurchase(amount_, minAmountOut_); + _; + } + + // ======== Internal Functions ======== // + + function _scaleQuoteTokenAmount(uint256 amount_) internal view returns (uint256) { + return amount_.mulDiv(10 ** _quoteTokenDecimals, _BASE_SCALE); + } + + function _scaleBaseTokenAmount(uint256 amount_) internal view returns (uint256) { + return amount_.mulDiv(10 ** _baseTokenDecimals, _BASE_SCALE); + } + + function _getAuctionLot(uint96 lotId_) internal view returns (IAuction.Lot memory) { + return _module.getLot(lotId_); + } + + function _getAuctionData(uint96 lotId_) + internal + view + returns (GradualDutchAuction.AuctionData memory) + { + ( + uint256 eqPrice, + uint256 minPrice, + uint256 lastAuctionStart, + UD60x18 decayConstant, + UD60x18 emissionsRate + ) = _module.auctionData(lotId_); + + return GradualDutchAuction.AuctionData({ + equilibriumPrice: eqPrice, + minimumPrice: minPrice, + lastAuctionStart: lastAuctionStart, + decayConstant: decayConstant, + emissionsRate: emissionsRate + }); + } +} From b0e0c96836ff88ec638d56ad18c706256dd43891 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 21 May 2024 16:20:15 -0500 Subject: [PATCH 22/69] fix: remove extra checks and update min decay period --- src/modules/auctions/GDA.sol | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index d3661df76..39941719b 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -43,16 +43,18 @@ contract GradualDutchAuction is AtomicAuctionModule { // Bounds for the decay period, which establishes the bounds for the decay constant // If a you want a longer or shorter period for the target, you can find another point on the curve that is in this range // and calculate the decay target for that point as your input - uint48 internal constant MIN_DECAY_PERIOD = 1 days; + uint48 internal constant MIN_DECAY_PERIOD = 1 hours; uint48 internal constant MAX_DECAY_PERIOD = 1 weeks; - // Decay period must be greater than or equal to 1 day and less than or equal to 1 week - // A minimum value of q1 = q0 * 0.01 and a min period of 1 day means: + // Decay period must be greater than or equal to 1 hours and less than or equal to 1 week + // A minimum value of q1 = q0 * 0.01 and a min period of 1 hours means: // MAX_LN_OUTPUT = ln(1/0.5) = 0_693147180559945309 - // MAX_LN_OUTPUT / 1 = 0_693147180559945309 + // MAX_LN_OUTPUT * 24 = 16_635532333438687426 + // -> implies a max duration of 8 days in the worst case (decaying 50% over an hour) // A maximum value of q1 = q0 * 0.99 and a max period of 7 days means: // MIN_LN_OUTPUT = ln(1/0.99) = 0_010050335853501441 // MIN_LN_OUTPUT / 7 = 0_001435762264785920 + // -> implies a max duration of ~52 years in the best case (decaying 1% over a week) /* solhint-enable private-vars-leading-underscore */ @@ -77,8 +79,8 @@ contract GradualDutchAuction is AtomicAuctionModule { GDAParams memory params = abi.decode(params_, (GDAParams)); // Validate parameters - // Equilibrium Price must not be zero and greater than minimum price (which can be zero) - if (params.equilibriumPrice == 0 || params.equilibriumPrice <= params.minimumPrice) { + // Equilibrium Price must not be zero + if (params.equilibriumPrice == 0) { revert Auction_InvalidParams(); } @@ -108,8 +110,10 @@ contract GradualDutchAuction is AtomicAuctionModule { UD60x18 qm = ud(params.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); // Check that q0 > q1 > qm + // Don't need to check q0 > q1 since: + // decayTarget >= 1e16 => q1 <= q0 * 0.99 => q0 > q1 // This ensures that the operand for the logarithm is positive - if (q0 <= q1 || q1 <= qm) { + if (q1 <= qm) { revert Auction_InvalidParams(); } From a464b8ed3406bdb7067b06eb9f04c9681b3ed860 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 21 May 2024 16:42:59 -0500 Subject: [PATCH 23/69] fix: divide decay constant seconds by one day --- src/modules/auctions/GDA.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 39941719b..87d7e1010 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -28,7 +28,7 @@ contract GradualDutchAuction is AtomicAuctionModule { uint256 equilibriumPrice; // initial price of one base token, where capacity and time are balanced uint256 minimumPrice; // minimum price for one base token uint256 decayTarget; // target decay percent over the first decay period of an auction (steepest part of the curve) - uint256 decayPeriod; // period over which the target decay percent is reached + uint256 decayPeriod; // period over which the target decay percent is reached, in seconds } // ========== STATE VARIABLES ========== // @@ -118,7 +118,7 @@ contract GradualDutchAuction is AtomicAuctionModule { } // Calculate the decay constant - decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(params.decayPeriod)); + decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(params.decayPeriod).div(ONE_DAY)); } // TODO other validation checks? @@ -127,8 +127,8 @@ contract GradualDutchAuction is AtomicAuctionModule { UD60x18 duration = convert(uint256(lot_.conclusion - lot_.start)).div(ONE_DAY); // The duration must be less than the max exponential input divided by the decay constant - // in order for the exponential operations to not overflow. The lowest this value can be - // is 192 days if the decay constant is at it's maximum value + // in order for the exponential operations to not overflow. See the minimum and maximum + // constant calculations for more information. if (duration > EXP_MAX_INPUT.div(decayConstant)) { revert Auction_InvalidParams(); } From 212da97ce43752e799b9dd13599979c0b4c3cc96 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 21 May 2024 16:43:21 -0500 Subject: [PATCH 24/69] test: wip on create auction tests for GDA --- test/modules/auctions/GDA/GDATest.sol | 7 +- test/modules/auctions/GDA/auction.t.sol | 198 ++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 test/modules/auctions/GDA/auction.t.sol diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index be7527fe8..667554007 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.19; // Libraries import {Test} from "forge-std/Test.sol"; -import {UD60x18} from "lib/prb-math/src/UD60x18.sol"; +import {UD60x18, uUNIT} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; // Mocks @@ -14,6 +14,7 @@ import {AtomicAuctionHouse} from "src/AtomicAuctionHouse.sol"; import {IAuction} from "src/interfaces/IAuction.sol"; import {GradualDutchAuction} from "src/modules/auctions/GDA.sol"; + abstract contract GdaTest is Test, Permit2User { using {PRBMath.mulDiv} for uint256; @@ -29,6 +30,7 @@ abstract contract GdaTest is Test, Permit2User { uint256 internal constant _MIN_PRICE = 2e18; uint256 internal constant _DECAY_TARGET = 10e16; // 10% uint256 internal constant _DECAY_PERIOD = 1 days; + UD60x18 internal constant _ONE_DAY = UD60x18.wrap(1 days * uUNIT); AtomicAuctionHouse internal _auctionHouse; GradualDutchAuction internal _module; @@ -135,9 +137,10 @@ abstract contract GdaTest is Test, Permit2User { _; } - function givenDecayTarget(uint256 decayTarget_) internal { + modifier givenDecayTarget(uint256 decayTarget_) { _gdaParams.decayTarget = decayTarget_; _auctionParams.implParams = abi.encode(_gdaParams); + _; } modifier givenDecayPeriod(uint256 decayPeriod_) { diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol new file mode 100644 index 000000000..4a4427ec7 --- /dev/null +++ b/test/modules/auctions/GDA/auction.t.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Module} from "src/modules/Modules.sol"; +import {IAuction} from "src/interfaces/IAuction.sol"; +// import {GradualDutchAuction} from "src/modules/auctions/GDA.sol"; + +import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; +import "lib/prb-math/src/Common.sol" as PRBMath; + +import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; +import {console2} from "lib/forge-std/src/console2.sol"; + +contract GdaCreateAuctionTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + // [X] when the caller is not the parent + // [X] it reverts + // [X] when the start time is in the past + // [X] it reverts + // [X] when the duration is less than the globally configured minimum + // [X] it reverts + // [X] when the equilibrium price is 0 + // [X] it reverts + // [X] when the minimum price is greater than or equal to the decay target price + // [X] it reverts + // [X] when the decay target is less than the minimum + // [X] it reverts + // [X] when the decay target is greater than the maximum + // [X] it reverts + // [X] when the decay period is less than the minimum + // [X] it reverts + // [X] when the decay period is greater than the maximum + // [X] it reverts + // [X] when the capacity is in quote token + // [X] it reverts + // [ ] when duration is greater than the max exp input divided by the calculated decay constant + // [ ] it reverts + // [ ] when the inputs are all valid + // [ ] it stores the auction data + // [ ] when the token decimals differ + // [ ] it handles the calculations correctly + + function test_notParent_reverts() public { + // Expect revert + bytes memory err = abi.encodeWithSelector(Module.Module_OnlyParent.selector, address(this)); + vm.expectRevert(err); + + // Call the function (without pranking to auction house) + _module.auction(_lotId, _auctionParams, _quoteTokenDecimals, _baseTokenDecimals); + } + + function test_startTimeInPast_reverts() + public + givenStartTimestamp(uint48(block.timestamp - 1)) + { + // Expect revert + bytes memory err = abi.encodeWithSelector( + IAuction.Auction_InvalidStart.selector, _auctionParams.start, uint48(block.timestamp) + ); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_durationLessThanMinimum_reverts() public givenDuration(uint48(1 hours) - 1) { + // Expect revert + bytes memory err = abi.encodeWithSelector( + IAuction.Auction_InvalidDuration.selector, _auctionParams.duration, uint48(1 hours) + ); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_equilibriumPriceIsZero_reverts() public givenEquilibriumPrice(0) { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_minPriceGreaterThanDecayTargePrice_reverts() + public + givenMinPrice(4e18) + givenDecayTarget(25e16) // 25% decay from 5e18 is 3.75e18 + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_minPriceEqualToDecayTargePrice_reverts() + public + givenMinPrice(4e18) + givenDecayTarget(20e16) // 20% decay from 5e18 is 4e18 + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_decayTargetLessThanMinimum_reverts() + public + givenDecayTarget(1e16 - 1) // slightly less than 1% + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_decayTargetGreaterThanMaximum_reverts() + public + givenDecayTarget(50e16 + 1) // slightly more than 50% + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_decayPeriodLessThanMinimum_reverts() + public + givenDecayPeriod(uint48(1 hours) - 1) + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_decayPeriodGreaterThanMaximum_reverts() + public + givenDecayPeriod(uint48(1 weeks) + 1) + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_capacityInQuote_reverts() public givenCapacityInQuote { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function testFuzz_durationGreaterThanMaxExpInputDividedByDecayConstant_reverts(uint8 decayTarget_, uint8 decayHours_) public { + // Normalize the inputs + uint256 decayTarget = uint256(decayTarget_ % 50 == 0 ? 50 : decayTarget_ % 50) * 1e16; + uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; + + // Calculate the decay constant + // q1 > qm here because qm < q0 * 0.50, which is the max decay target + uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; + UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); + UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); + UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + + // Calculate the decay constant + UD60x18 decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(decayPeriod).div(_ONE_DAY)); + + // Calculate the maximum duration in seconds + uint256 maxDuration = convert(EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); + console2.log("Max duration:", maxDuration); + + // Set the duration parameter to the max duration plus 1 + _auctionParams.duration = uint48(maxDuration + 1); + + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } +} From a05403f6cfb44435d1596e8b90538c0ccaa99adf Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 22 May 2024 10:17:56 -0500 Subject: [PATCH 25/69] test: finish GDA create auction tests --- test/modules/auctions/GDA/auction.t.sol | 115 ++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 8 deletions(-) diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 4a4427ec7..07002a963 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.19; import {Module} from "src/modules/Modules.sol"; import {IAuction} from "src/interfaces/IAuction.sol"; -// import {GradualDutchAuction} from "src/modules/auctions/GDA.sol"; +import {GradualDutchAuction} from "src/modules/auctions/GDA.sol"; import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; @@ -33,12 +33,12 @@ contract GdaCreateAuctionTest is GdaTest { // [X] it reverts // [X] when the capacity is in quote token // [X] it reverts - // [ ] when duration is greater than the max exp input divided by the calculated decay constant - // [ ] it reverts - // [ ] when the inputs are all valid - // [ ] it stores the auction data - // [ ] when the token decimals differ - // [ ] it handles the calculations correctly + // [X] when duration is greater than the max exp input divided by the calculated decay constant + // [X] it reverts + // [X] when the inputs are all valid + // [X] it stores the auction data + // [X] when the token decimals differ + // [X] it handles the calculations correctly function test_notParent_reverts() public { // Expect revert @@ -170,6 +170,8 @@ contract GdaCreateAuctionTest is GdaTest { // Normalize the inputs uint256 decayTarget = uint256(decayTarget_ % 50 == 0 ? 50 : decayTarget_ % 50) * 1e16; uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; + console2.log("Decay target:", decayTarget); + console2.log("Decay period:", decayPeriod); // Calculate the decay constant // q1 > qm here because qm < q0 * 0.50, which is the max decay target @@ -178,14 +180,23 @@ contract GdaCreateAuctionTest is GdaTest { UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + console2.log("q0:", q0.unwrap()); + console2.log("q1:", q1.unwrap()); + console2.log("qm:", qm.unwrap()); + // Calculate the decay constant UD60x18 decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(decayPeriod).div(_ONE_DAY)); + console2.log("Decay constant:", decayConstant.unwrap()); // Calculate the maximum duration in seconds uint256 maxDuration = convert(EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); console2.log("Max duration:", maxDuration); - // Set the duration parameter to the max duration plus 1 + // Set the decay target and decay period to the fuzzed values + // Set duration to the max duration plus 1 + _gdaParams.decayTarget = decayTarget; + _gdaParams.decayPeriod = decayPeriod; + _auctionParams.implParams = abi.encode(_gdaParams); _auctionParams.duration = uint48(maxDuration + 1); // Expect revert @@ -195,4 +206,92 @@ contract GdaCreateAuctionTest is GdaTest { // Call the function _createAuctionLot(); } + + function testFuzz_durationEqualMaxExpInputDividedByDecayConstant_succeeds(uint8 decayTarget_, uint8 decayHours_) public { + // Normalize the inputs + uint256 decayTarget = uint256(decayTarget_ % 50 == 0 ? 50 : decayTarget_ % 50) * 1e16; + uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; + console2.log("Decay target:", decayTarget); + console2.log("Decay period:", decayPeriod); + + // Calculate the decay constant + // q1 > qm here because qm < q0 * 0.50, which is the max decay target + uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; + UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); + UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); + UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + + console2.log("q0:", q0.unwrap()); + console2.log("q1:", q1.unwrap()); + console2.log("qm:", qm.unwrap()); + + // Calculate the decay constant + UD60x18 decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(decayPeriod).div(_ONE_DAY)); + console2.log("Decay constant:", decayConstant.unwrap()); + + // Calculate the maximum duration in seconds + uint256 maxDuration = convert(EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); + console2.log("Max duration:", maxDuration); + + // Set the decay target and decay period to the fuzzed values + // Set duration to the max duration + _gdaParams.decayTarget = decayTarget; + _gdaParams.decayPeriod = decayPeriod; + _auctionParams.implParams = abi.encode(_gdaParams); + _auctionParams.duration = uint48(maxDuration); + + // Call the function + _createAuctionLot(); + } + + function _assertAuctionData() internal { + // Calculate the decay constant from the input parameters + uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; + UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); + UD60x18 q1 = q0.mul(UNIT - ud(_gdaParams.decayTarget)).div(UNIT); + UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + UD60x18 decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(_gdaParams.decayPeriod).div(_ONE_DAY)); + + // Calculate the emissions rate + UD60x18 duration = convert(uint256(_auctionParams.duration)).div(_ONE_DAY); + UD60x18 emissionsRate = ud(_auctionParams.capacity.mulDiv(uUNIT, 10 ** _baseTokenDecimals)).div(duration); + + // Check the auction data + GradualDutchAuction.AuctionData memory auctionData = _module.getAuctionData(_lotId); + assertEq(auctionData.equilibriumPrice, _gdaParams.equilibriumPrice); + assertEq(auctionData.minimumPrice, _gdaParams.minimumPrice); + assertEq(auctionData.lastAuctionStart, _auctionParams.start); + assertEq(auctionData.decayConstant, decayConstant); + assertEq(auctionData.emissionsRate, emissionsRate); + } + + function test_allInputsValid_storesAuctionData() public { + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } + + function test_quoteTokensDecimalsSmaller() + public + givenQuoteTokenDecimals(9) + { + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } + + function test_quoteTokensDecimalsLarger() + public + givenBaseTokenDecimals(9) + { + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } } From 954de7ab6b6c715d95833cffa6f7398792385fda Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 22 May 2024 10:34:31 -0500 Subject: [PATCH 26/69] refactor: add GDA interface and fix test compile --- .../modules/auctions/IGradualDutchAuction.sol | 46 +++++++++++++++++++ src/modules/auctions/GDA.sol | 24 ++-------- test/modules/auctions/GDA/GDATest.sol | 10 ++-- test/modules/auctions/GDA/auction.t.sol | 36 ++++++++------- 4 files changed, 75 insertions(+), 41 deletions(-) create mode 100644 src/interfaces/modules/auctions/IGradualDutchAuction.sol diff --git a/src/interfaces/modules/auctions/IGradualDutchAuction.sol b/src/interfaces/modules/auctions/IGradualDutchAuction.sol new file mode 100644 index 000000000..8ea81d550 --- /dev/null +++ b/src/interfaces/modules/auctions/IGradualDutchAuction.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import {UD60x18} from "lib/prb-math/src/UD60x18.sol"; +import {IAtomicAuction} from "src/interfaces/modules/IAtomicAuction.sol"; + +/// @notice Interface for gradual dutch (atomic) auctions +interface IGradualDutchAuction is IAtomicAuction { + // ========== DATA STRUCTURES ========== // + + /// @notice Auction pricing data + /// @param equilibriumPrice The initial price of one base token, where capacity and time are balanced + /// @param minimumPrice The minimum price for one base token + /// @param lastAuctionStart The time that the last un-purchased auction started, may be in the future + /// @param decayConstant The speed at which the price decays, as UD60x18 + /// @param emissionsRate The number of tokens released per day, as UD60x18. Calculated as capacity / duration (in days) + struct AuctionData { + uint256 equilibriumPrice; + uint256 minimumPrice; + uint256 lastAuctionStart; + UD60x18 decayConstant; + UD60x18 emissionsRate; + } + + /// @notice Parameters to create a GDA + /// @param equilibriumPrice The initial price of one base token, where capacity and time are balanced + /// @param minimumPrice The minimum price for one base token + /// @param decayTarget The target decay percent over the first decay period of an auction (steepest part of the curve) + /// @param decayPeriod The period over which the target decay percent is reached, in seconds + struct GDAParams { + uint256 equilibriumPrice; // initial price of one base token, where capacity and time are balanced + uint256 minimumPrice; // minimum price for one base token + uint256 decayTarget; // target decay percent over the first decay period of an auction (steepest part of the curve) + uint256 decayPeriod; // period over which the target decay percent is reached, in seconds + } + + // ========== STATE VARIABLES ========== // + + /// @notice Returns the `AuctionData` for a lot + /// + /// @param lotId The lot ID + function auctionData(uint96 lotId) + external + view + returns (uint256, uint256, uint256, UD60x18, UD60x18); +} diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 87d7e1010..7587be166 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -6,31 +6,16 @@ import {Module} from "src/modules/Modules.sol"; import {AuctionModule} from "src/modules/Auction.sol"; import {Veecode, toVeecode} from "src/modules/Modules.sol"; import {AtomicAuctionModule} from "src/modules/auctions/AtomicAuctionModule.sol"; +import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; // External libraries import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; /// @notice Continuous Gradual Dutch Auction (GDA) module with exponential decay and a minimum price. -contract GradualDutchAuction is AtomicAuctionModule { +contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { using {PRBMath.mulDiv} for uint256; - /// @notice Auction pricing data - struct AuctionData { - uint256 equilibriumPrice; // initial price of one base token, where capacity and time are balanced - uint256 minimumPrice; // minimum price for one base token - uint256 lastAuctionStart; // time that the last un-purchased auction started, may be in the future - UD60x18 decayConstant; // speed at which the price decays, as UD60x18. - UD60x18 emissionsRate; // number of tokens released per day, as UD60x18. Calculated as capacity / duration. - } - - struct GDAParams { - uint256 equilibriumPrice; // initial price of one base token, where capacity and time are balanced - uint256 minimumPrice; // minimum price for one base token - uint256 decayTarget; // target decay percent over the first decay period of an auction (steepest part of the curve) - uint256 decayPeriod; // period over which the target decay percent is reached, in seconds - } - // ========== STATE VARIABLES ========== // /* solhint-disable private-vars-leading-underscore */ UD60x18 internal constant ONE_DAY = UD60x18.wrap(1 days * uUNIT); @@ -58,7 +43,7 @@ contract GradualDutchAuction is AtomicAuctionModule { /* solhint-enable private-vars-leading-underscore */ - mapping(uint256 id => AuctionData data) public auctionData; + mapping(uint96 lotId => AuctionData data) public auctionData; // ========== SETUP ========== // @@ -118,7 +103,8 @@ contract GradualDutchAuction is AtomicAuctionModule { } // Calculate the decay constant - decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(params.decayPeriod).div(ONE_DAY)); + decayConstant = + (q0 - qm).div(q1 - qm).ln().div(convert(params.decayPeriod).div(ONE_DAY)); } // TODO other validation checks? diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index 667554007..e72899bf5 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -11,10 +11,10 @@ import {Permit2User} from "test/lib/permit2/Permit2User.sol"; // Modules import {AtomicAuctionHouse} from "src/AtomicAuctionHouse.sol"; -import {IAuction} from "src/interfaces/IAuction.sol"; +import {IAuction} from "src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; import {GradualDutchAuction} from "src/modules/auctions/GDA.sol"; - abstract contract GdaTest is Test, Permit2User { using {PRBMath.mulDiv} for uint256; @@ -52,7 +52,7 @@ abstract contract GdaTest is Test, Permit2User { _start = uint48(block.timestamp) + 1; - _gdaParams = GradualDutchAuction.GDAParams({ + _gdaParams = IGradualDutchAuction.GDAParams({ equilibriumPrice: _INITIAL_PRICE, minimumPrice: _MIN_PRICE, decayTarget: _DECAY_TARGET, @@ -200,7 +200,7 @@ abstract contract GdaTest is Test, Permit2User { function _getAuctionData(uint96 lotId_) internal view - returns (GradualDutchAuction.AuctionData memory) + returns (IGradualDutchAuction.AuctionData memory) { ( uint256 eqPrice, @@ -210,7 +210,7 @@ abstract contract GdaTest is Test, Permit2User { UD60x18 emissionsRate ) = _module.auctionData(lotId_); - return GradualDutchAuction.AuctionData({ + return IGradualDutchAuction.AuctionData({ equilibriumPrice: eqPrice, minimumPrice: minPrice, lastAuctionStart: lastAuctionStart, diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 07002a963..80ded4c62 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.19; import {Module} from "src/modules/Modules.sol"; -import {IAuction} from "src/interfaces/IAuction.sol"; -import {GradualDutchAuction} from "src/modules/auctions/GDA.sol"; +import {IAuction} from "src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; @@ -166,7 +166,10 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function testFuzz_durationGreaterThanMaxExpInputDividedByDecayConstant_reverts(uint8 decayTarget_, uint8 decayHours_) public { + function testFuzz_durationGreaterThanMaxExpInputDividedByDecayConstant_reverts( + uint8 decayTarget_, + uint8 decayHours_ + ) public { // Normalize the inputs uint256 decayTarget = uint256(decayTarget_ % 50 == 0 ? 50 : decayTarget_ % 50) * 1e16; uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; @@ -207,7 +210,10 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function testFuzz_durationEqualMaxExpInputDividedByDecayConstant_succeeds(uint8 decayTarget_, uint8 decayHours_) public { + function testFuzz_durationEqualMaxExpInputDividedByDecayConstant_succeeds( + uint8 decayTarget_, + uint8 decayHours_ + ) public { // Normalize the inputs uint256 decayTarget = uint256(decayTarget_ % 50 == 0 ? 50 : decayTarget_ % 50) * 1e16; uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; @@ -250,19 +256,21 @@ contract GdaCreateAuctionTest is GdaTest { UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); UD60x18 q1 = q0.mul(UNIT - ud(_gdaParams.decayTarget)).div(UNIT); UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); - UD60x18 decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(_gdaParams.decayPeriod).div(_ONE_DAY)); + UD60x18 decayConstant = + (q0 - qm).div(q1 - qm).ln().div(convert(_gdaParams.decayPeriod).div(_ONE_DAY)); // Calculate the emissions rate UD60x18 duration = convert(uint256(_auctionParams.duration)).div(_ONE_DAY); - UD60x18 emissionsRate = ud(_auctionParams.capacity.mulDiv(uUNIT, 10 ** _baseTokenDecimals)).div(duration); + UD60x18 emissionsRate = + ud(_auctionParams.capacity.mulDiv(uUNIT, 10 ** _baseTokenDecimals)).div(duration); // Check the auction data - GradualDutchAuction.AuctionData memory auctionData = _module.getAuctionData(_lotId); + IGradualDutchAuction.AuctionData memory auctionData = _getAuctionData(_lotId); assertEq(auctionData.equilibriumPrice, _gdaParams.equilibriumPrice); assertEq(auctionData.minimumPrice, _gdaParams.minimumPrice); assertEq(auctionData.lastAuctionStart, _auctionParams.start); - assertEq(auctionData.decayConstant, decayConstant); - assertEq(auctionData.emissionsRate, emissionsRate); + assertEq(auctionData.decayConstant.unwrap(), decayConstant.unwrap()); + assertEq(auctionData.emissionsRate.unwrap(), emissionsRate.unwrap()); } function test_allInputsValid_storesAuctionData() public { @@ -273,10 +281,7 @@ contract GdaCreateAuctionTest is GdaTest { _assertAuctionData(); } - function test_quoteTokensDecimalsSmaller() - public - givenQuoteTokenDecimals(9) - { + function test_quoteTokensDecimalsSmaller() public givenQuoteTokenDecimals(9) { // Call the function _createAuctionLot(); @@ -284,10 +289,7 @@ contract GdaCreateAuctionTest is GdaTest { _assertAuctionData(); } - function test_quoteTokensDecimalsLarger() - public - givenBaseTokenDecimals(9) - { + function test_quoteTokensDecimalsLarger() public givenBaseTokenDecimals(9) { // Call the function _createAuctionLot(); From 24d04a64a2672d5b375e8c154ade57ff69233036 Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 22 May 2024 11:01:30 -0500 Subject: [PATCH 27/69] chore: update test salts --- script/salts/TestSaltConstants.sol | 4 +- script/salts/salts.json | 52 +++++++++---------- .../UniswapV3DTL/UniswapV3DTLTest.sol | 3 +- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/script/salts/TestSaltConstants.sol b/script/salts/TestSaltConstants.sol index f19fd1076..cda646c34 100644 --- a/script/salts/TestSaltConstants.sol +++ b/script/salts/TestSaltConstants.sol @@ -10,6 +10,6 @@ abstract contract TestSaltConstants { address internal constant _UNISWAP_V2_ROUTER = address(0x1EBC400fd43aC56937d4e14B8495B0f021e7c876); address internal constant _UNISWAP_V3_FACTORY = - address(0xE70b554757175BAb9eD3245C7f2b387ef09889Bd); - address internal constant _GUNI_FACTORY = address(0x6c4f6A2E6b9AFB0406919979cE3570741BCb866A); + address(0x7F1bb763E9acE13Ad3488E3d48D4AE1cde6E9604); + address internal constant _GUNI_FACTORY = address(0x2Bc907bF89D72479e6b6A38CC89fdE71672ba90e); } diff --git a/script/salts/salts.json b/script/salts/salts.json index 339b8c95e..2bdb776d6 100644 --- a/script/salts/salts.json +++ b/script/salts/salts.json @@ -1,44 +1,44 @@ { "AtomicAuctionHouse": { - "0xc34e46cfceb1e62d804e8197ba829a150e216e433ee21a68b96f1edd3abd4dd9": "0x87bc14fda5bd97c7e883ae1227f30462125ebcf6800a34d10f39af4ac46e84b9" + "0xc33a32af6cec8e866f5ee293e333e7c8f7b721b1f69f8a8d9e20a1f3b5135538": "0x0875d97e8c58e2cf11bb3ae95350381f3d4a4c81e598e28a1a7120c846509427" }, "BatchAuctionHouse": { - "0x4c4d86f9737bd18dab3f6dc74d2f5c610b7169459c90a457e0e126ed42ae3bba": "0xbe4a9dc1b73685497c6104df59c2a3d2c1c5039bd48b1b25e9c0a029f3744311" + "0xf6524cf091f758f882d723f4921ec11493aa37252ae058490142a4745456e655": "0xed0f0bb6a9b02cbaa2acb80a982f47a5c0bb5c597a988a8b917126e216687af6" }, "BlastAtomicAuctionHouse": { - "0x3d41c1d7705983200681645a648d410d545bba492ad801c3938664a6bf58d882": "0x11f6c4c27a20cd6e6925ddd316451417e2cdf68d28a8bd2be486f9709f89a301" + "0x51bc087e65e71bfc5ad0c2b79d088d131eb8772e04b2dc2f8110931bd570460e": "0xf7e06e4c18040172fe87ec7bef926bf1cee3e23ef8f62307bb0fc53e37e0285a" }, "BlastBatchAuctionHouse": { - "0xc640e527fdcd05d6135de917e29082984847300ec6bf4cf38393f8dbfa742b19": "0x1e7690f0ac2409cb3804ffe443d81e9685d882d4d1804c8bfb1056cc624afee8" + "0xb0ffc6f92c2cb79c8186b0ab12b62ca54c6058b7d4ef7132ae4ef9e9e44cb25e": "0x261929eb8d9c1b58179fe9eea857377f09d5118b0043c0324bdaa08ec8dc7d0e" }, "Test_CappedMerkleAllowlist": { - "0x949cc973758fec263c28001984b366da5a91b8c2808ff8a54d0a15d553fbb119": "0x3152e9b2cb8c87f150a66f3e6445095a816f624d33fe0837e565c6cf7dab4365", - "0xfd52ac3d9067d2a4bdcfa15682ed088ccf4d5745f11144f12855affdb373c101": "0x79a32d46238e13ee511f889d6ac54c9ccd4daad2731e8ebd2d43aecd298b96d2" + "0x7bc65722fd1bb5078612d7cf193c76eed38e4ee74d3eca1c64069fe208273bb8": "0xdd1697b2635ef96d4ef154a273cd602e85c9dd1bce8947257df399a149558cd1", + "0xd987f98da18ebf5a2cd0222a4a580f45dadac95efb1b198e81c30ce5caffda9c": "0x57a9ddca97366bca653e74e78ec8db3fbecf1d51224fb81ddcfa799dc7aed921" }, "Test_MockCallback": { - "0x0825d99059fe0dc95e0b5cd7b853a5f3f50b58f25c5812fc7fd01d69ff4ec679": "0xaef068ac55e0c666d812de6b407609e335ca5ae7a6506a2cdbd358f585e525ff", - "0x51526251ca3003df174c65f2fb581f61820ce57c14720e0d67480ed8c8b77925": "0x184e1b3bf410893ef16dac684268cccd9b9523b9e657a9552ef78a262908b925", - "0x6206c9845f35f3006fc507c15aaaf4a719380265cf472773a270ffe6d5eb5299": "0xf3bbe6de01fb20de349904d9ff756490603df785886714bf0534191e3ca2a4a8", - "0x6b7fa77ae82f95cc8c5fe53dca64702ea0dc8e3dad74749dc8cd77de2884e704": "0x3b596c24d36320ea2587f83d117568c7b1955f1ba0c7332515cb710a4fbd24e7", - "0x7310243dabd76344326f3659aabda0b846a9c8e9e7d50b990261c14f2b44d560": "0xaa0fbb3fb516da0c90661ff1c6661bdfb3289f3713b20ba748cf29fa551df4ab", - "0x7602309c05771de8281f2f3f08e74ec9f19f1bfdaac8cd970d9f7a8d62173b6e": "0xe7207baa253737ed4528f33832af600d35f0dab9da287427a1cde4aece3d2f4f", - "0x892e1120a976151ae5375dca978040abe705c8cbdd3c7b64b41ff1087ee01c22": "0x0eea40cdd63339d5edcee2ef0293bd697dea4700df9593409df2214be66255b1", - "0x8a69dcb863773a3bb971a969233286e75cba80abf6ec8d881cada7ea0ad674b0": "0x2336198a87f0125bdcc0ebd414e2d13a43e2d6fc362441434ec1df4f98f2e1c2", - "0x8d8681a539f3b359e94a05239187dc55cb9cb2dcfe01726d3053573206efe25f": "0x3b10d0e8c22f0a5fc2ad30d200dd1388c09a12a6c4d11b3bc11a1b737d59970e", - "0x918d87a20c837fa93ce3089d2cb6386b66f79bbfd0f09bd6784bc52108b69938": "0x18c4e478238eff4c365a40bef703b33cdaec806f75990a1c3a62c5efbd25e32b", - "0x9569f58e5e127ceb73049eea8119bb21f8c4328d3b4770da5a67075be3f77c12": "0xd0973a837015c6a4181dff7bda062f5508c40310f00e9f86ec4ba798f8f94c92", - "0x96f0481f00eeec717fa8c0d41efd04b6e3fdccbebbaa223ee2118449ebea2406": "0xc3b5eb691348b75e50cad16bdc3923b294989e3a71b0d408f987f826d6360a21", - "0x9c35a3189126603f4b187149f54e09082403dac10b3e899a503e28e897edd6f9": "0x4014be96f5f8b77abf62e2eb5525da9860b3a7b2123cffc2efb7ed785317e1e1", - "0x9db4bc605469ede684fbaa475d320b3fbe6a707deaa953ee1ba29ecb3c1125fb": "0x15bc9b139ba5f4eddfb512530c4f10ac787c6a40a5ec17178c5bfc672d3a5a09", - "0xb8e0b3cbd7273e83cfdbcb8269e5c3510a28892e815fe28bc0f515be0df2c8f9": "0xdae496fdac3c60f8358524489914a77abebb963dba1c92502718f35736feacb8", - "0xc38bb4813ebdb9978d2485ae5ad92e1152f8a955430a63210b69b2277eed9477": "0xd9f22a036a06e6fc68351883e06909e10fd724c2bf1950ae97cc3bcd294d0323", - "0xd75e639b43b1d29e1703abcd2616b6b67f3ae232d53915d472f3a5b6aae6083c": "0xc8b5f3b0c7934f7842cb79d5767c0ce28ba8b346ba3ed3b4d323a3714ed73fb6", - "0xf2644573f57aea10dffac6e7215b409fa19236ae57b735b20b9fac9834391850": "0x61019fffc5a77852ef75393c0f70ba85617088f55ec968c3c97a033d586c414d" + "0x01b7af3bd5995528cd1b70c406bf96f40b501b59378d63e17d9a9ae78055663e": "0xba2d03207f20427662d534546c8938da9758b6d0b61bfb3451cbf2c9c8f2d1cb", + "0x195bf8112833a64745044c707894b5ea74c058f230fa6addec18964a37505634": "0xc8e86354002b197139356dee6d98d03ff10824d99d4efd2f9ae0383a6366d776", + "0x29323df062ea047dbb563d6056be72ceb42309553346294fb324d57f886ab0d9": "0xff0a1bab2ff17f7ad91012e85aff0e795a7e3c8955d972758908c02215d59589", + "0x29df68d45f4a4846aec8166612be579ddd44bc85479093bc7ae61c3c80268a54": "0x3e3fb31b30067d2b7333781534b5e0e0cf376b4903ca1f86eedc5d925a432000", + "0x366a1e5b6ab68bfbf40cd612770977d38632f459331d8a3d70f112255e204ae8": "0xdf53fdac44120a0d33bb74612e822cf62d05b87fcfa4be23aa7c46d5497f509f", + "0x3b0fa23e6258d6f7c3b7f66830b3e535794c2f1589b742c663e7ee876cb7cd91": "0x8dce196d17aa70fb25f0880463f57ca022813e47e0684ddf43e24633b6a4639d", + "0x42df1ea076afd9c4acb2267bbe05c13c8d6dff46d156f716a53e202d8d2dd182": "0xb3b0f28057d0a4e7a39f0a805f4731600f9ce9ff216fa006421aa7b9c3c1dfe1", + "0x4417bc70450e924796d7a483712cc78c0f845d1e72bd7ca1c7fa2fce3c29d40b": "0xe25f5a5caf42660b98031ccd3cb26d96079dd80e562a7630c7f009680883a982", + "0x4e13f59e8e324bad3a5be8ae4d874e1e006a6e0f924cbae65bb09e657c475fa6": "0xd681d40f5a6b8235c4fb1996e0f57b601d0007f72470772cccd61f373e3eed3d", + "0x825a7aa4c5e04d316bc3cfa1b004f15ffe6ab10a5678b7c0cb7e67c95c029cb3": "0x7a67df6f21d16eb8df88013d34366527ec1599df350dccaa9b0d1613f3fd6063", + "0x934ea9eb5d9777443fdd456b81263ee65f069435f6e57e6dd72abf689e351b51": "0xbe6a6c7a651e5a95e10b16a705e08a01a09a2b4f38fb4d787fced94a9e1f319c", + "0x94a59606e2c9921ec1a9ffb6a322a99b5960ff15540ac20ee032b30e0b8c6db2": "0x2211ef20cefb195f58c8ea1f52f061e526baf51a6f6208ac4b853c46b80af386", + "0xad53ba7d48ea3dd86e279b9be6f1fde06d4b03f92390f02c1bd468057e714bf0": "0x9f634ca56e62a6ee43b865610ed4212a426410f38d1c679811abc7cd6b0bcbe0", + "0xd83912b3814c87af5780b78717fd2d79bb885dd359f01145fb33cc056c8cc349": "0x30a1218eb0b5356d1868474ac8b7c17b30b743829c993efa9ec81140fffbacba", + "0xde1f4a10835941cfd22d5914d16a081b78d243d8d6472a5fff404fdc7b4ac5bf": "0xed6d749cf9c2db8b45bb0242511af332b1f6131594992f013c020432015f8d76", + "0xe6a9608aac4e43a9151a1dc66ea260d8f97976e949a0352a87540dc9c67ff532": "0x3b7b22a44e056f154a7a1fe462fcd5a647a022003bdce9f6c22c8b21989df577", + "0xe8a02ee8f106f3643214e270ab9583a4c236c32f430d5120062e1dc23cac813e": "0x830cbc10da305a99512d68733aa8a743d60c01ec16e6b0cc3396c363829294c6", + "0xfe556cadfcd840e51def247edce136ef00a4d03a49f7cc5a02469cdef9f092b9": "0x37653a5fd90bbba91304f86994b69e32110342abb32d6c339147112cb273564b" }, "Test_UniswapV2DirectToLiquidity": { - "0x8aa87d37877643656e4c99ac5894ef1db7d58a30e3d0f576d3900163491b50de": "0x93ba4418a111f3fd9d30718f1d1adacf772b3c4515dac235676320b9b5753652" + "0x3badb50d69e3fac37d9b631efae54f0efdd2df4a571ca0645d930970267c9bba": "0x73864b1ae4f2122359ec348c954ec3e6e8583317a83a8bed6963dabe3ee4d76c" }, "Test_UniswapV3DirectToLiquidity": { - "0x205b2d8780f5fa0bb6edef93607aa4f671b524b5d2b2f3278d238d08dfdf900c": "0x52c359e9c03d7e4b2af98f5b70fc386406184608f7f882d2789be69a2743d09a" + "0x3387f04605727ee08ccb6e92b07f1451eec992c41912d0e1666dfe7b1bc1c71f": "0xf02cf2f69f69790d609db80391ea4ffe4e4bed83bafe2758469fc01c787808e7" } } diff --git a/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol b/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol index 619f9f5c3..e3bbb9ea1 100644 --- a/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol +++ b/test/callbacks/UniswapV3DTL/UniswapV3DTLTest.sol @@ -103,8 +103,9 @@ abstract contract UniswapV3DirectToLiquidityTest is "GUniFactory address has changed to %s. Update the address in TestSaltConstants and re-generate test salts.", address(_gUniFactory) ); - revert("UniswapV2Router address has changed. See logs."); + revert("GUniFactory address has changed. See logs."); } + vm.stopBroadcast(); // Initialize the GUniFactory address payable gelatoAddress = payable(address(0x10)); From f26456dab0624e9ab33eedbc68603d4e0ca0933c Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 22 May 2024 15:17:42 -0500 Subject: [PATCH 28/69] test: GDA priceFor tests --- test/modules/auctions/GDA/GDATest.sol | 4 +- test/modules/auctions/GDA/priceFor.t.sol | 96 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 test/modules/auctions/GDA/priceFor.t.sol diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index e72899bf5..07e12a05a 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -29,7 +29,7 @@ abstract contract GdaTest is Test, Permit2User { uint256 internal constant _INITIAL_PRICE = 5e18; uint256 internal constant _MIN_PRICE = 2e18; uint256 internal constant _DECAY_TARGET = 10e16; // 10% - uint256 internal constant _DECAY_PERIOD = 1 days; + uint256 internal constant _DECAY_PERIOD = 12 hours; UD60x18 internal constant _ONE_DAY = UD60x18.wrap(1 days * uUNIT); AtomicAuctionHouse internal _auctionHouse; @@ -159,7 +159,7 @@ abstract contract GdaTest is Test, Permit2User { } modifier givenLotHasStarted() { - vm.warp(_start + 1); + vm.warp(_start); _; } diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol new file mode 100644 index 000000000..27c90e391 --- /dev/null +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Module} from "src/modules/Modules.sol"; +import {IAuction} from "src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; + +import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; +import "lib/prb-math/src/Common.sol" as PRBMath; + +import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; +import {console2} from "lib/forge-std/src/console2.sol"; + +contract GdaPriceForTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + + // [ ] when the lot ID is invalid + // [ ] it reverts + // [ ] when payout is greater than remaining capacity + // [ ] it reverts + // [ ] when minimum price is zero + // [ ] it calculates the price correctly + // [ ] when minimum price is greater than zero + // [ ] it calculates the price correctly + // [ ] when payout is zero + // [ ] it returns zero + // + + function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { + // No lots have been created so all lots are invalid + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, lotId_); + vm.expectRevert(err); + _module.priceFor(lotId_, 1e18); + } + + function test_payoutGreaterThanRemainingCapacity_reverts() public givenLotIsCreated { + // Payout is greater than remaining capacity + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InsufficientCapacity.selector); + vm.expectRevert(err); + _module.priceFor(_lotId, _LOT_CAPACITY + 1); + } + + function test_minPriceZero() public givenMinPrice(0) givenLotIsCreated givenLotHasStarted { + // The timestamp is the start time so current time == last auction start. + // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. + uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + console2.log("1 second of token emissions:", payout); + + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e14); // 0.01% + + // Warp to the end of the decay period + vm.warp(_start + _DECAY_PERIOD); + // The first auction has concluded. The price should be the target price for the decay period. + price = _module.priceFor(_lotId, payout); + console2.log("Price for payout end of decay period:", price); + + expectedPrice = + _INITIAL_PRICE.mulDiv(1e18 - _DECAY_TARGET, 1e18).mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e14); // 0.01%, TODO is this good enough? Seems like it slightly underestimates + } + + function test_minPriceNonZero() public givenLotIsCreated givenLotHasStarted { + // The timestamp is the start time so current time == last auction start. + // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. + uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + console2.log("1 second of token emissions:", payout); + + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e14); // 0.01% + + // Warp to the end of the decay period + vm.warp(_start + _DECAY_PERIOD + 1); + // The first auction has concluded. The price should be the target price for the decay period. + price = _module.priceFor(_lotId, payout); + console2.log("Price for payout end of decay period:", price); + + expectedPrice = + _INITIAL_PRICE.mulDiv(1e18 - _DECAY_TARGET, 1e18).mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e14); // 0.01%, TODO is this good enough? Seems like it slightly underestimates + } +} From d5ca25fb354d330a9ab269639bbec4c82cf31f3a Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 22 May 2024 15:33:54 -0500 Subject: [PATCH 29/69] test: more priceFor test updates --- test/modules/auctions/GDA/priceFor.t.sol | 79 ++++++++++++++++++++---- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 27c90e391..4a9b647bb 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -14,17 +14,20 @@ import {console2} from "lib/forge-std/src/console2.sol"; contract GdaPriceForTest is GdaTest { using {PRBMath.mulDiv} for uint256; - // [ ] when the lot ID is invalid - // [ ] it reverts - // [ ] when payout is greater than remaining capacity - // [ ] it reverts - // [ ] when minimum price is zero - // [ ] it calculates the price correctly - // [ ] when minimum price is greater than zero - // [ ] it calculates the price correctly - // [ ] when payout is zero - // [ ] it returns zero - // + // [X] when the lot ID is invalid + // [X] it reverts + // [X] when payout is greater than remaining capacity + // [X] it reverts + // [X when minimum price is zero + // [X] it calculates the price correctly + // [X] when minimum price is greater than zero + // [X] it calculates the price correctly + // [X] when payout is zero + // [X] it returns zero + // [X] when large, reasonable values are used + // [X] it does not overflow + // TODO can we fuzz this better? maybe use some external calculations to compare the values? + // Otherwise, we're just recreating the same calculations here and not really validating anything function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { // No lots have been created so all lots are invalid @@ -93,4 +96,58 @@ contract GdaPriceForTest is GdaTest { assertApproxEqRel(price, expectedPrice, 1e14); // 0.01%, TODO is this good enough? Seems like it slightly underestimates } + + function test_minPriceNonZero_lastAuctionStartInFuture() public givenLotIsCreated { + // We don't start the auction so the lastAuctionStart is 1 second ahead of the current time. + // 1 seconds worth of tokens should be slightly more than the initial price. + uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + console2.log("1 second of token emissions:", payout); + + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + assertGe(price, expectedPrice); + } + + function test_minPriceNonZero_lastAuctionStartInPast() public givenLotIsCreated { + vm.warp(_start + 1); + //lastAuctionStart is 1 second behind the current time. + // 1 seconds worth of tokens should be slightly less than the initial price. + uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + console2.log("1 second of token emissions:", payout); + + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + assertLe(price, expectedPrice); + } + + function testFuzz_minPriceZero_noOverflows(uint256 payout_) + public + givenLotCapacity(1e75) // very large number, but not quite max (which overflows) + givenMinPrice(0) + givenLotIsCreated + givenLotHasStarted + { + vm.assume(payout_ <= _LOT_CAPACITY); + + _module.priceFor(_lotId, payout_); + } + + function testFuzz_minPriceNonZero_noOverflows(uint256 payout_) + public + givenLotCapacity(1e75) // very large number, but not quite max (which overflows) + givenLotIsCreated + givenLotHasStarted + { + vm.assume(payout_ <= _LOT_CAPACITY); + + _module.priceFor(_lotId, payout_); + } } From 6ef438d08cafa91c32551981ae0d99ddfd927f27 Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 22 May 2024 21:29:12 -0500 Subject: [PATCH 30/69] fix: gda price equations, includes temp logging --- src/modules/auctions/GDA.sol | 97 +++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 7587be166..8fe100d25 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -9,9 +9,13 @@ import {AtomicAuctionModule} from "src/modules/auctions/AtomicAuctionModule.sol" import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; // External libraries -import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; +import { + UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT, ZERO +} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; +import {console2} from "lib/forge-std/src/console2.sol"; + /// @notice Continuous Gradual Dutch Auction (GDA) module with exponential decay and a minimum price. contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { using {PRBMath.mulDiv} for uint256; @@ -93,6 +97,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { UD60x18 q0 = ud(params.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); UD60x18 q1 = q0.mul(UNIT - ud(params.decayTarget)).div(UNIT); UD60x18 qm = ud(params.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + console2.log("q0:", q0.unwrap()); + console2.log("q1:", q1.unwrap()); + console2.log("qm:", qm.unwrap()); // Check that q0 > q1 > qm // Don't need to check q0 > q1 since: @@ -105,12 +112,14 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Calculate the decay constant decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(params.decayPeriod).div(ONE_DAY)); + console2.log("decay constant:", decayConstant.unwrap()); } // TODO other validation checks? // Calculate duration of the auction in days UD60x18 duration = convert(uint256(lot_.conclusion - lot_.start)).div(ONE_DAY); + console2.log("duration:", duration.unwrap()); // The duration must be less than the max exponential input divided by the decay constant // in order for the exponential operations to not overflow. See the minimum and maximum @@ -122,6 +131,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Calculate emissions rate as number of tokens released per day UD60x18 emissionsRate = ud(lot_.capacity.mulDiv(uUNIT, 10 ** lot_.baseTokenDecimals)).div(duration); + console2.log("emissions rate:", emissionsRate.unwrap()); // Store auction data AuctionData storage data = auctionData[lotId_]; @@ -158,21 +168,25 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // ========== VIEW FUNCTIONS ========== // // For Continuous GDAs with exponential decay, the price of a given token t seconds after being emitted is: - // q(t) = (q0 - qm) * e^(-k*t) + qm + // q(t) = r * (q0 - qm) * e^(-k*t) + qm // where k is the decay constant, q0 is the initial price, and qm is the minimum price // Integrating this function from the last auction start time for a particular number of tokens, // gives the multiplier for the token price to determine amount of quote tokens required to purchase: - // Q(T) = ((q0 - qm) * (e^((k*P)/r) - 1)) / ke^(k*T) + (qm * P) / r + // Q(T) = (r * (q0 - qm) * (e^((k*P)/r) - 1)) / ke^(k*T) + (qm * P) // where T is the time since the last auction start, P is the number of payout tokens to purchase, // and r is the emissions rate (number of tokens released per second). // // If qm is 0, then the equation simplifies to: - // q(t) = q0 * e^(-k*t) + // q(t) = r * q0 * e^(-k*t) // Integrating this function from the last auction start time for a particular number of tokens, // gives the multiplier for the token price to determine amount of quote tokens required to purchase: - // Q(T) = (q0 * (e^((k*P)/r) - 1)) / ke^(k*T) + // Q(T) = (r * q0 * (e^((k*P)/r) - 1)) / ke^(k*T) // where T is the time since the last auction start, P is the number of payout tokens to purchase. function priceFor(uint96 lotId_, uint256 payout_) public view override returns (uint256) { + // Lot ID must be valid + _revertIfLotInvalid(lotId_); + + // Get lot and auction data Lot memory lot = lotData[lotId_]; AuctionData memory auction = auctionData[lotId_]; @@ -189,8 +203,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // In the auction creation, we checked that the equilibrium price is greater than the minimum price // Scale the result to 18 decimals uint256 quoteTokenScale = 10 ** lot.quoteTokenDecimals; - UD60x18 priceDiff = - ud((auction.equilibriumPrice - auction.minimumPrice).mulDiv(uUNIT, quoteTokenScale)); + UD60x18 priceDiff = ud( + (auction.equilibriumPrice - auction.minimumPrice).mulDiv(uUNIT, quoteTokenScale) + ).mul(auction.emissionsRate); // Calculate the second numerator factor: e^((k*P)/r) - 1 // This cannot exceed the max exponential input due to the bounds imbosed on auction creation @@ -219,8 +234,8 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // This cannot exceed the max exponential input due to the bounds imbosed on auction creation // last auction start - current time is guaranteed to be < duration. If not, the auction is over. UD60x18 ekt = auction.decayConstant.mul( - convert(auction.lastAuctionStart - block.timestamp).div(ONE_DAY) - ).exp(); + convert(auction.lastAuctionStart - block.timestamp) + ).div(ONE_DAY).exp(); // Calculate the first term in the formula result = priceDiff.mul(ekpr).mul(ekt).div(auction.decayConstant); @@ -229,7 +244,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // If minimum price is zero, then the first term is the result, otherwise we add the second term if (auction.minimumPrice > 0) { UD60x18 minPrice = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); - result = result + minPrice.mul(payout).div(auction.emissionsRate); + result = result + minPrice.mul(payout); } // Scale price back to quote token decimals @@ -239,6 +254,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { } function payoutFor(uint96 lotId_, uint256 amount_) public view override returns (uint256) { + // Lot ID must be valid + _revertIfLotInvalid(lotId_); + // Calculate the payout and emissions uint256 payout; (payout,) = _payoutAndEmissionsFor(lotId_, amount_); @@ -254,13 +272,13 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Two cases: // // 1. Minimum price is zero - // P = (r * ln((Q * k * e^(k*T) / q0) + 1)) / k + // P = (r * ln((Q * k * e^(k*T) / (r * q0)) + 1)) / k // where P is the number of payout tokens, Q is the number of quote tokens, // r is the emissions rate, k is the decay constant, q0 is the equilibrium price of the auction, // and T is the time since the last auction start // // 2. Minimum price is not zero - // P = (r * (k * Q / qm + C - W(C e^(k * Q / qm + C)))) / k + // P = (r * ((k * Q) / (r * qm) + C - W(C e^((k * Q) / (r * qm) + C)))) / k // where P is the number of payout tokens, Q is the number of quote tokens, // r is the emissions rate, k is the decay constant, qm is the minimum price of the auction, // q0 is the equilibrium price of the auction, T is the time since the last auction start, @@ -295,7 +313,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Calculate the logarithm // Operand is guaranteed to be >= 1, so the result is positive - logFactor = amount.mul(auction.decayConstant).mul(ekt).div(q0).add(UNIT).ln(); + logFactor = amount.mul(auction.decayConstant).mul(ekt).div( + auction.emissionsRate.mul(q0) + ).add(UNIT).ln(); } else { // T is negative: flip the e^(k * T) term to the denominator @@ -308,7 +328,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Calculate the logarithm // Operand is guaranteed to be >= 1, so the result is positive - logFactor = amount.mul(auction.decayConstant).div(ekt.mul(q0)).add(UNIT).ln(); + logFactor = amount.mul(auction.decayConstant).div( + ekt.mul(auction.emissionsRate).mul(q0) + ).add(UNIT).ln(); } // Calculate the payout @@ -320,7 +342,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { uint256 minPrice = auction.minimumPrice; uint256 payoutAtMinPrice = amount_.mulDiv(10 ** lotData[lotId_].baseTokenDecimals, minPrice); - if (payoutAtMinPrice > maxPayout(lotId_)) { + if (payoutAtMinPrice > lotData[lotId_].capacity) { revert Auction_InsufficientCapacity(); } } @@ -329,8 +351,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Can't overflow because quoteTokenScale <= uUNIT UD60x18 qm = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); - // Calculate first term: (k * Q) / qm - UD60x18 f = auction.decayConstant.mul(amount).div(qm); + // Calculate first term aka F: (k * Q) / (r * qm) + UD60x18 f = auction.decayConstant.mul(amount).div(auction.emissionsRate.mul(qm)); + console2.log("first term:", f.unwrap()); // Calculate second term aka C: (q0 - qm)/(qm * e^(k * T)) UD60x18 c; @@ -340,8 +363,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Current time - last auction start is guaranteed to be < duration. If not, the auction is over. // We have to divide twice to avoid multipling the exponential result by qm, which could overflow. c = q0.sub(qm).div(qm).div( - auction.decayConstant.mul(convert(block.timestamp - auction.lastAuctionStart)) - .exp() + auction.decayConstant.mul( + convert(block.timestamp - auction.lastAuctionStart).div(ONE_DAY) + ).exp() ); } else { // T is negative: flip the e^(k * T) term to the numerator @@ -349,20 +373,25 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // last auction start - current time is guaranteed to be < duration. If not, the auction is over. // We divide before multiplying here to avoid reduce the odds of an intermediate result overflowing. c = q0.sub(qm).div(qm).mul( - auction.decayConstant.mul(convert(auction.lastAuctionStart - block.timestamp)) - .exp() + auction.decayConstant.mul( + convert(auction.lastAuctionStart - block.timestamp).div(ONE_DAY) + ).exp() ); } + console2.log("second term:", c.unwrap()); - // Calculate the third term: W(C e^(k * Q / qm + C)) - UD60x18 w = c.add(f).exp().mul(c).productLn(); + // Calculate the third term: W(C e^(F + C)) + // 86 wei is the maximum error (TODO that I have found so far) + // for the lambert-W approximation, + // this makes sure the estimate is conservative + UD60x18 w = c.add(f).exp().mul(c).productLn() + ud(86); + console2.log("third term:", w.unwrap()); - // Calculate payout - // The intermediate term (f + c - w) cannot underflow because + // Without error correction, the intermediate term (f + c - w) cannot underflow because // firstTerm + c - thirdTerm >= 0 for all amounts >= 0. // // Proof: - // 1. k > 0, Q >= 0, qm > 0 => f >= 0 + // 1. k > 0, Q >= 0, r > 0, qm > 0 => f >= 0 // 2. q0 > qm, qm > 0 => c >= 0 // 3. f + c = W((f + c) * e^(f + c)) // 4. 1 & 2 => f + c >= 0, f + c >= f, f + c >= c @@ -370,23 +399,35 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // 6. 4 & 5 => W((f + c) * e^(f + c)) >= W(c * e^(f + c)) // 7. 3 & 6 => f + c >= W(c * e^(f + c)) // QED - payout = auction.emissionsRate.mul(f.add(c).sub(w)).div(auction.decayConstant); + // + // However, it is possible since we add a small correction to w. + // Therefore, we check for underflow on the term and set a floor at 0. + UD60x18 fcw = w > f.add(c) ? ZERO : f.add(c).sub(w); + payout = auction.emissionsRate.mul(fcw).div(auction.decayConstant); + console2.log("sum of terms:", fcw.unwrap()); + console2.log("emissions rate:", auction.emissionsRate.unwrap()); + console2.log("decay constant:", auction.decayConstant.unwrap()); + console2.log("payout:", payout.unwrap()); } // Calculate seconds of emissions from payout - uint256 secondsOfEmissions = payout.div(auction.emissionsRate).intoUint256(); + uint256 secondsOfEmissions = convert(payout.div(auction.emissionsRate.mul(ONE_DAY))); // Scale payout to payout token decimals and return return (payout.intoUint256().mulDiv(10 ** lot.baseTokenDecimals, uUNIT), secondsOfEmissions); } function maxPayout(uint96 lotId_) public view override returns (uint256) { + // Lot ID must be valid + _revertIfLotInvalid(lotId_); + // The max payout is the remaining capacity of the lot return lotData[lotId_].capacity; } function maxAmountAccepted(uint96 lotId_) external view override returns (uint256) { // The max amount accepted is the price to purchase the remaining capacity of the lot + // This function checks if the lot ID is valid return priceFor(lotId_, lotData[lotId_].capacity); } } From a127c2261bb67c1b8a34361e71d99b5936b169ac Mon Sep 17 00:00:00 2001 From: Oighty Date: Wed, 22 May 2024 21:29:50 -0500 Subject: [PATCH 31/69] test: wip GDA payoutFor tests --- test/modules/auctions/GDA/GDATest.sol | 2 +- test/modules/auctions/GDA/payoutFor.t.sol | 193 ++++++++++++++++++++++ test/modules/auctions/GDA/priceFor.t.sol | 10 +- 3 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 test/modules/auctions/GDA/payoutFor.t.sol diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index 07e12a05a..8a52af682 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -25,7 +25,7 @@ abstract contract GdaTest is Test, Permit2User { address internal constant _REFERRER = address(0x4); uint256 internal constant _LOT_CAPACITY = 10e18; - uint48 internal constant _DURATION = 1 days; + uint48 internal constant _DURATION = 2 days; uint256 internal constant _INITIAL_PRICE = 5e18; uint256 internal constant _MIN_PRICE = 2e18; uint256 internal constant _DECAY_TARGET = 10e16; // 10% diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol new file mode 100644 index 000000000..ae4d60578 --- /dev/null +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Module} from "src/modules/Modules.sol"; +import {IAuction} from "src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; + +import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; +import "lib/prb-math/src/Common.sol" as PRBMath; + +import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; +import {console2} from "lib/forge-std/src/console2.sol"; + +contract GdaPayoutForTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + + // [X] when the lot ID is invalid + // [X] it reverts + // [ ] when minimum price is zero + // [X] it calculates the payout correctly + // [ ] when last auction start is in the future + // [ ] it calculates the payout correctly + // [ ] when last auction start is in the past + // [ ] it calculates the payout correctly + // [ ] when minimum price is greater than zero + // [ ] when the payout for the amount at the minimum price is greater than the capacity + // [ ] it reverts + // [X] it calculates the payout correctly + // [ ] when last auction start is in the future + // [ ] it calculates the payout correctly + // [ ] when last auction start is in the past + // [ ] it calculates the payout correctly + // [X] when amount is zero + // [X] it returns zero + // [X] when large, reasonable values are used + // [X] it does not overflow + // TODO can we fuzz this better? maybe use some external calculations to compare the values? + // Otherwise, we're just recreating the same calculations here and not really validating anything + + function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { + // No lots have been created so all lots are invalid + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, lotId_); + vm.expectRevert(err); + _module.payoutFor(lotId_, 1e18); + } + + // function test_payoutGreaterThanRemainingCapacity_reverts() public givenLotIsCreated { + // // Payout is greater than remaining capacity + // bytes memory err = abi.encodeWithSelector(IAuction.Auction_InsufficientCapacity.selector); + // vm.expectRevert(err); + // _module.priceFor(_lotId, _LOT_CAPACITY + 1); + // } + + function test_minPriceZero_payoutZeroForAmountZero() + public + givenMinPrice(0) + givenLotIsCreated + { + uint256 amount = 0; + uint256 payout = _module.payoutFor(_lotId, amount); + console2.log("Payout for 0 amount:", payout); + assertEq(payout, 0); + } + + function test_minPriceNonZero_payoutZeroForAmountZero() + public + givenLotIsCreated + givenLotHasStarted + { + uint256 amount = 0; + uint256 payout = _module.payoutFor(_lotId, amount); + console2.log("Payout for 0 amount:", payout); + assertEq(payout, 0); + } + + function test_minPriceZero() public givenMinPrice(0) givenLotIsCreated givenLotHasStarted { + // The timestamp is the start time so current time == last auction start. + // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. + uint256 amount = _INITIAL_PRICE.mulDiv(_LOT_CAPACITY / _DURATION, _BASE_SCALE); // 1 seconds worth of tokens + console2.log("amount equivalent to 1 second of token emissions:", amount); + + uint256 payout = _module.payoutFor(_lotId, amount); + console2.log("Payout at start:", payout); + + uint256 expectedPayout = amount.mulDiv(_BASE_SCALE, _INITIAL_PRICE); + console2.log("Expected payout at start:", expectedPayout); + + // The payout should be conservative (less than or equal to the expected payout) + assertApproxEqRel(payout, expectedPayout, 1e14); // 0.01% + assertLe(payout, expectedPayout); + + // Warp to the end of the decay period + vm.warp(_start + _DECAY_PERIOD); + // The first auction has concluded. The price should be the target price for the decay period. + uint256 decayedPrice = _INITIAL_PRICE.mulDiv(1e18 - _DECAY_TARGET, 1e18); + amount = decayedPrice.mulDiv(_LOT_CAPACITY / _DURATION, _BASE_SCALE); + payout = _module.payoutFor(_lotId, amount); + console2.log("Payout at end of decay period:", payout); + + expectedPayout = amount.mulDiv(_BASE_SCALE, decayedPrice); + console2.log("Expected payout at end of decay period:", expectedPayout); + + // The payout should be conservative (less than or equal to the expected payout) + assertApproxEqRel(payout, expectedPayout, 1e14); // 0.01% + assertLe(payout, expectedPayout); + } + + function test_minPriceNonZero() public givenLotIsCreated givenLotHasStarted { + // The timestamp is the start time so current time == last auction start. + // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. + uint256 amount = _INITIAL_PRICE.mulDiv(_LOT_CAPACITY / _DURATION, _BASE_SCALE); // 1 seconds worth of tokens + console2.log("amount equivalent to 1 second of token emissions:", amount); + + uint256 payout = _module.payoutFor(_lotId, amount); + console2.log("Payout at start:", payout); + + uint256 expectedPayout = amount.mulDiv(_BASE_SCALE, _INITIAL_PRICE); + console2.log("Expected payout at start:", expectedPayout); + + // The payout should be conservative (less than or equal to the expected payout) + assertApproxEqRel(payout, expectedPayout, 1e14); // 0.01% + assertLe(payout, expectedPayout); + + // Warp to the end of the decay period + vm.warp(_start + _DECAY_PERIOD); + // The first auction has concluded. The price should be the target price for the decay period. + uint256 decayedPrice = _INITIAL_PRICE.mulDiv(1e18 - _DECAY_TARGET, 1e18); + amount = decayedPrice.mulDiv(_LOT_CAPACITY / _DURATION, _BASE_SCALE); + payout = _module.payoutFor(_lotId, amount); + console2.log("Payout at end of decay period:", payout); + + expectedPayout = amount.mulDiv(_BASE_SCALE, decayedPrice); + console2.log("Expected payout at end of decay period:", expectedPayout); + + // The payout should be conservative (less than or equal to the expected payout) + assertApproxEqRel(payout, expectedPayout, 1e14); // 0.01% + assertLe(payout, expectedPayout); + } + + // function test_minPriceNonZero_lastAuctionStartInFuture() public givenLotIsCreated { + // // We don't start the auction so the lastAuctionStart is 1 second ahead of the current time. + // // 1 seconds worth of tokens should be slightly more than the initial price. + // uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + // console2.log("1 second of token emissions:", payout); + + // uint256 price = _module.priceFor(_lotId, payout); + // console2.log("Price for payout at beginning:", price); + + // uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + // console2.log("Expected price:", expectedPrice); + + // assertGe(price, expectedPrice); + // } + + // function test_minPriceNonZero_lastAuctionStartInPast() public givenLotIsCreated { + // vm.warp(_start + 1); + // //lastAuctionStart is 1 second behind the current time. + // // 1 seconds worth of tokens should be slightly less than the initial price. + // uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + // console2.log("1 second of token emissions:", payout); + + // uint256 price = _module.priceFor(_lotId, payout); + // console2.log("Price for payout at beginning:", price); + + // uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + // console2.log("Expected price:", expectedPrice); + + // assertLe(price, expectedPrice); + // } + + // function testFuzz_minPriceZero_noOverflows(uint256 payout_) + // public + // givenLotCapacity(1e75) // very large number, but not quite max (which overflows) + // givenMinPrice(0) + // givenLotIsCreated + // givenLotHasStarted + // { + // vm.assume(payout_ <= _LOT_CAPACITY); + + // _module.priceFor(_lotId, payout_); + // } + + // function testFuzz_minPriceNonZero_noOverflows(uint256 payout_) + // public + // givenLotCapacity(1e75) // very large number, but not quite max (which overflows) + // givenLotIsCreated + // givenLotHasStarted + // { + // vm.assume(payout_ <= _LOT_CAPACITY); + + // _module.priceFor(_lotId, payout_); + // } +} diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 4a9b647bb..68c657feb 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -20,10 +20,16 @@ contract GdaPriceForTest is GdaTest { // [X] it reverts // [X when minimum price is zero // [X] it calculates the price correctly + // [X] when last auction start is in the future + // [X] it calculates the price correctly + // [X] when last auction start is in the past + // [X] it calculates the price correctly // [X] when minimum price is greater than zero // [X] it calculates the price correctly - // [X] when payout is zero - // [X] it returns zero + // [X] when last auction start is in the future + // [X] it calculates the price correctly + // [X] when last auction start is in the past + // [X] it calculates the price correctly // [X] when large, reasonable values are used // [X] it does not overflow // TODO can we fuzz this better? maybe use some external calculations to compare the values? From 3b0ddada87aab1a4990988a5a0747ddfded291bf Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 23 May 2024 16:19:41 -0500 Subject: [PATCH 32/69] fix: add'l validation and temp logging in GDA --- src/modules/auctions/GDA.sol | 37 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 8fe100d25..34ee64ac5 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -68,14 +68,23 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { GDAParams memory params = abi.decode(params_, (GDAParams)); // Validate parameters - // Equilibrium Price must not be zero - if (params.equilibriumPrice == 0) { + // Equilibrium Price must be greater than 1000 wei and less than u128 max to avoid various math errors + if (params.equilibriumPrice < 1000 || params.equilibriumPrice > type(uint128).max) { revert Auction_InvalidParams(); } // Capacity must be in base token if (lot_.capacityInQuote) revert Auction_InvalidParams(); + // Capacity must at least as large as the auction duration so that the emissions rate is not zero + // and no larger than u128 max to avoid various math errors + if ( + lot_.capacity < uint256(lot_.conclusion - lot_.start) + || lot_.capacity > type(uint128).max + ) { + revert Auction_InvalidParams(); + } + // Minimum price can be zero, but the equations default back to the basic GDA implementation // Validate the decay parameters and calculate the decay constant @@ -182,6 +191,8 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // gives the multiplier for the token price to determine amount of quote tokens required to purchase: // Q(T) = (r * q0 * (e^((k*P)/r) - 1)) / ke^(k*T) // where T is the time since the last auction start, P is the number of payout tokens to purchase. + // + // Note: this function is an estimate. The actual price returned will vary some due to the precision of the calculations. function priceFor(uint96 lotId_, uint256 payout_) public view override returns (uint256) { // Lot ID must be valid _revertIfLotInvalid(lotId_); @@ -206,6 +217,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { UD60x18 priceDiff = ud( (auction.equilibriumPrice - auction.minimumPrice).mulDiv(uUNIT, quoteTokenScale) ).mul(auction.emissionsRate); + console2.log("price diff:", priceDiff.unwrap()); // Calculate the second numerator factor: e^((k*P)/r) - 1 // This cannot exceed the max exponential input due to the bounds imbosed on auction creation @@ -213,6 +225,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // payout must be less then or equal to initial capacity // therefore, the resulting exponent is at most decay constant * duration UD60x18 ekpr = auction.decayConstant.mul(payout).div(auction.emissionsRate).exp().sub(UNIT); + console2.log("ekpr:", ekpr.unwrap()); // Handle cases of T being positive or negative UD60x18 result; @@ -224,9 +237,11 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { UD60x18 kekt = auction.decayConstant.mul( convert(block.timestamp - auction.lastAuctionStart).div(ONE_DAY) ).exp().mul(auction.decayConstant); + console2.log("kekt:", kekt.unwrap()); // Calculate the first term in the formula result = priceDiff.mul(ekpr).div(kekt); + console2.log("result:", result.unwrap()); } else { // T is negative: flip the e^(k * T) term to the numerator @@ -236,15 +251,18 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { UD60x18 ekt = auction.decayConstant.mul( convert(auction.lastAuctionStart - block.timestamp) ).div(ONE_DAY).exp(); + console2.log("ekt:", ekt.unwrap()); // Calculate the first term in the formula result = priceDiff.mul(ekpr).mul(ekt).div(auction.decayConstant); + console2.log("result:", result.unwrap()); } // If minimum price is zero, then the first term is the result, otherwise we add the second term if (auction.minimumPrice > 0) { UD60x18 minPrice = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); result = result + minPrice.mul(payout); + console2.log("result with min price", result.unwrap()); } // Scale price back to quote token decimals @@ -337,15 +355,6 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { payout = auction.emissionsRate.mul(logFactor).div(auction.decayConstant); } else { // Auction has a minimum price - { - // Check that the amount / minPrice is not greater than the max payout (i.e. remaining capacity) - uint256 minPrice = auction.minimumPrice; - uint256 payoutAtMinPrice = - amount_.mulDiv(10 ** lotData[lotId_].baseTokenDecimals, minPrice); - if (payoutAtMinPrice > lotData[lotId_].capacity) { - revert Auction_InsufficientCapacity(); - } - } // Convert minimum price to 18 decimals // Can't overflow because quoteTokenScale <= uUNIT @@ -381,10 +390,10 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { console2.log("second term:", c.unwrap()); // Calculate the third term: W(C e^(F + C)) - // 86 wei is the maximum error (TODO that I have found so far) + // 150 wei is the maximum error (TODO that I have found so far) // for the lambert-W approximation, // this makes sure the estimate is conservative - UD60x18 w = c.add(f).exp().mul(c).productLn() + ud(86); + UD60x18 w = c.add(f).exp().mul(c).productLn() + ud(150); console2.log("third term:", w.unwrap()); // Without error correction, the intermediate term (f + c - w) cannot underflow because @@ -417,7 +426,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { return (payout.intoUint256().mulDiv(10 ** lot.baseTokenDecimals, uUNIT), secondsOfEmissions); } - function maxPayout(uint96 lotId_) public view override returns (uint256) { + function maxPayout(uint96 lotId_) external view override returns (uint256) { // Lot ID must be valid _revertIfLotInvalid(lotId_); From ab575b70590cfb7f984440af6069a722b293798f Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 23 May 2024 20:48:11 -0500 Subject: [PATCH 33/69] test: add'l GDA tests, still some TODOs --- test/modules/auctions/GDA/auction.t.sol | 53 ++- test/modules/auctions/GDA/cancelAuction.t.sol | 82 ++++ .../auctions/GDA/maxAmountAccepted.t.sol | 54 +++ test/modules/auctions/GDA/maxPayout.t.sol | 54 +++ test/modules/auctions/GDA/payoutFor.t.sol | 258 ++++++++---- test/modules/auctions/GDA/priceFor.t.sol | 143 ++++++- test/modules/auctions/GDA/purchase.t.sol | 375 ++++++++++++++++++ 7 files changed, 936 insertions(+), 83 deletions(-) create mode 100644 test/modules/auctions/GDA/cancelAuction.t.sol create mode 100644 test/modules/auctions/GDA/maxAmountAccepted.t.sol create mode 100644 test/modules/auctions/GDA/maxPayout.t.sol create mode 100644 test/modules/auctions/GDA/purchase.t.sol diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 80ded4c62..e0f8a8c5d 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -19,7 +19,9 @@ contract GdaCreateAuctionTest is GdaTest { // [X] it reverts // [X] when the duration is less than the globally configured minimum // [X] it reverts - // [X] when the equilibrium price is 0 + // [X] when the equilibrium price is less than 1000 + // [X] it reverts + // [X] when the equilibrium price is greater than the max uint128 value // [X] it reverts // [X] when the minimum price is greater than or equal to the decay target price // [X] it reverts @@ -33,6 +35,10 @@ contract GdaCreateAuctionTest is GdaTest { // [X] it reverts // [X] when the capacity is in quote token // [X] it reverts + // [X] when capacity is less than the duration (in seconds) + // [X] it reverts + // [X] when capacity is greater than the max uint128 value + // [X] it reverts // [X] when duration is greater than the max exp input divided by the calculated decay constant // [X] it reverts // [X] when the inputs are all valid @@ -74,7 +80,50 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_equilibriumPriceIsZero_reverts() public givenEquilibriumPrice(0) { + function test_equilibriumPriceIsLessThanMin_reverts(uint16 price_) + public + givenEquilibriumPrice(uint256(price_) % 1000) + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_equilibriumPriceGreaterThanMax_reverts(uint256 price_) + public + givenEquilibriumPrice(price_) + { + vm.assume(price_ > type(uint128).max); + + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_capacityLessThanDuration_reverts(uint256 capacity_) + public + givenLotCapacity(capacity_ % _DURATION) + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_capacityGreaterThanMax_reverts(uint256 capacity_) + public + givenLotCapacity(capacity_) + { + vm.assume(capacity_ > type(uint128).max); + // Expect revert bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); vm.expectRevert(err); diff --git a/test/modules/auctions/GDA/cancelAuction.t.sol b/test/modules/auctions/GDA/cancelAuction.t.sol new file mode 100644 index 000000000..d821fa211 --- /dev/null +++ b/test/modules/auctions/GDA/cancelAuction.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Module} from "src/modules/Modules.sol"; +import {IAuction} from "src/interfaces/modules/IAuction.sol"; + +import {GdaTest} from "test/modules/auctions/Gda/GdaTest.sol"; + +contract GdaCancelAuctionTest is GdaTest { + // [X] when the caller is not the parent + // [X] it reverts + // [X] when the lot id is invalid + // [X] it reverts + // [X] when the auction has concluded + // [X] it reverts + // [X] when the auction has been cancelled + // [X] it reverts + // [X] when the auction has started + // [X] it reverts + // [X] it updates the conclusion, capacity and status + + function test_notParent_reverts() public { + // Expect revert + bytes memory err = abi.encodeWithSelector(Module.Module_OnlyParent.selector, address(this)); + vm.expectRevert(err); + + // Call the function + _module.cancelAuction(_lotId); + } + + function test_invalidLotId_reverts() public { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, _lotId); + vm.expectRevert(err); + + // Call the function + _cancelAuctionLot(); + } + + function test_auctionConcluded_reverts(uint48 conclusionElapsed_) public givenLotIsCreated { + uint48 conclusionElapsed = uint48(bound(conclusionElapsed_, 0, 1 days)); + + // Warp to the conclusion + vm.warp(_start + _DURATION + conclusionElapsed); + + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_LotNotActive.selector, _lotId); + vm.expectRevert(err); + + // Call the function + _cancelAuctionLot(); + } + + function test_auctionCancelled_reverts() public givenLotIsCreated givenLotIsCancelled { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_LotNotActive.selector, _lotId); + vm.expectRevert(err); + + // Call the function + _cancelAuctionLot(); + } + + function test_afterStart() public givenLotIsCreated givenLotHasStarted { + // Call the function + _cancelAuctionLot(); + + // Check the state + IAuction.Lot memory lotData = _getAuctionLot(_lotId); + assertEq(lotData.conclusion, uint48(block.timestamp)); + assertEq(lotData.capacity, 0); + } + + function test_beforeStart() public givenLotIsCreated { + // Call the function + _cancelAuctionLot(); + + // Check the state + IAuction.Lot memory lotData = _getAuctionLot(_lotId); + assertEq(lotData.conclusion, uint48(block.timestamp)); + assertEq(lotData.capacity, 0); + } +} diff --git a/test/modules/auctions/GDA/maxAmountAccepted.t.sol b/test/modules/auctions/GDA/maxAmountAccepted.t.sol new file mode 100644 index 000000000..2d1e5e294 --- /dev/null +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IAuction} from "src/interfaces/modules/IAuction.sol"; +import {uUNIT} from "lib/prb-math/src/UD60x18.sol"; +import "lib/prb-math/src/Common.sol" as PRBMath; + +import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; + +contract GdaMaxAmountAcceptedTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + // [X] when the lot ID is invalid + // [X] it reverts + // [X] it returns the price for the remaining capacity of the lot + + function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { + // No lots have been created so all lots are invalid + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, lotId_); + vm.expectRevert(err); + _module.maxAmountAccepted(lotId_); + } + + function testFuzz_maxAmountAccepted_success( + uint128 capacity_, + uint128 price_ + ) public givenLotCapacity(uint256(capacity_)) givenEquilibriumPrice(uint256(price_)) { + vm.assume(capacity_ >= _DURATION); + uint256 decayedPrice = uint256(price_).mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); + vm.assume(decayedPrice > _gdaParams.minimumPrice); // must have room for decay + _createAuctionLot(); + + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + + function testFuzz_maxAmountAccepted_minPriceZero_success( + uint128 capacity_, + uint128 price_ + ) + public + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(0) + { + vm.assume(capacity_ >= _DURATION); + vm.assume(price_ >= 1e3); + _createAuctionLot(); + + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } +} diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol new file mode 100644 index 000000000..87f740a2b --- /dev/null +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IAuction} from "src/interfaces/modules/IAuction.sol"; +import {uUNIT} from "lib/prb-math/src/UD60x18.sol"; +import "lib/prb-math/src/Common.sol" as PRBMath; + +import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; + +contract GdaMaxPayoutTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + // [X] when the lot ID is invalid + // [X] it reverts + // [X] it returns the remaining capacity of the lot + + function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { + // No lots have been created so all lots are invalid + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, lotId_); + vm.expectRevert(err); + _module.maxPayout(lotId_); + } + + function testFuzz_maxPayout_success( + uint128 capacity_, + uint128 price_ + ) public givenLotCapacity(uint256(capacity_)) givenEquilibriumPrice(uint256(price_)) { + vm.assume(capacity_ >= _DURATION); + uint256 decayedPrice = uint256(price_).mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); + vm.assume(decayedPrice > _gdaParams.minimumPrice); // must have room for decay + _createAuctionLot(); + + uint256 maxPayout = _module.maxPayout(_lotId); + uint256 expectedMaxPayout = capacity_; + assertEq(expectedMaxPayout, maxPayout); + } + + function testFuzz_maxPayout_minPriceZero_success( + uint128 capacity_, + uint128 price_ + ) + public + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(0) + { + vm.assume(capacity_ >= _DURATION); + vm.assume(price_ >= 1e3); + _createAuctionLot(); + + uint256 maxPayout = _module.maxPayout(_lotId); + uint256 expectedMaxPayout = capacity_; + assertEq(expectedMaxPayout, maxPayout); + } +} diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index ae4d60578..d4c8eebc6 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -16,26 +16,25 @@ contract GdaPayoutForTest is GdaTest { // [X] when the lot ID is invalid // [X] it reverts - // [ ] when minimum price is zero + // [X] when the calculated payout is greater than the remaining capacity of the lot + // [X] it reverts + // [X] when minimum price is zero // [X] it calculates the payout correctly - // [ ] when last auction start is in the future - // [ ] it calculates the payout correctly - // [ ] when last auction start is in the past - // [ ] it calculates the payout correctly - // [ ] when minimum price is greater than zero - // [ ] when the payout for the amount at the minimum price is greater than the capacity - // [ ] it reverts + // [X] when last auction start is in the future + // [X] it calculates the payout correctly + // [X] when last auction start is in the past + // [X] it calculates the payout correctly + // [X] when minimum price is greater than zero // [X] it calculates the payout correctly - // [ ] when last auction start is in the future - // [ ] it calculates the payout correctly - // [ ] when last auction start is in the past - // [ ] it calculates the payout correctly + // [X] when last auction start is in the future + // [X] it calculates the payout correctly + // [X] when last auction start is in the past + // [X] it calculates the payout correctly // [X] when amount is zero // [X] it returns zero // [X] when large, reasonable values are used // [X] it does not overflow - // TODO can we fuzz this better? maybe use some external calculations to compare the values? - // Otherwise, we're just recreating the same calculations here and not really validating anything + // TODO can we fuzz this better? function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { // No lots have been created so all lots are invalid @@ -44,12 +43,19 @@ contract GdaPayoutForTest is GdaTest { _module.payoutFor(lotId_, 1e18); } - // function test_payoutGreaterThanRemainingCapacity_reverts() public givenLotIsCreated { - // // Payout is greater than remaining capacity - // bytes memory err = abi.encodeWithSelector(IAuction.Auction_InsufficientCapacity.selector); - // vm.expectRevert(err); - // _module.priceFor(_lotId, _LOT_CAPACITY + 1); - // } + function test_amountGreaterThanMaxAccepted_reverts() + public + givenLotIsCreated + givenLotHasStarted + { + uint256 maxAccepted = _module.maxAmountAccepted(_lotId); + + // Payout is greater than remaining capacity + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InsufficientCapacity.selector); + vm.expectRevert(err); + + _module.payoutFor(_lotId, maxAccepted + 100_000); // TODO due to precision issues, it must be somewhat over the max amount accepted to revert + } function test_minPriceZero_payoutZeroForAmountZero() public @@ -105,6 +111,41 @@ contract GdaPayoutForTest is GdaTest { assertLe(payout, expectedPayout); } + function test_minPriceZero_lastAuctionStartInFuture() + public + givenMinPrice(0) + givenLotIsCreated + { + // We don't start the auction so the lastAuctionStart is 1 second ahead of the current time. + // Payout should be slightly less than 1 seconds worth of tokens + uint256 amount = _INITIAL_PRICE.mulDiv(_LOT_CAPACITY / _DURATION, _BASE_SCALE); // 1 seconds worth of tokens + console2.log("amount equivalent to 1 second of token emissions:", amount); + + uint256 payout = _module.payoutFor(_lotId, amount); + console2.log("Payout:", payout); + + uint256 expectedPayout = amount.mulDiv(_BASE_SCALE, _INITIAL_PRICE); + console2.log("Expected payout:", expectedPayout); + + assertLe(payout, expectedPayout); + } + + function test_minPriceZero_lastAuctionStartInPast() public givenMinPrice(0) givenLotIsCreated { + vm.warp(_start + 1); + //lastAuctionStart is 1 second behind the current time. + // Payout should be slightly more than 1 seconds worth of tokens + uint256 amount = _INITIAL_PRICE.mulDiv(_LOT_CAPACITY / _DURATION, _BASE_SCALE); // 1 seconds worth of tokens + console2.log("amount equivalent to 1 second of token emissions:", amount); + + uint256 payout = _module.payoutFor(_lotId, amount); + console2.log("Payout:", payout); + + uint256 expectedPayout = amount.mulDiv(_BASE_SCALE, _INITIAL_PRICE); + console2.log("Expected payout:", expectedPayout); + + assertGe(payout, expectedPayout); + } + function test_minPriceNonZero() public givenLotIsCreated givenLotHasStarted { // The timestamp is the start time so current time == last auction start. // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. @@ -137,57 +178,128 @@ contract GdaPayoutForTest is GdaTest { assertLe(payout, expectedPayout); } - // function test_minPriceNonZero_lastAuctionStartInFuture() public givenLotIsCreated { - // // We don't start the auction so the lastAuctionStart is 1 second ahead of the current time. - // // 1 seconds worth of tokens should be slightly more than the initial price. - // uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens - // console2.log("1 second of token emissions:", payout); - - // uint256 price = _module.priceFor(_lotId, payout); - // console2.log("Price for payout at beginning:", price); - - // uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); - // console2.log("Expected price:", expectedPrice); - - // assertGe(price, expectedPrice); - // } - - // function test_minPriceNonZero_lastAuctionStartInPast() public givenLotIsCreated { - // vm.warp(_start + 1); - // //lastAuctionStart is 1 second behind the current time. - // // 1 seconds worth of tokens should be slightly less than the initial price. - // uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens - // console2.log("1 second of token emissions:", payout); - - // uint256 price = _module.priceFor(_lotId, payout); - // console2.log("Price for payout at beginning:", price); - - // uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); - // console2.log("Expected price:", expectedPrice); - - // assertLe(price, expectedPrice); - // } - - // function testFuzz_minPriceZero_noOverflows(uint256 payout_) - // public - // givenLotCapacity(1e75) // very large number, but not quite max (which overflows) - // givenMinPrice(0) - // givenLotIsCreated - // givenLotHasStarted - // { - // vm.assume(payout_ <= _LOT_CAPACITY); - - // _module.priceFor(_lotId, payout_); - // } - - // function testFuzz_minPriceNonZero_noOverflows(uint256 payout_) - // public - // givenLotCapacity(1e75) // very large number, but not quite max (which overflows) - // givenLotIsCreated - // givenLotHasStarted - // { - // vm.assume(payout_ <= _LOT_CAPACITY); - - // _module.priceFor(_lotId, payout_); - // } + function test_minPriceNonZero_lastAuctionStartInFuture() public givenLotIsCreated { + // We don't start the auction so the lastAuctionStart is 1 second ahead of the current time. + // Payout should be slightly less than 1 seconds worth of tokens + uint256 amount = _INITIAL_PRICE.mulDiv(_LOT_CAPACITY / _DURATION, _BASE_SCALE); // 1 seconds worth of tokens + console2.log("amount equivalent to 1 second of token emissions:", amount); + + uint256 payout = _module.payoutFor(_lotId, amount); + console2.log("Payout:", payout); + + uint256 expectedPayout = amount.mulDiv(_BASE_SCALE, _INITIAL_PRICE); + console2.log("Expected payout:", expectedPayout); + + assertLe(payout, expectedPayout); + } + + function test_minPriceNonZero_lastAuctionStartInPast() public givenLotIsCreated { + vm.warp(_start + 1); + //lastAuctionStart is 1 second behind the current time. + // Payout should be slightly more than 1 seconds worth of tokens + uint256 amount = _INITIAL_PRICE.mulDiv(_LOT_CAPACITY / _DURATION, _BASE_SCALE); // 1 seconds worth of tokens + console2.log("amount equivalent to 1 second of token emissions:", amount); + + uint256 payout = _module.payoutFor(_lotId, amount); + console2.log("Payout:", payout); + + uint256 expectedPayout = amount.mulDiv(_BASE_SCALE, _INITIAL_PRICE); + console2.log("Expected payout:", expectedPayout); + + assertGe(payout, expectedPayout); + } + + function testFuzz_minPriceZero_noOverflows( + uint128 capacity_, + uint128 amount_ + ) public givenLotCapacity(uint256(capacity_)) givenMinPrice(0) { + vm.assume(capacity_ >= _DURATION); + _createAuctionLot(); + vm.assume(amount_ <= _module.maxAmountAccepted(_lotId)); + + vm.warp(_start); + + _module.payoutFor(_lotId, amount_); + } + + function testFuzz_minPriceNonZero_noOverflows( + uint128 capacity_, + uint128 amount_ + ) public givenLotCapacity(uint256(capacity_)) { + vm.assume(capacity_ >= _DURATION); + _createAuctionLot(); + vm.assume(amount_ <= _module.maxAmountAccepted(_lotId)); + + vm.warp(_start); + + _module.payoutFor(_lotId, amount_); + } + + function testFuzz_minPriceZero_varyingTimesteps(uint48 timestep_) + public + givenMinPrice(0) + givenLotIsCreated + givenLotHasStarted + { + // Warp to the timestep + uint48 timestep = timestep_ % _DURATION; + console2.log("Warping to timestep:", timestep); + vm.warp(_start + timestep); + + // Calculated the expected price of the oldest auction at the timestep + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + UD60x18 q0 = ud(_INITIAL_PRICE.mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = data.emissionsRate; + UD60x18 k = data.decayConstant; + UD60x18 t = convert(timestep).div(_ONE_DAY); + UD60x18 qt = q0.mul(r).div(k.mul(t).exp()); + console2.log("Expected price at timestep:", qt.unwrap()); + + // Set amount to 1 seconds worth of tokens (which is qt divided by 1 day) + uint256 amount = qt.intoUint256().mulDiv(10 ** _quoteTokenDecimals, uUNIT) / 1 days; + + // Calculate the payout + uint256 payout = _module.payoutFor(_lotId, amount); + + // Calculate the expected payout + uint256 expectedPayout = _LOT_CAPACITY / _DURATION; + + // The payout should be conservative (less than or equal to the expected payout) + assertLe(payout, expectedPayout); + assertApproxEqRel(payout, expectedPayout, 1e14); // 0.01% + } + + function testFuzz_minPriceNonZero_varyingTimesteps(uint48 timestep_) + public + givenLotIsCreated + givenLotHasStarted + { + // Warp to the timestep + uint48 timestep = timestep_ % _DURATION; + console2.log("Warping to timestep:", timestep); + vm.warp(_start + timestep); + + // Calculated the expected price of the oldest auction at the timestep + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + UD60x18 q0 = ud(_INITIAL_PRICE.mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 qm = ud(_MIN_PRICE.mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = data.emissionsRate; + UD60x18 k = data.decayConstant; + UD60x18 t = convert(timestep).div(_ONE_DAY); + UD60x18 qt = (q0 - qm).div(k.mul(t).exp()).add(qm).mul(r); + console2.log("Expected price at timestep:", qt.unwrap()); + + // Set amount to 1 seconds worth of tokens (which is qt divided by 1 day) + uint256 amount = qt.intoUint256().mulDiv(10 ** _quoteTokenDecimals, uUNIT) / 1 days; + + // Calculate the payout + uint256 payout = _module.payoutFor(_lotId, amount); + + // Calculate the expected payout + uint256 expectedPayout = _LOT_CAPACITY / _DURATION; + + // The payout should be conservative (less than or equal to the expected payout) + assertLe(payout, expectedPayout); + assertApproxEqRel(payout, expectedPayout, 1e14); // 0.01% + } } diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 68c657feb..6ef02d429 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -134,26 +134,153 @@ contract GdaPriceForTest is GdaTest { assertLe(price, expectedPrice); } - function testFuzz_minPriceZero_noOverflows(uint256 payout_) + function testFuzz_minPriceZero_noOverflows( + uint128 capacity_, + uint128 payout_ + ) public givenLotCapacity(uint256(capacity_)) givenMinPrice(0) { + vm.assume(capacity_ >= _DURATION); + vm.assume(payout_ <= capacity_); + _createAuctionLot(); + + vm.warp(_start); + + _module.priceFor(_lotId, payout_); + } + + function testFuzz_minPriceNonZero_noOverflows( + uint128 capacity_, + uint128 payout_ + ) public givenLotCapacity(uint256(capacity_)) { + vm.assume(capacity_ >= _DURATION); + vm.assume(payout_ <= capacity_); + _createAuctionLot(); + + vm.warp(_start); + + _module.priceFor(_lotId, payout_); + } + + function testFuzz_minPriceZero_varyingSetup( + uint128 capacity_, + uint128 price_ + ) + public + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(0) + { + vm.assume(capacity_ >= _DURATION); + vm.assume(price_ >= 1000); + _createAuctionLot(); + + vm.warp(_start); + + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + uint256 payout = _auctionParams.capacity / _DURATION; // 1 seconds worth of tokens + console2.log("Payout:", payout); + uint256 price = _module.priceFor(_lotId, payout); + uint256 expectedPrice = _gdaParams.equilibriumPrice.mulDiv(payout, _BASE_SCALE); + // assertApproxEqAbs(price, expectedPrice, 1e18); TODO how to think about these bounds? some extremes have large errors + + vm.warp(_start + _DECAY_PERIOD); + price = _module.priceFor(_lotId, payout); + expectedPrice = expectedPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); + // assertApproxEqAbs(price, expectedPrice, 1e18); + } + + function testFuzz_minPriceNonZero_varyingSetup( + uint128 capacity_, + uint128 price_ + ) public givenLotCapacity(uint256(capacity_)) givenEquilibriumPrice(uint256(price_)) { + vm.assume(capacity_ >= _DURATION); + uint256 decayedPrice = uint256(price_).mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); + vm.assume(decayedPrice > _gdaParams.minimumPrice); // must have room for decay + _createAuctionLot(); + + vm.warp(_start); + + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + uint256 payout = _auctionParams.capacity / _DURATION; // 1 seconds worth of tokens + console2.log("Payout:", payout); + uint256 price = _module.priceFor(_lotId, payout); + uint256 expectedPrice = _gdaParams.equilibriumPrice.mulDiv(payout, _BASE_SCALE); + // assertApproxEqAbs(price, expectedPrice, 1e18); TODO how to think about these bounds? some extremes have large errors + + vm.warp(_start + _DECAY_PERIOD); + price = _module.priceFor(_lotId, payout); + expectedPrice = expectedPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); + // assertApproxEqAbs(price, expectedPrice, 1e18); + } + + function testFuzz_minPriceZero_varyingTimesteps(uint48 timestep_) public - givenLotCapacity(1e75) // very large number, but not quite max (which overflows) givenMinPrice(0) givenLotIsCreated givenLotHasStarted { - vm.assume(payout_ <= _LOT_CAPACITY); + // Warp to the timestep + uint48 timestep = timestep_ % _DURATION; + console2.log("Warping to timestep:", timestep); + vm.warp(_start + timestep); + + // Calculated the expected price of the oldest auction at the timestep + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + UD60x18 q0 = ud(_INITIAL_PRICE.mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = data.emissionsRate; + UD60x18 k = data.decayConstant; + UD60x18 t = convert(timestep).div(_ONE_DAY); + UD60x18 qt = q0.mul(r).div(k.mul(t).exp()); + console2.log("Expected price at timestep:", qt.unwrap()); + + // Set payout to 1 seconds worth of tokens + uint256 payout = _LOT_CAPACITY / _DURATION; + + // Calculate the price + uint256 price = _module.priceFor(_lotId, payout); - _module.priceFor(_lotId, payout_); + // Calculate the expected price (qt divided by 1 day) + uint256 expectedPrice = qt.intoUint256().mulDiv(10 ** _quoteTokenDecimals, uUNIT) / 1 days; + + // The price should be conservative (greater than or equal to the expected price) + assertGe(price, expectedPrice); + assertApproxEqRel(price, expectedPrice, 1e14); // 0.01% } - function testFuzz_minPriceNonZero_noOverflows(uint256 payout_) + function testFuzz_minPriceNonZero_varyingTimesteps(uint48 timestep_) public - givenLotCapacity(1e75) // very large number, but not quite max (which overflows) givenLotIsCreated givenLotHasStarted { - vm.assume(payout_ <= _LOT_CAPACITY); + // Warp to the timestep + uint48 timestep = timestep_ % _DURATION; + console2.log("Warping to timestep:", timestep); + vm.warp(_start + timestep); + + // Calculated the expected price of the oldest auction at the timestep + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + UD60x18 q0 = ud(_INITIAL_PRICE.mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 qm = ud(_MIN_PRICE.mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = data.emissionsRate; + UD60x18 k = data.decayConstant; + UD60x18 t = convert(timestep).div(_ONE_DAY); + UD60x18 qt = (q0 - qm).div(k.mul(t).exp()).add(qm).mul(r); + console2.log("Expected price at timestep:", qt.unwrap()); + + // Set payout to 1 seconds worth of tokens + uint256 payout = _LOT_CAPACITY / _DURATION; + + // Calculate the price + uint256 price = _module.priceFor(_lotId, payout); - _module.priceFor(_lotId, payout_); + // Calculate the expected price (qt divided by 1 day) + uint256 expectedPrice = qt.intoUint256().mulDiv(10 ** _quoteTokenDecimals, uUNIT) / 1 days; + + // The price should be conservative (greater than or equal to the expected price) + assertGe(price, expectedPrice); + assertApproxEqRel(price, expectedPrice, 1e14); // 0.01% } } diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol new file mode 100644 index 000000000..06917a1d9 --- /dev/null +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Module} from "src/modules/Modules.sol"; +import {IAuction} from "src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; + +import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; +import {console2} from "lib/forge-std/src/console2.sol"; + +contract GdaPurchaseTest is GdaTest { + uint256 internal _purchaseAmount = 5e18; + uint256 internal _purchaseAmountOut; + + modifier setPurchaseAmount(uint256 amount) { + _purchaseAmount = amount; + _; + } + + modifier setAmountOut() { + _purchaseAmountOut = _module.payoutFor(_lotId, _purchaseAmount); + _; + } + + // [X] when the caller is not the parent + // [X] it reverts + // [X] when the lot id is invalid + // [X] it reverts + // [X] when the auction has concluded + // [X] it reverts + // [X] when the auction has been cancelled + // [X] it reverts + // [X] when the auction has not started + // [X] it reverts + // [X] when there is insufficient capacity + // [X] it reverts + // [ ] when the amount is more than the max amount accepted + // [ ] it reverts + // [X] when the token decimals are different + // [X] it handles the purchase correctly + // [X] it updates the capacity, purchased, sold, and last auction start + + function test_notParent_reverts() public givenLotIsCreated givenLotHasStarted setAmountOut { + // Expect revert + bytes memory err = abi.encodeWithSelector(Module.Module_OnlyParent.selector, address(this)); + vm.expectRevert(err); + + // Call the function + _module.purchase(_lotId, _purchaseAmount, abi.encode(_purchaseAmountOut)); + } + + function test_invalidLotId_reverts() public { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, _lotId); + vm.expectRevert(err); + + // Call the function + _createPurchase(_purchaseAmount, _purchaseAmountOut); + } + + function test_auctionConcluded_reverts() + public + givenLotIsCreated + givenLotHasConcluded + setAmountOut + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_LotNotActive.selector, _lotId); + vm.expectRevert(err); + + // Call the function + _createPurchase(_purchaseAmount, _purchaseAmountOut); + } + + function test_auctionCancelled_reverts() + public + givenLotIsCreated + setAmountOut + givenLotIsCancelled + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_LotNotActive.selector, _lotId); + vm.expectRevert(err); + + // Call the function + _createPurchase(_purchaseAmount, _purchaseAmountOut); + } + + function test_auctionNotStarted_reverts() public givenLotIsCreated setAmountOut { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_LotNotActive.selector, _lotId); + vm.expectRevert(err); + + // Call the function + _createPurchase(_purchaseAmount, _purchaseAmountOut); + } + + function test_whenCapacityIsInsufficient_reverts() + public + givenLotCapacity(15e17) + givenLotIsCreated + givenLotHasStarted + setAmountOut + givenPurchase(_purchaseAmount, _purchaseAmountOut) // Payout ~0.95, remaining capacity is 1.5 - 0.95 = 0.55 + { + // Expect revert + bytes memory err = abi.encodeWithSelector(IAuction.Auction_InsufficientCapacity.selector); + vm.expectRevert(err); + + // Call the function + _createPurchase(_purchaseAmount, _purchaseAmountOut); + } + + function testFuzz_amountGreaterThanMaxAccepted_reverts(uint256 amount_) + public + givenLotIsCreated + givenLotHasStarted + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + vm.assume(amount_ > maxAmountAccepted); + + // Expect revert (may fail due to math issues or the capacity check) + vm.expectRevert(); + + // Call the function + _createPurchase(amount_, 0); // We don't set the minAmountOut slippage check since trying to calculate the payout would revert + } + + function testFuzz_minPriceNonZero_success(uint256 amount_) + public + givenLotIsCreated + givenLotHasStarted + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + console2.log("amount", amount); + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, _scaleBaseTokenAmount(_LOT_CAPACITY) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceNonZero_success_quoteTokenDecimalsLarger(uint256 amount_) + public + givenQuoteTokenDecimals(17) + givenBaseTokenDecimals(13) + givenLotIsCreated + givenLotHasStarted + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, _scaleBaseTokenAmount(_LOT_CAPACITY) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceNonZero_success_quoteTokenDecimalsSmaller(uint256 amount_) + public + givenQuoteTokenDecimals(13) + givenBaseTokenDecimals(17) + givenLotIsCreated + givenLotHasStarted + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, _scaleBaseTokenAmount(_LOT_CAPACITY) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceNonZero_afterDecay_success(uint256 amount_) + public + givenLotIsCreated + { + // Warp forward in time to late in the auction + vm.warp(_start + _DURATION - 1 hours); + + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, _scaleBaseTokenAmount(_LOT_CAPACITY) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + // Limit capacity to u128 here so it uses reasonable values + function testFuzz_minPriceNonZero_varyingCapacity_success( + uint256 amount_, + uint128 capacity_ + ) public givenLotCapacity(uint256(capacity_)) { + vm.assume(capacity_ >= _DURATION); // Divide by zero issues with really small numbers which prevent purchasing. + // Create the lot + _createAuctionLot(); + + // Warp to start + vm.warp(_start); + + // Normalize the amount + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, uint256(capacity_) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceZero_success(uint256 amount_) + public + givenMinPrice(0) + givenLotIsCreated + givenLotHasStarted + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + console2.log("amount", amount); + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, _scaleBaseTokenAmount(_LOT_CAPACITY) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceZero_success_quoteTokenDecimalsLarger(uint256 amount_) + public + givenMinPrice(0) + givenQuoteTokenDecimals(17) + givenBaseTokenDecimals(13) + givenLotIsCreated + givenLotHasStarted + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, _scaleBaseTokenAmount(_LOT_CAPACITY) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceZero_success_quoteTokenDecimalsSmaller(uint256 amount_) + public + givenMinPrice(0) + givenQuoteTokenDecimals(13) + givenBaseTokenDecimals(17) + givenLotIsCreated + givenLotHasStarted + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, _scaleBaseTokenAmount(_LOT_CAPACITY) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceZero_afterDecay_success(uint256 amount_) + public + givenMinPrice(0) + givenLotIsCreated + { + // Warp forward in time to late in the auction + vm.warp(_start + _DURATION - 1 hours); + + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, _scaleBaseTokenAmount(_LOT_CAPACITY) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + // Limit capacity to u128 here so it uses reasonable values + function testFuzz_minPriceZero_varyingCapacity_success( + uint256 amount_, + uint128 capacity_ + ) public givenMinPrice(0) givenLotCapacity(uint256(capacity_)) { + vm.assume(capacity_ >= _DURATION); // Divide by zero issues with really small numbers which prevent purchasing. + // Create the lot + _createAuctionLot(); + + // Warp to start + vm.warp(_start); + + // Normalize the amount + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % maxAmountAccepted + 1; + + // Warp to start + vm.warp(_start); + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, uint256(capacity_) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } +} From bee920c05d7da2ce24444f52a865329c6fff2d37 Mon Sep 17 00:00:00 2001 From: Oighty Date: Fri, 24 May 2024 12:37:09 -0500 Subject: [PATCH 34/69] chore: update prb-math dependency --- lib/prb-math | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prb-math b/lib/prb-math index dd984cbde..78c70b6af 160000 --- a/lib/prb-math +++ b/lib/prb-math @@ -1 +1 @@ -Subproject commit dd984cbde943d8371cb6fe7c58e9be1af945aefb +Subproject commit 78c70b6afe7cff292b52ef6955c636d7f0e04639 From b52abc6caa6700e7110e92b6d6eefc28f43e8a43 Mon Sep 17 00:00:00 2001 From: Oighty Date: Fri, 24 May 2024 13:42:43 -0500 Subject: [PATCH 35/69] fix: reduce error correction based on library improvements --- src/modules/auctions/GDA.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 34ee64ac5..eaee48047 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -390,10 +390,10 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { console2.log("second term:", c.unwrap()); // Calculate the third term: W(C e^(F + C)) - // 150 wei is the maximum error (TODO that I have found so far) - // for the lambert-W approximation, - // this makes sure the estimate is conservative - UD60x18 w = c.add(f).exp().mul(c).productLn() + ud(150); + // 17 wei is the maximum error for values in the + // range of possible values for the lambert-W approximation, + // this makes sure the estimate is conservative. + UD60x18 w = c.add(f).exp().mul(c).productLn() + ud(17); console2.log("third term:", w.unwrap()); // Without error correction, the intermediate term (f + c - w) cannot underflow because From 849f40c1db3c6c11481fca11db1f475edd8e345c Mon Sep 17 00:00:00 2001 From: Oighty Date: Fri, 24 May 2024 13:47:17 -0500 Subject: [PATCH 36/69] fix: compile error in test --- test/modules/auctions/GDA/cancelAuction.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/modules/auctions/GDA/cancelAuction.t.sol b/test/modules/auctions/GDA/cancelAuction.t.sol index d821fa211..5431ae92f 100644 --- a/test/modules/auctions/GDA/cancelAuction.t.sol +++ b/test/modules/auctions/GDA/cancelAuction.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import {Module} from "src/modules/Modules.sol"; import {IAuction} from "src/interfaces/modules/IAuction.sol"; -import {GdaTest} from "test/modules/auctions/Gda/GdaTest.sol"; +import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; contract GdaCancelAuctionTest is GdaTest { // [X] when the caller is not the parent From 14929094872f4593fa57cad179e36a2b3b61242d Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 28 May 2024 10:10:13 -0500 Subject: [PATCH 37/69] wip: more GDA error bounds --- src/modules/auctions/GDA.sol | 142 ++++++++++++++++--- test/modules/auctions/GDA/GDATest.sol | 3 +- test/modules/auctions/GDA/auction.t.sol | 63 +++++++-- test/modules/auctions/GDA/maxPayout.t.sol | 16 +-- test/modules/auctions/GDA/payoutFor.t.sol | 76 ++++++++++- test/modules/auctions/GDA/priceFor.t.sol | 12 +- test/modules/auctions/GDA/purchase.t.sol | 159 +++++++++++++++++++--- 7 files changed, 401 insertions(+), 70 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index eaee48047..1f8f62be0 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -10,7 +10,7 @@ import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutc // External libraries import { - UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT, ZERO + UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT, ZERO, HALF_UNIT, MAX_UD60x18 } from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; @@ -27,23 +27,28 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Decay target over the first period must fit within these bounds // We use 18 decimals so we don't have to convert it to use as a UD60x18 uint256 internal constant MIN_DECAY_TARGET = 1e16; // 1% - uint256 internal constant MAX_DECAY_TARGET = 50e16; // 50% + uint256 internal constant MAX_DECAY_TARGET = 49e16; // 49% // Bounds for the decay period, which establishes the bounds for the decay constant // If a you want a longer or shorter period for the target, you can find another point on the curve that is in this range // and calculate the decay target for that point as your input - uint48 internal constant MIN_DECAY_PERIOD = 1 hours; + uint48 internal constant MIN_DECAY_PERIOD = 6 hours; uint48 internal constant MAX_DECAY_PERIOD = 1 weeks; - // Decay period must be greater than or equal to 1 hours and less than or equal to 1 week - // A minimum value of q1 = q0 * 0.01 and a min period of 1 hours means: - // MAX_LN_OUTPUT = ln(1/0.5) = 0_693147180559945309 - // MAX_LN_OUTPUT * 24 = 16_635532333438687426 - // -> implies a max duration of 8 days in the worst case (decaying 50% over an hour) + // Decay period must be greater than or equal to 1 day and less than or equal to 1 week + // A minimum value of q1 = q0 * 0.01 and a min period of 1 day means: + // MAX_LN_OUTPUT = ln(1/0.51) = 0_673344553263765596 + // MAX_DECAY_CONSTANT = MAX_LN_OUTPUT * 24 = 16_160269278330374304 + // -> For auctions without a min price, implies a max duration of 8 days in the worst case (decaying 49% over an hour) + // -> For auctions with a min price, implies a max duration of 0.302 days (7.25 hours) in the worst case (decaying 49% over an hour) // A maximum value of q1 = q0 * 0.99 and a max period of 7 days means: // MIN_LN_OUTPUT = ln(1/0.99) = 0_010050335853501441 // MIN_LN_OUTPUT / 7 = 0_001435762264785920 - // -> implies a max duration of ~52 years in the best case (decaying 1% over a week) + // -> For auctions without a min price, implies a max duration of ~52 years in the best case (decaying 1% over a week) + // -> For auctions with a min price, implies a max duration of ~9 years in the best case (decaying 1% over a week) + + // Precomputed: ln(133.084258667509499440) = 4.890982451446117211 using the PRBMath ln function (off by 10 wei) + UD60x18 internal constant LN_OF_EXP_MAX_INPUT = UD60x18.wrap(4_890982451446117211); /* solhint-enable private-vars-leading-underscore */ @@ -68,8 +73,42 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { GDAParams memory params = abi.decode(params_, (GDAParams)); // Validate parameters - // Equilibrium Price must be greater than 1000 wei and less than u128 max to avoid various math errors - if (params.equilibriumPrice < 1000 || params.equilibriumPrice > type(uint128).max) { + // Validate price and capacity values are large enough to avoid precision errors + // We do this by requiring them to be atleast 10^(tokenDecimals / 2), we round up to make it more strict. + // This sets a floor value for the price of: + // - 10^-9 quote tokens per base token when quote decimals are 18. + // - 10^-5 quote tokens per base token when quote decimals are 9. + // - 10^-3 quote tokens per base token when quote decimals are 6. + // This sets a floor value for capacity of: + // - 10^9 base tokens when base decimals are 18. + // - 10^5 base tokens when base decimals are 9. + // - 10^3 base tokens when base decimals are 6. + { + int8 priceDecimals = _getValueDecimals(params.equilibriumPrice, lot_.quoteTokenDecimals); + int8 capacityDecimals = _getValueDecimals(lot_.capacity, lot_.baseTokenDecimals); + + uint8 halfQuoteDecimals = lot_.quoteTokenDecimals % 2 == 0 ? lot_.quoteTokenDecimals / 2 : lot_.quoteTokenDecimals / 2 + 1; + uint8 halfBaseDecimals = lot_.baseTokenDecimals % 2 == 0 ? lot_.baseTokenDecimals / 2 : lot_.baseTokenDecimals / 2 + 1; + + if (priceDecimals < - int8(halfQuoteDecimals) + || capacityDecimals < - int8(halfBaseDecimals)) { + revert Auction_InvalidParams(); + } + + // Also validate the minimum price if it is not zero + if (params.minimumPrice > 0) { + int8 minPriceDecimals = _getValueDecimals(params.minimumPrice, lot_.quoteTokenDecimals); + if (minPriceDecimals < - int8(halfQuoteDecimals)) { + revert Auction_InvalidParams(); + } + } + } + + // Equilibrium Price less than u128 max + // This sets a fairly high ceiling: + // 2^128 - 1 / 10^18 = 34.028*10^38 / 10^18 = max price 34.028*10^20 + // quote tokens per base token when quote token decimals are 18. + if (params.equilibriumPrice > type(uint128).max) { revert Auction_InvalidParams(); } @@ -96,25 +135,38 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { } // Decay period must be between the set bounds + // These bounds also ensure the decay constant is not zero if (params.decayPeriod < MIN_DECAY_PERIOD || params.decayPeriod > MAX_DECAY_PERIOD) { revert Auction_InvalidParams(); } UD60x18 decayConstant; + UD60x18 q0; + UD60x18 qm; { uint256 quoteTokenScale = 10 ** lot_.quoteTokenDecimals; - UD60x18 q0 = ud(params.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); + q0 = ud(params.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); UD60x18 q1 = q0.mul(UNIT - ud(params.decayTarget)).div(UNIT); - UD60x18 qm = ud(params.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + qm = ud(params.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); console2.log("q0:", q0.unwrap()); console2.log("q1:", q1.unwrap()); console2.log("qm:", qm.unwrap()); - // Check that q0 > q1 > qm - // Don't need to check q0 > q1 since: - // decayTarget >= 1e16 => q1 <= q0 * 0.99 => q0 > q1 + // Check that q0 > 0.99q0 >= q1 > 0.99q1 => qm + // Don't need to check q0 > 0.99q0 >= q1 since: + // decayTarget >= 1e16 => q0 * 0.99 >= q1 // This ensures that the operand for the logarithm is positive - if (q1 <= qm) { + // We enforce a minimum difference for q1 and qm as well to avoid small dividends. + if (q1 - qm < ud(1e16)) { + revert Auction_InvalidParams(); + } + + // If qm is not zero, then we require that it be not less than half of q0 + // This is to ensure that we do not exceed the maximum input for the exponential function + // It is also a sane default. + // Another piece of this check is done during a purchase to make sure that the amount + // provided is does not exceed the price of the capacity. + if (qm > ZERO && qm < q0.mul(HALF_UNIT)) { revert Auction_InvalidParams(); } @@ -133,7 +185,12 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // The duration must be less than the max exponential input divided by the decay constant // in order for the exponential operations to not overflow. See the minimum and maximum // constant calculations for more information. - if (duration > EXP_MAX_INPUT.div(decayConstant)) { + if (qm == ZERO && duration > EXP_MAX_INPUT.div(decayConstant)) { + revert Auction_InvalidParams(); + } + // In the case of a non-zero min price, the duration must be less than the natural logarithm + // of the max input divided by the decay constant to avoid overflows in the operand of the W function. + if (qm > ZERO && duration > LN_OF_EXP_MAX_INPUT.div(decayConstant)) { revert Auction_InvalidParams(); } @@ -142,6 +199,17 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { ud(lot_.capacity.mulDiv(uUNIT, 10 ** lot_.baseTokenDecimals)).div(duration); console2.log("emissions rate:", emissionsRate.unwrap()); + // To avoid divide by zero issues, we also must check that: + // if qm is zero, then q0 * r > 0 + // if qm is not zero, then qm * r > 0, which also implies the other. + if (qm == ZERO && q0.mul(emissionsRate) == ZERO) { + revert Auction_InvalidParams(); + } + + if (qm > ZERO && qm.mul(emissionsRate) == ZERO) { + revert Auction_InvalidParams(); + } + // Store auction data AuctionData storage data = auctionData[lotId_]; data.equilibriumPrice = params.equilibriumPrice; @@ -308,6 +376,12 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { Lot memory lot = lotData[lotId_]; AuctionData memory auction = auctionData[lotId_]; + // Ensure the amount does not exceed the max amount accepted + uint256 maxAmount = maxAmountAccepted(lotId_); + if (amount_ > maxAmount) { + revert Auction_InsufficientCapacity(); + } + // Get quote token scale and convert equilibrium price to 18 decimals uint256 quoteTokenScale = 10 ** lot.quoteTokenDecimals; UD60x18 q0 = ud(auction.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); @@ -328,6 +402,8 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { UD60x18 ekt = auction.decayConstant.mul( convert(block.timestamp - auction.lastAuctionStart).div(ONE_DAY) ).exp(); + console2.log("ekt:", ekt.unwrap()); + console2.log("r * q0:", auction.emissionsRate.mul(q0).unwrap()); // Calculate the logarithm // Operand is guaranteed to be >= 1, so the result is positive @@ -343,6 +419,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { UD60x18 ekt = auction.decayConstant.mul( convert(auction.lastAuctionStart - block.timestamp) ).exp(); + console2.log("ekt:", ekt.unwrap()); // Calculate the logarithm // Operand is guaranteed to be >= 1, so the result is positive @@ -393,7 +470,15 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // 17 wei is the maximum error for values in the // range of possible values for the lambert-W approximation, // this makes sure the estimate is conservative. - UD60x18 w = c.add(f).exp().mul(c).productLn() + ud(17); + // + // We prevent overflow in the operand of the W function via the duration < LN_OF_EXP_MAX_INPUT / decayConstant check. + // This means that e^(f + c) will always be < EXP_MAX_INPUT / c. + // We check for overflow before adding the error correction. + UD60x18 w = c.add(f).exp().mul(c).productLn(); + { + UD60x18 err = ud(17); + w = w > MAX_UD60x18.sub(err) ? MAX_UD60x18 : w.add(err); + } console2.log("third term:", w.unwrap()); // Without error correction, the intermediate term (f + c - w) cannot underflow because @@ -434,9 +519,26 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { return lotData[lotId_].capacity; } - function maxAmountAccepted(uint96 lotId_) external view override returns (uint256) { + function maxAmountAccepted(uint96 lotId_) public view override returns (uint256) { // The max amount accepted is the price to purchase the remaining capacity of the lot // This function checks if the lot ID is valid return priceFor(lotId_, lotData[lotId_].capacity); } + + // ========== INTERNAL FUNCTIONS ========== // + + /// @notice Helper function to calculate number of value decimals based on the stated token decimals. + /// @param value_ The value to calculate the number of decimals for + /// @return The number of decimals + function _getValueDecimals(uint256 value_, uint8 tokenDecimals_) internal pure returns (int8) { + int8 decimals; + while (value_ >= 10) { + value_ = value_ / 10; + decimals++; + } + + // Subtract the stated decimals from the calculated decimals to get the relative value decimals. + // Required to do it this way vs. normalizing at the beginning since value decimals can be negative. + return decimals - int8(tokenDecimals_); + } } diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index 8a52af682..e8e3744e7 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -27,10 +27,11 @@ abstract contract GdaTest is Test, Permit2User { uint256 internal constant _LOT_CAPACITY = 10e18; uint48 internal constant _DURATION = 2 days; uint256 internal constant _INITIAL_PRICE = 5e18; - uint256 internal constant _MIN_PRICE = 2e18; + uint256 internal constant _MIN_PRICE = 25e17; uint256 internal constant _DECAY_TARGET = 10e16; // 10% uint256 internal constant _DECAY_PERIOD = 12 hours; UD60x18 internal constant _ONE_DAY = UD60x18.wrap(1 days * uUNIT); + UD60x18 internal constant LN_OF_EXP_MAX_INPUT = UD60x18.wrap(4_890982451446117211); AtomicAuctionHouse internal _auctionHouse; GradualDutchAuction internal _module; diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index e0f8a8c5d..fa8918fa0 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -19,7 +19,7 @@ contract GdaCreateAuctionTest is GdaTest { // [X] it reverts // [X] when the duration is less than the globally configured minimum // [X] it reverts - // [X] when the equilibrium price is less than 1000 + // [X] when the equilibrium price is less than 10^(quotetoken decimals / 2) // [X] it reverts // [X] when the equilibrium price is greater than the max uint128 value // [X] it reverts @@ -39,7 +39,9 @@ contract GdaCreateAuctionTest is GdaTest { // [X] it reverts // [X] when capacity is greater than the max uint128 value // [X] it reverts - // [X] when duration is greater than the max exp input divided by the calculated decay constant + // [X] when min price is nonzero and duration is greater than the ln of the max exp input divided by the calculated decay constant + // [X] it reverts + // [X] when min price is zero and duration is greater than the max exp input divided by the calculated decay constant // [X] it reverts // [X] when the inputs are all valid // [X] it stores the auction data @@ -80,9 +82,9 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_equilibriumPriceIsLessThanMin_reverts(uint16 price_) + function test_equilibriumPriceIsLessThanMin_reverts(uint128 price_) public - givenEquilibriumPrice(uint256(price_) % 1000) + givenEquilibriumPrice(uint256(price_) % 1e9) { // Expect revert bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); @@ -132,7 +134,7 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_minPriceGreaterThanDecayTargePrice_reverts() + function test_minPriceGreaterThanDecayTargetPrice_reverts() public givenMinPrice(4e18) givenDecayTarget(25e16) // 25% decay from 5e18 is 3.75e18 @@ -145,7 +147,7 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_minPriceEqualToDecayTargePrice_reverts() + function test_minPriceEqualToDecayTargetPrice_reverts() public givenMinPrice(4e18) givenDecayTarget(20e16) // 20% decay from 5e18 is 4e18 @@ -172,7 +174,7 @@ contract GdaCreateAuctionTest is GdaTest { function test_decayTargetGreaterThanMaximum_reverts() public - givenDecayTarget(50e16 + 1) // slightly more than 50% + givenDecayTarget(49e16 + 1) // slightly more than 49% { // Expect revert bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); @@ -220,7 +222,7 @@ contract GdaCreateAuctionTest is GdaTest { uint8 decayHours_ ) public { // Normalize the inputs - uint256 decayTarget = uint256(decayTarget_ % 50 == 0 ? 50 : decayTarget_ % 50) * 1e16; + uint256 decayTarget = uint256(decayTarget_ % 49 == 0 ? 49 : decayTarget_ % 49) * 1e16; uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; console2.log("Decay target:", decayTarget); console2.log("Decay period:", decayPeriod); @@ -259,12 +261,53 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function testFuzz_durationEqualMaxExpInputDividedByDecayConstant_succeeds( + function testFuzz_minPriceNonZero_durationEqualLnMaxExpInputDividedByDecayConstant_succeeds( uint8 decayTarget_, uint8 decayHours_ ) public { // Normalize the inputs - uint256 decayTarget = uint256(decayTarget_ % 50 == 0 ? 50 : decayTarget_ % 50) * 1e16; + uint256 decayTarget = uint256(decayTarget_ % 49 == 0 ? 49 : decayTarget_ % 49) * 1e16; + uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; + console2.log("Decay target:", decayTarget); + console2.log("Decay period:", decayPeriod); + + // Calculate the decay constant + // q1 > qm here because qm < q0 * 0.50, which is the max decay target + uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; + UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); + UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); + UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + + console2.log("q0:", q0.unwrap()); + console2.log("q1:", q1.unwrap()); + console2.log("qm:", qm.unwrap()); + + // Calculate the decay constant + UD60x18 decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(decayPeriod).div(_ONE_DAY)); + console2.log("Decay constant:", decayConstant.unwrap()); + + // Calculate the maximum duration in seconds + uint256 maxDuration = convert(LN_OF_EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); + console2.log("Max duration:", maxDuration); + + // Set the decay target and decay period to the fuzzed values + // Set duration to the max duration + _gdaParams.decayTarget = decayTarget; + _gdaParams.decayPeriod = decayPeriod; + _auctionParams.implParams = abi.encode(_gdaParams); + _auctionParams.duration = uint48(maxDuration); + + // Call the function + _createAuctionLot(); + } + + function testFuzz_minPriceZero_durationEqualMaxExpInputDividedByDecayConstant_succeeds( + uint8 decayTarget_, + uint8 decayHours_ + ) public givenMinPrice(0) + { + // Normalize the inputs + uint256 decayTarget = uint256(decayTarget_ % 49 == 0 ? 49 : decayTarget_ % 49) * 1e16; uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; console2.log("Decay target:", decayTarget); console2.log("Decay period:", decayPeriod); diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol index 87f740a2b..af24096bf 100644 --- a/test/modules/auctions/GDA/maxPayout.t.sol +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -21,12 +21,9 @@ contract GdaMaxPayoutTest is GdaTest { } function testFuzz_maxPayout_success( - uint128 capacity_, - uint128 price_ - ) public givenLotCapacity(uint256(capacity_)) givenEquilibriumPrice(uint256(price_)) { - vm.assume(capacity_ >= _DURATION); - uint256 decayedPrice = uint256(price_).mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); - vm.assume(decayedPrice > _gdaParams.minimumPrice); // must have room for decay + uint128 capacity_ + ) public givenLotCapacity(uint256(capacity_)) { + vm.assume(capacity_ >= 1e9); _createAuctionLot(); uint256 maxPayout = _module.maxPayout(_lotId); @@ -35,16 +32,13 @@ contract GdaMaxPayoutTest is GdaTest { } function testFuzz_maxPayout_minPriceZero_success( - uint128 capacity_, - uint128 price_ + uint128 capacity_ ) public givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) givenMinPrice(0) { - vm.assume(capacity_ >= _DURATION); - vm.assume(price_ >= 1e3); + vm.assume(capacity_ >= 1e9); _createAuctionLot(); uint256 maxPayout = _module.maxPayout(_lotId); diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index d4c8eebc6..7f3f6dc1e 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -5,7 +5,7 @@ import {Module} from "src/modules/Modules.sol"; import {IAuction} from "src/interfaces/modules/IAuction.sol"; import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; +import {UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; @@ -235,6 +235,80 @@ contract GdaPayoutForTest is GdaTest { _module.payoutFor(_lotId, amount_); } + function testFuzz_minPriceZero_varyingSetup( + uint128 capacity_, + uint128 price_ + ) + public + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(0) + { + vm.assume(price_ >= 1e9); + vm.assume(capacity_ >= 1e9); + UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); + vm.assume(q0.mul(r) > ZERO); + _createAuctionLot(); + + vm.warp(_start); + + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + uint256 expectedPayout = _auctionParams.capacity / _DURATION; + uint256 amount = _gdaParams.equilibriumPrice.mulDiv(expectedPayout, _BASE_SCALE); + console2.log("Amount:", amount); + uint256 payout = _module.payoutFor(_lotId, amount); + assertLe(payout, expectedPayout); + // assertApproxEqRel(payout, expectedPayout, 1e16); //TODO how to think about these bounds? some extremes have large errors + + vm.warp(_start + _DECAY_PERIOD); + amount = _gdaParams.equilibriumPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT).mulDiv(expectedPayout, _BASE_SCALE); + payout = _module.payoutFor(_lotId, amount); + assertLe(payout, expectedPayout); + // assertApproxEqRel(payout, expectedPayout, 1e16); + } + + function testFuzz_minPriceNonZero_varyingSetup( + uint128 capacity_, + uint128 price_, + uint128 minPrice_ + ) + public + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(minPrice_ < price_ / 2 ? uint256(price_ / 2) : uint256(minPrice_)) + givenDuration(1 days) + { + vm.assume(capacity_ >= 1e9); + vm.assume(minPrice_ >= 1e9); + vm.assume(uint256(price_) * 9 / 10 > _gdaParams.minimumPrice); // must have clearance for the decay target + // vm.assume(minPrice_ >= price_ / 2); // requirement when min price is not zero + UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _auctionParams.duration)); + vm.assume(q0.mul(r) > ZERO); + _createAuctionLot(); + + vm.warp(_start); + + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + uint256 expectedPayout = _auctionParams.capacity / _auctionParams.duration; + uint256 amount = _gdaParams.equilibriumPrice.mulDiv(expectedPayout, _BASE_SCALE); + console2.log("Amount:", amount); + uint256 payout = _module.payoutFor(_lotId, amount); + assertLe(payout, expectedPayout); + // assertApproxEqRel(payout, expectedPayout, 1e16); //TODO how to think about these bounds? some extremes have large errors + + vm.warp(_start + _auctionParams.duration); + amount = _gdaParams.equilibriumPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT).mulDiv(expectedPayout, _BASE_SCALE); + payout = _module.payoutFor(_lotId, amount); + assertLe(payout, expectedPayout); + // assertApproxEqRel(payout, expectedPayout, 1e16); + } + function testFuzz_minPriceZero_varyingTimesteps(uint48 timestep_) public givenMinPrice(0) diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 6ef02d429..e1669f23a 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -169,8 +169,8 @@ contract GdaPriceForTest is GdaTest { givenEquilibriumPrice(uint256(price_)) givenMinPrice(0) { - vm.assume(capacity_ >= _DURATION); - vm.assume(price_ >= 1000); + vm.assume(capacity_ >= _DURATION * 1000); + vm.assume(price_ >= 1e9); _createAuctionLot(); vm.warp(_start); @@ -182,12 +182,12 @@ contract GdaPriceForTest is GdaTest { console2.log("Payout:", payout); uint256 price = _module.priceFor(_lotId, payout); uint256 expectedPrice = _gdaParams.equilibriumPrice.mulDiv(payout, _BASE_SCALE); - // assertApproxEqAbs(price, expectedPrice, 1e18); TODO how to think about these bounds? some extremes have large errors + assertGe(price, expectedPrice); vm.warp(_start + _DECAY_PERIOD); price = _module.priceFor(_lotId, payout); expectedPrice = expectedPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); - // assertApproxEqAbs(price, expectedPrice, 1e18); + assertGe(price, expectedPrice); } function testFuzz_minPriceNonZero_varyingSetup( @@ -208,12 +208,12 @@ contract GdaPriceForTest is GdaTest { console2.log("Payout:", payout); uint256 price = _module.priceFor(_lotId, payout); uint256 expectedPrice = _gdaParams.equilibriumPrice.mulDiv(payout, _BASE_SCALE); - // assertApproxEqAbs(price, expectedPrice, 1e18); TODO how to think about these bounds? some extremes have large errors + assertGe(price, expectedPrice); vm.warp(_start + _DECAY_PERIOD); price = _module.priceFor(_lotId, payout); expectedPrice = expectedPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); - // assertApproxEqAbs(price, expectedPrice, 1e18); + assertGe(price, expectedPrice); } function testFuzz_minPriceZero_varyingTimesteps(uint48 timestep_) diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index 06917a1d9..cf3a0616c 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -5,10 +5,15 @@ import {Module} from "src/modules/Modules.sol"; import {IAuction} from "src/interfaces/modules/IAuction.sol"; import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; +import {UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; +import "lib/prb-math/src/Common.sol" as PRBMath; + import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; import {console2} from "lib/forge-std/src/console2.sol"; contract GdaPurchaseTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + uint256 internal _purchaseAmount = 5e18; uint256 internal _purchaseAmountOut; @@ -132,7 +137,7 @@ contract GdaPurchaseTest is GdaTest { givenLotHasStarted { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); console2.log("amount", amount); // Calculate expected values @@ -156,7 +161,7 @@ contract GdaPurchaseTest is GdaTest { givenLotHasStarted { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); @@ -179,7 +184,7 @@ contract GdaPurchaseTest is GdaTest { givenLotHasStarted { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); @@ -202,7 +207,7 @@ contract GdaPurchaseTest is GdaTest { vm.warp(_start + _DURATION - 1 hours); uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); @@ -218,20 +223,35 @@ contract GdaPurchaseTest is GdaTest { } // Limit capacity to u128 here so it uses reasonable values - function testFuzz_minPriceNonZero_varyingCapacity_success( + function testFuzz_minPriceNonZero_varyingSetup( uint256 amount_, - uint128 capacity_ - ) public givenLotCapacity(uint256(capacity_)) { - vm.assume(capacity_ >= _DURATION); // Divide by zero issues with really small numbers which prevent purchasing. - // Create the lot + uint128 capacity_, + uint128 price_, + uint128 minPrice_ + ) + public + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(minPrice_ < price_ / 2 ? uint256(price_ / 2) : uint256(minPrice_)) + { + vm.assume(capacity_ >= 1e9); + vm.assume(_gdaParams.minimumPrice >= 1e9); + _auctionParams.duration = uint48(1 days); + vm.assume(uint256(price_) * 9 / 10 > _gdaParams.minimumPrice); // must have clearance for the decay target + // vm.assume(minPrice_ >= price_ / 2); // requirement when min price is not zero + UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _auctionParams.duration)); + vm.assume(q0.mul(r) > ZERO); _createAuctionLot(); - // Warp to start vm.warp(_start); + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + // Normalize the amount uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); @@ -253,7 +273,7 @@ contract GdaPurchaseTest is GdaTest { givenLotHasStarted { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); console2.log("amount", amount); // Calculate expected values @@ -278,7 +298,7 @@ contract GdaPurchaseTest is GdaTest { givenLotHasStarted { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); @@ -302,7 +322,7 @@ contract GdaPurchaseTest is GdaTest { givenLotHasStarted { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); @@ -326,7 +346,7 @@ contract GdaPurchaseTest is GdaTest { vm.warp(_start + _DURATION - 1 hours); uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); @@ -342,20 +362,117 @@ contract GdaPurchaseTest is GdaTest { } // Limit capacity to u128 here so it uses reasonable values - function testFuzz_minPriceZero_varyingCapacity_success( + function testFuzz_minPriceZero_varyingSetup( uint256 amount_, - uint128 capacity_ - ) public givenMinPrice(0) givenLotCapacity(uint256(capacity_)) { - vm.assume(capacity_ >= _DURATION); // Divide by zero issues with really small numbers which prevent purchasing. - // Create the lot + uint128 capacity_, + uint128 price_ + ) + public + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(0) + { + vm.assume(price_ >= 1e9); + vm.assume(capacity_ >= 1e9); + UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); + vm.assume(q0.mul(r) > ZERO); _createAuctionLot(); + vm.warp(_start); + + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + // Normalize the amount + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % (maxAmountAccepted + 1); + // Warp to start vm.warp(_start); + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, uint256(capacity_) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceZero_varyingSetup_quoteDecimalsSmaller( + uint256 amount_, + uint128 capacity_, + uint128 price_ + ) + public + givenQuoteTokenDecimals(6) + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(0) + { + vm.assume(price_ >= 1e3); + vm.assume(capacity_ >= 1e9); + UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); + vm.assume(q0.mul(r) > ZERO); + _createAuctionLot(); + + vm.warp(_start); + + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + // Normalize the amount + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % (maxAmountAccepted + 1); + + // Warp to start + vm.warp(_start); + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, uint256(capacity_) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceZero_varyingSetup_quoteDecimalsLarger( + uint256 amount_, + uint128 capacity_, + uint128 price_ + ) + public + givenBaseTokenDecimals(6) + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(0) + { + vm.assume(price_ >= 1e9); + vm.assume(capacity_ >= 1e3); + UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); + vm.assume(q0.mul(r) > ZERO); + _createAuctionLot(); + + vm.warp(_start); + + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + // Normalize the amount uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); - uint256 amount = amount_ % maxAmountAccepted + 1; + uint256 amount = amount_ % (maxAmountAccepted + 1); // Warp to start vm.warp(_start); From 73744fe27b20bd03de431ecef82fc3d24a6d0ff1 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 28 May 2024 15:22:57 -0500 Subject: [PATCH 38/69] fix: update and clarify decay constant + min price bounds --- .../modules/auctions/IGradualDutchAuction.sol | 4 + src/modules/auctions/GDA.sol | 202 +++++++++++------- 2 files changed, 129 insertions(+), 77 deletions(-) diff --git a/src/interfaces/modules/auctions/IGradualDutchAuction.sol b/src/interfaces/modules/auctions/IGradualDutchAuction.sol index 8ea81d550..9820cfa6b 100644 --- a/src/interfaces/modules/auctions/IGradualDutchAuction.sol +++ b/src/interfaces/modules/auctions/IGradualDutchAuction.sol @@ -6,6 +6,10 @@ import {IAtomicAuction} from "src/interfaces/modules/IAtomicAuction.sol"; /// @notice Interface for gradual dutch (atomic) auctions interface IGradualDutchAuction is IAtomicAuction { + // ========== ERRORS ========== // + + error GDA_InvalidParams(uint256 step); // the step tells you where the error occurred + // ========== DATA STRUCTURES ========== // /// @notice Auction pricing data diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 1f8f62be0..444475f4c 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -10,7 +10,14 @@ import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutc // External libraries import { - UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT, ZERO, HALF_UNIT, MAX_UD60x18 + UD60x18, + ud, + convert, + UNIT, + uUNIT, + EXP_MAX_INPUT, + ZERO, + HALF_UNIT } from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; @@ -27,7 +34,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Decay target over the first period must fit within these bounds // We use 18 decimals so we don't have to convert it to use as a UD60x18 uint256 internal constant MIN_DECAY_TARGET = 1e16; // 1% - uint256 internal constant MAX_DECAY_TARGET = 49e16; // 49% + uint256 internal constant MAX_DECAY_TARGET = 40e16; // 40% // Bounds for the decay period, which establishes the bounds for the decay constant // If a you want a longer or shorter period for the target, you can find another point on the curve that is in this range @@ -35,20 +42,58 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { uint48 internal constant MIN_DECAY_PERIOD = 6 hours; uint48 internal constant MAX_DECAY_PERIOD = 1 weeks; - // Decay period must be greater than or equal to 1 day and less than or equal to 1 week - // A minimum value of q1 = q0 * 0.01 and a min period of 1 day means: - // MAX_LN_OUTPUT = ln(1/0.51) = 0_673344553263765596 - // MAX_DECAY_CONSTANT = MAX_LN_OUTPUT * 24 = 16_160269278330374304 - // -> For auctions without a min price, implies a max duration of 8 days in the worst case (decaying 49% over an hour) - // -> For auctions with a min price, implies a max duration of 0.302 days (7.25 hours) in the worst case (decaying 49% over an hour) - // A maximum value of q1 = q0 * 0.99 and a max period of 7 days means: - // MIN_LN_OUTPUT = ln(1/0.99) = 0_010050335853501441 - // MIN_LN_OUTPUT / 7 = 0_001435762264785920 - // -> For auctions without a min price, implies a max duration of ~52 years in the best case (decaying 1% over a week) - // -> For auctions with a min price, implies a max duration of ~9 years in the best case (decaying 1% over a week) - - // Precomputed: ln(133.084258667509499440) = 4.890982451446117211 using the PRBMath ln function (off by 10 wei) - UD60x18 internal constant LN_OF_EXP_MAX_INPUT = UD60x18.wrap(4_890982451446117211); + // The constants above provide boundaries for decay constant used in the price and payout calculations. + // There are two variants of the calculations, one for auctions with a minimum price and one without. + // We use the above bounds to calculate maximum and minimum values for the decay constant under both scenarios. + // The formula to compute the decay constant is: + // ln((q0 - qm) / (q1 - qm)) / dp + // where q0 is the equilibrium price, q1 = q0 * (1 - K) is the target price, qm is the minimum price, + // dp is the decay period (in days), and K is the decay target. + // + // Generally, the maximum value is achieved with the + // - largest allowable decay target + // - smallest allowable decay period + // - highest allowable min price (if applicable) + // + // Conversely, the minimum value is achieved with the + // - smallest allowable decay target + // - the largest allowable decay period + // - lowest allowable min price (if applicable) + // + // We are mostly concerned with the maximum value, since it implies a limit for the duration of the auction with that configuration. + // The above constants were chosen to optimize flexibility while ensuring adequate auction durations are available for use cases. + // + // 1. Without a minimum price + // We must bound the duration for an auction without a min price + // to the maximum exponential input divided by the decay constant. + // d_max = E / k + // + // Maximum decay constant: K = 40%, dp = 6 hours + // k_max = ln((1.0 - 0)/(0.6 - 0)) / (1/4) = 2.043302495063962733 + // d_max = E / k_max = 133.084258667509499440 / 2.043302495063962733 = 65.132 (days) + // + // Minimum decay constant: K = 1%, dp = 1 week + // k_min = ln((1.0 - 0)/(0.99 - 0)) / 7 = 0.001435762264785920 + // d_max = E / k_min = 133.084258667509499440 / 0.001435762264785920 = 92692 days = ~253 years + // + // 2. With a minimum price + // We must bound the duration for an auction with a min price + // to the natural logarithm of the maximum exponential input divided by the decay constant. + // This is a much smaller number than the above, and our main constraint. + // d_max = ln(E - 1) / k + // + // Maximum decay constant (worst case): K = 40%, dp = 6 hours, qm = 0.5q0 + // (we restrict qm to be atleast 10% of q0 less than q1) + // k_max = ln((1.0 - 0.5)/(0.6 - 0.5)) / (1/4) = 6.437751649736401498 + // d_max = ln(E - 1) / k_max = 4.883440042183455484 / 6.437751649736401498 = 0.759 (days) = ~18 hours + // + // Minimum decay constant (best case): K = 1%, dp = 1 week, qm = 0.5q0 + // k_min = ln((1.0 - 0.5)/(0.99 - 0.5)) / 7 = 0.002886101045359921 + // d_max = ln(E - 1) / k_min = 4.883440042183455484 / 0.002886101045359921 = 1692 days = ~4.6 years + // + // The maximum input for the productLn function is EXP_MAX_INPUT - UNIT. Therefore, we must restrict + // Precomputed: ln(133.084258667509499440 - 1) = 4.883440042183455484 using the PRBMath ln function (7 wei less than actual) + UD60x18 internal constant LN_OF_PRODUCT_LN_MAX = UD60x18.wrap(4_883_440_042_183_455_484); /* solhint-enable private-vars-leading-underscore */ @@ -83,61 +128,53 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // - 10^9 base tokens when base decimals are 18. // - 10^5 base tokens when base decimals are 9. // - 10^3 base tokens when base decimals are 6. + // + // Additionally, we set a ceiling of uint128 max for the price and capacity. + // 2^128 - 1 / 10^18 = 34.028*10^38 / 10^18 = max price 34.028*10^20 + // quote tokens per base token when quote token decimals + // or base tokens to be sold when decimals are 18. { int8 priceDecimals = _getValueDecimals(params.equilibriumPrice, lot_.quoteTokenDecimals); int8 capacityDecimals = _getValueDecimals(lot_.capacity, lot_.baseTokenDecimals); - uint8 halfQuoteDecimals = lot_.quoteTokenDecimals % 2 == 0 ? lot_.quoteTokenDecimals / 2 : lot_.quoteTokenDecimals / 2 + 1; - uint8 halfBaseDecimals = lot_.baseTokenDecimals % 2 == 0 ? lot_.baseTokenDecimals / 2 : lot_.baseTokenDecimals / 2 + 1; - - if (priceDecimals < - int8(halfQuoteDecimals) - || capacityDecimals < - int8(halfBaseDecimals)) { - revert Auction_InvalidParams(); + uint8 halfQuoteDecimals = lot_.quoteTokenDecimals % 2 == 0 + ? lot_.quoteTokenDecimals / 2 + : lot_.quoteTokenDecimals / 2 + 1; + uint8 halfBaseDecimals = lot_.baseTokenDecimals % 2 == 0 + ? lot_.baseTokenDecimals / 2 + : lot_.baseTokenDecimals / 2 + 1; + + if ( + priceDecimals < -int8(halfQuoteDecimals) + || params.equilibriumPrice > type(uint128).max + ) { + revert GDA_InvalidParams(0); } - // Also validate the minimum price if it is not zero - if (params.minimumPrice > 0) { - int8 minPriceDecimals = _getValueDecimals(params.minimumPrice, lot_.quoteTokenDecimals); - if (minPriceDecimals < - int8(halfQuoteDecimals)) { - revert Auction_InvalidParams(); - } + // We also do not allow capacity to be in quote tokens + if ( + lot_.capacityInQuote || capacityDecimals < -int8(halfBaseDecimals) + || lot_.capacity > type(uint128).max + ) { + revert GDA_InvalidParams(1); } - } - // Equilibrium Price less than u128 max - // This sets a fairly high ceiling: - // 2^128 - 1 / 10^18 = 34.028*10^38 / 10^18 = max price 34.028*10^20 - // quote tokens per base token when quote token decimals are 18. - if (params.equilibriumPrice > type(uint128).max) { - revert Auction_InvalidParams(); - } - - // Capacity must be in base token - if (lot_.capacityInQuote) revert Auction_InvalidParams(); - - // Capacity must at least as large as the auction duration so that the emissions rate is not zero - // and no larger than u128 max to avoid various math errors - if ( - lot_.capacity < uint256(lot_.conclusion - lot_.start) - || lot_.capacity > type(uint128).max - ) { - revert Auction_InvalidParams(); + // Minimum price can be zero, but the equations default back to the basic GDA implementation + // If non-zero, its bounds are validated below. } - // Minimum price can be zero, but the equations default back to the basic GDA implementation - // Validate the decay parameters and calculate the decay constant // k = ln((q0 - qm) / (q1 - qm)) / dp // require q0 > q1 > qm // q1 = q0 * (1 - d1) if (params.decayTarget > MAX_DECAY_TARGET || params.decayTarget < MIN_DECAY_TARGET) { - revert Auction_InvalidParams(); + revert GDA_InvalidParams(2); } // Decay period must be between the set bounds // These bounds also ensure the decay constant is not zero if (params.decayPeriod < MIN_DECAY_PERIOD || params.decayPeriod > MAX_DECAY_PERIOD) { - revert Auction_InvalidParams(); + revert GDA_InvalidParams(3); } UD60x18 decayConstant; @@ -152,24 +189,38 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { console2.log("q1:", q1.unwrap()); console2.log("qm:", qm.unwrap()); - // Check that q0 > 0.99q0 >= q1 > 0.99q1 => qm + // If qm is not zero, then we require q0 > 0.99q0 >= q1 = q0(1-K) > q0(1-K-0.1) >= qm >= 0.5q0 // Don't need to check q0 > 0.99q0 >= q1 since: // decayTarget >= 1e16 => q0 * 0.99 >= q1 - // This ensures that the operand for the logarithm is positive - // We enforce a minimum difference for q1 and qm as well to avoid small dividends. - if (q1 - qm < ud(1e16)) { - revert Auction_InvalidParams(); - } - - // If qm is not zero, then we require that it be not less than half of q0 - // This is to ensure that we do not exceed the maximum input for the exponential function + // We require the ordering of the prices to ensure the decay constant can be + // calculated (i.e. the operand in the logarithm is positive and greater than 1). + // We require the minimum price to be at least half of the equilibrium price to + // avoid overflows in exponential functions during the auction. // It is also a sane default. - // Another piece of this check is done during a purchase to make sure that the amount - // provided is does not exceed the price of the capacity. - if (qm > ZERO && qm < q0.mul(HALF_UNIT)) { - revert Auction_InvalidParams(); + // Another piece of the second check is done during a purchase to make sure that the amount + // provided does not exceed the price of the remaining capacity. + + if ( + qm > ZERO + && (q0.mul(UNIT - ud(params.decayTarget + 10e16)) < qm || qm < q0.mul(HALF_UNIT)) + ) { + revert GDA_InvalidParams(4); } + // More simply, the above checks and the decay bounds create an "allowable" range for qm: + // K is the decay target. q0(1-K) = q1. + // If K is 40%, then the only valid qm is 0.5*q0 (or zero). + // + // | valid qm | + // |----------|---------|-------------| + // q0 q0(1-K) q0(0.9-K) 0.5*q0 + // + // While this may seem like strict bounds, it is possible to choose the decay target + // and period to affect a wider range of qm. + // For example, if you want to set a decay target of 20% per day, then your max qm would be 30% below q0. + // However, you can achieve rougly the same goal by setting a decay target of 5% every 6 hours, + // which would allow a qm up to 15% below q0. + // Calculate the decay constant decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(params.decayPeriod).div(ONE_DAY)); @@ -186,12 +237,12 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // in order for the exponential operations to not overflow. See the minimum and maximum // constant calculations for more information. if (qm == ZERO && duration > EXP_MAX_INPUT.div(decayConstant)) { - revert Auction_InvalidParams(); + revert GDA_InvalidParams(5); } // In the case of a non-zero min price, the duration must be less than the natural logarithm // of the max input divided by the decay constant to avoid overflows in the operand of the W function. - if (qm > ZERO && duration > LN_OF_EXP_MAX_INPUT.div(decayConstant)) { - revert Auction_InvalidParams(); + if (qm > ZERO && duration > LN_OF_PRODUCT_LN_MAX.div(decayConstant)) { + revert GDA_InvalidParams(6); } // Calculate emissions rate as number of tokens released per day @@ -203,11 +254,11 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // if qm is zero, then q0 * r > 0 // if qm is not zero, then qm * r > 0, which also implies the other. if (qm == ZERO && q0.mul(emissionsRate) == ZERO) { - revert Auction_InvalidParams(); + revert GDA_InvalidParams(7); } if (qm > ZERO && qm.mul(emissionsRate) == ZERO) { - revert Auction_InvalidParams(); + revert GDA_InvalidParams(8); } // Store auction data @@ -470,15 +521,12 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // 17 wei is the maximum error for values in the // range of possible values for the lambert-W approximation, // this makes sure the estimate is conservative. - // - // We prevent overflow in the operand of the W function via the duration < LN_OF_EXP_MAX_INPUT / decayConstant check. + // + // We prevent overflow in the operand of the W function via the duration < LN_OF_PRODUCT_LN_MAX / decayConstant check. // This means that e^(f + c) will always be < EXP_MAX_INPUT / c. - // We check for overflow before adding the error correction. - UD60x18 w = c.add(f).exp().mul(c).productLn(); - { - UD60x18 err = ud(17); - w = w > MAX_UD60x18.sub(err) ? MAX_UD60x18 : w.add(err); - } + // We do not need to check for overflow before adding the error correction term + // since the maximum value returned from the lambert-W function is ~131*10^18 + UD60x18 w = c.add(f).exp().mul(c).productLn() + ud(17); console2.log("third term:", w.unwrap()); // Without error correction, the intermediate term (f + c - w) cannot underflow because From fa4647064714df7140dfac35423d6172b6e979d8 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 28 May 2024 15:23:15 -0500 Subject: [PATCH 39/69] test: update GDA create auction tests --- test/modules/auctions/GDA/GDATest.sol | 2 +- test/modules/auctions/GDA/auction.t.sol | 185 +++++++++++++++++++----- 2 files changed, 148 insertions(+), 39 deletions(-) diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index e8e3744e7..52c11ea7d 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -31,7 +31,7 @@ abstract contract GdaTest is Test, Permit2User { uint256 internal constant _DECAY_TARGET = 10e16; // 10% uint256 internal constant _DECAY_PERIOD = 12 hours; UD60x18 internal constant _ONE_DAY = UD60x18.wrap(1 days * uUNIT); - UD60x18 internal constant LN_OF_EXP_MAX_INPUT = UD60x18.wrap(4_890982451446117211); + UD60x18 internal constant LN_OF_PRODUCT_LN_MAX = UD60x18.wrap(4_883_440_042_183_455_484); AtomicAuctionHouse internal _auctionHouse; GradualDutchAuction internal _module; diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index fa8918fa0..15a1ca4f3 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -23,7 +23,7 @@ contract GdaCreateAuctionTest is GdaTest { // [X] it reverts // [X] when the equilibrium price is greater than the max uint128 value // [X] it reverts - // [X] when the minimum price is greater than or equal to the decay target price + // [X] when the minimum price is greater than 10% of equilibrium price less than the decay target price // [X] it reverts // [X] when the decay target is less than the minimum // [X] it reverts @@ -87,7 +87,8 @@ contract GdaCreateAuctionTest is GdaTest { givenEquilibriumPrice(uint256(price_) % 1e9) { // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 0); vm.expectRevert(err); // Call the function @@ -101,19 +102,31 @@ contract GdaCreateAuctionTest is GdaTest { vm.assume(price_ > type(uint128).max); // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 0); vm.expectRevert(err); // Call the function _createAuctionLot(); } - function test_capacityLessThanDuration_reverts(uint256 capacity_) + function test_capacityInQuote_reverts() public givenCapacityInQuote { + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 1); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_capacityLessThanMin_reverts(uint256 capacity_) public - givenLotCapacity(capacity_ % _DURATION) + givenLotCapacity(capacity_ % 1e9) { // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 1); vm.expectRevert(err); // Call the function @@ -127,7 +140,8 @@ contract GdaCreateAuctionTest is GdaTest { vm.assume(capacity_ > type(uint128).max); // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 1); vm.expectRevert(err); // Call the function @@ -140,7 +154,8 @@ contract GdaCreateAuctionTest is GdaTest { givenDecayTarget(25e16) // 25% decay from 5e18 is 3.75e18 { // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 4); vm.expectRevert(err); // Call the function @@ -153,82 +168,92 @@ contract GdaCreateAuctionTest is GdaTest { givenDecayTarget(20e16) // 20% decay from 5e18 is 4e18 { // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 4); vm.expectRevert(err); // Call the function _createAuctionLot(); } - function test_decayTargetLessThanMinimum_reverts() + function test_minPriceGreaterThanMin_reverts() public - givenDecayTarget(1e16 - 1) // slightly less than 1% + givenDecayTarget(20e16) // 20% decay from 5e18 is 4e18 + givenMinPrice(35e17 + 1) // 30% decrease (10% more than decay) from 5e18 is 3.5e18, we go slightly higher { // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 4); vm.expectRevert(err); // Call the function _createAuctionLot(); } - function test_decayTargetGreaterThanMaximum_reverts() + function test_decayTargetLessThanMinimum_reverts() public - givenDecayTarget(49e16 + 1) // slightly more than 49% + givenDecayTarget(1e16 - 1) // slightly less than 1% { // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 2); vm.expectRevert(err); // Call the function _createAuctionLot(); } - function test_decayPeriodLessThanMinimum_reverts() + function test_decayTargetGreaterThanMaximum_reverts() public - givenDecayPeriod(uint48(1 hours) - 1) + givenDecayTarget(40e16 + 1) // slightly more than 40% { // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 2); vm.expectRevert(err); // Call the function _createAuctionLot(); } - function test_decayPeriodGreaterThanMaximum_reverts() + function test_decayPeriodLessThanMinimum_reverts() public - givenDecayPeriod(uint48(1 weeks) + 1) + givenDecayPeriod(uint48(6 hours) - 1) { // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 3); vm.expectRevert(err); // Call the function _createAuctionLot(); } - function test_capacityInQuote_reverts() public givenCapacityInQuote { + function test_decayPeriodGreaterThanMaximum_reverts() + public + givenDecayPeriod(uint48(1 weeks) + 1) + { // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 3); vm.expectRevert(err); // Call the function _createAuctionLot(); } - function testFuzz_durationGreaterThanMaxExpInputDividedByDecayConstant_reverts( + function testFuzz_minPriceNonZero_durationGreaterThanLimit_reverts( uint8 decayTarget_, uint8 decayHours_ ) public { // Normalize the inputs - uint256 decayTarget = uint256(decayTarget_ % 49 == 0 ? 49 : decayTarget_ % 49) * 1e16; - uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; + uint256 decayTarget = uint256(decayTarget_ % 40 == 0 ? 40 : decayTarget_ % 40) * 1e16; + uint256 decayPeriod = uint256(decayHours_ % 163) * 1 hours + 6 hours; console2.log("Decay target:", decayTarget); console2.log("Decay period:", decayPeriod); // Calculate the decay constant - // q1 > qm here because qm < q0 * 0.50, which is the max decay target + // q1 > qm here because qm = 0.5 * q0, and the max decay target is 0.4 uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); @@ -254,25 +279,26 @@ contract GdaCreateAuctionTest is GdaTest { _auctionParams.duration = uint48(maxDuration + 1); // Expect revert - bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidParams.selector); + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 6); vm.expectRevert(err); // Call the function _createAuctionLot(); } - function testFuzz_minPriceNonZero_durationEqualLnMaxExpInputDividedByDecayConstant_succeeds( + function testFuzz_minPriceNonZero_durationEqualToLimit_succeeds( uint8 decayTarget_, uint8 decayHours_ ) public { // Normalize the inputs - uint256 decayTarget = uint256(decayTarget_ % 49 == 0 ? 49 : decayTarget_ % 49) * 1e16; - uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; + uint256 decayTarget = uint256(decayTarget_ % 40 == 0 ? 40 : decayTarget_ % 40) * 1e16; + uint256 decayPeriod = uint256(decayHours_ % 163) * 1 hours + 6 hours; console2.log("Decay target:", decayTarget); console2.log("Decay period:", decayPeriod); // Calculate the decay constant - // q1 > qm here because qm < q0 * 0.50, which is the max decay target + // q1 > qm here because qm = 0.5 * q0, and the max decay target is 0.4 uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); @@ -287,7 +313,7 @@ contract GdaCreateAuctionTest is GdaTest { console2.log("Decay constant:", decayConstant.unwrap()); // Calculate the maximum duration in seconds - uint256 maxDuration = convert(LN_OF_EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); + uint256 maxDuration = convert(LN_OF_PRODUCT_LN_MAX.div(decayConstant).mul(_ONE_DAY)); console2.log("Max duration:", maxDuration); // Set the decay target and decay period to the fuzzed values @@ -301,14 +327,13 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function testFuzz_minPriceZero_durationEqualMaxExpInputDividedByDecayConstant_succeeds( + function testFuzz_minPriceZero_durationGreaterThanLimit_reverts( uint8 decayTarget_, uint8 decayHours_ - ) public givenMinPrice(0) - { + ) public givenMinPrice(0) { // Normalize the inputs - uint256 decayTarget = uint256(decayTarget_ % 49 == 0 ? 49 : decayTarget_ % 49) * 1e16; - uint256 decayPeriod = uint256(decayHours_ % 168 == 0 ? 168 : decayHours_ % 168) * 1 hours; + uint256 decayTarget = uint256(decayTarget_ % 40 == 0 ? 40 : decayTarget_ % 40) * 1e16; + uint256 decayPeriod = uint256(decayHours_ % 163) * 1 hours + 6 hours; console2.log("Decay target:", decayTarget); console2.log("Decay period:", decayPeriod); @@ -331,6 +356,50 @@ contract GdaCreateAuctionTest is GdaTest { uint256 maxDuration = convert(EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); console2.log("Max duration:", maxDuration); + // Set the decay target and decay period to the fuzzed values + // Set duration to the max duration plus 1 + _gdaParams.decayTarget = decayTarget; + _gdaParams.decayPeriod = decayPeriod; + _auctionParams.implParams = abi.encode(_gdaParams); + _auctionParams.duration = uint48(maxDuration + 1); + + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 5); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function testFuzz_minPriceZero_durationEqualToLimit_succeeds( + uint8 decayTarget_, + uint8 decayHours_ + ) public givenMinPrice(0) { + // Normalize the inputs + uint256 decayTarget = uint256(decayTarget_ % 40 == 0 ? 40 : decayTarget_ % 40) * 1e16; + uint256 decayPeriod = uint256(decayHours_ % 163) * 1 hours + 6 hours; + console2.log("Decay target:", decayTarget); + console2.log("Decay period:", decayPeriod); + + // Calculate the decay constant + uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; + UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); + UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); + UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + + console2.log("q0:", q0.unwrap()); + console2.log("q1:", q1.unwrap()); + console2.log("qm:", qm.unwrap()); + + // Calculate the decay constant + UD60x18 decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(decayPeriod).div(_ONE_DAY)); + console2.log("Decay constant:", decayConstant.unwrap()); + + // Calculate the maximum duration in seconds + uint256 maxDuration = convert(EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); + console2.log("Max duration:", maxDuration); + // Set the decay target and decay period to the fuzzed values // Set duration to the max duration _gdaParams.decayTarget = decayTarget; @@ -342,6 +411,46 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } + function test_minPriceZero_EqPriceTimesEmissionsZero_reverts() + public + givenMinPrice(0) + givenLotCapacity(1e9) // Smallest value for capacity is 10^(baseDecimals / 2). We divide the by the duration to get the emissions rate during creation. + givenEquilibriumPrice(1e9) // Smallest value for equilibrium price is 10^(quoteDecimals / 2) + { + // Should revert with the standard duration of 2 days, since: + // 1e9 * (1e9 * 1e18 / 2e18) / 1e18 + // = 1e9 * 5e8 / 1e18 + // = 5e17 / 1e18 = 0.5 (which gets truncated to zero) + + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 7); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_minPriceNonZero_MinPriceTimesEmissionsZero_reverts() + public + givenMinPrice(1e9) // Smallest value for min price is 10^(quoteDecimals / 2) + givenEquilibriumPrice(2e9) // Must be no more than 2x the min price + givenLotCapacity(1e9) // Smallest value for capacity is 10^(baseDecimals / 2). We divide the by the duration to get the emissions rate during creation. + { + // Should revert with the standard duration of 2 days, since: + // 1e9 * (1e9 * 1e18 / 2e18) / 1e18 + // = 1e9 * 5e8 / 1e18 + // = 5e17 / 1e18 = 0.5 (which gets truncated to zero) + + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 8); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + function _assertAuctionData() internal { // Calculate the decay constant from the input parameters uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; From 2e3e4635c4acedeefeda39305d93e1ab84faeda4 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 28 May 2024 16:07:10 -0500 Subject: [PATCH 40/69] test: max amount out GDA test updates --- .../auctions/GDA/maxAmountAccepted.t.sol | 129 +++++++++++++++++- 1 file changed, 122 insertions(+), 7 deletions(-) diff --git a/test/modules/auctions/GDA/maxAmountAccepted.t.sol b/test/modules/auctions/GDA/maxAmountAccepted.t.sol index 2d1e5e294..0760a006e 100644 --- a/test/modules/auctions/GDA/maxAmountAccepted.t.sol +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -6,6 +6,7 @@ import {uUNIT} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; +import {console2} from "lib/forge-std/src/console2.sol"; contract GdaMaxAmountAcceptedTest is GdaTest { using {PRBMath.mulDiv} for uint256; @@ -20,13 +21,22 @@ contract GdaMaxAmountAcceptedTest is GdaTest { _module.maxAmountAccepted(lotId_); } - function testFuzz_maxAmountAccepted_success( + function testFuzz_maxAmountAccepted_minPriceNonZero_success( uint128 capacity_, uint128 price_ - ) public givenLotCapacity(uint256(capacity_)) givenEquilibriumPrice(uint256(price_)) { - vm.assume(capacity_ >= _DURATION); - uint256 decayedPrice = uint256(price_).mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); - vm.assume(decayedPrice > _gdaParams.minimumPrice); // must have room for decay + ) + public + givenDuration(1 days) + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice((uint256(price_) / 2) + (price_ % 2 == 0 ? 0 : 1)) + { + vm.assume( + capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) + ); + vm.assume( + price_ >= 2 * 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) + ); _createAuctionLot(); uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); @@ -39,12 +49,117 @@ contract GdaMaxAmountAcceptedTest is GdaTest { uint128 price_ ) public + givenDuration(1 days) + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(0) + { + console2.log( + "price floor:", + 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) + ); + vm.assume( + capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) + ); + vm.assume( + price_ >= 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) + ); + _createAuctionLot(); + + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + + function testFuzz_maxAmountAccepted_minPriceNonZero_quoteDecimalsSmaller_success( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenQuoteTokenDecimals(6) + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice((uint256(price_) / 2) + (price_ % 2 == 0 ? 0 : 1)) + { + vm.assume( + capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) + ); + vm.assume( + price_ >= 2 * 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) + ); + _createAuctionLot(); + + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + + function testFuzz_maxAmountAccepted_minPriceZero_quoteDecimalsSmaller_success( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenQuoteTokenDecimals(6) + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(0) + { + vm.assume( + capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) + ); + vm.assume( + price_ >= 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) + ); + _createAuctionLot(); + + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + + function testFuzz_maxAmountAccepted_minPriceNonZero_quoteDecimalsLarger_success( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenBaseTokenDecimals(6) + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice((uint256(price_) / 2) + (price_ % 2 == 0 ? 0 : 1)) + { + vm.assume( + capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) + ); + vm.assume( + price_ >= 2 * 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) + ); + _createAuctionLot(); + + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + + function testFuzz_maxAmountAccepted_minPriceZero_quoteDecimalsLarger_success( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenBaseTokenDecimals(6) givenLotCapacity(uint256(capacity_)) givenEquilibriumPrice(uint256(price_)) givenMinPrice(0) { - vm.assume(capacity_ >= _DURATION); - vm.assume(price_ >= 1e3); + vm.assume( + capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) + ); + vm.assume( + price_ >= 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) + ); _createAuctionLot(); uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); From af65d5c44438af211011eed6576b3a087fba1437 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 28 May 2024 16:07:30 -0500 Subject: [PATCH 41/69] wip: GDA priceFor test updates --- src/modules/auctions/GDA.sol | 13 ++++++++++++- test/modules/auctions/GDA/priceFor.t.sol | 19 ++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 444475f4c..1a7f66a8f 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -359,7 +359,13 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { console2.log("kekt:", kekt.unwrap()); // Calculate the first term in the formula - result = priceDiff.mul(ekpr).div(kekt); + // We convert this to a uint256 and manually perform the mulDiv + // to avoid precision loss from the division by 10^18 in the first + // multiplication operation. + // Since we are multiplying and then dividing, the extra 10^18s + // are cancelled out. + // result = priceDiff.mul(ekpr).div(kekt); + result = ud(priceDiff.intoUint256().mulDiv(ekpr.intoUint256(), kekt.intoUint256())); console2.log("result:", result.unwrap()); } else { // T is negative: flip the e^(k * T) term to the numerator @@ -373,6 +379,8 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { console2.log("ekt:", ekt.unwrap()); // Calculate the first term in the formula + // TODO should we manually calculate this to avoid precision loss? + // A little bit trickier to make sure we avoid overflow here. result = priceDiff.mul(ekpr).mul(ekt).div(auction.decayConstant); console2.log("result:", result.unwrap()); } @@ -387,6 +395,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Scale price back to quote token decimals uint256 amount = result.intoUint256().mulDiv(quoteTokenScale, uUNIT); + // TODO do we need to add an error correction term here? + // Various results are slightly lower than expected. + return amount; } diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index e1669f23a..f3f4da975 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -138,7 +138,7 @@ contract GdaPriceForTest is GdaTest { uint128 capacity_, uint128 payout_ ) public givenLotCapacity(uint256(capacity_)) givenMinPrice(0) { - vm.assume(capacity_ >= _DURATION); + vm.assume(capacity_ >= 1e9); vm.assume(payout_ <= capacity_); _createAuctionLot(); @@ -151,7 +151,7 @@ contract GdaPriceForTest is GdaTest { uint128 capacity_, uint128 payout_ ) public givenLotCapacity(uint256(capacity_)) { - vm.assume(capacity_ >= _DURATION); + vm.assume(capacity_ >= 1e9); vm.assume(payout_ <= capacity_); _createAuctionLot(); @@ -165,11 +165,12 @@ contract GdaPriceForTest is GdaTest { uint128 price_ ) public + givenDuration(1 days) givenLotCapacity(uint256(capacity_)) givenEquilibriumPrice(uint256(price_)) givenMinPrice(0) { - vm.assume(capacity_ >= _DURATION * 1000); + vm.assume(capacity_ >= 1e9); vm.assume(price_ >= 1e9); _createAuctionLot(); @@ -193,10 +194,14 @@ contract GdaPriceForTest is GdaTest { function testFuzz_minPriceNonZero_varyingSetup( uint128 capacity_, uint128 price_ - ) public givenLotCapacity(uint256(capacity_)) givenEquilibriumPrice(uint256(price_)) { - vm.assume(capacity_ >= _DURATION); - uint256 decayedPrice = uint256(price_).mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); - vm.assume(decayedPrice > _gdaParams.minimumPrice); // must have room for decay + ) + public + givenLotCapacity(uint256(capacity_)) + givenEquilibriumPrice(uint256(price_)) + givenMinPrice(price_ / 2) + { + vm.assume(capacity_ >= 1e9); + vm.assume(price_ >= 1e9); _createAuctionLot(); vm.warp(_start); From 1371ddd3d2d215b846bcc7a5e6ee3fe03a0a9177 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 28 May 2024 16:08:21 -0500 Subject: [PATCH 42/69] chore: remove default verbosity from foundry config --- foundry.toml | 1 - package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/foundry.toml b/foundry.toml index 1ab0eb8ef..51850aa7d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,7 +6,6 @@ fs_permissions = [{access = "read-write", path = "./bytecode/"}, {access = "read ffi = true solc_version = "0.8.19" evm_version = "paris" -verbosity = 3 no-match-test = "optimal" [fuzz] diff --git a/package.json b/package.json index eb3cfd86e..70573301f 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "solhint:all": "solhint --fix --config ./.solhint.json 'src/**/*.sol' 'test/**/*.sol' 'script/**/*.sol'", "solhint:check": "solhint --config ./.solhint.json 'src/**/*.sol'", "solhint": "solhint --fix --config ./.solhint.json 'src/**/*.sol'", - "test": "forge test" + "test": "forge test -vvv" }, "keywords": [], "author": "", From 1b0fba96c95f9587ea7e676cfddfcb2ddf62517b Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 28 May 2024 16:08:39 -0500 Subject: [PATCH 43/69] chore: lint --- test/modules/auctions/GDA/maxPayout.t.sol | 11 +++++------ test/modules/auctions/GDA/payoutFor.t.sol | 21 ++++++++++++++++----- test/modules/auctions/GDA/purchase.t.sol | 19 ++++++++++++++----- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol index af24096bf..45c5dbd04 100644 --- a/test/modules/auctions/GDA/maxPayout.t.sol +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -20,9 +20,10 @@ contract GdaMaxPayoutTest is GdaTest { _module.maxPayout(lotId_); } - function testFuzz_maxPayout_success( - uint128 capacity_ - ) public givenLotCapacity(uint256(capacity_)) { + function testFuzz_maxPayout_success(uint128 capacity_) + public + givenLotCapacity(uint256(capacity_)) + { vm.assume(capacity_ >= 1e9); _createAuctionLot(); @@ -31,9 +32,7 @@ contract GdaMaxPayoutTest is GdaTest { assertEq(expectedMaxPayout, maxPayout); } - function testFuzz_maxPayout_minPriceZero_success( - uint128 capacity_ - ) + function testFuzz_maxPayout_minPriceZero_success(uint128 capacity_) public givenLotCapacity(uint256(capacity_)) givenMinPrice(0) diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index 7f3f6dc1e..b69622849 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -5,7 +5,9 @@ import {Module} from "src/modules/Modules.sol"; import {IAuction} from "src/interfaces/modules/IAuction.sol"; import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import {UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; +import { + UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT +} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; @@ -247,7 +249,8 @@ contract GdaPayoutForTest is GdaTest { vm.assume(price_ >= 1e9); vm.assume(capacity_ >= 1e9); UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); + UD60x18 r = + ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); vm.assume(q0.mul(r) > ZERO); _createAuctionLot(); @@ -264,7 +267,9 @@ contract GdaPayoutForTest is GdaTest { // assertApproxEqRel(payout, expectedPayout, 1e16); //TODO how to think about these bounds? some extremes have large errors vm.warp(_start + _DECAY_PERIOD); - amount = _gdaParams.equilibriumPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT).mulDiv(expectedPayout, _BASE_SCALE); + amount = _gdaParams.equilibriumPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT).mulDiv( + expectedPayout, _BASE_SCALE + ); payout = _module.payoutFor(_lotId, amount); assertLe(payout, expectedPayout); // assertApproxEqRel(payout, expectedPayout, 1e16); @@ -286,7 +291,11 @@ contract GdaPayoutForTest is GdaTest { vm.assume(uint256(price_) * 9 / 10 > _gdaParams.minimumPrice); // must have clearance for the decay target // vm.assume(minPrice_ >= price_ / 2); // requirement when min price is not zero UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _auctionParams.duration)); + UD60x18 r = ud( + uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv( + 1 days, _auctionParams.duration + ) + ); vm.assume(q0.mul(r) > ZERO); _createAuctionLot(); @@ -303,7 +312,9 @@ contract GdaPayoutForTest is GdaTest { // assertApproxEqRel(payout, expectedPayout, 1e16); //TODO how to think about these bounds? some extremes have large errors vm.warp(_start + _auctionParams.duration); - amount = _gdaParams.equilibriumPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT).mulDiv(expectedPayout, _BASE_SCALE); + amount = _gdaParams.equilibriumPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT).mulDiv( + expectedPayout, _BASE_SCALE + ); payout = _module.payoutFor(_lotId, amount); assertLe(payout, expectedPayout); // assertApproxEqRel(payout, expectedPayout, 1e16); diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index cf3a0616c..ff5c9d578 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -5,7 +5,9 @@ import {Module} from "src/modules/Modules.sol"; import {IAuction} from "src/interfaces/modules/IAuction.sol"; import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import {UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; +import { + UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT +} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; @@ -240,7 +242,11 @@ contract GdaPurchaseTest is GdaTest { vm.assume(uint256(price_) * 9 / 10 > _gdaParams.minimumPrice); // must have clearance for the decay target // vm.assume(minPrice_ >= price_ / 2); // requirement when min price is not zero UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _auctionParams.duration)); + UD60x18 r = ud( + uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv( + 1 days, _auctionParams.duration + ) + ); vm.assume(q0.mul(r) > ZERO); _createAuctionLot(); @@ -375,7 +381,8 @@ contract GdaPurchaseTest is GdaTest { vm.assume(price_ >= 1e9); vm.assume(capacity_ >= 1e9); UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); + UD60x18 r = + ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); vm.assume(q0.mul(r) > ZERO); _createAuctionLot(); @@ -418,7 +425,8 @@ contract GdaPurchaseTest is GdaTest { vm.assume(price_ >= 1e3); vm.assume(capacity_ >= 1e9); UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); + UD60x18 r = + ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); vm.assume(q0.mul(r) > ZERO); _createAuctionLot(); @@ -461,7 +469,8 @@ contract GdaPurchaseTest is GdaTest { vm.assume(price_ >= 1e9); vm.assume(capacity_ >= 1e3); UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); + UD60x18 r = + ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); vm.assume(q0.mul(r) > ZERO); _createAuctionLot(); From 70a965539c19af9148214bc2beadb026000e5811 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 28 May 2024 22:08:15 -0500 Subject: [PATCH 44/69] test: GDA test improvements, most passing --- src/modules/auctions/GDA.sol | 11 +- test/modules/auctions/GDA/GDATest.sol | 58 +++++- test/modules/auctions/GDA/auction.t.sol | 17 +- .../auctions/GDA/maxAmountAccepted.t.sol | 106 ++++------- test/modules/auctions/GDA/maxPayout.t.sol | 14 +- test/modules/auctions/GDA/payoutFor.t.sol | 66 +++---- test/modules/auctions/GDA/priceFor.t.sol | 93 +++++----- test/modules/auctions/GDA/purchase.t.sol | 170 +++++++++++------- 8 files changed, 290 insertions(+), 245 deletions(-) diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 1a7f66a8f..51afd9081 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -395,9 +395,14 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Scale price back to quote token decimals uint256 amount = result.intoUint256().mulDiv(quoteTokenScale, uUNIT); - // TODO do we need to add an error correction term here? - // Various results are slightly lower than expected. - + // TODO? Add 0.01% to correct for errors and have a conservative estimate + // Problem: this makes maxAmountAccepted return values that are too high and revert purchases + // We do not use this for calculation input/output amounts during purchase + // Therefore, it doesn't need to be exact. + // Since it can/will be used off-chain to estimate the amount of quote tokens + // required to purchase a certain number of tokens, it's better to be + // conservative so that slippage amounts can be set appropriately. + // return (amount * (1e4 + 1)) / 1e4; return amount; } diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index 52c11ea7d..a814a378f 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.19; // Libraries import {Test} from "forge-std/Test.sol"; -import {UD60x18, uUNIT} from "lib/prb-math/src/UD60x18.sol"; +import {UD60x18, ud, uUNIT, ZERO} from "lib/prb-math/src/UD60x18.sol"; import "lib/prb-math/src/Common.sol" as PRBMath; // Mocks @@ -126,18 +126,66 @@ abstract contract GdaTest is Test, Permit2User { _; } - modifier givenEquilibriumPrice(uint256 price_) { - _gdaParams.equilibriumPrice = price_; + modifier givenEquilibriumPrice(uint128 price_) { + _gdaParams.equilibriumPrice = uint256(price_); _auctionParams.implParams = abi.encode(_gdaParams); _; } - modifier givenMinPrice(uint256 minPrice_) { - _gdaParams.minimumPrice = minPrice_; + modifier givenMinPrice(uint128 minPrice_) { + _gdaParams.minimumPrice = uint256(minPrice_); _auctionParams.implParams = abi.encode(_gdaParams); _; } + modifier givenMinIsHalfPrice(uint128 price_) { + _gdaParams.minimumPrice = (uint256(price_) / 2) + (price_ % 2 == 0 ? 0 : 1); + _auctionParams.implParams = abi.encode(_gdaParams); + _; + } + + modifier validateCapacity() { + vm.assume( + _auctionParams.capacity + >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) + ); + _; + } + + modifier validatePrice() { + vm.assume( + _gdaParams.equilibriumPrice + >= 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) + ); + _; + } + + modifier validateMinPrice() { + vm.assume( + _gdaParams.minimumPrice >= _gdaParams.equilibriumPrice / 2 + && _gdaParams.minimumPrice + <= _gdaParams.equilibriumPrice.mulDiv(uUNIT - (_gdaParams.decayTarget + 10e16), uUNIT) + ); + _; + } + + modifier validatePriceTimesEmissionsRate() { + UD60x18 r = ud( + _auctionParams.capacity.mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv( + 1 days, _auctionParams.duration + ) + ); + + if (_gdaParams.minimumPrice == 0) { + UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + vm.assume(q0.mul(r) > ZERO); + } else { + UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); + vm.assume(qm.mul(r) > ZERO); + } + _; + } + modifier givenDecayTarget(uint256 decayTarget_) { _gdaParams.decayTarget = decayTarget_; _auctionParams.implParams = abi.encode(_gdaParams); diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 15a1ca4f3..09d1613a5 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -84,7 +84,7 @@ contract GdaCreateAuctionTest is GdaTest { function test_equilibriumPriceIsLessThanMin_reverts(uint128 price_) public - givenEquilibriumPrice(uint256(price_) % 1e9) + givenEquilibriumPrice(price_ % 1e9) { // Expect revert bytes memory err = @@ -95,11 +95,10 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_equilibriumPriceGreaterThanMax_reverts(uint256 price_) - public - givenEquilibriumPrice(price_) - { + function test_equilibriumPriceGreaterThanMax_reverts(uint256 price_) public { vm.assume(price_ > type(uint128).max); + _gdaParams.equilibriumPrice = price_; + _auctionParams.implParams = abi.encode(_gdaParams); // Expect revert bytes memory err = @@ -120,7 +119,7 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_capacityLessThanMin_reverts(uint256 capacity_) + function test_capacityLessThanMin_reverts(uint128 capacity_) public givenLotCapacity(capacity_ % 1e9) { @@ -133,11 +132,9 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_capacityGreaterThanMax_reverts(uint256 capacity_) - public - givenLotCapacity(capacity_) - { + function test_capacityGreaterThanMax_reverts(uint256 capacity_) public { vm.assume(capacity_ > type(uint128).max); + _auctionParams.capacity = capacity_; // Expect revert bytes memory err = diff --git a/test/modules/auctions/GDA/maxAmountAccepted.t.sol b/test/modules/auctions/GDA/maxAmountAccepted.t.sol index 0760a006e..3aed026e9 100644 --- a/test/modules/auctions/GDA/maxAmountAccepted.t.sol +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -27,18 +27,14 @@ contract GdaMaxAmountAcceptedTest is GdaTest { ) public givenDuration(1 days) - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) - givenMinPrice((uint256(price_) / 2) + (price_ % 2 == 0 ? 0 : 1)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated { - vm.assume( - capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) - ); - vm.assume( - price_ >= 2 * 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) - ); - _createAuctionLot(); - uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 expectedAmount = _module.priceFor(_lotId, capacity_); assertEq(expectedAmount, maxAmountAccepted); @@ -50,22 +46,14 @@ contract GdaMaxAmountAcceptedTest is GdaTest { ) public givenDuration(1 days) - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated { - console2.log( - "price floor:", - 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) - ); - vm.assume( - capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) - ); - vm.assume( - price_ >= 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) - ); - _createAuctionLot(); - uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 expectedAmount = _module.priceFor(_lotId, capacity_); assertEq(expectedAmount, maxAmountAccepted); @@ -78,18 +66,14 @@ contract GdaMaxAmountAcceptedTest is GdaTest { public givenDuration(1 days) givenQuoteTokenDecimals(6) - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) - givenMinPrice((uint256(price_) / 2) + (price_ % 2 == 0 ? 0 : 1)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated { - vm.assume( - capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) - ); - vm.assume( - price_ >= 2 * 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) - ); - _createAuctionLot(); - uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 expectedAmount = _module.priceFor(_lotId, capacity_); assertEq(expectedAmount, maxAmountAccepted); @@ -102,18 +86,14 @@ contract GdaMaxAmountAcceptedTest is GdaTest { public givenDuration(1 days) givenQuoteTokenDecimals(6) - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated { - vm.assume( - capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) - ); - vm.assume( - price_ >= 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) - ); - _createAuctionLot(); - uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 expectedAmount = _module.priceFor(_lotId, capacity_); assertEq(expectedAmount, maxAmountAccepted); @@ -126,18 +106,14 @@ contract GdaMaxAmountAcceptedTest is GdaTest { public givenDuration(1 days) givenBaseTokenDecimals(6) - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) - givenMinPrice((uint256(price_) / 2) + (price_ % 2 == 0 ? 0 : 1)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated { - vm.assume( - capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) - ); - vm.assume( - price_ >= 2 * 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) - ); - _createAuctionLot(); - uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 expectedAmount = _module.priceFor(_lotId, capacity_); assertEq(expectedAmount, maxAmountAccepted); @@ -150,18 +126,14 @@ contract GdaMaxAmountAcceptedTest is GdaTest { public givenDuration(1 days) givenBaseTokenDecimals(6) - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated { - vm.assume( - capacity_ >= 10 ** ((_baseTokenDecimals / 2) + (_baseTokenDecimals % 2 == 0 ? 0 : 1)) - ); - vm.assume( - price_ >= 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1)) - ); - _createAuctionLot(); - uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 expectedAmount = _module.priceFor(_lotId, capacity_); assertEq(expectedAmount, maxAmountAccepted); diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol index 45c5dbd04..2535e84d6 100644 --- a/test/modules/auctions/GDA/maxPayout.t.sol +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -22,11 +22,10 @@ contract GdaMaxPayoutTest is GdaTest { function testFuzz_maxPayout_success(uint128 capacity_) public - givenLotCapacity(uint256(capacity_)) + givenLotCapacity(capacity_) + validateCapacity + givenLotIsCreated { - vm.assume(capacity_ >= 1e9); - _createAuctionLot(); - uint256 maxPayout = _module.maxPayout(_lotId); uint256 expectedMaxPayout = capacity_; assertEq(expectedMaxPayout, maxPayout); @@ -34,12 +33,11 @@ contract GdaMaxPayoutTest is GdaTest { function testFuzz_maxPayout_minPriceZero_success(uint128 capacity_) public - givenLotCapacity(uint256(capacity_)) + givenLotCapacity(capacity_) + validateCapacity givenMinPrice(0) + givenLotIsCreated { - vm.assume(capacity_ >= 1e9); - _createAuctionLot(); - uint256 maxPayout = _module.maxPayout(_lotId); uint256 expectedMaxPayout = capacity_; assertEq(expectedMaxPayout, maxPayout); diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index b69622849..739691a56 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -214,26 +214,25 @@ contract GdaPayoutForTest is GdaTest { function testFuzz_minPriceZero_noOverflows( uint128 capacity_, uint128 amount_ - ) public givenLotCapacity(uint256(capacity_)) givenMinPrice(0) { - vm.assume(capacity_ >= _DURATION); - _createAuctionLot(); + ) + public + givenLotCapacity(capacity_) + givenMinPrice(0) + validateCapacity + givenLotIsCreated + givenLotHasStarted + { vm.assume(amount_ <= _module.maxAmountAccepted(_lotId)); - vm.warp(_start); - _module.payoutFor(_lotId, amount_); } function testFuzz_minPriceNonZero_noOverflows( uint128 capacity_, uint128 amount_ - ) public givenLotCapacity(uint256(capacity_)) { - vm.assume(capacity_ >= _DURATION); - _createAuctionLot(); + ) public givenLotCapacity(capacity_) validateCapacity givenLotIsCreated givenLotHasStarted { vm.assume(amount_ <= _module.maxAmountAccepted(_lotId)); - vm.warp(_start); - _module.payoutFor(_lotId, amount_); } @@ -242,20 +241,15 @@ contract GdaPayoutForTest is GdaTest { uint128 price_ ) public - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted { - vm.assume(price_ >= 1e9); - vm.assume(capacity_ >= 1e9); - UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = - ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); - vm.assume(q0.mul(r) > ZERO); - _createAuctionLot(); - - vm.warp(_start); - console2.log("Capacity:", capacity_); console2.log("Price:", price_); @@ -281,26 +275,16 @@ contract GdaPayoutForTest is GdaTest { uint128 minPrice_ ) public - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) - givenMinPrice(minPrice_ < price_ / 2 ? uint256(price_ / 2) : uint256(minPrice_)) - givenDuration(1 days) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(minPrice_) + validateCapacity + validatePrice + validateMinPrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted { - vm.assume(capacity_ >= 1e9); - vm.assume(minPrice_ >= 1e9); - vm.assume(uint256(price_) * 9 / 10 > _gdaParams.minimumPrice); // must have clearance for the decay target - // vm.assume(minPrice_ >= price_ / 2); // requirement when min price is not zero - UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = ud( - uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv( - 1 days, _auctionParams.duration - ) - ); - vm.assume(q0.mul(r) > ZERO); - _createAuctionLot(); - - vm.warp(_start); - console2.log("Capacity:", capacity_); console2.log("Price:", price_); diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index f3f4da975..75a4a578a 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -52,7 +52,7 @@ contract GdaPriceForTest is GdaTest { function test_minPriceZero() public givenMinPrice(0) givenLotIsCreated givenLotHasStarted { // The timestamp is the start time so current time == last auction start. // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. - uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + uint256 payout = _LOT_CAPACITY / _auctionParams.duration; // 1 seconds worth of tokens console2.log("1 second of token emissions:", payout); uint256 price = _module.priceFor(_lotId, payout); @@ -61,7 +61,7 @@ contract GdaPriceForTest is GdaTest { uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); console2.log("Expected price:", expectedPrice); - assertApproxEqRel(price, expectedPrice, 1e14); // 0.01% + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% // Warp to the end of the decay period vm.warp(_start + _DECAY_PERIOD); @@ -73,13 +73,13 @@ contract GdaPriceForTest is GdaTest { _INITIAL_PRICE.mulDiv(1e18 - _DECAY_TARGET, 1e18).mulDiv(payout, _BASE_SCALE); console2.log("Expected price:", expectedPrice); - assertApproxEqRel(price, expectedPrice, 1e14); // 0.01%, TODO is this good enough? Seems like it slightly underestimates + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1%, TODO is this good enough? Seems like it slightly underestimates } function test_minPriceNonZero() public givenLotIsCreated givenLotHasStarted { // The timestamp is the start time so current time == last auction start. // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. - uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + uint256 payout = _LOT_CAPACITY / _auctionParams.duration; // 1 seconds worth of tokens console2.log("1 second of token emissions:", payout); uint256 price = _module.priceFor(_lotId, payout); @@ -88,7 +88,7 @@ contract GdaPriceForTest is GdaTest { uint256 expectedPrice = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); console2.log("Expected price:", expectedPrice); - assertApproxEqRel(price, expectedPrice, 1e14); // 0.01% + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% // Warp to the end of the decay period vm.warp(_start + _DECAY_PERIOD + 1); @@ -100,13 +100,13 @@ contract GdaPriceForTest is GdaTest { _INITIAL_PRICE.mulDiv(1e18 - _DECAY_TARGET, 1e18).mulDiv(payout, _BASE_SCALE); console2.log("Expected price:", expectedPrice); - assertApproxEqRel(price, expectedPrice, 1e14); // 0.01%, TODO is this good enough? Seems like it slightly underestimates + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1%, TODO is this good enough? Seems like it slightly underestimates } function test_minPriceNonZero_lastAuctionStartInFuture() public givenLotIsCreated { // We don't start the auction so the lastAuctionStart is 1 second ahead of the current time. // 1 seconds worth of tokens should be slightly more than the initial price. - uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + uint256 payout = _LOT_CAPACITY / _auctionParams.duration; // 1 seconds worth of tokens console2.log("1 second of token emissions:", payout); uint256 price = _module.priceFor(_lotId, payout); @@ -119,10 +119,12 @@ contract GdaPriceForTest is GdaTest { } function test_minPriceNonZero_lastAuctionStartInPast() public givenLotIsCreated { - vm.warp(_start + 1); - //lastAuctionStart is 1 second behind the current time. + vm.warp(_start + 1000); + // lastAuctionStart is 1000 seconds behind the current time. + // We have to go further than 1 second due to the error correction in priceFor, + // which increases the estimate slightly. // 1 seconds worth of tokens should be slightly less than the initial price. - uint256 payout = _LOT_CAPACITY / _DURATION; // 1 seconds worth of tokens + uint256 payout = _LOT_CAPACITY / _auctionParams.duration; // 1 seconds worth of tokens console2.log("1 second of token emissions:", payout); uint256 price = _module.priceFor(_lotId, payout); @@ -137,12 +139,15 @@ contract GdaPriceForTest is GdaTest { function testFuzz_minPriceZero_noOverflows( uint128 capacity_, uint128 payout_ - ) public givenLotCapacity(uint256(capacity_)) givenMinPrice(0) { - vm.assume(capacity_ >= 1e9); + ) + public + givenLotCapacity(capacity_) + givenMinPrice(0) + validateCapacity + givenLotIsCreated + givenLotHasStarted + { vm.assume(payout_ <= capacity_); - _createAuctionLot(); - - vm.warp(_start); _module.priceFor(_lotId, payout_); } @@ -150,12 +155,8 @@ contract GdaPriceForTest is GdaTest { function testFuzz_minPriceNonZero_noOverflows( uint128 capacity_, uint128 payout_ - ) public givenLotCapacity(uint256(capacity_)) { - vm.assume(capacity_ >= 1e9); + ) public givenLotCapacity(capacity_) validateCapacity givenLotIsCreated givenLotHasStarted { vm.assume(payout_ <= capacity_); - _createAuctionLot(); - - vm.warp(_start); _module.priceFor(_lotId, payout_); } @@ -166,20 +167,19 @@ contract GdaPriceForTest is GdaTest { ) public givenDuration(1 days) - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted { - vm.assume(capacity_ >= 1e9); - vm.assume(price_ >= 1e9); - _createAuctionLot(); - - vm.warp(_start); - console2.log("Capacity:", capacity_); console2.log("Price:", price_); - uint256 payout = _auctionParams.capacity / _DURATION; // 1 seconds worth of tokens + uint256 payout = _auctionParams.capacity / _auctionParams.duration; // 1 seconds worth of tokens console2.log("Payout:", payout); uint256 price = _module.priceFor(_lotId, payout); uint256 expectedPrice = _gdaParams.equilibriumPrice.mulDiv(payout, _BASE_SCALE); @@ -196,20 +196,25 @@ contract GdaPriceForTest is GdaTest { uint128 price_ ) public - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) - givenMinPrice(price_ / 2) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted { - vm.assume(capacity_ >= 1e9); - vm.assume(price_ >= 1e9); - _createAuctionLot(); - - vm.warp(_start); + // // Validate price is slightly higher than needed to avoid uncorrected errors in tiny + // // amounts from causing the test to fail. + // vm.assume(price_ >= 1e4 * 10 ** ((_quoteTokenDecimals / 2) + (_quoteTokenDecimals % 2 == 0 ? 0 : 1))); + // _createAuctionLot(); + // vm.warp(_start); console2.log("Capacity:", capacity_); console2.log("Price:", price_); - uint256 payout = _auctionParams.capacity / _DURATION; // 1 seconds worth of tokens + uint256 payout = _auctionParams.capacity / _auctionParams.duration; // 1 seconds worth of tokens console2.log("Payout:", payout); uint256 price = _module.priceFor(_lotId, payout); uint256 expectedPrice = _gdaParams.equilibriumPrice.mulDiv(payout, _BASE_SCALE); @@ -228,7 +233,7 @@ contract GdaPriceForTest is GdaTest { givenLotHasStarted { // Warp to the timestep - uint48 timestep = timestep_ % _DURATION; + uint48 timestep = timestep_ % _auctionParams.duration; console2.log("Warping to timestep:", timestep); vm.warp(_start + timestep); @@ -242,7 +247,7 @@ contract GdaPriceForTest is GdaTest { console2.log("Expected price at timestep:", qt.unwrap()); // Set payout to 1 seconds worth of tokens - uint256 payout = _LOT_CAPACITY / _DURATION; + uint256 payout = _LOT_CAPACITY / _auctionParams.duration; // Calculate the price uint256 price = _module.priceFor(_lotId, payout); @@ -252,7 +257,7 @@ contract GdaPriceForTest is GdaTest { // The price should be conservative (greater than or equal to the expected price) assertGe(price, expectedPrice); - assertApproxEqRel(price, expectedPrice, 1e14); // 0.01% + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% } function testFuzz_minPriceNonZero_varyingTimesteps(uint48 timestep_) @@ -261,7 +266,7 @@ contract GdaPriceForTest is GdaTest { givenLotHasStarted { // Warp to the timestep - uint48 timestep = timestep_ % _DURATION; + uint48 timestep = timestep_ % _auctionParams.duration; console2.log("Warping to timestep:", timestep); vm.warp(_start + timestep); @@ -276,7 +281,7 @@ contract GdaPriceForTest is GdaTest { console2.log("Expected price at timestep:", qt.unwrap()); // Set payout to 1 seconds worth of tokens - uint256 payout = _LOT_CAPACITY / _DURATION; + uint256 payout = _LOT_CAPACITY / _auctionParams.duration; // Calculate the price uint256 price = _module.priceFor(_lotId, payout); @@ -286,6 +291,6 @@ contract GdaPriceForTest is GdaTest { // The price should be conservative (greater than or equal to the expected price) assertGe(price, expectedPrice); - assertApproxEqRel(price, expectedPrice, 1e14); // 0.01% + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% } } diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index ff5c9d578..e8aa33395 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -41,8 +41,8 @@ contract GdaPurchaseTest is GdaTest { // [X] it reverts // [X] when there is insufficient capacity // [X] it reverts - // [ ] when the amount is more than the max amount accepted - // [ ] it reverts + // [X] when the amount is more than the max amount accepted + // [X] it reverts // [X] when the token decimals are different // [X] it handles the purchase correctly // [X] it updates the capacity, purchased, sold, and last auction start @@ -228,30 +228,90 @@ contract GdaPurchaseTest is GdaTest { function testFuzz_minPriceNonZero_varyingSetup( uint256 amount_, uint128 capacity_, - uint128 price_, - uint128 minPrice_ + uint128 price_ + ) + public + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted + { + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + // Normalize the amount + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % (maxAmountAccepted + 1); + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, uint256(capacity_) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceNonZero_varyingSetup_quoteDecimalsSmaller( + uint256 amount_, + uint128 capacity_, + uint128 price_ ) public - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) - givenMinPrice(minPrice_ < price_ / 2 ? uint256(price_ / 2) : uint256(minPrice_)) + givenQuoteTokenDecimals(6) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted { - vm.assume(capacity_ >= 1e9); - vm.assume(_gdaParams.minimumPrice >= 1e9); - _auctionParams.duration = uint48(1 days); - vm.assume(uint256(price_) * 9 / 10 > _gdaParams.minimumPrice); // must have clearance for the decay target - // vm.assume(minPrice_ >= price_ / 2); // requirement when min price is not zero - UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = ud( - uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv( - 1 days, _auctionParams.duration - ) - ); - vm.assume(q0.mul(r) > ZERO); - _createAuctionLot(); - - vm.warp(_start); + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + // Normalize the amount + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % (maxAmountAccepted + 1); + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, uint256(capacity_) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + + function testFuzz_minPriceNonZero_varyingSetup_quoteDecimalsLarger( + uint256 amount_, + uint128 capacity_, + uint128 price_ + ) + public + givenBaseTokenDecimals(6) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted + { console2.log("Capacity:", capacity_); console2.log("Price:", price_); @@ -374,20 +434,15 @@ contract GdaPurchaseTest is GdaTest { uint128 price_ ) public - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted { - vm.assume(price_ >= 1e9); - vm.assume(capacity_ >= 1e9); - UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = - ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); - vm.assume(q0.mul(r) > ZERO); - _createAuctionLot(); - - vm.warp(_start); - console2.log("Capacity:", capacity_); console2.log("Price:", price_); @@ -395,9 +450,6 @@ contract GdaPurchaseTest is GdaTest { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 amount = amount_ % (maxAmountAccepted + 1); - // Warp to start - vm.warp(_start); - // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); @@ -418,20 +470,15 @@ contract GdaPurchaseTest is GdaTest { ) public givenQuoteTokenDecimals(6) - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted { - vm.assume(price_ >= 1e3); - vm.assume(capacity_ >= 1e9); - UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = - ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); - vm.assume(q0.mul(r) > ZERO); - _createAuctionLot(); - - vm.warp(_start); - console2.log("Capacity:", capacity_); console2.log("Price:", price_); @@ -439,9 +486,6 @@ contract GdaPurchaseTest is GdaTest { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 amount = amount_ % (maxAmountAccepted + 1); - // Warp to start - vm.warp(_start); - // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); @@ -462,20 +506,15 @@ contract GdaPurchaseTest is GdaTest { ) public givenBaseTokenDecimals(6) - givenLotCapacity(uint256(capacity_)) - givenEquilibriumPrice(uint256(price_)) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted { - vm.assume(price_ >= 1e9); - vm.assume(capacity_ >= 1e3); - UD60x18 q0 = ud(uint256(price_).mulDiv(uUNIT, 10 ** _quoteTokenDecimals)); - UD60x18 r = - ud(uint256(capacity_).mulDiv(uUNIT, 10 ** _baseTokenDecimals).mulDiv(1 days, _DURATION)); - vm.assume(q0.mul(r) > ZERO); - _createAuctionLot(); - - vm.warp(_start); - console2.log("Capacity:", capacity_); console2.log("Price:", price_); @@ -483,9 +522,6 @@ contract GdaPurchaseTest is GdaTest { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 amount = amount_ % (maxAmountAccepted + 1); - // Warp to start - vm.warp(_start); - // Calculate expected values uint256 expectedPayout = _module.payoutFor(_lotId, amount); From 83498e2a02f544f83a338c1359dee94b4aeb989a Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 17 Sep 2024 15:14:16 -0500 Subject: [PATCH 45/69] fix: priceFor rounding behavior --- lib/prb-math | 2 +- src/modules/auctions/GDA.sol | 55 +++++++++++------------------------- 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/lib/prb-math b/lib/prb-math index 78c70b6af..5e9978528 160000 --- a/lib/prb-math +++ b/lib/prb-math @@ -1 +1 @@ -Subproject commit 78c70b6afe7cff292b52ef6955c636d7f0e04639 +Subproject commit 5e997852851690511d881cebc013ac852436cc95 diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index 51afd9081..b56129b74 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -25,7 +25,7 @@ import {console2} from "lib/forge-std/src/console2.sol"; /// @notice Continuous Gradual Dutch Auction (GDA) module with exponential decay and a minimum price. contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { - using {PRBMath.mulDiv} for uint256; + using {PRBMath.mulDiv, PRBMath.mulDivUp} for uint256; // ========== STATE VARIABLES ========== // /* solhint-disable private-vars-leading-underscore */ @@ -312,6 +312,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // where T is the time since the last auction start, P is the number of payout tokens to purchase. // // Note: this function is an estimate. The actual price returned will vary some due to the precision of the calculations. + // Numerator mulDiv operations are rounded up and denominator mulDiv operations are rounded down to ensure the total rounding is conservative (up). function priceFor(uint96 lotId_, uint256 payout_) public view override returns (uint256) { // Lot ID must be valid _revertIfLotInvalid(lotId_); @@ -336,15 +337,14 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { UD60x18 priceDiff = ud( (auction.equilibriumPrice - auction.minimumPrice).mulDiv(uUNIT, quoteTokenScale) ).mul(auction.emissionsRate); - console2.log("price diff:", priceDiff.unwrap()); // Calculate the second numerator factor: e^((k*P)/r) - 1 // This cannot exceed the max exponential input due to the bounds imbosed on auction creation // emissions rate = initial capacity / duration // payout must be less then or equal to initial capacity // therefore, the resulting exponent is at most decay constant * duration - UD60x18 ekpr = auction.decayConstant.mul(payout).div(auction.emissionsRate).exp().sub(UNIT); - console2.log("ekpr:", ekpr.unwrap()); + // We convert this to a uint256 and manually perform the mulDivUp to reduce unnecessary operations + UD60x18 ekpr = ud(auction.decayConstant.intoUint256().mulDivUp(payout.intoUint256(),auction.emissionsRate.intoUint256())).exp().sub(UNIT); // Handle cases of T being positive or negative UD60x18 result; @@ -353,10 +353,10 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Calculate the denominator: ke^(k*T) // This cannot exceed the max exponential input due to the bounds imbosed on auction creation // Current time - last auction start is guaranteed to be < duration. If not, the auction is over. + // We round down here (the default behavior) since it is a component of the denominator. UD60x18 kekt = auction.decayConstant.mul( convert(block.timestamp - auction.lastAuctionStart).div(ONE_DAY) ).exp().mul(auction.decayConstant); - console2.log("kekt:", kekt.unwrap()); // Calculate the first term in the formula // We convert this to a uint256 and manually perform the mulDiv @@ -364,45 +364,30 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // multiplication operation. // Since we are multiplying and then dividing, the extra 10^18s // are cancelled out. - // result = priceDiff.mul(ekpr).div(kekt); - result = ud(priceDiff.intoUint256().mulDiv(ekpr.intoUint256(), kekt.intoUint256())); - console2.log("result:", result.unwrap()); + // We round the overall result up to be conservative. + result = ud(priceDiff.intoUint256().mulDivUp(ekpr.intoUint256(), kekt.intoUint256())); } else { // T is negative: flip the e^(k * T) term to the numerator // Calculate the exponential: e^(k*T) // This cannot exceed the max exponential input due to the bounds imbosed on auction creation // last auction start - current time is guaranteed to be < duration. If not, the auction is over. - UD60x18 ekt = auction.decayConstant.mul( - convert(auction.lastAuctionStart - block.timestamp) - ).div(ONE_DAY).exp(); - console2.log("ekt:", ekt.unwrap()); + // We round up here since it is a component of the numerator. + UD60x18 ekt = ud(auction.decayConstant.intoUint256().mulDivUp(auction.lastAuctionStart - block.timestamp, ONE_DAY.intoUint256())).exp(); // Calculate the first term in the formula - // TODO should we manually calculate this to avoid precision loss? - // A little bit trickier to make sure we avoid overflow here. - result = priceDiff.mul(ekpr).mul(ekt).div(auction.decayConstant); - console2.log("result:", result.unwrap()); + // We round the overall result up to be conservative. + result = ud(priceDiff.intoUint256().mulDivUp(ekpr.intoUint256(), uUNIT).mulDivUp(ekt.intoUint256(), auction.decayConstant.intoUint256())); } // If minimum price is zero, then the first term is the result, otherwise we add the second term if (auction.minimumPrice > 0) { - UD60x18 minPrice = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); - result = result + minPrice.mul(payout); - console2.log("result with min price", result.unwrap()); + uint256 minPrice = auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale); + result = result + ud(minPrice.mulDivUp(payout.intoUint256(), uUNIT)); } - // Scale price back to quote token decimals + // Scale price back to quote token decimals and return uint256 amount = result.intoUint256().mulDiv(quoteTokenScale, uUNIT); - - // TODO? Add 0.01% to correct for errors and have a conservative estimate - // Problem: this makes maxAmountAccepted return values that are too high and revert purchases - // We do not use this for calculation input/output amounts during purchase - // Therefore, it doesn't need to be exact. - // Since it can/will be used off-chain to estimate the amount of quote tokens - // required to purchase a certain number of tokens, it's better to be - // conservative so that slippage amounts can be set appropriately. - // return (amount * (1e4 + 1)) / 1e4; return amount; } @@ -436,6 +421,8 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // r is the emissions rate, k is the decay constant, qm is the minimum price of the auction, // q0 is the equilibrium price of the auction, T is the time since the last auction start, // C = (q0 - qm)/(qm * e^(k * T)), and W is the Lambert-W function (productLn). + // + // The default behavior for integer division is to round down, which is want we want in this case. function _payoutAndEmissionsFor( uint96 lotId_, uint256 amount_ @@ -469,8 +456,6 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { UD60x18 ekt = auction.decayConstant.mul( convert(block.timestamp - auction.lastAuctionStart).div(ONE_DAY) ).exp(); - console2.log("ekt:", ekt.unwrap()); - console2.log("r * q0:", auction.emissionsRate.mul(q0).unwrap()); // Calculate the logarithm // Operand is guaranteed to be >= 1, so the result is positive @@ -486,7 +471,6 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { UD60x18 ekt = auction.decayConstant.mul( convert(auction.lastAuctionStart - block.timestamp) ).exp(); - console2.log("ekt:", ekt.unwrap()); // Calculate the logarithm // Operand is guaranteed to be >= 1, so the result is positive @@ -506,7 +490,6 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Calculate first term aka F: (k * Q) / (r * qm) UD60x18 f = auction.decayConstant.mul(amount).div(auction.emissionsRate.mul(qm)); - console2.log("first term:", f.unwrap()); // Calculate second term aka C: (q0 - qm)/(qm * e^(k * T)) UD60x18 c; @@ -531,7 +514,6 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { ).exp() ); } - console2.log("second term:", c.unwrap()); // Calculate the third term: W(C e^(F + C)) // 17 wei is the maximum error for values in the @@ -543,7 +525,6 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // We do not need to check for overflow before adding the error correction term // since the maximum value returned from the lambert-W function is ~131*10^18 UD60x18 w = c.add(f).exp().mul(c).productLn() + ud(17); - console2.log("third term:", w.unwrap()); // Without error correction, the intermediate term (f + c - w) cannot underflow because // firstTerm + c - thirdTerm >= 0 for all amounts >= 0. @@ -562,10 +543,6 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Therefore, we check for underflow on the term and set a floor at 0. UD60x18 fcw = w > f.add(c) ? ZERO : f.add(c).sub(w); payout = auction.emissionsRate.mul(fcw).div(auction.decayConstant); - console2.log("sum of terms:", fcw.unwrap()); - console2.log("emissions rate:", auction.emissionsRate.unwrap()); - console2.log("decay constant:", auction.decayConstant.unwrap()); - console2.log("payout:", payout.unwrap()); } // Calculate seconds of emissions from payout From c7d754c9f4f01ea434de9564d34c4622e858e67a Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 17 Sep 2024 15:14:45 -0500 Subject: [PATCH 46/69] test: update failing GDA payoutFor and priceFor tests --- foundry.toml | 1 + test/modules/auctions/GDA/payoutFor.t.sol | 10 +++------- test/modules/auctions/GDA/priceFor.t.sol | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/foundry.toml b/foundry.toml index 51850aa7d..15a36d2b7 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,6 +10,7 @@ no-match-test = "optimal" [fuzz] runs = 1024 +max_test_rejects = 20000000 # Remappings are setup using remappings.txt, since forge seems to ignore remappings here diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index 739691a56..829a392c1 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -253,20 +253,18 @@ contract GdaPayoutForTest is GdaTest { console2.log("Capacity:", capacity_); console2.log("Price:", price_); - uint256 expectedPayout = _auctionParams.capacity / _DURATION; + uint256 expectedPayout = _auctionParams.capacity / _auctionParams.duration; uint256 amount = _gdaParams.equilibriumPrice.mulDiv(expectedPayout, _BASE_SCALE); console2.log("Amount:", amount); uint256 payout = _module.payoutFor(_lotId, amount); assertLe(payout, expectedPayout); - // assertApproxEqRel(payout, expectedPayout, 1e16); //TODO how to think about these bounds? some extremes have large errors - vm.warp(_start + _DECAY_PERIOD); + vm.warp(_start + _gdaParams.decayPeriod); amount = _gdaParams.equilibriumPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT).mulDiv( expectedPayout, _BASE_SCALE ); payout = _module.payoutFor(_lotId, amount); assertLe(payout, expectedPayout); - // assertApproxEqRel(payout, expectedPayout, 1e16); } function testFuzz_minPriceNonZero_varyingSetup( @@ -293,15 +291,13 @@ contract GdaPayoutForTest is GdaTest { console2.log("Amount:", amount); uint256 payout = _module.payoutFor(_lotId, amount); assertLe(payout, expectedPayout); - // assertApproxEqRel(payout, expectedPayout, 1e16); //TODO how to think about these bounds? some extremes have large errors - vm.warp(_start + _auctionParams.duration); + vm.warp(_start + _gdaParams.decayPeriod); amount = _gdaParams.equilibriumPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT).mulDiv( expectedPayout, _BASE_SCALE ); payout = _module.payoutFor(_lotId, amount); assertLe(payout, expectedPayout); - // assertApproxEqRel(payout, expectedPayout, 1e16); } function testFuzz_minPriceZero_varyingTimesteps(uint48 timestep_) diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 75a4a578a..7de1588e4 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -220,7 +220,7 @@ contract GdaPriceForTest is GdaTest { uint256 expectedPrice = _gdaParams.equilibriumPrice.mulDiv(payout, _BASE_SCALE); assertGe(price, expectedPrice); - vm.warp(_start + _DECAY_PERIOD); + vm.warp(_start + _gdaParams.decayPeriod); price = _module.priceFor(_lotId, payout); expectedPrice = expectedPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); assertGe(price, expectedPrice); From 421c13153badd400703069021efbf1ea222037b1 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 17 Sep 2024 15:23:30 -0500 Subject: [PATCH 47/69] chore: lint --- .../modules/auctions/IGradualDutchAuction.sol | 7 +-- src/modules/auctions/GDA.sol | 20 +++++-- test/modules/auctions/GDA/GDATest.sol | 8 +-- test/modules/auctions/GDA/auction.t.sol | 14 ++--- test/modules/auctions/GDA/maxPayout.t.sol | 19 ++----- test/modules/auctions/GDA/payoutFor.t.sol | 17 ++---- test/modules/auctions/GDA/priceFor.t.sol | 17 ++---- test/modules/auctions/GDA/purchase.t.sol | 56 +++++++++---------- 8 files changed, 73 insertions(+), 85 deletions(-) diff --git a/src/interfaces/modules/auctions/IGradualDutchAuction.sol b/src/interfaces/modules/auctions/IGradualDutchAuction.sol index 9820cfa6b..afa45fc66 100644 --- a/src/interfaces/modules/auctions/IGradualDutchAuction.sol +++ b/src/interfaces/modules/auctions/IGradualDutchAuction.sol @@ -43,8 +43,7 @@ interface IGradualDutchAuction is IAtomicAuction { /// @notice Returns the `AuctionData` for a lot /// /// @param lotId The lot ID - function auctionData(uint96 lotId) - external - view - returns (uint256, uint256, uint256, UD60x18, UD60x18); + function auctionData( + uint96 lotId + ) external view returns (uint256, uint256, uint256, UD60x18, UD60x18); } diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/GDA.sol index b56129b74..ea891b3b6 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/GDA.sol @@ -344,7 +344,11 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // payout must be less then or equal to initial capacity // therefore, the resulting exponent is at most decay constant * duration // We convert this to a uint256 and manually perform the mulDivUp to reduce unnecessary operations - UD60x18 ekpr = ud(auction.decayConstant.intoUint256().mulDivUp(payout.intoUint256(),auction.emissionsRate.intoUint256())).exp().sub(UNIT); + UD60x18 ekpr = ud( + auction.decayConstant.intoUint256().mulDivUp( + payout.intoUint256(), auction.emissionsRate.intoUint256() + ) + ).exp().sub(UNIT); // Handle cases of T being positive or negative UD60x18 result; @@ -373,11 +377,19 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // This cannot exceed the max exponential input due to the bounds imbosed on auction creation // last auction start - current time is guaranteed to be < duration. If not, the auction is over. // We round up here since it is a component of the numerator. - UD60x18 ekt = ud(auction.decayConstant.intoUint256().mulDivUp(auction.lastAuctionStart - block.timestamp, ONE_DAY.intoUint256())).exp(); + UD60x18 ekt = ud( + auction.decayConstant.intoUint256().mulDivUp( + auction.lastAuctionStart - block.timestamp, ONE_DAY.intoUint256() + ) + ).exp(); // Calculate the first term in the formula // We round the overall result up to be conservative. - result = ud(priceDiff.intoUint256().mulDivUp(ekpr.intoUint256(), uUNIT).mulDivUp(ekt.intoUint256(), auction.decayConstant.intoUint256())); + result = ud( + priceDiff.intoUint256().mulDivUp(ekpr.intoUint256(), uUNIT).mulDivUp( + ekt.intoUint256(), auction.decayConstant.intoUint256() + ) + ); } // If minimum price is zero, then the first term is the result, otherwise we add the second term @@ -421,7 +433,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // r is the emissions rate, k is the decay constant, qm is the minimum price of the auction, // q0 is the equilibrium price of the auction, T is the time since the last auction start, // C = (q0 - qm)/(qm * e^(k * T)), and W is the Lambert-W function (productLn). - // + // // The default behavior for integer division is to round down, which is want we want in this case. function _payoutAndEmissionsFor( uint96 lotId_, diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index a814a378f..d244a3f56 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -246,11 +246,9 @@ abstract contract GdaTest is Test, Permit2User { return _module.getLot(lotId_); } - function _getAuctionData(uint96 lotId_) - internal - view - returns (IGradualDutchAuction.AuctionData memory) - { + function _getAuctionData( + uint96 lotId_ + ) internal view returns (IGradualDutchAuction.AuctionData memory) { ( uint256 eqPrice, uint256 minPrice, diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 09d1613a5..9dc38ea88 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -82,10 +82,9 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_equilibriumPriceIsLessThanMin_reverts(uint128 price_) - public - givenEquilibriumPrice(price_ % 1e9) - { + function test_equilibriumPriceIsLessThanMin_reverts( + uint128 price_ + ) public givenEquilibriumPrice(price_ % 1e9) { // Expect revert bytes memory err = abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 0); @@ -119,10 +118,9 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_capacityLessThanMin_reverts(uint128 capacity_) - public - givenLotCapacity(capacity_ % 1e9) - { + function test_capacityLessThanMin_reverts( + uint128 capacity_ + ) public givenLotCapacity(capacity_ % 1e9) { // Expect revert bytes memory err = abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 1); diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol index 2535e84d6..d507ee639 100644 --- a/test/modules/auctions/GDA/maxPayout.t.sol +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -20,24 +20,17 @@ contract GdaMaxPayoutTest is GdaTest { _module.maxPayout(lotId_); } - function testFuzz_maxPayout_success(uint128 capacity_) - public - givenLotCapacity(capacity_) - validateCapacity - givenLotIsCreated - { + function testFuzz_maxPayout_success( + uint128 capacity_ + ) public givenLotCapacity(capacity_) validateCapacity givenLotIsCreated { uint256 maxPayout = _module.maxPayout(_lotId); uint256 expectedMaxPayout = capacity_; assertEq(expectedMaxPayout, maxPayout); } - function testFuzz_maxPayout_minPriceZero_success(uint128 capacity_) - public - givenLotCapacity(capacity_) - validateCapacity - givenMinPrice(0) - givenLotIsCreated - { + function testFuzz_maxPayout_minPriceZero_success( + uint128 capacity_ + ) public givenLotCapacity(capacity_) validateCapacity givenMinPrice(0) givenLotIsCreated { uint256 maxPayout = _module.maxPayout(_lotId); uint256 expectedMaxPayout = capacity_; assertEq(expectedMaxPayout, maxPayout); diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index 829a392c1..36e970387 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -300,12 +300,9 @@ contract GdaPayoutForTest is GdaTest { assertLe(payout, expectedPayout); } - function testFuzz_minPriceZero_varyingTimesteps(uint48 timestep_) - public - givenMinPrice(0) - givenLotIsCreated - givenLotHasStarted - { + function testFuzz_minPriceZero_varyingTimesteps( + uint48 timestep_ + ) public givenMinPrice(0) givenLotIsCreated givenLotHasStarted { // Warp to the timestep uint48 timestep = timestep_ % _DURATION; console2.log("Warping to timestep:", timestep); @@ -334,11 +331,9 @@ contract GdaPayoutForTest is GdaTest { assertApproxEqRel(payout, expectedPayout, 1e14); // 0.01% } - function testFuzz_minPriceNonZero_varyingTimesteps(uint48 timestep_) - public - givenLotIsCreated - givenLotHasStarted - { + function testFuzz_minPriceNonZero_varyingTimesteps( + uint48 timestep_ + ) public givenLotIsCreated givenLotHasStarted { // Warp to the timestep uint48 timestep = timestep_ % _DURATION; console2.log("Warping to timestep:", timestep); diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 7de1588e4..53721d373 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -226,12 +226,9 @@ contract GdaPriceForTest is GdaTest { assertGe(price, expectedPrice); } - function testFuzz_minPriceZero_varyingTimesteps(uint48 timestep_) - public - givenMinPrice(0) - givenLotIsCreated - givenLotHasStarted - { + function testFuzz_minPriceZero_varyingTimesteps( + uint48 timestep_ + ) public givenMinPrice(0) givenLotIsCreated givenLotHasStarted { // Warp to the timestep uint48 timestep = timestep_ % _auctionParams.duration; console2.log("Warping to timestep:", timestep); @@ -260,11 +257,9 @@ contract GdaPriceForTest is GdaTest { assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% } - function testFuzz_minPriceNonZero_varyingTimesteps(uint48 timestep_) - public - givenLotIsCreated - givenLotHasStarted - { + function testFuzz_minPriceNonZero_varyingTimesteps( + uint48 timestep_ + ) public givenLotIsCreated givenLotHasStarted { // Warp to the timestep uint48 timestep = timestep_ % _auctionParams.duration; console2.log("Warping to timestep:", timestep); diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index e8aa33395..6beb88e05 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -118,11 +118,9 @@ contract GdaPurchaseTest is GdaTest { _createPurchase(_purchaseAmount, _purchaseAmountOut); } - function testFuzz_amountGreaterThanMaxAccepted_reverts(uint256 amount_) - public - givenLotIsCreated - givenLotHasStarted - { + function testFuzz_amountGreaterThanMaxAccepted_reverts( + uint256 amount_ + ) public givenLotIsCreated givenLotHasStarted { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); vm.assume(amount_ > maxAmountAccepted); @@ -133,11 +131,9 @@ contract GdaPurchaseTest is GdaTest { _createPurchase(amount_, 0); // We don't set the minAmountOut slippage check since trying to calculate the payout would revert } - function testFuzz_minPriceNonZero_success(uint256 amount_) - public - givenLotIsCreated - givenLotHasStarted - { + function testFuzz_minPriceNonZero_success( + uint256 amount_ + ) public givenLotIsCreated givenLotHasStarted { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 amount = amount_ % (maxAmountAccepted + 1); console2.log("amount", amount); @@ -155,7 +151,9 @@ contract GdaPurchaseTest is GdaTest { assertEq(lot.sold, expectedPayout, "sold"); } - function testFuzz_minPriceNonZero_success_quoteTokenDecimalsLarger(uint256 amount_) + function testFuzz_minPriceNonZero_success_quoteTokenDecimalsLarger( + uint256 amount_ + ) public givenQuoteTokenDecimals(17) givenBaseTokenDecimals(13) @@ -178,7 +176,9 @@ contract GdaPurchaseTest is GdaTest { assertEq(lot.sold, expectedPayout, "sold"); } - function testFuzz_minPriceNonZero_success_quoteTokenDecimalsSmaller(uint256 amount_) + function testFuzz_minPriceNonZero_success_quoteTokenDecimalsSmaller( + uint256 amount_ + ) public givenQuoteTokenDecimals(13) givenBaseTokenDecimals(17) @@ -201,10 +201,9 @@ contract GdaPurchaseTest is GdaTest { assertEq(lot.sold, expectedPayout, "sold"); } - function testFuzz_minPriceNonZero_afterDecay_success(uint256 amount_) - public - givenLotIsCreated - { + function testFuzz_minPriceNonZero_afterDecay_success( + uint256 amount_ + ) public givenLotIsCreated { // Warp forward in time to late in the auction vm.warp(_start + _DURATION - 1 hours); @@ -332,12 +331,9 @@ contract GdaPurchaseTest is GdaTest { assertEq(lot.sold, expectedPayout, "sold"); } - function testFuzz_minPriceZero_success(uint256 amount_) - public - givenMinPrice(0) - givenLotIsCreated - givenLotHasStarted - { + function testFuzz_minPriceZero_success( + uint256 amount_ + ) public givenMinPrice(0) givenLotIsCreated givenLotHasStarted { uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); uint256 amount = amount_ % (maxAmountAccepted + 1); console2.log("amount", amount); @@ -355,7 +351,9 @@ contract GdaPurchaseTest is GdaTest { assertEq(lot.sold, expectedPayout, "sold"); } - function testFuzz_minPriceZero_success_quoteTokenDecimalsLarger(uint256 amount_) + function testFuzz_minPriceZero_success_quoteTokenDecimalsLarger( + uint256 amount_ + ) public givenMinPrice(0) givenQuoteTokenDecimals(17) @@ -379,7 +377,9 @@ contract GdaPurchaseTest is GdaTest { assertEq(lot.sold, expectedPayout, "sold"); } - function testFuzz_minPriceZero_success_quoteTokenDecimalsSmaller(uint256 amount_) + function testFuzz_minPriceZero_success_quoteTokenDecimalsSmaller( + uint256 amount_ + ) public givenMinPrice(0) givenQuoteTokenDecimals(13) @@ -403,11 +403,9 @@ contract GdaPurchaseTest is GdaTest { assertEq(lot.sold, expectedPayout, "sold"); } - function testFuzz_minPriceZero_afterDecay_success(uint256 amount_) - public - givenMinPrice(0) - givenLotIsCreated - { + function testFuzz_minPriceZero_afterDecay_success( + uint256 amount_ + ) public givenMinPrice(0) givenLotIsCreated { // Warp forward in time to late in the auction vm.warp(_start + _DURATION - 1 hours); From 0fbc3cb25c16c04f2dd015176de1ae1205d8177c Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 17 Sep 2024 15:56:44 -0500 Subject: [PATCH 48/69] chore: fix remapping and compile issues --- script/salts/salts.json | 36 +++++++++---------- src/modules/auctions/{ => atomic}/GDA.sol | 15 ++++---- test/AtomicAuctionHouse/AuctionHouseTest.sol | 1 + test/BatchAuctionHouse/AuctionHouseTest.sol | 1 + test/modules/auctions/GDA/GDATest.sol | 16 ++++----- test/modules/auctions/GDA/auction.t.sol | 14 ++++---- test/modules/auctions/GDA/cancelAuction.t.sol | 6 ++-- .../auctions/GDA/maxAmountAccepted.t.sol | 10 +++--- test/modules/auctions/GDA/maxPayout.t.sol | 8 ++--- test/modules/auctions/GDA/payoutFor.t.sol | 16 ++++----- test/modules/auctions/GDA/priceFor.t.sol | 14 ++++---- test/modules/auctions/GDA/purchase.t.sol | 16 ++++----- 12 files changed, 75 insertions(+), 78 deletions(-) rename src/modules/auctions/{ => atomic}/GDA.sol (98%) diff --git a/script/salts/salts.json b/script/salts/salts.json index 06eda5864..d9486f558 100644 --- a/script/salts/salts.json +++ b/script/salts/salts.json @@ -24,23 +24,23 @@ "0xf877dc218ee7538ffb3ad6f8fd1b4694cbb6f38d9e0b7a4d9a19ace3c24830a2": "0xad2a1f39589d55fd4fb667bda19d168106901d33100bb9c50fc57b1735024bcd" }, "Test_MockCallback": { - "0x01841051d995f193994d87cc5321c5e0b180a226c6f951ab3f5c56176924b0cf": "0x9d8de47a5460a81aef092c4b61c9f2a9fd0f54c97274e392a9555e9d2d1e1710", - "0x06cf9ff7a063a7fc1e0b4a7d18212b17834e88a6b3fc550a62877f0cd405d85b": "0xd2ec65ab90d963102d6e7874beb98a42784a34417675e47bddcf503c6858b987", - "0x17606ccfd3f57516c9b03d3b81affabde9c64a206e808373b8aa36aff4b002c9": "0x97c30d3b95ed3817655f5868ba43c8cce64028f596c46779cef1beb6d6e4d407", - "0x26ced0c667f25d41c7eca6f22e6ca7ac8973443efef73c5f71f64118d3b5b711": "0x201969779bdc1d837056538c70f34c0aeb1d5df46ec1ad12826fed468ece068d", - "0x319a3731827c0a9841ca032b6f68775ccb2c0ceaf5528e914b42454a8e92eca6": "0xe14afdc6ad14fa8237e00a115bfa70f729d4cb63b566b55e184ca3248ad718bc", - "0x321e36c7755f0c0174dafae1f53661efcb802b8643e0793992713d73f9e85bb4": "0x7107f71a5f167cc15d36d5f4efeac4deaa9289e4b02a989ecd37c8308e26710d", - "0x3aadf72f8d2c5f6f7c74f7657d125aab00a19bf0a608b5738368d440ee6d6cd8": "0x4a5d9cdb5916177c82a0930839a2442ebad34b7ec15f61d91e2f348ac0eaa8d6", - "0x48a666de441ed423f11614aa6c0867bd32a9d77bfec36a5d8a31ce6d1781c381": "0xf2fe90d36d281706b431835743fc3d86606cf79b7597052e9a026f1ae7a2e3d4", - "0x6981e018db3125d14ca54c7c658ac16aa032b4739d01992b3e3ec1c42c857f7e": "0xbf6b3b727d6134587a377f175b9df1585f0e0eccc2bf3af7c6b20656a018668e", - "0x7091f552e5e92800b15a36997ca6294118351724cc7c58fa249405580eed1dae": "0x13b315f0f8c481fed5bb27bf913a2b642eefd230d8da1a86bc60af04f7f9b25d", - "0x772fc41a79c386cec03e58c6cfdef24eaa74628d1419d83965d76e5ac2bd9621": "0xacfca0ddb3de0d280ea48d2e78223aba782a0207b3c2056c0d3c867b0b084e52", - "0x7aff7199ffe5a50443a1dbe43fd6ad7791c7837003c5e57c5b630e6b9092491b": "0x3108223651eaa1fd79433ba0c9c932c31c1707a2149b59e81e6a6c65a1eafd0a", - "0x9a071a61820d44365b51755858759fbdb78a29feb70bac5f02ede7ad71622219": "0x1fb685cccf345205bb1917b2a3305428c26f629ab8fff078a9f6d99c898816ef", - "0xc247e7a2a55fbcd8c361ec133eb9a696af8ae510b454d9e42b314cbb5dcf37d1": "0xd7293cb870687d2f863dfe79892931908c7a76d20e9fec96358349a8787a2e96", - "0xc88879b36b5bc9b856491ea74b98eb1c7b8158933c11faafeecaf40ceb79e800": "0x1b433833ae2acfa6790776c2bdb7da39cc50a0106393da7264306ac132b29dc0", - "0xca89e73f7a2c70d2af4bc7384f4911ae1b1e7cec1f16a46d566adfcedbd5bb83": "0xebb91f13f82d8855e1ffedeb8ac8f35f4d4ece7ebbaa4fc27802403a908c2d36", - "0xd641570f656540d0a9c13bbb9feb07c10bbc3997ecfcb2e2a76753a55c1f10b1": "0x5488d2fad36f14ae33fe5c251756ea699a7f4fcd10a4519ffb65d532e54b8fa5", - "0xe2ebb96968e3cb4a4b3425e3f012c052293c38c0ebec9112ec26b6f345c1fe48": "0x64fabcd82cc78669e55741bdf0fde6bf1e60ffbed5f3345b6e93dd86b77cbe36" + "0x00b0cb85b4a448d535c32b432ca3414a5bb88ba6295022c61fdb112299cc701d": "0x5f53a77536a8d4f9e16b72259f09284cf342cf413cb5f073c1d79bfd619ce98d", + "0x07427eb7abc3eb9f407cfbf47f97cf1c35f3cdd5f521864a047093d94ce25510": "0x7a7e32ccbccc4a2ee06552a5e3c5071b83b3c6c15c1345c08d32904f015403a0", + "0x0a229fd95c9ed1b1000092d0e2fd7d2114c75f6123a48c2199c81c37c14099b2": "0xc0ef466b3aa33c839780835ba2ff0a6b6d426cb9d89bbe9d05891cc77cd49c21", + "0x2e292a56408fa2febc4cd23c7a9961fa2b0c5dbae9fa7d397e9fb3153d0fa900": "0x933eb8d879ae2f9c4e6d76de8af9b32391850785f9196fc6be65cd98fff09088", + "0x31fa4132f7f76799e69cb97cef1e2db702cbdea7be5458f218342727ebdcd5bb": "0x748cd4ca6d05a1a804976062b1bb4340d07c3f9b6716b473ded094da9ec864c4", + "0x4bbe022c8c8d02e1be60027230a3fe65db72afc7c1780a57b36f0c1ebf522b60": "0x1ab83d0dbea03e6fb34be7c5dbcd434c6c8e65b9a6b48a5217e84de21f687e7b", + "0x60154bba2453aa6282ea1fb356f6abdd06f5b1149803cbe3bf26f0b31e4441bc": "0x9a5918b99a142d1b979adad8415e3847e06e3952773a150aad8aac8f6497a85d", + "0x71f82fd68d0bfcc6b481a9abfc5280dcc2532165835cec2dbf20c29cec1d67e0": "0x62a6926256c2a6f7285588034cfeaeed7abc2a09b21276ddeeb88b054939ad86", + "0x72db56dad49dcb5ef9e7c49cec5ce30ce0d986455f9261a4fc515318234aead6": "0x27a470eff5b98bd0df8c14fd624181c0e0d824e19417b87e842cdfa4644da71d", + "0x863dabd6458602bcfda9857fd7f18fcba01a21fd9f5a354478cc985b67f61697": "0x860d83d790926910c430e8d558e549f51a035e38f6f01b01cecd78dbfaf7ab7e", + "0x86c6cd765dda545f407561cf4711a3c886e1d92df7ea02ba63c1b0fe9480a65b": "0xd4a83aa6339eb819af0df77f7a3a853b744b2492c28835bc832546074879c266", + "0xba556994a94dc0bc511025a60195ff669936387b02e813c7a2bd943ee6fc0784": "0x072b4ee85615319565c77e2e1d52bafe7ef31082e07aaba7b1c48f645266d1a2", + "0xc01403f7e03e367510127d5ec7007c74074edb2ea58754acbdb0e570acac396b": "0x738f5120c6bdc8da3e45acb0bb376535b9c0e22cffa905654e2e6779801b226b", + "0xc2266a1cf2d2af88754600907c74f277aa78f4da5a902c464a391ab24d974d34": "0xea1f5d40d1aa0b00d398252a1c39bd7b28742b568df3eaf84135c9d7f3169dfc", + "0xcc541a55e310b47c815e818b7b70a10e2fed5b9a5756d2b72ebc6714118b2e70": "0x453dae5149ffc7c23b172f17b042754f4a148e40d5ae57a2350eb49b36d53c1c", + "0xd119416b475accd9e59a97021c088b1b5df55fa29d22e231520638641a44c433": "0x8c13dd001be1740014577ba60df4d903f45bc9dd06be2203518cb77e3980be95", + "0xf74f63cef50f9e1e8a2505f72c60421fb8ffd911104dc5122701db403a394092": "0x2ba0ca02a645e0479bd1deb0bfdb59ab04d3dcbdafeecf3c024f5f619d89df5b", + "0xfef7d294b1e9ad2c2d69e2a5f683407a1103571b78d6b77d256102ec2dc82bde": "0x86c8ceb1eaeea01e55511361df1fac06cc02aa88b0a79cd07f81eb795d3e2ebc" } } diff --git a/src/modules/auctions/GDA.sol b/src/modules/auctions/atomic/GDA.sol similarity index 98% rename from src/modules/auctions/GDA.sol rename to src/modules/auctions/atomic/GDA.sol index ea891b3b6..47218ae79 100644 --- a/src/modules/auctions/GDA.sol +++ b/src/modules/auctions/atomic/GDA.sol @@ -2,11 +2,10 @@ pragma solidity 0.8.19; // Protocol dependencies -import {Module} from "src/modules/Modules.sol"; -import {AuctionModule} from "src/modules/Auction.sol"; -import {Veecode, toVeecode} from "src/modules/Modules.sol"; -import {AtomicAuctionModule} from "src/modules/auctions/AtomicAuctionModule.sol"; -import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; +import {Module, Veecode, toVeecode} from "../../Modules.sol"; +import {AuctionModule} from "../../Auction.sol"; +import {AtomicAuctionModule} from "../AtomicAuctionModule.sol"; +import {IGradualDutchAuction} from "../../../interfaces/modules/auctions/IGradualDutchAuction.sol"; // External libraries import { @@ -18,10 +17,10 @@ import { EXP_MAX_INPUT, ZERO, HALF_UNIT -} from "lib/prb-math/src/UD60x18.sol"; -import "lib/prb-math/src/Common.sol" as PRBMath; +} from "../../../../lib/prb-math/src/UD60x18.sol"; +import "../../../../lib/prb-math/src/Common.sol" as PRBMath; -import {console2} from "lib/forge-std/src/console2.sol"; +import {console2} from "@forge-std-1.9.1/console2.sol"; /// @notice Continuous Gradual Dutch Auction (GDA) module with exponential decay and a minimum price. contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { diff --git a/test/AtomicAuctionHouse/AuctionHouseTest.sol b/test/AtomicAuctionHouse/AuctionHouseTest.sol index 4e6c2b580..479e16598 100644 --- a/test/AtomicAuctionHouse/AuctionHouseTest.sol +++ b/test/AtomicAuctionHouse/AuctionHouseTest.sol @@ -31,6 +31,7 @@ import {AuctionModule} from "../../src/modules/Auction.sol"; import {Veecode, toKeycode, keycodeFromVeecode, Keycode} from "../../src/modules/Keycode.sol"; import {WithSalts} from "../lib/WithSalts.sol"; +import {TestSaltConstants} from "../../script/salts/TestSaltConstants.sol"; abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSaltConstants { MockFeeOnTransferERC20 internal _baseToken; diff --git a/test/BatchAuctionHouse/AuctionHouseTest.sol b/test/BatchAuctionHouse/AuctionHouseTest.sol index 7a5e702a5..6552921f6 100644 --- a/test/BatchAuctionHouse/AuctionHouseTest.sol +++ b/test/BatchAuctionHouse/AuctionHouseTest.sol @@ -31,6 +31,7 @@ import {AuctionModule} from "../../src/modules/Auction.sol"; import {Veecode, toKeycode, keycodeFromVeecode, Keycode} from "../../src/modules/Keycode.sol"; import {WithSalts} from "../lib/WithSalts.sol"; +import {TestSaltConstants} from "../../script/salts/TestSaltConstants.sol"; abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSaltConstants { MockFeeOnTransferERC20 internal _baseToken; diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index d244a3f56..b62d277b3 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -2,18 +2,18 @@ pragma solidity 0.8.19; // Libraries -import {Test} from "forge-std/Test.sol"; -import {UD60x18, ud, uUNIT, ZERO} from "lib/prb-math/src/UD60x18.sol"; -import "lib/prb-math/src/Common.sol" as PRBMath; +import {Test} from "@forge-std-1.9.1/Test.sol"; +import {UD60x18, ud, uUNIT, ZERO} from "../../../../lib/prb-math/src/UD60x18.sol"; +import "../../../../lib/prb-math/src/Common.sol" as PRBMath; // Mocks -import {Permit2User} from "test/lib/permit2/Permit2User.sol"; +import {Permit2User} from "../../../lib/permit2/Permit2User.sol"; // Modules -import {AtomicAuctionHouse} from "src/AtomicAuctionHouse.sol"; -import {IAuction} from "src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import {GradualDutchAuction} from "src/modules/auctions/GDA.sol"; +import {AtomicAuctionHouse} from "../../../../src/AtomicAuctionHouse.sol"; +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; +import {GradualDutchAuction} from "../../../../src/modules/auctions/atomic/GDA.sol"; abstract contract GdaTest is Test, Permit2User { using {PRBMath.mulDiv} for uint256; diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 9dc38ea88..abca47554 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {Module} from "src/modules/Modules.sol"; -import {IAuction} from "src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; +import {Module} from "../../../../src/modules/Modules.sol"; +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; -import "lib/prb-math/src/Common.sol" as PRBMath; +import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import "../../../../lib/prb-math/src/Common.sol" as PRBMath; -import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; -import {console2} from "lib/forge-std/src/console2.sol"; +import {GdaTest} from "./GDATest.sol"; +import {console2} from "@forge-std-1.9.1/console2.sol"; contract GdaCreateAuctionTest is GdaTest { using {PRBMath.mulDiv} for uint256; diff --git a/test/modules/auctions/GDA/cancelAuction.t.sol b/test/modules/auctions/GDA/cancelAuction.t.sol index 5431ae92f..47fdc51be 100644 --- a/test/modules/auctions/GDA/cancelAuction.t.sol +++ b/test/modules/auctions/GDA/cancelAuction.t.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {Module} from "src/modules/Modules.sol"; -import {IAuction} from "src/interfaces/modules/IAuction.sol"; +import {Module} from "../../../../src/modules/Modules.sol"; +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; +import {GdaTest} from "./GDATest.sol"; contract GdaCancelAuctionTest is GdaTest { // [X] when the caller is not the parent diff --git a/test/modules/auctions/GDA/maxAmountAccepted.t.sol b/test/modules/auctions/GDA/maxAmountAccepted.t.sol index 3aed026e9..948ba599b 100644 --- a/test/modules/auctions/GDA/maxAmountAccepted.t.sol +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {IAuction} from "src/interfaces/modules/IAuction.sol"; -import {uUNIT} from "lib/prb-math/src/UD60x18.sol"; -import "lib/prb-math/src/Common.sol" as PRBMath; +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import {uUNIT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import "../../../../lib/prb-math/src/Common.sol" as PRBMath; -import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; -import {console2} from "lib/forge-std/src/console2.sol"; +import {GdaTest} from "./GDATest.sol"; +import {console2} from "@forge-std-1.9.1/console2.sol"; contract GdaMaxAmountAcceptedTest is GdaTest { using {PRBMath.mulDiv} for uint256; diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol index d507ee639..bc6059072 100644 --- a/test/modules/auctions/GDA/maxPayout.t.sol +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {IAuction} from "src/interfaces/modules/IAuction.sol"; -import {uUNIT} from "lib/prb-math/src/UD60x18.sol"; -import "lib/prb-math/src/Common.sol" as PRBMath; +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import {uUNIT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import "../../../../lib/prb-math/src/Common.sol" as PRBMath; -import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; +import {GdaTest} from "./GDATest.sol"; contract GdaMaxPayoutTest is GdaTest { using {PRBMath.mulDiv} for uint256; diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index 36e970387..be9ef37f3 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {Module} from "src/modules/Modules.sol"; -import {IAuction} from "src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; +import {Module} from "../../../../src/modules/Modules.sol"; +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import { - UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT -} from "lib/prb-math/src/UD60x18.sol"; -import "lib/prb-math/src/Common.sol" as PRBMath; +import {UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import "../../../../lib/prb-math/src/Common.sol" as PRBMath; -import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; -import {console2} from "lib/forge-std/src/console2.sol"; +import {GdaTest} from "./GDATest.sol"; +import {console2} from "@forge-std-1.9.1/console2.sol"; contract GdaPayoutForTest is GdaTest { using {PRBMath.mulDiv} for uint256; diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 53721d373..56c5939be 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {Module} from "src/modules/Modules.sol"; -import {IAuction} from "src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; +import {Module} from "../../../../src/modules/Modules.sol"; +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "lib/prb-math/src/UD60x18.sol"; -import "lib/prb-math/src/Common.sol" as PRBMath; +import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import "../../../../lib/prb-math/src/Common.sol" as PRBMath; -import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; -import {console2} from "lib/forge-std/src/console2.sol"; +import {GdaTest} from "./GDATest.sol"; +import {console2} from "@forge-std-1.9.1/console2.sol"; contract GdaPriceForTest is GdaTest { using {PRBMath.mulDiv} for uint256; diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index 6beb88e05..3b06c9241 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {Module} from "src/modules/Modules.sol"; -import {IAuction} from "src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "src/interfaces/modules/auctions/IGradualDutchAuction.sol"; +import {Module} from "../../../../src/modules/Modules.sol"; +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import { - UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT -} from "lib/prb-math/src/UD60x18.sol"; -import "lib/prb-math/src/Common.sol" as PRBMath; +import {UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import "../../../../lib/prb-math/src/Common.sol" as PRBMath; -import {GdaTest} from "test/modules/auctions/GDA/GDATest.sol"; -import {console2} from "lib/forge-std/src/console2.sol"; +import {GdaTest} from "./GDATest.sol"; +import {console2} from "@forge-std-1.9.1/console2.sol"; contract GdaPurchaseTest is GdaTest { using {PRBMath.mulDiv} for uint256; From fb9a2525f6101a8071e3dafd62ba89d20b9a70d2 Mon Sep 17 00:00:00 2001 From: Oighty Date: Tue, 17 Sep 2024 16:06:06 -0500 Subject: [PATCH 49/69] chore: lint --- test/modules/auctions/GDA/GDATest.sol | 3 ++- test/modules/auctions/GDA/auction.t.sol | 14 +++++++++++--- test/modules/auctions/GDA/payoutFor.t.sol | 15 ++++++++++++--- test/modules/auctions/GDA/priceFor.t.sol | 14 +++++++++++--- test/modules/auctions/GDA/purchase.t.sol | 15 ++++++++++++--- 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index b62d277b3..ff33a29f7 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -12,7 +12,8 @@ import {Permit2User} from "../../../lib/permit2/Permit2User.sol"; // Modules import {AtomicAuctionHouse} from "../../../../src/AtomicAuctionHouse.sol"; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; +import {IGradualDutchAuction} from + "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; import {GradualDutchAuction} from "../../../../src/modules/auctions/atomic/GDA.sol"; abstract contract GdaTest is Test, Permit2User { diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index abca47554..3bb8e76d6 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -3,9 +3,17 @@ pragma solidity 0.8.19; import {Module} from "../../../../src/modules/Modules.sol"; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; - -import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import {IGradualDutchAuction} from + "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; + +import { + UD60x18, + ud, + convert, + UNIT, + uUNIT, + EXP_MAX_INPUT +} from "../../../../lib/prb-math/src/UD60x18.sol"; import "../../../../lib/prb-math/src/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index be9ef37f3..fa467e640 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -3,9 +3,18 @@ pragma solidity 0.8.19; import {Module} from "../../../../src/modules/Modules.sol"; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; - -import {UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import {IGradualDutchAuction} from + "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; + +import { + UD60x18, + ud, + convert, + UNIT, + uUNIT, + ZERO, + EXP_MAX_INPUT +} from "../../../../lib/prb-math/src/UD60x18.sol"; import "../../../../lib/prb-math/src/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 56c5939be..92e1b8bf8 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -3,9 +3,17 @@ pragma solidity 0.8.19; import {Module} from "../../../../src/modules/Modules.sol"; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; - -import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import {IGradualDutchAuction} from + "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; + +import { + UD60x18, + ud, + convert, + UNIT, + uUNIT, + EXP_MAX_INPUT +} from "../../../../lib/prb-math/src/UD60x18.sol"; import "../../../../lib/prb-math/src/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index 3b06c9241..1f020c9fd 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -3,9 +3,18 @@ pragma solidity 0.8.19; import {Module} from "../../../../src/modules/Modules.sol"; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; - -import {UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT} from "../../../../lib/prb-math/src/UD60x18.sol"; +import {IGradualDutchAuction} from + "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; + +import { + UD60x18, + ud, + convert, + UNIT, + uUNIT, + ZERO, + EXP_MAX_INPUT +} from "../../../../lib/prb-math/src/UD60x18.sol"; import "../../../../lib/prb-math/src/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; From ffa97617c4d69ccac1691d8bff5ba56945126914 Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 19 Sep 2024 13:31:42 -0500 Subject: [PATCH 50/69] chore: soldeer updates --- foundry.toml | 5 +++-- remappings.txt | 7 ++++--- soldeer.lock | 37 +++++++++++++++++++++++-------------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/foundry.toml b/foundry.toml index 61edb41e0..16ad78a17 100644 --- a/foundry.toml +++ b/foundry.toml @@ -29,7 +29,8 @@ ignore = [ [dependencies] forge-std = { version = "1.9.1" } -solmate = { version = "6.7.0", url = "git@github.com:transmissions11/solmate.git", commit = "c892309933b25c03d32b1b0d674df7ae292ba925" } +solmate = { version = "6.7.0" } solady = { version = "0.0.124" } "@openzeppelin-contracts" = { version = "4.9.2" } -clones-with-immutable-args = { version = "1.1.1", git = "git@github.com:wighawag/clones-with-immutable-args.git", rev = "f5ca191afea933d50a36d101009b5644dc28bc99" } +clones-with-immutable-args = { version = "1.1.1", git = "https://github.com/wighawag/clones-with-immutable-args.git", rev = "f5ca191afea933d50a36d101009b5644dc28bc99" } +prb-math = { version = "4.0-axis", git = "https://github.com/Oighty/prb-math" } \ No newline at end of file diff --git a/remappings.txt b/remappings.txt index 03efc5cba..07fbfb648 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,6 @@ +@clones-with-immutable-args-1.1.1=dependencies/clones-with-immutable-args-1.1.1/src @forge-std-1.9.1=dependencies/forge-std-1.9.1/src -@solmate-6.7.0=dependencies/solmate-6.7.0/src -@solady-0.0.124=dependencies/solady-0.0.124/src @openzeppelin-contracts-4.9.2=dependencies/@openzeppelin-contracts-4.9.2 -@clones-with-immutable-args-1.1.1=dependencies/clones-with-immutable-args-1.1.1/src \ No newline at end of file +@solady-0.0.124=dependencies/solady-0.0.124/src +@solmate-6.7.0=dependencies/solmate-6.7.0/src +prb-math-4.0-axis/=dependencies/prb-math-4.0-axis/ diff --git a/soldeer.lock b/soldeer.lock index a6863b27c..c38a43a66 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -1,30 +1,39 @@ +[[dependencies]] +name = "@openzeppelin-contracts" +version = "4.9.2" +source = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/4_9_2_22-01-2024_13:13:52_contracts.zip" +checksum = "0f4450671798ea5659e6391876a3cf443ca50a696d9b556ac622ec7660bce306" +integrity = "f69bd90f264280204b2a1172a02a2d20f3611f5993a61e5215647bec696a8420" + +[[dependencies]] +name = "clones-with-immutable-args" +version = "1.1.1" +source = "https://github.com/wighawag/clones-with-immutable-args.git" +checksum = "f5ca191afea933d50a36d101009b5644dc28bc99" [[dependencies]] name = "forge-std" version = "1.9.1" source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_9_1_03-07-2024_14:44:59_forge-std-v1.9.1.zip" checksum = "110b35ad3604d91a919c521c71206c18cd07b29c750bd90b5cbbaf37288c9636" +integrity = "389f8bfe6b6aad01915b1e38e6d4839f8189e8d4792b42be4e10d0a96a358e3f" [[dependencies]] -name = "solmate" -version = "6.7.0" -source = "git@github.com:transmissions11/solmate.git" -checksum = "97bdb2003b70382996a79a406813f76417b1cf90" +name = "prb-math" +version = "4.0-axis" +source = "https://github.com/Oighty/prb-math" +checksum = "5e997852851690511d881cebc013ac852436cc95" [[dependencies]] name = "solady" version = "0.0.124" source = "https://soldeer-revisions.s3.amazonaws.com/solady/0_0_124_22-01-2024_13:28:04_solady.zip" checksum = "9342385eaad08f9bb5408be0b41b241dd2b974c001f7da8c3b1ac552b52ce16b" +integrity = "29d93e52694d8e858cf5a737257f4a6f21aefccaf803174fd00b9d686172ab27" [[dependencies]] -name = "@openzeppelin-contracts" -version = "4.9.2" -source = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/4_9_2_22-01-2024_13:13:52_contracts.zip" -checksum = "0f4450671798ea5659e6391876a3cf443ca50a696d9b556ac622ec7660bce306" - -[[dependencies]] -name = "clones-with-immutable-args" -version = "1.1.1" -source = "git@github.com:wighawag/clones-with-immutable-args.git" -checksum = "f5ca191afea933d50a36d101009b5644dc28bc99" +name = "solmate" +version = "6.7.0" +source = "https://soldeer-revisions.s3.amazonaws.com/solmate/6_7_0_22-01-2024_13:21:00_solmate.zip" +checksum = "dd0f08cdaaaad1de0ac45993d4959351ba89c2d9325a0b5df5570357064f2c33" +integrity = "ec330877af853f9d34b2b1bf692fb33c9f56450625f5c4abdcf0d3405839730e" From cc1c3b94097dc45416b78a16b4ec28aada527062 Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 19 Sep 2024 13:32:28 -0500 Subject: [PATCH 51/69] chore: remove submodule dep --- .gitmodules | 3 --- lib/prb-math | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/prb-math diff --git a/.gitmodules b/.gitmodules index 2c66ec556..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "lib/prb-math"] - path = lib/prb-math - url = https://github.com/Oighty/prb-math diff --git a/lib/prb-math b/lib/prb-math deleted file mode 160000 index 5e9978528..000000000 --- a/lib/prb-math +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5e997852851690511d881cebc013ac852436cc95 From 18ded86a1278f8d60c4b2f78c6d294d5c6b4a7f4 Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 19 Sep 2024 14:05:49 -0500 Subject: [PATCH 52/69] chore: update GDA deps --- foundry.toml | 4 ++-- remappings.txt | 2 +- soldeer.lock | 5 ++--- src/interfaces/modules/auctions/IGradualDutchAuction.sol | 2 +- src/modules/auctions/atomic/GDA.sol | 4 ++-- test/modules/auctions/GDA/GDATest.sol | 4 ++-- test/modules/auctions/GDA/auction.t.sol | 4 ++-- test/modules/auctions/GDA/maxAmountAccepted.t.sol | 4 ++-- test/modules/auctions/GDA/maxPayout.t.sol | 4 ++-- test/modules/auctions/GDA/payoutFor.t.sol | 4 ++-- test/modules/auctions/GDA/priceFor.t.sol | 4 ++-- test/modules/auctions/GDA/purchase.t.sol | 4 ++-- 12 files changed, 22 insertions(+), 23 deletions(-) diff --git a/foundry.toml b/foundry.toml index 16ad78a17..563d696f8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,7 +8,7 @@ evm_version = "paris" no-match-test = "optimal" [fuzz] -runs = 1024 +runs = 4096 max_test_rejects = 20000000 # Remappings are setup using remappings.txt, since forge seems to ignore remappings here @@ -29,7 +29,7 @@ ignore = [ [dependencies] forge-std = { version = "1.9.1" } -solmate = { version = "6.7.0" } +solmate = { version = "6.7.0", git = "https://github.com/transmissions11/solmate.git", rev = "c892309933b25c03d32b1b0d674df7ae292ba925" } solady = { version = "0.0.124" } "@openzeppelin-contracts" = { version = "4.9.2" } clones-with-immutable-args = { version = "1.1.1", git = "https://github.com/wighawag/clones-with-immutable-args.git", rev = "f5ca191afea933d50a36d101009b5644dc28bc99" } diff --git a/remappings.txt b/remappings.txt index 07fbfb648..d916e50d1 100644 --- a/remappings.txt +++ b/remappings.txt @@ -3,4 +3,4 @@ @openzeppelin-contracts-4.9.2=dependencies/@openzeppelin-contracts-4.9.2 @solady-0.0.124=dependencies/solady-0.0.124/src @solmate-6.7.0=dependencies/solmate-6.7.0/src -prb-math-4.0-axis/=dependencies/prb-math-4.0-axis/ +prb-math-4.0-axis/=dependencies/prb-math-4.0-axis/src diff --git a/soldeer.lock b/soldeer.lock index c38a43a66..fd976d0a1 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -34,6 +34,5 @@ integrity = "29d93e52694d8e858cf5a737257f4a6f21aefccaf803174fd00b9d686172ab27" [[dependencies]] name = "solmate" version = "6.7.0" -source = "https://soldeer-revisions.s3.amazonaws.com/solmate/6_7_0_22-01-2024_13:21:00_solmate.zip" -checksum = "dd0f08cdaaaad1de0ac45993d4959351ba89c2d9325a0b5df5570357064f2c33" -integrity = "ec330877af853f9d34b2b1bf692fb33c9f56450625f5c4abdcf0d3405839730e" +source = "https://github.com/transmissions11/solmate.git" +checksum = "c892309933b25c03d32b1b0d674df7ae292ba925" diff --git a/src/interfaces/modules/auctions/IGradualDutchAuction.sol b/src/interfaces/modules/auctions/IGradualDutchAuction.sol index afa45fc66..957a7868e 100644 --- a/src/interfaces/modules/auctions/IGradualDutchAuction.sol +++ b/src/interfaces/modules/auctions/IGradualDutchAuction.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import {UD60x18} from "lib/prb-math/src/UD60x18.sol"; +import {UD60x18} from "prb-math-4.0-axis/UD60x18.sol"; import {IAtomicAuction} from "src/interfaces/modules/IAtomicAuction.sol"; /// @notice Interface for gradual dutch (atomic) auctions diff --git a/src/modules/auctions/atomic/GDA.sol b/src/modules/auctions/atomic/GDA.sol index 47218ae79..478591522 100644 --- a/src/modules/auctions/atomic/GDA.sol +++ b/src/modules/auctions/atomic/GDA.sol @@ -17,8 +17,8 @@ import { EXP_MAX_INPUT, ZERO, HALF_UNIT -} from "../../../../lib/prb-math/src/UD60x18.sol"; -import "../../../../lib/prb-math/src/Common.sol" as PRBMath; +} from "prb-math-4.0-axis/UD60x18.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; import {console2} from "@forge-std-1.9.1/console2.sol"; diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index ff33a29f7..38c484d9f 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.19; // Libraries import {Test} from "@forge-std-1.9.1/Test.sol"; -import {UD60x18, ud, uUNIT, ZERO} from "../../../../lib/prb-math/src/UD60x18.sol"; -import "../../../../lib/prb-math/src/Common.sol" as PRBMath; +import {UD60x18, ud, uUNIT, ZERO} from "prb-math-4.0-axis/UD60x18.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; // Mocks import {Permit2User} from "../../../lib/permit2/Permit2User.sol"; diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 3bb8e76d6..3c2d23699 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -13,8 +13,8 @@ import { UNIT, uUNIT, EXP_MAX_INPUT -} from "../../../../lib/prb-math/src/UD60x18.sol"; -import "../../../../lib/prb-math/src/Common.sol" as PRBMath; +} from "prb-math-4.0-axis/UD60x18.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; import {console2} from "@forge-std-1.9.1/console2.sol"; diff --git a/test/modules/auctions/GDA/maxAmountAccepted.t.sol b/test/modules/auctions/GDA/maxAmountAccepted.t.sol index 948ba599b..08cec560c 100644 --- a/test/modules/auctions/GDA/maxAmountAccepted.t.sol +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.19; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {uUNIT} from "../../../../lib/prb-math/src/UD60x18.sol"; -import "../../../../lib/prb-math/src/Common.sol" as PRBMath; +import {uUNIT} from "prb-math-4.0-axis/UD60x18.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; import {console2} from "@forge-std-1.9.1/console2.sol"; diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol index bc6059072..76dc4c5d4 100644 --- a/test/modules/auctions/GDA/maxPayout.t.sol +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.19; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {uUNIT} from "../../../../lib/prb-math/src/UD60x18.sol"; -import "../../../../lib/prb-math/src/Common.sol" as PRBMath; +import {uUNIT} from "prb-math-4.0-axis/UD60x18.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index fa467e640..3c3d588f2 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -14,8 +14,8 @@ import { uUNIT, ZERO, EXP_MAX_INPUT -} from "../../../../lib/prb-math/src/UD60x18.sol"; -import "../../../../lib/prb-math/src/Common.sol" as PRBMath; +} from "prb-math-4.0-axis/UD60x18.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; import {console2} from "@forge-std-1.9.1/console2.sol"; diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 92e1b8bf8..5588188cd 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -13,8 +13,8 @@ import { UNIT, uUNIT, EXP_MAX_INPUT -} from "../../../../lib/prb-math/src/UD60x18.sol"; -import "../../../../lib/prb-math/src/Common.sol" as PRBMath; +} from "prb-math-4.0-axis/UD60x18.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; import {console2} from "@forge-std-1.9.1/console2.sol"; diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index 1f020c9fd..0e010da9d 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -14,8 +14,8 @@ import { uUNIT, ZERO, EXP_MAX_INPUT -} from "../../../../lib/prb-math/src/UD60x18.sol"; -import "../../../../lib/prb-math/src/Common.sol" as PRBMath; +} from "prb-math-4.0-axis/UD60x18.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; import {console2} from "@forge-std-1.9.1/console2.sol"; From e9dc8d4fae9efb5d26f108a96896b9d9b49ccfd4 Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 19 Sep 2024 14:06:48 -0500 Subject: [PATCH 53/69] chore: lint (new forge fmt) --- script/deploy/Deploy.s.sol | 36 +++++-- script/deploy/DeployBlast.s.sol | 8 +- script/deploy/WithEnvironment.s.sol | 12 ++- script/ops/Batch.s.sol | 8 +- script/ops/lib/BatchScript.sol | 16 ++- script/ops/lib/Surl.sol | 20 +++- script/salts/WithSalts.s.sol | 4 +- .../auctionHouse/AuctionHouseSalts.s.sol | 4 +- .../auctionHouse/AuctionHouseSaltsBlast.s.sol | 4 +- script/salts/test/TestSalts.s.sol | 4 +- src/AtomicCatalogue.sol | 12 ++- src/BatchAuctionHouse.sol | 8 +- src/BatchCatalogue.sol | 8 +- src/bases/AuctionHouse.sol | 24 +++-- src/bases/BaseCallback.sol | 4 +- src/bases/Catalogue.sol | 28 +++-- src/bases/FeeManager.sol | 8 +- src/blast/BlastAuctionHouse.sol | 12 ++- src/blast/modules/BlastGas.sol | 4 +- src/interfaces/IAtomicCatalogue.sol | 8 +- src/interfaces/IAuctionHouse.sol | 8 +- src/interfaces/IBatchAuctionHouse.sol | 4 +- src/interfaces/IBatchCatalogue.sol | 4 +- src/interfaces/ICatalogue.sol | 24 +++-- src/interfaces/IFeeManager.sol | 8 +- src/interfaces/modules/IAtomicAuction.sol | 8 +- src/interfaces/modules/IAuction.sol | 28 +++-- src/interfaces/modules/IBatchAuction.sol | 12 ++- src/interfaces/modules/IDerivative.sol | 12 ++- .../auctions/IEncryptedMarginalPrice.sol | 4 +- .../modules/auctions/IFixedPriceSale.sol | 4 +- src/lib/Callbacks.sol | 8 +- src/lib/ECIES.sol | 8 +- src/lib/ERC6909Metadata.sol | 20 +++- src/lib/MaxPriorityQueue.sol | 24 +++-- src/lib/SVG.sol | 44 ++++++-- src/lib/Uint2Str.sol | 4 +- src/modules/Auction.sol | 68 +++++++++--- src/modules/Derivative.sol | 8 +- src/modules/Keycode.sol | 28 +++-- src/modules/Modules.sol | 32 ++++-- src/modules/auctions/BatchAuctionModule.sol | 24 +++-- src/modules/auctions/atomic/FPS.sol | 16 ++- src/modules/auctions/atomic/GDA.sol | 16 ++- src/modules/auctions/batch/EMP.sol | 36 +++++-- src/modules/auctions/batch/FPB.sol | 28 +++-- src/modules/derivatives/LinearVesting.sol | 20 +++- src/modules/derivatives/LinearVestingCard.sol | 20 +++- test/AtomicAuctionHouse/AuctionHouseTest.sol | 84 +++++++++++---- test/AtomicAuctionHouse/cancelAuction.t.sol | 4 +- test/AtomicAuctionHouse/purchase.t.sol | 12 ++- test/AtomicAuctionHouse/setFee.t.sol | 12 ++- test/AtomicAuctionHouse/setProtocol.t.sol | 4 +- test/AuctionHouse/collectPayment.t.sol | 4 +- test/AuctionHouse/sendPayment.t.sol | 4 +- test/AuctionHouse/sendPayout.t.sol | 4 +- test/BatchAuctionHouse/AuctionHouseTest.sol | 100 +++++++++++++----- test/BatchAuctionHouse/claimBids.t.sol | 8 +- test/BatchAuctionHouse/settle.t.sol | 4 +- test/callbacks/MockCallback.sol | 43 +++++--- test/lib/BidEncoding.t.sol | 4 +- test/lib/ECIES/decrypt.t.sol | 4 +- test/lib/ECIES/encrypt.t.sol | 4 +- test/lib/ECIES/isValid.t.sol | 16 ++- test/lib/mocks/MockFeeOnTransferERC20.sol | 8 +- .../Auction/MockAtomicAuctionModule.sol | 20 +++- .../Auction/MockBatchAuctionModule.sol | 24 +++-- test/modules/Auction/auction.t.sol | 16 ++- test/modules/Auction/cancel.t.sol | 4 +- .../modules/Condenser/MockCondenserModule.sol | 4 +- test/modules/Modules/Keycode.t.sol | 4 +- test/modules/Modules/MockModule.sol | 20 +++- test/modules/Modules/MockWithModules.sol | 8 +- test/modules/auctions/EMP/EMPTest.sol | 64 ++++++++--- test/modules/auctions/EMP/cancelAuction.t.sol | 4 +- test/modules/auctions/EMP/settle.t.sol | 4 +- test/modules/auctions/FPB/FPBTest.sol | 76 +++++++++---- test/modules/auctions/FPB/bid.t.sol | 4 +- test/modules/auctions/FPB/cancelAuction.t.sol | 4 +- test/modules/auctions/FPS/FPSTest.sol | 52 ++++++--- test/modules/auctions/FPS/auction.t.sol | 12 ++- test/modules/auctions/FPS/cancelAuction.t.sol | 4 +- test/modules/auctions/GDA/GDATest.sol | 60 ++++++++--- test/modules/auctions/GDA/auction.t.sol | 17 ++- test/modules/auctions/GDA/cancelAuction.t.sol | 4 +- .../auctions/GDA/maxAmountAccepted.t.sol | 4 +- test/modules/auctions/GDA/maxPayout.t.sol | 4 +- test/modules/auctions/GDA/payoutFor.t.sol | 12 +-- test/modules/auctions/GDA/priceFor.t.sol | 13 +-- test/modules/auctions/GDA/purchase.t.sol | 12 +-- test/modules/derivatives/LinearVesting.t.sol | 24 +++-- .../LinearVestingEMPAIntegration.t.sol | 4 +- .../mocks/MockDerivativeModule.sol | 40 +++++-- 93 files changed, 1169 insertions(+), 420 deletions(-) diff --git a/script/deploy/Deploy.s.sol b/script/deploy/Deploy.s.sol index 95a355040..b520baa14 100644 --- a/script/deploy/Deploy.s.sol +++ b/script/deploy/Deploy.s.sol @@ -220,7 +220,9 @@ contract Deploy is Script, WithEnvironment, WithSalts { if (saveDeployment) _saveDeployment(chain_); } - function _saveDeployment(string memory chain_) internal { + function _saveDeployment( + string memory chain_ + ) internal { // Create the deployments folder if it doesn't exist if (!vm.isDir("./deployments")) { console2.log("Creating deployments directory"); @@ -349,7 +351,9 @@ contract Deploy is Script, WithEnvironment, WithSalts { // ========== CATALOGUE DEPLOYMENTS ========== // - function deployAtomicCatalogue(bytes memory) public virtual returns (address, string memory) { + function deployAtomicCatalogue( + bytes memory + ) public virtual returns (address, string memory) { // No args used console2.log(""); console2.log("Deploying AtomicCatalogue"); @@ -378,7 +382,9 @@ contract Deploy is Script, WithEnvironment, WithSalts { return (address(atomicCatalogue), _PREFIX_DEPLOYMENT_ROOT); } - function deployBatchCatalogue(bytes memory) public virtual returns (address, string memory) { + function deployBatchCatalogue( + bytes memory + ) public virtual returns (address, string memory) { // No args used console2.log(""); console2.log("Deploying BatchCatalogue"); @@ -442,7 +448,9 @@ contract Deploy is Script, WithEnvironment, WithSalts { return (address(amEmp), _PREFIX_AUCTION_MODULES); } - function deployFixedPriceSale(bytes memory) public virtual returns (address, string memory) { + function deployFixedPriceSale( + bytes memory + ) public virtual returns (address, string memory) { // No args used console2.log(""); console2.log("Deploying FixedPriceSale"); @@ -471,7 +479,9 @@ contract Deploy is Script, WithEnvironment, WithSalts { return (address(amFps), _PREFIX_AUCTION_MODULES); } - function deployFixedPriceBatch(bytes memory) public virtual returns (address, string memory) { + function deployFixedPriceBatch( + bytes memory + ) public virtual returns (address, string memory) { // No args used console2.log(""); console2.log("Deploying FixedPriceBatch"); @@ -566,17 +576,23 @@ contract Deploy is Script, WithEnvironment, WithSalts { // ========== HELPER FUNCTIONS ========== // - function _isAtomicAuctionHouse(string memory deploymentName) internal pure returns (bool) { + function _isAtomicAuctionHouse( + string memory deploymentName + ) internal pure returns (bool) { return keccak256(bytes(deploymentName)) == keccak256(_ATOMIC_AUCTION_HOUSE_NAME) || keccak256(bytes(deploymentName)) == keccak256(_BLAST_ATOMIC_AUCTION_HOUSE_NAME); } - function _isBatchAuctionHouse(string memory deploymentName) internal pure returns (bool) { + function _isBatchAuctionHouse( + string memory deploymentName + ) internal pure returns (bool) { return keccak256(bytes(deploymentName)) == keccak256(_BATCH_AUCTION_HOUSE_NAME) || keccak256(bytes(deploymentName)) == keccak256(_BLAST_BATCH_AUCTION_HOUSE_NAME); } - function _isAuctionHouse(string memory deploymentName) internal pure returns (bool) { + function _isAuctionHouse( + string memory deploymentName + ) internal pure returns (bool) { return _isAtomicAuctionHouse(deploymentName) || _isBatchAuctionHouse(deploymentName); } @@ -623,7 +639,9 @@ contract Deploy is Script, WithEnvironment, WithSalts { /// /// @param key_ Key to look for /// @return address Returns the address - function _getAddressNotZero(string memory key_) internal view returns (address) { + function _getAddressNotZero( + string memory key_ + ) internal view returns (address) { // Get from the deployed addresses first address deployedAddress = deployedTo[key_]; diff --git a/script/deploy/DeployBlast.s.sol b/script/deploy/DeployBlast.s.sol index a53686b41..d127173aa 100644 --- a/script/deploy/DeployBlast.s.sol +++ b/script/deploy/DeployBlast.s.sol @@ -124,7 +124,9 @@ contract DeployBlast is Deploy { return (address(amEmp), _PREFIX_AUCTION_MODULES); } - function deployFixedPriceSale(bytes memory) public override returns (address, string memory) { + function deployFixedPriceSale( + bytes memory + ) public override returns (address, string memory) { // No args used console2.log(""); console2.log("Deploying BlastFPS (Fixed Price Sale)"); @@ -152,7 +154,9 @@ contract DeployBlast is Deploy { return (address(amFps), _PREFIX_AUCTION_MODULES); } - function deployFixedPriceBatch(bytes memory) public override returns (address, string memory) { + function deployFixedPriceBatch( + bytes memory + ) public override returns (address, string memory) { // No args used console2.log(""); console2.log("Deploying BlastFPB (Fixed Price Batch)"); diff --git a/script/deploy/WithEnvironment.s.sol b/script/deploy/WithEnvironment.s.sol index 98a572b77..4dd0fc8cc 100644 --- a/script/deploy/WithEnvironment.s.sol +++ b/script/deploy/WithEnvironment.s.sol @@ -11,7 +11,9 @@ abstract contract WithEnvironment is Script { string public chain; string public env; - function _loadEnv(string calldata chain_) internal { + function _loadEnv( + string calldata chain_ + ) internal { chain = chain_; console2.log("Using chain:", chain_); @@ -24,7 +26,9 @@ abstract contract WithEnvironment is Script { /// /// @param key_ The key to look up in the environment file /// @return address The address from the environment file, or the zero address - function _envAddress(string memory key_) internal view returns (address) { + function _envAddress( + string memory key_ + ) internal view returns (address) { console2.log(" Checking in env.json"); string memory fullKey = string.concat(".current.", chain, ".", key_); address addr; @@ -47,7 +51,9 @@ abstract contract WithEnvironment is Script { /// /// @param key_ The key to look up in the environment file /// @return address The address from the environment file - function _envAddressNotZero(string memory key_) internal view returns (address) { + function _envAddressNotZero( + string memory key_ + ) internal view returns (address) { address addr = _envAddress(key_); require( addr != address(0), string.concat("WithEnvironment: key '", key_, "' has zero address") diff --git a/script/ops/Batch.s.sol b/script/ops/Batch.s.sol index 2b8f0a7d8..c1c9654ca 100644 --- a/script/ops/Batch.s.sol +++ b/script/ops/Batch.s.sol @@ -11,7 +11,9 @@ abstract contract Batch is BatchScript { string internal chain; address safe; - modifier isBatch(bool send_) { + modifier isBatch( + bool send_ + ) { // Load environment addresses for chain chain = vm.envString("CHAIN"); env = vm.readFile("./script/env.json"); @@ -26,7 +28,9 @@ abstract contract Batch is BatchScript { executeBatch(safe, send_); } - function envAddress(string memory key) internal view returns (address) { + function envAddress( + string memory key + ) internal view returns (address) { return env.readAddress(string.concat(".", chain, ".", key)); } } diff --git a/script/ops/lib/BatchScript.sol b/script/ops/lib/BatchScript.sol index f91215683..fbf656210 100644 --- a/script/ops/lib/BatchScript.sol +++ b/script/ops/lib/BatchScript.sol @@ -155,7 +155,9 @@ abstract contract BatchScript is Script, DelegatePrank { } // Encodes the stored encoded transactions into a single Multisend transaction - function _createBatch(address safe_) internal returns (Batch memory batch) { + function _createBatch( + address safe_ + ) internal returns (Batch memory batch) { // Set initial batch fields batch.to = SAFE_MULTISEND_ADDRESS; batch.value = 0; @@ -359,7 +361,9 @@ abstract contract BatchScript is Script, DelegatePrank { return payload; } - function _stripSlashQuotes(string memory str_) internal returns (string memory) { + function _stripSlashQuotes( + string memory str_ + ) internal returns (string memory) { // solhint-disable quotes // Remove slash quotes from string string memory command = string.concat( @@ -386,7 +390,9 @@ abstract contract BatchScript is Script, DelegatePrank { return string(res); } - function _getNonce(address safe_) internal returns (uint256) { + function _getNonce( + address safe_ + ) internal returns (uint256) { string memory endpoint = string.concat(SAFE_API_BASE_URL, vm.toString(safe_), "/"); (uint256 status, bytes memory data) = endpoint.get(); if (status == 200) { @@ -397,7 +403,9 @@ abstract contract BatchScript is Script, DelegatePrank { } } - function _getSafeAPIEndpoint(address safe_) internal view returns (string memory) { + function _getSafeAPIEndpoint( + address safe_ + ) internal view returns (string memory) { return string.concat(SAFE_API_BASE_URL, vm.toString(safe_), SAFE_API_MULTISIG_SEND); } diff --git a/script/ops/lib/Surl.sol b/script/ops/lib/Surl.sol index 4459e8e44..8cc58c5c6 100644 --- a/script/ops/lib/Surl.sol +++ b/script/ops/lib/Surl.sol @@ -6,7 +6,9 @@ import {Vm} from "@forge-std-1.9.1/Vm.sol"; library Surl { Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); - function get(string memory self) internal returns (uint256 status, bytes memory data) { + function get( + string memory self + ) internal returns (uint256 status, bytes memory data) { string[] memory empty = new string[](0); return get(self, empty); } @@ -18,7 +20,9 @@ library Surl { return curl(self, headers, "", "GET"); } - function del(string memory self) internal returns (uint256 status, bytes memory data) { + function del( + string memory self + ) internal returns (uint256 status, bytes memory data) { string[] memory empty = new string[](0); return curl(self, empty, "", "DELETE"); } @@ -39,7 +43,9 @@ library Surl { return curl(self, headers, body, "DELETE"); } - function patch(string memory self) internal returns (uint256 status, bytes memory data) { + function patch( + string memory self + ) internal returns (uint256 status, bytes memory data) { string[] memory empty = new string[](0); return curl(self, empty, "", "PATCH"); } @@ -60,7 +66,9 @@ library Surl { return curl(self, headers, body, "PATCH"); } - function post(string memory self) internal returns (uint256 status, bytes memory data) { + function post( + string memory self + ) internal returns (uint256 status, bytes memory data) { string[] memory empty = new string[](0); return curl(self, empty, "", "POST"); } @@ -81,7 +89,9 @@ library Surl { return curl(self, headers, body, "POST"); } - function put(string memory self) internal returns (uint256 status, bytes memory data) { + function put( + string memory self + ) internal returns (uint256 status, bytes memory data) { string[] memory empty = new string[](0); return curl(self, empty, "", "PUT"); } diff --git a/script/salts/WithSalts.s.sol b/script/salts/WithSalts.s.sol index f03c0b2af..2f5035f2b 100644 --- a/script/salts/WithSalts.s.sol +++ b/script/salts/WithSalts.s.sol @@ -16,7 +16,9 @@ contract WithSalts is Script { return string.concat("./", _BYTECODE_DIR); } - function _getBytecodePath(string memory name_) internal pure returns (string memory) { + function _getBytecodePath( + string memory name_ + ) internal pure returns (string memory) { return string.concat(_getBytecodeDirectory(), "/", name_, ".bin"); } diff --git a/script/salts/auctionHouse/AuctionHouseSalts.s.sol b/script/salts/auctionHouse/AuctionHouseSalts.s.sol index 03fe81eb7..446debc56 100644 --- a/script/salts/auctionHouse/AuctionHouseSalts.s.sol +++ b/script/salts/auctionHouse/AuctionHouseSalts.s.sol @@ -14,7 +14,9 @@ contract AuctionHouseSalts is Script, WithEnvironment, WithSalts { address internal _envPermit2; address internal _envProtocol; - function _setUp(string calldata chain_) internal { + function _setUp( + string calldata chain_ + ) internal { _loadEnv(chain_); _createBytecodeDirectory(); diff --git a/script/salts/auctionHouse/AuctionHouseSaltsBlast.s.sol b/script/salts/auctionHouse/AuctionHouseSaltsBlast.s.sol index 6c4135b43..252defd69 100644 --- a/script/salts/auctionHouse/AuctionHouseSaltsBlast.s.sol +++ b/script/salts/auctionHouse/AuctionHouseSaltsBlast.s.sol @@ -17,7 +17,9 @@ contract AuctionHouseSaltsBlast is Script, WithEnvironment, WithSalts { address internal _envWeth; address internal _envUsdb; - function _setUp(string calldata chain_) internal { + function _setUp( + string calldata chain_ + ) internal { _loadEnv(chain_); // Cache required variables diff --git a/script/salts/test/TestSalts.s.sol b/script/salts/test/TestSalts.s.sol index c12717c3b..533296aac 100644 --- a/script/salts/test/TestSalts.s.sol +++ b/script/salts/test/TestSalts.s.sol @@ -15,7 +15,9 @@ import {TestConstants} from "../../../test/Constants.sol"; contract TestSalts is Script, WithEnvironment, Permit2User, WithSalts, TestConstants { string internal constant _MOCK_CALLBACK = "MockCallback"; - function _setUp(string calldata chain_) internal { + function _setUp( + string calldata chain_ + ) internal { _loadEnv(chain_); _createBytecodeDirectory(); } diff --git a/src/AtomicCatalogue.sol b/src/AtomicCatalogue.sol index f04dfbb38..d28e54fe9 100644 --- a/src/AtomicCatalogue.sol +++ b/src/AtomicCatalogue.sol @@ -14,7 +14,9 @@ import {Catalogue} from "./bases/Catalogue.sol"; contract AtomicCatalogue is IAtomicCatalogue, Catalogue { // ========== CONSTRUCTOR ========== // - constructor(address auctionHouse_) Catalogue(auctionHouse_) {} + constructor( + address auctionHouse_ + ) Catalogue(auctionHouse_) {} // ========== ATOMIC AUCTION ========== // @@ -49,7 +51,9 @@ contract AtomicCatalogue is IAtomicCatalogue, Catalogue { } /// @inheritdoc IAtomicCatalogue - function maxPayout(uint96 lotId_) external view returns (uint256) { + function maxPayout( + uint96 lotId_ + ) external view returns (uint256) { IAtomicAuction module = IAtomicAuction(address(IAuctionHouse(auctionHouse).getAuctionModuleForId(lotId_))); @@ -60,7 +64,9 @@ contract AtomicCatalogue is IAtomicCatalogue, Catalogue { } /// @inheritdoc IAtomicCatalogue - function maxAmountAccepted(uint96 lotId_) external view returns (uint256) { + function maxAmountAccepted( + uint96 lotId_ + ) external view returns (uint256) { IAtomicAuction module = IAtomicAuction(address(IAuctionHouse(auctionHouse).getAuctionModuleForId(lotId_))); diff --git a/src/BatchAuctionHouse.sol b/src/BatchAuctionHouse.sol index fc2b4f823..4386d4c83 100644 --- a/src/BatchAuctionHouse.sol +++ b/src/BatchAuctionHouse.sol @@ -476,7 +476,9 @@ contract BatchAuctionHouse is IBatchAuctionHouse, AuctionHouse { /// Note that this function will not revert if the `onCancel` callback reverts. /// /// @param lotId_ The lot ID to abort - function abort(uint96 lotId_) external override nonReentrant { + function abort( + uint96 lotId_ + ) external override nonReentrant { // Validation _isLotValid(lotId_); @@ -525,7 +527,9 @@ contract BatchAuctionHouse is IBatchAuctionHouse, AuctionHouse { // ========== INTERNAL FUNCTIONS ========== // - function getBatchModuleForId(uint96 lotId_) public view returns (BatchAuctionModule) { + function getBatchModuleForId( + uint96 lotId_ + ) public view returns (BatchAuctionModule) { return BatchAuctionModule(address(_getAuctionModuleForId(lotId_))); } } diff --git a/src/BatchCatalogue.sol b/src/BatchCatalogue.sol index b188ed920..0dddc0f76 100644 --- a/src/BatchCatalogue.sol +++ b/src/BatchCatalogue.sol @@ -13,12 +13,16 @@ import {Catalogue} from "./bases/Catalogue.sol"; contract BatchCatalogue is IBatchCatalogue, Catalogue { // ========== CONSTRUCTOR ========== // - constructor(address auctionHouse_) Catalogue(auctionHouse_) {} + constructor( + address auctionHouse_ + ) Catalogue(auctionHouse_) {} // ========== RETRIEVING BIDS ========== // /// @inheritdoc IBatchCatalogue - function getNumBids(uint96 lotId_) external view returns (uint256) { + function getNumBids( + uint96 lotId_ + ) external view returns (uint256) { IBatchAuction module = IBatchAuction(address(IAuctionHouse(auctionHouse).getAuctionModuleForId(lotId_))); diff --git a/src/bases/AuctionHouse.sol b/src/bases/AuctionHouse.sol index 6831c89c2..1ab5009ed 100644 --- a/src/bases/AuctionHouse.sol +++ b/src/bases/AuctionHouse.sol @@ -288,7 +288,9 @@ abstract contract AuctionHouse is IAuctionHouse, WithModules, ReentrancyGuard, F /// @dev The function reverts if: /// - The lot ID is invalid /// - The module for the auction type is not installed - function getAuctionModuleForId(uint96 lotId_) external view override returns (IAuction) { + function getAuctionModuleForId( + uint96 lotId_ + ) external view override returns (IAuction) { _isLotValid(lotId_); return _getAuctionModuleForId(lotId_); @@ -298,7 +300,9 @@ abstract contract AuctionHouse is IAuctionHouse, WithModules, ReentrancyGuard, F /// @dev The function reverts if: /// - The lot ID is invalid /// - The module for the derivative type is not installed - function getDerivativeModuleForId(uint96 lotId_) external view override returns (IDerivative) { + function getDerivativeModuleForId( + uint96 lotId_ + ) external view override returns (IDerivative) { _isLotValid(lotId_); return _getDerivativeModuleForId(lotId_); @@ -312,7 +316,9 @@ abstract contract AuctionHouse is IAuctionHouse, WithModules, ReentrancyGuard, F /// /// @param lotId_ ID of the auction lot /// @return AuctionModule - function _getAuctionModuleForId(uint96 lotId_) internal view returns (AuctionModule) { + function _getAuctionModuleForId( + uint96 lotId_ + ) internal view returns (AuctionModule) { // Load module, will revert if not installed return AuctionModule(_getModuleIfInstalled(lotRouting[lotId_].auctionReference)); } @@ -323,7 +329,9 @@ abstract contract AuctionHouse is IAuctionHouse, WithModules, ReentrancyGuard, F /// /// @param lotId_ ID of the auction lot /// @return DerivativeModule - function _getDerivativeModuleForId(uint96 lotId_) internal view returns (DerivativeModule) { + function _getDerivativeModuleForId( + uint96 lotId_ + ) internal view returns (DerivativeModule) { // Load module, will revert if not installed. Also reverts if no derivative is specified. return DerivativeModule(_getModuleIfInstalled(lotRouting[lotId_].derivativeReference)); } @@ -361,7 +369,9 @@ abstract contract AuctionHouse is IAuctionHouse, WithModules, ReentrancyGuard, F /// @dev Reverts if the lot ID is invalid /// /// @param lotId_ ID of the auction lot - function _isLotValid(uint96 lotId_) internal view { + function _isLotValid( + uint96 lotId_ + ) internal view { if (lotId_ >= lotCounter) revert InvalidLotId(lotId_); } @@ -455,7 +465,9 @@ abstract contract AuctionHouse is IAuctionHouse, WithModules, ReentrancyGuard, F /// @inheritdoc IFeeManager /// @dev Implemented in this contract as it required access to the `onlyOwner` modifier - function setProtocol(address protocol_) external override onlyOwner { + function setProtocol( + address protocol_ + ) external override onlyOwner { _protocol = protocol_; } diff --git a/src/bases/BaseCallback.sol b/src/bases/BaseCallback.sol index d885718ee..486f0530c 100644 --- a/src/bases/BaseCallback.sol +++ b/src/bases/BaseCallback.sol @@ -42,7 +42,9 @@ abstract contract BaseCallback is ICallback { _; } - modifier onlyRegisteredLot(uint96 lotId_) { + modifier onlyRegisteredLot( + uint96 lotId_ + ) { if (!lotIdRegistered[lotId_]) revert Callback_NotAuthorized(); _; } diff --git a/src/bases/Catalogue.sol b/src/bases/Catalogue.sol index b5e325381..ad45e8a60 100644 --- a/src/bases/Catalogue.sol +++ b/src/bases/Catalogue.sol @@ -24,14 +24,18 @@ abstract contract Catalogue is ICatalogue { // ========== CONSTRUCTOR ========== // - constructor(address auctionHouse_) { + constructor( + address auctionHouse_ + ) { auctionHouse = auctionHouse_; } // ========== AUCTION INFORMATION ========== // /// @inheritdoc ICatalogue - function getRouting(uint96 lotId_) public view returns (IAuctionHouse.Routing memory) { + function getRouting( + uint96 lotId_ + ) public view returns (IAuctionHouse.Routing memory) { ( address seller, address baseToken, @@ -58,7 +62,9 @@ abstract contract Catalogue is ICatalogue { } /// @inheritdoc ICatalogue - function getFeeData(uint96 lotId_) public view returns (IAuctionHouse.FeeData memory) { + function getFeeData( + uint96 lotId_ + ) public view returns (IAuctionHouse.FeeData memory) { ( address curator, bool curated, @@ -77,7 +83,9 @@ abstract contract Catalogue is ICatalogue { } /// @inheritdoc ICatalogue - function isLive(uint96 lotId_) public view returns (bool) { + function isLive( + uint96 lotId_ + ) public view returns (bool) { IAuction module = IAuctionHouse(auctionHouse).getAuctionModuleForId(lotId_); // Get isLive from module @@ -85,7 +93,9 @@ abstract contract Catalogue is ICatalogue { } /// @inheritdoc ICatalogue - function isUpcoming(uint96 lotId_) public view returns (bool) { + function isUpcoming( + uint96 lotId_ + ) public view returns (bool) { IAuction module = IAuctionHouse(auctionHouse).getAuctionModuleForId(lotId_); // Get isUpcoming from module @@ -93,7 +103,9 @@ abstract contract Catalogue is ICatalogue { } /// @inheritdoc ICatalogue - function hasEnded(uint96 lotId_) external view returns (bool) { + function hasEnded( + uint96 lotId_ + ) external view returns (bool) { IAuction module = IAuctionHouse(auctionHouse).getAuctionModuleForId(lotId_); // Get hasEnded from module @@ -101,7 +113,9 @@ abstract contract Catalogue is ICatalogue { } /// @inheritdoc ICatalogue - function remainingCapacity(uint96 lotId_) external view returns (uint256) { + function remainingCapacity( + uint96 lotId_ + ) external view returns (uint256) { IAuction module = IAuctionHouse(auctionHouse).getAuctionModuleForId(lotId_); // Get remaining capacity from module diff --git a/src/bases/FeeManager.sol b/src/bases/FeeManager.sol index 3153a468d..928189502 100644 --- a/src/bases/FeeManager.sol +++ b/src/bases/FeeManager.sol @@ -34,7 +34,9 @@ abstract contract FeeManager is IFeeManager, ReentrancyGuard { // ========== CONSTRUCTOR ========== // - constructor(address protocol_) { + constructor( + address protocol_ + ) { _protocol = protocol_; } @@ -111,7 +113,9 @@ abstract contract FeeManager is IFeeManager, ReentrancyGuard { /// @inheritdoc IFeeManager /// @dev This function reverts if: /// - re-entrancy is detected - function claimRewards(address token_) external nonReentrant { + function claimRewards( + address token_ + ) external nonReentrant { ERC20 token = ERC20(token_); uint256 amount = rewards[msg.sender][token]; rewards[msg.sender][token] = 0; diff --git a/src/blast/BlastAuctionHouse.sol b/src/blast/BlastAuctionHouse.sol index bc9a5c841..14e81c82a 100644 --- a/src/blast/BlastAuctionHouse.sol +++ b/src/blast/BlastAuctionHouse.sol @@ -21,12 +21,16 @@ interface IBlast { interface IERC20Rebasing { // changes the yield mode of the caller and update the balance // to reflect the configuration - function configure(YieldMode) external returns (uint256); + function configure( + YieldMode + ) external returns (uint256); // "claimable" yield mode accounts can call this this claim their yield // to another address function claim(address recipient, uint256 amount) external returns (uint256); // read the claimable amount for an account - function getClaimableAmount(address account) external view returns (uint256); + function getClaimableAmount( + address account + ) external view returns (uint256); } abstract contract BlastAuctionHouse is AuctionHouse { @@ -68,7 +72,9 @@ abstract contract BlastAuctionHouse is AuctionHouse { _BLAST.claimMaxGas(address(this), _protocol); } - function claimModuleGas(Veecode reference_) external { + function claimModuleGas( + Veecode reference_ + ) external { // Claim the gas consumed by the module, send to protocol _BLAST.claimMaxGas(address(_getModuleIfInstalled(reference_)), _protocol); } diff --git a/src/blast/modules/BlastGas.sol b/src/blast/modules/BlastGas.sol index abd235930..7c1fddc8d 100644 --- a/src/blast/modules/BlastGas.sol +++ b/src/blast/modules/BlastGas.sol @@ -4,7 +4,9 @@ pragma solidity 0.8.19; interface IBlast { function configureClaimableGas() external; - function configureGovernor(address governor_) external; + function configureGovernor( + address governor_ + ) external; } abstract contract BlastGas { diff --git a/src/interfaces/IAtomicCatalogue.sol b/src/interfaces/IAtomicCatalogue.sol index ccc32811c..d405ac139 100644 --- a/src/interfaces/IAtomicCatalogue.sol +++ b/src/interfaces/IAtomicCatalogue.sol @@ -24,11 +24,15 @@ interface IAtomicCatalogue is ICatalogue { /// /// @param lotId_ ID of the auction lot /// @return payout The maximum amount of baseToken (in native decimals) that can be received by the buyer - function maxPayout(uint96 lotId_) external view returns (uint256 payout); + function maxPayout( + uint96 lotId_ + ) external view returns (uint256 payout); /// @notice Returns the max amount accepted for a given lot /// /// @param lotId_ ID of the auction lot /// @return maxAmount The maximum amount of quoteToken (in native decimals) that can be accepted by the auction - function maxAmountAccepted(uint96 lotId_) external view returns (uint256 maxAmount); + function maxAmountAccepted( + uint96 lotId_ + ) external view returns (uint256 maxAmount); } diff --git a/src/interfaces/IAuctionHouse.sol b/src/interfaces/IAuctionHouse.sol index e0c1eed17..458cbda88 100644 --- a/src/interfaces/IAuctionHouse.sol +++ b/src/interfaces/IAuctionHouse.sol @@ -200,12 +200,16 @@ interface IAuctionHouse { /// /// @param lotId_ ID of the auction lot /// @return module The auction module - function getAuctionModuleForId(uint96 lotId_) external view returns (IAuction module); + function getAuctionModuleForId( + uint96 lotId_ + ) external view returns (IAuction module); /// @notice Gets the derivative module for a given lot ID /// @dev Will revert if the lot does not have a derivative module /// /// @param lotId_ ID of the auction lot /// @return module The derivative module - function getDerivativeModuleForId(uint96 lotId_) external view returns (IDerivative module); + function getDerivativeModuleForId( + uint96 lotId_ + ) external view returns (IDerivative module); } diff --git a/src/interfaces/IBatchAuctionHouse.sol b/src/interfaces/IBatchAuctionHouse.sol index 338d5b8d0..f831473e1 100644 --- a/src/interfaces/IBatchAuctionHouse.sol +++ b/src/interfaces/IBatchAuctionHouse.sol @@ -97,5 +97,7 @@ interface IBatchAuctionHouse is IAuctionHouse { /// 3. Refund the seller /// /// @param lotId_ The lot id - function abort(uint96 lotId_) external; + function abort( + uint96 lotId_ + ) external; } diff --git a/src/interfaces/IBatchCatalogue.sol b/src/interfaces/IBatchCatalogue.sol index 95a2f1320..e9dc6ad01 100644 --- a/src/interfaces/IBatchCatalogue.sol +++ b/src/interfaces/IBatchCatalogue.sol @@ -11,7 +11,9 @@ interface IBatchCatalogue is ICatalogue { /// /// @param lotId_ The lot ID /// @return numBids The number of bids - function getNumBids(uint96 lotId_) external view returns (uint256 numBids); + function getNumBids( + uint96 lotId_ + ) external view returns (uint256 numBids); /// @notice Get the bid IDs from the given index /// diff --git a/src/interfaces/ICatalogue.sol b/src/interfaces/ICatalogue.sol index 6b6371a2e..e8649f504 100644 --- a/src/interfaces/ICatalogue.sol +++ b/src/interfaces/ICatalogue.sol @@ -24,27 +24,39 @@ interface ICatalogue { /// - The lot ID is invalid /// /// @param lotId_ ID of the auction lot - function getRouting(uint96 lotId_) external view returns (IAuctionHouse.Routing memory); + function getRouting( + uint96 lotId_ + ) external view returns (IAuctionHouse.Routing memory); /// @notice Gets the fee data for a given lot ID /// @dev The function reverts if: /// - The lot ID is invalid /// /// @param lotId_ ID of the auction lot - function getFeeData(uint96 lotId_) external view returns (IAuctionHouse.FeeData memory); + function getFeeData( + uint96 lotId_ + ) external view returns (IAuctionHouse.FeeData memory); /// @notice Is the auction currently accepting bids or purchases? /// @dev Auctions that have been created, but not yet started will return false - function isLive(uint96 lotId_) external view returns (bool); + function isLive( + uint96 lotId_ + ) external view returns (bool); /// @notice Is the auction upcoming? (i.e. has not started yet) - function isUpcoming(uint96 lotId_) external view returns (bool); + function isUpcoming( + uint96 lotId_ + ) external view returns (bool); /// @notice Has the auction ended? (i.e. reached its conclusion and no more bids/purchases can be made) - function hasEnded(uint96 lotId_) external view returns (bool); + function hasEnded( + uint96 lotId_ + ) external view returns (bool); /// @notice Capacity remaining for the auction. May be in quote or base tokens, depending on what is allowed for the auction type - function remainingCapacity(uint96 lotId_) external view returns (uint256); + function remainingCapacity( + uint96 lotId_ + ) external view returns (uint256); // ========== RETRIEVING AUCTIONS ========== // diff --git a/src/interfaces/IFeeManager.sol b/src/interfaces/IFeeManager.sol index 802cd46ae..c67215e2c 100644 --- a/src/interfaces/IFeeManager.sol +++ b/src/interfaces/IFeeManager.sol @@ -87,7 +87,9 @@ interface IFeeManager { /// @notice Claims the rewards for a specific token and the sender /// /// @param token_ Token to claim rewards for - function claimRewards(address token_) external; + function claimRewards( + address token_ + ) external; /// @notice Gets the rewards for a specific recipient and token /// @@ -113,7 +115,9 @@ interface IFeeManager { /// @dev Access controlled: only owner /// /// @param protocol_ Address of the protocol - function setProtocol(address protocol_) external; + function setProtocol( + address protocol_ + ) external; /// @notice Gets the protocol address function getProtocol() external view returns (address); diff --git a/src/interfaces/modules/IAtomicAuction.sol b/src/interfaces/modules/IAtomicAuction.sol index 7a0f59b96..dd768ea3d 100644 --- a/src/interfaces/modules/IAtomicAuction.sol +++ b/src/interfaces/modules/IAtomicAuction.sol @@ -47,11 +47,15 @@ interface IAtomicAuction is IAuction { /// /// @param lotId_ ID of the auction lot /// @return payout The maximum amount of baseToken (in native decimals) that can be received by the buyer - function maxPayout(uint96 lotId_) external view returns (uint256 payout); + function maxPayout( + uint96 lotId_ + ) external view returns (uint256 payout); /// @notice Returns the max amount accepted for a given lot /// /// @param lotId_ ID of the auction lot /// @return maxAmount The maximum amount of quoteToken (in native decimals) that can be accepted by the auction - function maxAmountAccepted(uint96 lotId_) external view returns (uint256 maxAmount); + function maxAmountAccepted( + uint96 lotId_ + ) external view returns (uint256 maxAmount); } diff --git a/src/interfaces/modules/IAuction.sol b/src/interfaces/modules/IAuction.sol index 304ae6df9..9a25f99f1 100644 --- a/src/interfaces/modules/IAuction.sol +++ b/src/interfaces/modules/IAuction.sol @@ -120,7 +120,9 @@ interface IAuction { /// - Update the lot data /// /// @param lotId_ The lot id - function cancelAuction(uint96 lotId_) external; + function cancelAuction( + uint96 lotId_ + ) external; // ========== AUCTION INFORMATION ========== // @@ -131,7 +133,9 @@ interface IAuction { /// /// @param lotId_ The lot id /// @return bool Whether or not the lot is active - function isLive(uint96 lotId_) external view returns (bool); + function isLive( + uint96 lotId_ + ) external view returns (bool); /// @notice Returns whether the auction is upcoming /// @dev The implementing function should handle the following: @@ -140,7 +144,9 @@ interface IAuction { /// /// @param lotId_ The lot id /// @return bool Whether or not the lot is upcoming - function isUpcoming(uint96 lotId_) external view returns (bool); + function isUpcoming( + uint96 lotId_ + ) external view returns (bool); /// @notice Returns whether the auction has ended /// @dev The implementing function should handle the following: @@ -149,7 +155,9 @@ interface IAuction { /// /// @param lotId_ The lot id /// @return bool Whether or not the lot is active - function hasEnded(uint96 lotId_) external view returns (bool); + function hasEnded( + uint96 lotId_ + ) external view returns (bool); /// @notice Get the remaining capacity of a lot /// @dev The implementing function should handle the following: @@ -157,7 +165,9 @@ interface IAuction { /// /// @param lotId_ The lot id /// @return uint96 The remaining capacity of the lot - function remainingCapacity(uint96 lotId_) external view returns (uint256); + function remainingCapacity( + uint96 lotId_ + ) external view returns (uint256); /// @notice Get whether or not the capacity is in quote tokens /// @dev The implementing function should handle the following: @@ -166,12 +176,16 @@ interface IAuction { /// /// @param lotId_ The lot id /// @return bool Whether or not the capacity is in quote tokens - function capacityInQuote(uint96 lotId_) external view returns (bool); + function capacityInQuote( + uint96 lotId_ + ) external view returns (bool); /// @notice Get the lot data for a given lot ID /// /// @param lotId_ The lot ID - function getLot(uint96 lotId_) external view returns (Lot memory); + function getLot( + uint96 lotId_ + ) external view returns (Lot memory); /// @notice Get the auction type function auctionType() external view returns (AuctionType); diff --git a/src/interfaces/modules/IBatchAuction.sol b/src/interfaces/modules/IBatchAuction.sol index d566f06b7..33ba8d8f0 100644 --- a/src/interfaces/modules/IBatchAuction.sol +++ b/src/interfaces/modules/IBatchAuction.sol @@ -42,7 +42,9 @@ interface IBatchAuction is IAuction { /// @dev Stored during settlement /// /// @param lotId The lot ID - function lotAuctionOutput(uint96 lotId) external view returns (bytes memory); + function lotAuctionOutput( + uint96 lotId + ) external view returns (bytes memory); // ========== BATCH OPERATIONS ========== // @@ -129,7 +131,9 @@ interface IBatchAuction is IAuction { /// - Set the auction in a state that allows bidders to claim refunds /// /// @param lotId_ The lot id - function abort(uint96 lotId_) external; + function abort( + uint96 lotId_ + ) external; // ========== VIEW FUNCTIONS ========== // @@ -137,7 +141,9 @@ interface IBatchAuction is IAuction { /// /// @param lotId_ The lot ID /// @return numBids The number of bids - function getNumBids(uint96 lotId_) external view returns (uint256 numBids); + function getNumBids( + uint96 lotId_ + ) external view returns (uint256 numBids); /// @notice Get the bid IDs from the given index /// diff --git a/src/interfaces/modules/IDerivative.sol b/src/interfaces/modules/IDerivative.sol index 32f880a01..e40066c4b 100644 --- a/src/interfaces/modules/IDerivative.sol +++ b/src/interfaces/modules/IDerivative.sol @@ -103,7 +103,9 @@ interface IDerivative { /// @notice Redeem all available derivative tokens for underlying collateral /// /// @param tokenId_ The ID of the derivative token to redeem - function redeemMax(uint256 tokenId_) external; + function redeemMax( + uint256 tokenId_ + ) external; /// @notice Redeem derivative tokens for underlying collateral /// @@ -137,7 +139,9 @@ interface IDerivative { /// @notice Access controlled: only callable by the derivative issuer via the auction house. /// /// @param tokenId_ The ID of the derivative token to reclaim - function reclaim(uint256 tokenId_) external; + function reclaim( + uint256 tokenId_ + ) external; /// @notice Transforms an existing derivative issued by this contract into something else. Derivative is burned and collateral sent to the auction house. /// @notice Access controlled: only callable by the auction house. @@ -187,5 +191,7 @@ interface IDerivative { /// /// @param tokenId The ID of the derivative token /// @return tokenData The metadata for the derivative token - function getTokenMetadata(uint256 tokenId) external view returns (Token memory tokenData); + function getTokenMetadata( + uint256 tokenId + ) external view returns (Token memory tokenData); } diff --git a/src/interfaces/modules/auctions/IEncryptedMarginalPrice.sol b/src/interfaces/modules/auctions/IEncryptedMarginalPrice.sol index 35b5c0483..1ada69212 100644 --- a/src/interfaces/modules/auctions/IEncryptedMarginalPrice.sol +++ b/src/interfaces/modules/auctions/IEncryptedMarginalPrice.sol @@ -177,7 +177,9 @@ interface IEncryptedMarginalPrice { /// /// @param lotId_ The lot ID /// @return numBids The number of decrypted bids remaining in the queue - function getNumBidsInQueue(uint96 lotId_) external view returns (uint256 numBids); + function getNumBidsInQueue( + uint96 lotId_ + ) external view returns (uint256 numBids); // ========== AUCTION INFORMATION ========== // diff --git a/src/interfaces/modules/auctions/IFixedPriceSale.sol b/src/interfaces/modules/auctions/IFixedPriceSale.sol index 1679cf0e1..845b84d67 100644 --- a/src/interfaces/modules/auctions/IFixedPriceSale.sol +++ b/src/interfaces/modules/auctions/IFixedPriceSale.sol @@ -43,5 +43,7 @@ interface IFixedPriceSale { /// @param lotId The lot ID /// @return price The fixed price of the lot /// @return maxPayout The maximum payout per purchase, in terms of the base token - function auctionData(uint96 lotId) external view returns (uint256 price, uint256 maxPayout); + function auctionData( + uint96 lotId + ) external view returns (uint256 price, uint256 maxPayout); } diff --git a/src/lib/Callbacks.sol b/src/lib/Callbacks.sol index f091c40c3..446fda4fd 100644 --- a/src/lib/Callbacks.sol +++ b/src/lib/Callbacks.sol @@ -69,7 +69,9 @@ library Callbacks { /// @notice Ensures that the callbacks contract includes at least one of the required flags and more if sending/receiving tokens /// @param callbacks The callbacks contract to verify - function isValidCallbacksAddress(ICallback callbacks) internal pure returns (bool) { + function isValidCallbacksAddress( + ICallback callbacks + ) internal pure returns (bool) { // Ensure that if the contract is expected to send base tokens, then it implements atleast onCreate and onCurate OR onPurchase (atomic auctions may not be prefunded). if ( callbacks.hasPermission(SEND_BASE_TOKENS_FLAG) @@ -227,7 +229,9 @@ library Callbacks { } /// @notice bubble up revert if present. Else throw FailedCallback error - function _revert(bytes memory result) private pure { + function _revert( + bytes memory result + ) private pure { if (result.length > 0) { assembly { revert(add(0x20, result), mload(result)) diff --git a/src/lib/ECIES.sol b/src/lib/ECIES.sol index 8d239add8..2ae90d829 100644 --- a/src/lib/ECIES.sol +++ b/src/lib/ECIES.sol @@ -131,7 +131,9 @@ library ECIES { /// @notice Checks whether a point is on the alt_bn128 curve. /// @param p - The point to check (consists of x and y coordinates). - function isOnBn128(Point memory p) internal pure returns (bool) { + function isOnBn128( + Point memory p + ) internal pure returns (bool) { // check if the provided point is on the bn128 curve y**2 = x**3 + 3, which has generator point (1, 2) return _fieldmul(p.y, p.y) == _fieldadd(_fieldmul(p.x, _fieldmul(p.x, p.x)), 3); } @@ -143,7 +145,9 @@ library ECIES { /// 3. Not the point at infinity (0, 0) /// 4. The x coordinate is less than the field modulus /// 5. The y coordinate is less than the field modulus - function isValid(Point memory p) internal pure returns (bool) { + function isValid( + Point memory p + ) internal pure returns (bool) { return isOnBn128(p) && !(p.x == 1 && p.y == 2) && !(p.x == 0 && p.y == 0) && (p.x < FIELD_MODULUS) && (p.y < FIELD_MODULUS); } diff --git a/src/lib/ERC6909Metadata.sol b/src/lib/ERC6909Metadata.sol index 3d83cd01c..35a92fe1b 100644 --- a/src/lib/ERC6909Metadata.sol +++ b/src/lib/ERC6909Metadata.sol @@ -6,29 +6,39 @@ abstract contract ERC6909Metadata { /// /// @param tokenId_ The ID of the token /// @return string The name of the token - function name(uint256 tokenId_) public view virtual returns (string memory); + function name( + uint256 tokenId_ + ) public view virtual returns (string memory); /// @notice Returns the symbol of the token /// /// @param tokenId_ The ID of the token /// @return string The symbol of the token - function symbol(uint256 tokenId_) public view virtual returns (string memory); + function symbol( + uint256 tokenId_ + ) public view virtual returns (string memory); /// @notice Returns the number of decimals used by the token /// /// @param tokenId_ The ID of the token /// @return uint8 The number of decimals used by the token - function decimals(uint256 tokenId_) public view virtual returns (uint8); + function decimals( + uint256 tokenId_ + ) public view virtual returns (uint8); /// @notice Returns the URI of the token /// /// @param tokenId_ The ID of the token /// @return string The URI of the token - function tokenURI(uint256 tokenId_) public view virtual returns (string memory); + function tokenURI( + uint256 tokenId_ + ) public view virtual returns (string memory); /// @notice Returns the total supply of the token /// /// @param tokenId_ The ID of the token /// @return uint256 The total supply of the token - function totalSupply(uint256 tokenId_) public view virtual returns (uint256); + function totalSupply( + uint256 tokenId_ + ) public view virtual returns (uint256); } diff --git a/src/lib/MaxPriorityQueue.sol b/src/lib/MaxPriorityQueue.sol index 9d73ba255..6ea257f06 100644 --- a/src/lib/MaxPriorityQueue.sol +++ b/src/lib/MaxPriorityQueue.sol @@ -12,7 +12,9 @@ library BidEncoding { return bytes32(abi.encodePacked(bidId, amountIn, minAmountOut)); } - function decode(bytes32 data) internal pure returns (uint64, uint96, uint96) { + function decode( + bytes32 data + ) internal pure returns (uint64, uint96, uint96) { uint64 bidId = uint64(uint256(data >> 192)); uint96 amountIn = uint96(uint256(data >> 96)); uint96 minAmountOut = uint96(uint256(data)); @@ -60,7 +62,9 @@ library MaxPriorityQueue { // ========== INITIALIZE ========== // - function initialize(Queue storage self) internal { + function initialize( + Queue storage self + ) internal { self.nextBid[QUEUE_START] = QUEUE_END; } @@ -117,7 +121,9 @@ library MaxPriorityQueue { // ========== REMOVAL ========== // /// @notice Remove the max bid from the queue and return it. - function delMax(Queue storage self) internal returns (uint64, uint96, uint96) { + function delMax( + Queue storage self + ) internal returns (uint64, uint96, uint96) { // Get the max bid bytes32 maxKey = self.nextBid[QUEUE_START]; require(maxKey != QUEUE_END, "queue is empty"); @@ -136,7 +142,9 @@ library MaxPriorityQueue { // ========== INSPECTION ========== // /// @notice Return the max bid from the queue without removing it. - function getMax(Queue storage self) internal view returns (uint64, uint96, uint96) { + function getMax( + Queue storage self + ) internal view returns (uint64, uint96, uint96) { return self.nextBid[QUEUE_START].decode(); } @@ -146,12 +154,16 @@ library MaxPriorityQueue { } /// @notice Return the number of bids in the queue. - function getNumBids(Queue storage self) internal view returns (uint256) { + function getNumBids( + Queue storage self + ) internal view returns (uint256) { return self.numBids; } /// @notice Return true if the queue is empty. - function isEmpty(Queue storage self) internal view returns (bool) { + function isEmpty( + Queue storage self + ) internal view returns (bool) { return self.numBids == 0; } } diff --git a/src/lib/SVG.sol b/src/lib/SVG.sol index 417bfe947..91fd99ade 100644 --- a/src/lib/SVG.sol +++ b/src/lib/SVG.sol @@ -17,22 +17,30 @@ library utils { } // formats getting a css variable - function getCssVar(string memory _key) internal pure returns (string memory) { + function getCssVar( + string memory _key + ) internal pure returns (string memory) { return string.concat("var(--", _key, ")"); } // formats getting a def URL - function getDefURL(string memory _id) internal pure returns (string memory) { + function getDefURL( + string memory _id + ) internal pure returns (string memory) { return string.concat("url(#", _id, ")"); } // formats rgba white with a specified opacity / alpha - function white_a(uint256 _a) internal pure returns (string memory) { + function white_a( + uint256 _a + ) internal pure returns (string memory) { return rgba(255, 255, 255, _a); } // formats rgba black with a specified opacity / alpha - function black_a(uint256 _a) internal pure returns (string memory) { + function black_a( + uint256 _a + ) internal pure returns (string memory) { return rgba(0, 0, 0, _a); } @@ -63,7 +71,9 @@ library utils { } // returns the length of a string in characters - function utfStringLength(string memory _str) internal pure returns (uint256 length) { + function utfStringLength( + string memory _str + ) internal pure returns (uint256 length) { uint256 i = 0; bytes memory string_rep = bytes(_str); @@ -87,7 +97,9 @@ library utils { } // converts an unsigned integer to a string - function uint2str(uint256 _i) internal pure returns (string memory _uintAsString) { + function uint2str( + uint256 _i + ) internal pure returns (string memory _uintAsString) { if (_i == 0) { return "0"; } @@ -149,7 +161,9 @@ library svg { return el("circle", _props, _children); } - function circle(string memory _props) internal pure returns (string memory) { + function circle( + string memory _props + ) internal pure returns (string memory) { return el("circle", _props); } @@ -160,7 +174,9 @@ library svg { return el("ellipse", _props, _children); } - function ellipse(string memory _props) internal pure returns (string memory) { + function ellipse( + string memory _props + ) internal pure returns (string memory) { return el("ellipse", _props); } @@ -171,7 +187,9 @@ library svg { return el("rect", _props, _children); } - function rect(string memory _props) internal pure returns (string memory) { + function rect( + string memory _props + ) internal pure returns (string memory) { return el("rect", _props); } @@ -182,7 +200,9 @@ library svg { return el("filter", _props, _children); } - function cdata(string memory _content) internal pure returns (string memory) { + function cdata( + string memory _content + ) internal pure returns (string memory) { return string.concat(""); } @@ -218,7 +238,9 @@ library svg { ); } - function animateTransform(string memory _props) internal pure returns (string memory) { + function animateTransform( + string memory _props + ) internal pure returns (string memory) { return el("animateTransform", _props); } diff --git a/src/lib/Uint2Str.sol b/src/lib/Uint2Str.sol index a28d399d5..66bdc774a 100644 --- a/src/lib/Uint2Str.sol +++ b/src/lib/Uint2Str.sol @@ -4,7 +4,9 @@ pragma solidity ^0.8; // Some fancy math to convert a uint into a string, courtesy of Provable Things. // Updated to work with solc 0.8.0. // https://github.com/provable-things/ethereum-api/blob/master/provableAPI_0.6.sol -function uint2str(uint256 _i) pure returns (string memory) { +function uint2str( + uint256 _i +) pure returns (string memory) { if (_i == 0) { return "0"; } diff --git a/src/modules/Auction.sol b/src/modules/Auction.sol index 72f5f888c..3d56bb8fe 100644 --- a/src/modules/Auction.sol +++ b/src/modules/Auction.sol @@ -19,7 +19,9 @@ abstract contract AuctionModule is IAuction, Module { // ========== CONSTRUCTOR ========== // - constructor(address auctionHouse_) Module(auctionHouse_) {} + constructor( + address auctionHouse_ + ) Module(auctionHouse_) {} /// @inheritdoc Module function TYPE() public pure override returns (Type) { @@ -95,7 +97,9 @@ abstract contract AuctionModule is IAuction, Module { /// - the lot has concluded /// /// @param lotId_ The lot id - function cancelAuction(uint96 lotId_) external virtual override onlyInternal { + function cancelAuction( + uint96 lotId_ + ) external virtual override onlyInternal { // Validation _revertIfLotInvalid(lotId_); _revertIfLotConcluded(lotId_); @@ -114,7 +118,9 @@ abstract contract AuctionModule is IAuction, Module { /// @dev Auction modules should override this to perform any additional logic /// /// @param lotId_ The lot ID - function _cancelAuction(uint96 lotId_) internal virtual; + function _cancelAuction( + uint96 lotId_ + ) internal virtual; // ========== AUCTION INFORMATION ========== // @@ -126,7 +132,9 @@ abstract contract AuctionModule is IAuction, Module { /// /// @param lotId_ The lot ID /// @return bool Whether or not the lot is active - function isLive(uint96 lotId_) public view override returns (bool) { + function isLive( + uint96 lotId_ + ) public view override returns (bool) { return ( lotData[lotId_].capacity != 0 && uint48(block.timestamp) < lotData[lotId_].conclusion && uint48(block.timestamp) >= lotData[lotId_].start @@ -134,7 +142,9 @@ abstract contract AuctionModule is IAuction, Module { } /// @inheritdoc IAuction - function isUpcoming(uint96 lotId_) public view override returns (bool) { + function isUpcoming( + uint96 lotId_ + ) public view override returns (bool) { return ( lotData[lotId_].capacity != 0 && uint48(block.timestamp) < lotData[lotId_].conclusion && uint48(block.timestamp) < lotData[lotId_].start @@ -142,23 +152,31 @@ abstract contract AuctionModule is IAuction, Module { } /// @inheritdoc IAuction - function hasEnded(uint96 lotId_) public view override returns (bool) { + function hasEnded( + uint96 lotId_ + ) public view override returns (bool) { return uint48(block.timestamp) >= lotData[lotId_].conclusion || lotData[lotId_].capacity == 0; } /// @inheritdoc IAuction - function remainingCapacity(uint96 lotId_) external view override returns (uint256) { + function remainingCapacity( + uint96 lotId_ + ) external view override returns (uint256) { return lotData[lotId_].capacity; } /// @inheritdoc IAuction - function capacityInQuote(uint96 lotId_) external view override returns (bool) { + function capacityInQuote( + uint96 lotId_ + ) external view override returns (bool) { return lotData[lotId_].capacityInQuote; } /// @inheritdoc IAuction - function getLot(uint96 lotId_) external view override returns (Lot memory) { + function getLot( + uint96 lotId_ + ) external view override returns (Lot memory) { return lotData[lotId_]; } @@ -167,7 +185,9 @@ abstract contract AuctionModule is IAuction, Module { /// @notice Set the minimum auction duration /// @dev This function must be called by the parent AuctionHouse, and /// can be called by governance using `execOnModule`. - function setMinAuctionDuration(uint48 duration_) external onlyParent { + function setMinAuctionDuration( + uint48 duration_ + ) external onlyParent { minAuctionDuration = duration_; } @@ -178,25 +198,33 @@ abstract contract AuctionModule is IAuction, Module { /// Inheriting contracts can override this to implement custom logic /// /// @param lotId_ The lot ID - function _revertIfLotInvalid(uint96 lotId_) internal view virtual { + function _revertIfLotInvalid( + uint96 lotId_ + ) internal view virtual { if (lotData[lotId_].start == 0) revert Auction_InvalidLotId(lotId_); } /// @notice Checks that the lot represented by `lotId_` has not started /// @dev Should revert if the lot has not started - function _revertIfBeforeLotStart(uint96 lotId_) internal view virtual { + function _revertIfBeforeLotStart( + uint96 lotId_ + ) internal view virtual { if (uint48(block.timestamp) < lotData[lotId_].start) revert Auction_LotNotActive(lotId_); } /// @notice Checks that the lot represented by `lotId_` has started /// @dev Should revert if the lot has started - function _revertIfLotStarted(uint96 lotId_) internal view virtual { + function _revertIfLotStarted( + uint96 lotId_ + ) internal view virtual { if (uint48(block.timestamp) >= lotData[lotId_].start) revert Auction_LotActive(lotId_); } /// @notice Checks that the lot represented by `lotId_` has not concluded /// @dev Should revert if the lot has not concluded - function _revertIfBeforeLotConcluded(uint96 lotId_) internal view virtual { + function _revertIfBeforeLotConcluded( + uint96 lotId_ + ) internal view virtual { if (uint48(block.timestamp) < lotData[lotId_].conclusion && lotData[lotId_].capacity > 0) { revert Auction_LotNotConcluded(lotId_); } @@ -204,7 +232,9 @@ abstract contract AuctionModule is IAuction, Module { /// @notice Checks that the lot represented by `lotId_` has not concluded /// @dev Should revert if the lot has concluded - function _revertIfLotConcluded(uint96 lotId_) internal view virtual { + function _revertIfLotConcluded( + uint96 lotId_ + ) internal view virtual { // Beyond the conclusion time if (uint48(block.timestamp) >= lotData[lotId_].conclusion) { revert Auction_LotNotActive(lotId_); @@ -219,7 +249,9 @@ abstract contract AuctionModule is IAuction, Module { /// Inheriting contracts can override this to implement custom logic /// /// @param lotId_ The lot ID - function _revertIfLotInactive(uint96 lotId_) internal view virtual { + function _revertIfLotInactive( + uint96 lotId_ + ) internal view virtual { if (!isLive(lotId_)) revert Auction_LotNotActive(lotId_); } @@ -228,7 +260,9 @@ abstract contract AuctionModule is IAuction, Module { /// Inheriting contracts can override this to implement custom logic /// /// @param lotId_ The lot ID - function _revertIfLotActive(uint96 lotId_) internal view virtual { + function _revertIfLotActive( + uint96 lotId_ + ) internal view virtual { if (isLive(lotId_)) revert Auction_LotActive(lotId_); } } diff --git a/src/modules/Derivative.sol b/src/modules/Derivative.sol index 5a4c2e420..6d782bb2e 100644 --- a/src/modules/Derivative.sol +++ b/src/modules/Derivative.sol @@ -18,14 +18,18 @@ abstract contract DerivativeModule is IDerivative, ERC6909, ERC6909Metadata, Mod // ========== DERIVATIVE INFORMATION ========== // /// @inheritdoc IDerivative - function getTokenMetadata(uint256 tokenId) external view virtual returns (Token memory) { + function getTokenMetadata( + uint256 tokenId + ) external view virtual returns (Token memory) { return tokenMetadata[tokenId]; } // ========== ERC6909 TOKEN SUPPLY EXTENSION ========== // /// @inheritdoc ERC6909Metadata - function totalSupply(uint256 tokenId) public view virtual override returns (uint256) { + function totalSupply( + uint256 tokenId + ) public view virtual override returns (uint256) { return tokenMetadata[tokenId].supply; } } diff --git a/src/modules/Keycode.sol b/src/modules/Keycode.sol index 6082e6935..7761b091a 100644 --- a/src/modules/Keycode.sol +++ b/src/modules/Keycode.sol @@ -13,11 +13,15 @@ type Veecode is bytes7; error InvalidVeecode(Veecode veecode_); -function toKeycode(bytes5 keycode_) pure returns (Keycode) { +function toKeycode( + bytes5 keycode_ +) pure returns (Keycode) { return Keycode.wrap(keycode_); } -function fromKeycode(Keycode keycode_) pure returns (bytes5) { +function fromKeycode( + Keycode keycode_ +) pure returns (bytes5) { return Keycode.unwrap(keycode_); } @@ -32,16 +36,22 @@ function wrapVeecode(Keycode keycode_, uint8 version_) pure returns (Veecode) { } // solhint-disable-next-line func-visibility -function toVeecode(bytes7 veecode_) pure returns (Veecode) { +function toVeecode( + bytes7 veecode_ +) pure returns (Veecode) { return Veecode.wrap(veecode_); } // solhint-disable-next-line func-visibility -function fromVeecode(Veecode veecode_) pure returns (bytes7) { +function fromVeecode( + Veecode veecode_ +) pure returns (bytes7) { return Veecode.unwrap(veecode_); } -function unwrapVeecode(Veecode veecode_) pure returns (Keycode, uint8) { +function unwrapVeecode( + Veecode veecode_ +) pure returns (Keycode, uint8) { bytes7 unwrapped = Veecode.unwrap(veecode_); // Get the version from the first 2 bytes @@ -57,13 +67,17 @@ function unwrapVeecode(Veecode veecode_) pure returns (Keycode, uint8) { return (keycode, version); } -function keycodeFromVeecode(Veecode veecode_) pure returns (Keycode) { +function keycodeFromVeecode( + Veecode veecode_ +) pure returns (Keycode) { (Keycode keycode,) = unwrapVeecode(veecode_); return keycode; } // solhint-disable-next-line func-visibility -function ensureValidVeecode(Veecode veecode_) pure { +function ensureValidVeecode( + Veecode veecode_ +) pure { bytes7 unwrapped = Veecode.unwrap(veecode_); for (uint256 i; i < 7;) { bytes1 char = unwrapped[i]; diff --git a/src/modules/Modules.sol b/src/modules/Modules.sol index bd94dd6aa..0c156a09e 100644 --- a/src/modules/Modules.sol +++ b/src/modules/Modules.sol @@ -24,7 +24,9 @@ abstract contract WithModules is Owned { // ========= CONSTRUCTOR ========= // - constructor(address owner_) Owned(owner_) {} + constructor( + address owner_ + ) Owned(owner_) {} // ========= STRUCTS ========= // @@ -64,7 +66,9 @@ abstract contract WithModules is Owned { /// @dev - The module version is not one greater than the latest version /// /// @param newModule_ The new module - function installModule(Module newModule_) external onlyOwner { + function installModule( + Module newModule_ + ) external onlyOwner { // Validate new module is a contract, has correct parent, and has valid Keycode _ensureContract(address(newModule_)); Veecode veecode = newModule_.VEECODE(); @@ -92,7 +96,9 @@ abstract contract WithModules is Owned { emit ModuleInstalled(keycode, version, address(newModule_)); } - function _ensureContract(address target_) internal view { + function _ensureContract( + address target_ + ) internal view { if (target_.code.length == 0) revert TargetNotAContract(target_); } @@ -107,7 +113,9 @@ abstract contract WithModules is Owned { /// @dev - The module is already sunset /// /// @param keycode_ The module keycode - function sunsetModule(Keycode keycode_) external onlyOwner { + function sunsetModule( + Keycode keycode_ + ) external onlyOwner { // Check that the module is installed if (!_moduleIsInstalled(keycode_)) revert ModuleNotInstalled(keycode_, 0); @@ -125,7 +133,9 @@ abstract contract WithModules is Owned { /// /// @param keycode_ The module keycode /// @return True if the module is installed, false otherwise - function _moduleIsInstalled(Keycode keycode_) internal view returns (bool) { + function _moduleIsInstalled( + Keycode keycode_ + ) internal view returns (bool) { // Any module that has been installed will have a latest version greater than 0 // We can check not equal here to save gas return getModuleStatus[keycode_].latestVersion != uint8(0); @@ -138,7 +148,9 @@ abstract contract WithModules is Owned { /// /// @param keycode_ The module keycode /// @return The address of the latest version of the module - function _getLatestModuleIfActive(Keycode keycode_) internal view returns (address) { + function _getLatestModuleIfActive( + Keycode keycode_ + ) internal view returns (address) { // Check that the module is installed ModStatus memory status = getModuleStatus[keycode_]; if (status.latestVersion == uint8(0)) revert ModuleNotInstalled(keycode_, 0); @@ -184,7 +196,9 @@ abstract contract WithModules is Owned { /// /// @param veecode_ The module Veecode /// @return The address of the module - function _getModuleIfInstalled(Veecode veecode_) internal view returns (address) { + function _getModuleIfInstalled( + Veecode veecode_ + ) internal view returns (address) { // In this case, it's simpler to check that the stored address is not zero Module mod = getModuleForVeecode[veecode_]; if (address(mod) == address(0)) { @@ -259,7 +273,9 @@ abstract contract Module { // ========= CONSTRUCTOR ========= // - constructor(address parent_) { + constructor( + address parent_ + ) { if (parent_ == address(0)) revert Module_InvalidParent(parent_); PARENT = parent_; diff --git a/src/modules/auctions/BatchAuctionModule.sol b/src/modules/auctions/BatchAuctionModule.sol index b0e438c7f..6349617ee 100644 --- a/src/modules/auctions/BatchAuctionModule.sol +++ b/src/modules/auctions/BatchAuctionModule.sol @@ -244,7 +244,9 @@ abstract contract BatchAuctionModule is IBatchAuction, AuctionModule { /// - The lot has not concluded /// - The lot is in the dedicated settle period /// - The lot is settled (after which it cannot be aborted) - function abort(uint96 lotId_) external virtual override onlyInternal { + function abort( + uint96 lotId_ + ) external virtual override onlyInternal { // Standard validation _revertIfLotInvalid(lotId_); _revertIfBeforeLotConcluded(lotId_); @@ -261,11 +263,15 @@ abstract contract BatchAuctionModule is IBatchAuction, AuctionModule { /// - Updating auction-specific data /// /// @param lotId_ The lot ID - function _abort(uint96 lotId_) internal virtual; + function _abort( + uint96 lotId_ + ) internal virtual; // ========== ADMIN CONFIGURATION ========== // - function setDedicatedSettlePeriod(uint48 period_) external onlyParent { + function setDedicatedSettlePeriod( + uint48 period_ + ) external onlyParent { // Dedicated settle period cannot be more than 7 days if (period_ > 7 days) revert Auction_InvalidParams(); @@ -279,14 +285,18 @@ abstract contract BatchAuctionModule is IBatchAuction, AuctionModule { /// Inheriting contracts must override this to implement custom logic /// /// @param lotId_ The lot ID - function _revertIfLotSettled(uint96 lotId_) internal view virtual; + function _revertIfLotSettled( + uint96 lotId_ + ) internal view virtual; /// @notice Checks that the lot represented by `lotId_` is settled /// @dev Should revert if the lot is not settled /// Inheriting contracts must override this to implement custom logic /// /// @param lotId_ The lot ID - function _revertIfLotNotSettled(uint96 lotId_) internal view virtual; + function _revertIfLotNotSettled( + uint96 lotId_ + ) internal view virtual; /// @notice Checks that the lot and bid combination is valid /// @dev Should revert if the bid is invalid @@ -317,7 +327,9 @@ abstract contract BatchAuctionModule is IBatchAuction, AuctionModule { /// @param bidId_ The bid ID function _revertIfBidClaimed(uint96 lotId_, uint64 bidId_) internal view virtual; - function _revertIfDedicatedSettlePeriod(uint96 lotId_) internal view { + function _revertIfDedicatedSettlePeriod( + uint96 lotId_ + ) internal view { // Auction must not be in the dedicated settle period uint48 conclusion = lotData[lotId_].conclusion; if ( diff --git a/src/modules/auctions/atomic/FPS.sol b/src/modules/auctions/atomic/FPS.sol index 0fbaf27a5..242a21502 100644 --- a/src/modules/auctions/atomic/FPS.sol +++ b/src/modules/auctions/atomic/FPS.sol @@ -24,7 +24,9 @@ contract FixedPriceSale is AtomicAuctionModule, IFixedPriceSale { // ========== SETUP ========== // - constructor(address auctionHouse_) AuctionModule(auctionHouse_) { + constructor( + address auctionHouse_ + ) AuctionModule(auctionHouse_) { // Set the minimum auction duration to 1 day initially minAuctionDuration = 1 days; } @@ -80,7 +82,9 @@ contract FixedPriceSale is AtomicAuctionModule, IFixedPriceSale { /// - The lot ID has been validated /// - The caller has been authorized /// - The auction has not concluded - function _cancelAuction(uint96 lotId_) internal pure override {} + function _cancelAuction( + uint96 lotId_ + ) internal pure override {} // ========== PURCHASE ========== // @@ -134,12 +138,16 @@ contract FixedPriceSale is AtomicAuctionModule, IFixedPriceSale { } /// @inheritdoc IAtomicAuction - function maxPayout(uint96 lotId_) public view override returns (uint256) { + function maxPayout( + uint96 lotId_ + ) public view override returns (uint256) { return auctionData[lotId_].maxPayout; } /// @inheritdoc IAtomicAuction - function maxAmountAccepted(uint96 lotId_) public view override returns (uint256) { + function maxAmountAccepted( + uint96 lotId_ + ) public view override returns (uint256) { return Math.mulDivUp( auctionData[lotId_].maxPayout, auctionData[lotId_].price, diff --git a/src/modules/auctions/atomic/GDA.sol b/src/modules/auctions/atomic/GDA.sol index 478591522..81de2f4ad 100644 --- a/src/modules/auctions/atomic/GDA.sol +++ b/src/modules/auctions/atomic/GDA.sol @@ -100,7 +100,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // ========== SETUP ========== // - constructor(address auctionHouse_) AuctionModule(auctionHouse_) { + constructor( + address auctionHouse_ + ) AuctionModule(auctionHouse_) { // Initially setting the minimum GDA duration to 1 hour minAuctionDuration = 1 hours; } @@ -270,7 +272,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { } // Do not need to do anything extra here - function _cancelAuction(uint96 lotId_) internal override {} + function _cancelAuction( + uint96 lotId_ + ) internal override {} // ========== PURCHASE ========== // @@ -563,7 +567,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { return (payout.intoUint256().mulDiv(10 ** lot.baseTokenDecimals, uUNIT), secondsOfEmissions); } - function maxPayout(uint96 lotId_) external view override returns (uint256) { + function maxPayout( + uint96 lotId_ + ) external view override returns (uint256) { // Lot ID must be valid _revertIfLotInvalid(lotId_); @@ -571,7 +577,9 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { return lotData[lotId_].capacity; } - function maxAmountAccepted(uint96 lotId_) public view override returns (uint256) { + function maxAmountAccepted( + uint96 lotId_ + ) public view override returns (uint256) { // The max amount accepted is the price to purchase the remaining capacity of the lot // This function checks if the lot ID is valid return priceFor(lotId_, lotData[lotId_].capacity); diff --git a/src/modules/auctions/batch/EMP.sol b/src/modules/auctions/batch/EMP.sol index 2a17255a7..3378fbc9f 100644 --- a/src/modules/auctions/batch/EMP.sol +++ b/src/modules/auctions/batch/EMP.sol @@ -88,7 +88,9 @@ contract EncryptedMarginalPrice is BatchAuctionModule, IEncryptedMarginalPrice { // ========== SETUP ========== // - constructor(address auctionHouse_) AuctionModule(auctionHouse_) { + constructor( + address auctionHouse_ + ) AuctionModule(auctionHouse_) { // Set the minimum auction duration to 1 day initially minAuctionDuration = 1 days; @@ -165,7 +167,9 @@ contract EncryptedMarginalPrice is BatchAuctionModule, IEncryptedMarginalPrice { /// /// This function reverts if: /// - The auction is active or has not concluded - function _cancelAuction(uint96 lotId_) internal override { + function _cancelAuction( + uint96 lotId_ + ) internal override { // Validation // Batch auctions cannot be cancelled once started, otherwise the seller could cancel the auction after bids have been submitted _revertIfLotActive(lotId_); @@ -579,7 +583,9 @@ contract EncryptedMarginalPrice is BatchAuctionModule, IEncryptedMarginalPrice { } /// @inheritdoc IEncryptedMarginalPrice - function getNumBidsInQueue(uint96 lotId_) external view override returns (uint256) { + function getNumBidsInQueue( + uint96 lotId_ + ) external view override returns (uint256) { return decryptedBids[lotId_].getNumBids(); } @@ -865,7 +871,9 @@ contract EncryptedMarginalPrice is BatchAuctionModule, IEncryptedMarginalPrice { /// /// This function reverts if: /// - None - function _abort(uint96 lotId_) internal override { + function _abort( + uint96 lotId_ + ) internal override { // Set the auction status to settled auctionData[lotId_].status = LotStatus.Settled; @@ -921,7 +929,9 @@ contract EncryptedMarginalPrice is BatchAuctionModule, IEncryptedMarginalPrice { /// @inheritdoc IBatchAuction /// @dev This function reverts if: /// - The lot ID is invalid - function getNumBids(uint96 lotId_) external view override returns (uint256) { + function getNumBids( + uint96 lotId_ + ) external view override returns (uint256) { _revertIfLotInvalid(lotId_); return auctionData[lotId_].bidIds.length; @@ -1040,7 +1050,9 @@ contract EncryptedMarginalPrice is BatchAuctionModule, IEncryptedMarginalPrice { // ========== VALIDATION ========== // /// @inheritdoc AuctionModule - function _revertIfLotActive(uint96 lotId_) internal view override { + function _revertIfLotActive( + uint96 lotId_ + ) internal view override { if ( auctionData[lotId_].status == LotStatus.Created && lotData[lotId_].start <= block.timestamp @@ -1049,7 +1061,9 @@ contract EncryptedMarginalPrice is BatchAuctionModule, IEncryptedMarginalPrice { } /// @notice Reverts if the private key has been submitted for the lot - function _revertIfKeySubmitted(uint96 lotId_) internal view { + function _revertIfKeySubmitted( + uint96 lotId_ + ) internal view { // Private key must not have been submitted yet if (auctionData[lotId_].privateKey != 0) { revert Auction_WrongState(lotId_); @@ -1057,7 +1071,9 @@ contract EncryptedMarginalPrice is BatchAuctionModule, IEncryptedMarginalPrice { } /// @inheritdoc BatchAuctionModule - function _revertIfLotSettled(uint96 lotId_) internal view override { + function _revertIfLotSettled( + uint96 lotId_ + ) internal view override { // Auction must not be settled if (auctionData[lotId_].status == LotStatus.Settled) { revert Auction_WrongState(lotId_); @@ -1065,7 +1081,9 @@ contract EncryptedMarginalPrice is BatchAuctionModule, IEncryptedMarginalPrice { } /// @inheritdoc BatchAuctionModule - function _revertIfLotNotSettled(uint96 lotId_) internal view override { + function _revertIfLotNotSettled( + uint96 lotId_ + ) internal view override { // Auction must be settled if (auctionData[lotId_].status != LotStatus.Settled) { revert Auction_WrongState(lotId_); diff --git a/src/modules/auctions/batch/FPB.sol b/src/modules/auctions/batch/FPB.sol index 9a2b6e7f3..ced95861a 100644 --- a/src/modules/auctions/batch/FPB.sol +++ b/src/modules/auctions/batch/FPB.sol @@ -34,7 +34,9 @@ contract FixedPriceBatch is BatchAuctionModule, IFixedPriceBatch { // ========== SETUP ========== // - constructor(address auctionHouse_) AuctionModule(auctionHouse_) { + constructor( + address auctionHouse_ + ) AuctionModule(auctionHouse_) { // Set the minimum auction duration to 1 day initially minAuctionDuration = 1 days; @@ -92,7 +94,9 @@ contract FixedPriceBatch is BatchAuctionModule, IFixedPriceBatch { /// /// This function reverts if: /// - The auction is active or has not concluded - function _cancelAuction(uint96 lotId_) internal override { + function _cancelAuction( + uint96 lotId_ + ) internal override { // Validation // Batch auctions cannot be cancelled once started, otherwise the seller could cancel the auction after bids have been submitted _revertIfLotActive(lotId_); @@ -339,7 +343,9 @@ contract FixedPriceBatch is BatchAuctionModule, IFixedPriceBatch { /// /// This function reverts if: /// - None - function _abort(uint96 lotId_) internal override { + function _abort( + uint96 lotId_ + ) internal override { // Set the auction status to settled _auctionData[lotId_].status = LotStatus.Settled; @@ -391,7 +397,9 @@ contract FixedPriceBatch is BatchAuctionModule, IFixedPriceBatch { /// @inheritdoc IBatchAuction /// @dev This function is not implemented in fixed price batch since bid IDs are not stored in an array /// A proxy is using the nextBidId to determine how many bids have been submitted, but this doesn't consider refunds - function getNumBids(uint96) external view override returns (uint256) {} + function getNumBids( + uint96 + ) external view override returns (uint256) {} /// @inheritdoc IBatchAuction /// @dev This function is not implemented in fixed price batch since bid IDs are not stored in an array @@ -467,7 +475,9 @@ contract FixedPriceBatch is BatchAuctionModule, IFixedPriceBatch { // ========== VALIDATION ========== // /// @inheritdoc AuctionModule - function _revertIfLotActive(uint96 lotId_) internal view override { + function _revertIfLotActive( + uint96 lotId_ + ) internal view override { if ( _auctionData[lotId_].status == LotStatus.Created && lotData[lotId_].start <= block.timestamp @@ -476,7 +486,9 @@ contract FixedPriceBatch is BatchAuctionModule, IFixedPriceBatch { } /// @inheritdoc BatchAuctionModule - function _revertIfLotSettled(uint96 lotId_) internal view override { + function _revertIfLotSettled( + uint96 lotId_ + ) internal view override { // Auction must not be settled if (_auctionData[lotId_].status == LotStatus.Settled) { revert Auction_WrongState(lotId_); @@ -484,7 +496,9 @@ contract FixedPriceBatch is BatchAuctionModule, IFixedPriceBatch { } /// @inheritdoc BatchAuctionModule - function _revertIfLotNotSettled(uint96 lotId_) internal view override { + function _revertIfLotNotSettled( + uint96 lotId_ + ) internal view override { // Auction must be settled if (_auctionData[lotId_].status != LotStatus.Settled) { revert Auction_WrongState(lotId_); diff --git a/src/modules/derivatives/LinearVesting.sol b/src/modules/derivatives/LinearVesting.sol index 726d29cb7..d9e593536 100644 --- a/src/modules/derivatives/LinearVesting.sol +++ b/src/modules/derivatives/LinearVesting.sol @@ -44,7 +44,9 @@ contract LinearVesting is DerivativeModule, ILinearVesting, LinearVestingCard { // ========== MODULE SETUP ========== // - constructor(address parent_) Module(parent_) LinearVestingCard() { + constructor( + address parent_ + ) Module(parent_) LinearVestingCard() { // Deploy the clone implementation _IMPLEMENTATION = address(new SoulboundCloneERC20()); } @@ -61,12 +63,16 @@ contract LinearVesting is DerivativeModule, ILinearVesting, LinearVestingCard { // ========== MODIFIERS ========== // - modifier onlyValidTokenId(uint256 tokenId_) { + modifier onlyValidTokenId( + uint256 tokenId_ + ) { if (tokenMetadata[tokenId_].exists == false) revert InvalidParams(); _; } - modifier onlyDeployedWrapped(uint256 tokenId_) { + modifier onlyDeployedWrapped( + uint256 tokenId_ + ) { if (tokenMetadata[tokenId_].wrapped == address(0)) { revert InvalidParams(); } @@ -291,7 +297,9 @@ contract LinearVesting is DerivativeModule, ILinearVesting, LinearVestingCard { } /// @inheritdoc IDerivative - function redeemMax(uint256 tokenId_) external virtual override onlyValidTokenId(tokenId_) { + function redeemMax( + uint256 tokenId_ + ) external virtual override onlyValidTokenId(tokenId_) { // Determine the redeemable amount uint256 redeemableAmount = redeemable(msg.sender, tokenId_); @@ -386,7 +394,9 @@ contract LinearVesting is DerivativeModule, ILinearVesting, LinearVestingCard { /// @inheritdoc IDerivative /// @dev Not implemented - function reclaim(uint256) external virtual override { + function reclaim( + uint256 + ) external virtual override { revert IDerivative.Derivative_NotImplemented(); } diff --git a/src/modules/derivatives/LinearVestingCard.sol b/src/modules/derivatives/LinearVestingCard.sol index c7956d241..27c6698ba 100644 --- a/src/modules/derivatives/LinearVestingCard.sol +++ b/src/modules/derivatives/LinearVestingCard.sol @@ -43,7 +43,9 @@ contract LinearVestingCard { // ========== ATTRIBUTES ========== // - function _attributes(Info memory tokenInfo) internal pure returns (string memory) { + function _attributes( + Info memory tokenInfo + ) internal pure returns (string memory) { return string.concat( '[{"trait_type":"Token ID","value":"', Strings.toString(tokenInfo.tokenId), @@ -67,7 +69,9 @@ contract LinearVestingCard { } // ========== RENDERER ========== // - function _render(Info memory tokenInfo) internal view returns (string memory) { + function _render( + Info memory tokenInfo + ) internal view returns (string memory) { return string.concat( '', svg.el( @@ -98,7 +102,9 @@ contract LinearVestingCard { // ========== COMPONENTS ========== // - function _title(string memory symbol) internal pure returns (string memory) { + function _title( + string memory symbol + ) internal pure returns (string memory) { return string.concat( svg.text(string.concat('x="145" y="40" font-size="20" ', _TEXT_STYLE), "Linear Vesting"), svg.text(string.concat('x="145" y="100" font-size="56" ', _TEXT_STYLE), symbol) @@ -121,7 +127,9 @@ contract LinearVestingCard { ); } - function _identifier(uint256 tokenId) internal view returns (string memory) { + function _identifier( + uint256 tokenId + ) internal view returns (string memory) { return string.concat( svg.text(string.concat('x="145" y="460" font-size="10" ', _TEXT_STYLE), _addrString), svg.text( @@ -197,7 +205,9 @@ contract LinearVestingCard { ); } - function _animateLine(uint256 len) internal pure returns (string memory) { + function _animateLine( + uint256 len + ) internal pure returns (string memory) { return svg.rect( string.concat( 'x="62" y="161" width="12" height="8" fill="url(#blueGreenGradient)" rx="4" ry="4"' diff --git a/test/AtomicAuctionHouse/AuctionHouseTest.sol b/test/AtomicAuctionHouse/AuctionHouseTest.sol index 479e16598..e5f9b702b 100644 --- a/test/AtomicAuctionHouse/AuctionHouseTest.sol +++ b/test/AtomicAuctionHouse/AuctionHouseTest.sol @@ -149,17 +149,23 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa // ===== Helper Functions ===== // - function _scaleQuoteTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleQuoteTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return FixedPointMathLib.mulDivDown(amount_, 10 ** _quoteToken.decimals(), _BASE_SCALE); } - function _scaleBaseTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleBaseTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return FixedPointMathLib.mulDivDown(amount_, 10 ** _baseToken.decimals(), _BASE_SCALE); } // ===== Modifiers ===== // - function _setBaseTokenDecimals(uint8 decimals_) internal { + function _setBaseTokenDecimals( + uint8 decimals_ + ) internal { _baseToken = new MockFeeOnTransferERC20("Base Token", "BASE", decimals_); uint256 lotCapacity = _scaleBaseTokenAmount(_LOT_CAPACITY); @@ -171,19 +177,25 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa _auctionParams.capacity = lotCapacity; } - modifier givenBaseTokenHasDecimals(uint8 decimals_) { + modifier givenBaseTokenHasDecimals( + uint8 decimals_ + ) { _setBaseTokenDecimals(decimals_); _; } - function _setQuoteTokenDecimals(uint8 decimals_) internal { + function _setQuoteTokenDecimals( + uint8 decimals_ + ) internal { _quoteToken = new MockFeeOnTransferERC20("Quote Token", "QUOTE", decimals_); // Update routing params _routingParams.quoteToken = address(_quoteToken); } - modifier givenQuoteTokenHasDecimals(uint8 decimals_) { + modifier givenQuoteTokenHasDecimals( + uint8 decimals_ + ) { _setQuoteTokenDecimals(decimals_); _; } @@ -295,7 +307,9 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa _; } - modifier whenPermit2ApprovalIsProvided(uint256 amount_) { + modifier whenPermit2ApprovalIsProvided( + uint256 amount_ + ) { // Approve the Permit2 contract to spend the quote token vm.prank(_bidder); _quoteToken.approve(_permit2Address, type(uint256).max); @@ -322,22 +336,30 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa _quoteToken.approve(address(_auctionHouse), amount_); } - modifier givenUserHasQuoteTokenBalance(uint256 amount_) { + modifier givenUserHasQuoteTokenBalance( + uint256 amount_ + ) { _sendUserQuoteTokenBalance(_bidder, amount_); _; } - modifier givenUserHasQuoteTokenAllowance(uint256 amount_) { + modifier givenUserHasQuoteTokenAllowance( + uint256 amount_ + ) { _approveUserQuoteTokenAllowance(_bidder, amount_); _; } - modifier givenSellerHasBaseTokenBalance(uint256 amount_) { + modifier givenSellerHasBaseTokenBalance( + uint256 amount_ + ) { _baseToken.mint(_SELLER, amount_); _; } - modifier givenSellerHasBaseTokenAllowance(uint256 amount_) { + modifier givenSellerHasBaseTokenAllowance( + uint256 amount_ + ) { vm.prank(_SELLER); _baseToken.approve(address(_auctionHouse), amount_); _; @@ -378,12 +400,16 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa _; } - modifier givenCallbackHasBaseTokenBalance(uint256 amount_) { + modifier givenCallbackHasBaseTokenBalance( + uint256 amount_ + ) { _baseToken.mint(address(_callback), amount_); _; } - modifier givenCallbackHasBaseTokenAllowance(uint256 amount_) { + modifier givenCallbackHasBaseTokenAllowance( + uint256 amount_ + ) { vm.prank(address(_callback)); _baseToken.approve(address(_auctionHouse), amount_); _; @@ -464,7 +490,9 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa _; } - function _setMaxReferrerFee(uint24 fee_) internal { + function _setMaxReferrerFee( + uint24 fee_ + ) internal { vm.prank(_OWNER); _auctionHouse.setFee(_auctionModuleKeycode, IFeeManager.FeeType.MaxReferrer, fee_); _maxReferrerFeePercentActual = fee_; @@ -475,12 +503,16 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa _; } - function _setReferrerFee(uint24 fee_) internal { + function _setReferrerFee( + uint24 fee_ + ) internal { _referrerFeePercentActual = fee_; _routingParams.referrerFee = fee_; } - modifier givenReferrerFee(uint24 fee_) { + modifier givenReferrerFee( + uint24 fee_ + ) { _setReferrerFee(fee_); _; } @@ -490,7 +522,9 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa _; } - function _setCuratorFee(uint24 fee_) internal { + function _setCuratorFee( + uint24 fee_ + ) internal { vm.prank(_CURATOR); _auctionHouse.setCuratorFee(_auctionModuleKeycode, fee_); _curatorFeePercentActual = fee_; @@ -509,7 +543,9 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa _; } - function _setProtocolFee(uint24 fee_) internal { + function _setProtocolFee( + uint24 fee_ + ) internal { vm.prank(_OWNER); _auctionHouse.setFee(_auctionModuleKeycode, IFeeManager.FeeType.Protocol, fee_); _protocolFeePercentActual = fee_; @@ -522,7 +558,9 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa // ===== Helpers ===== // - function _getLotRouting(uint96 lotId_) internal view returns (IAuctionHouse.Routing memory) { + function _getLotRouting( + uint96 lotId_ + ) internal view returns (IAuctionHouse.Routing memory) { ( address seller_, address baseToken_, @@ -548,7 +586,9 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa }); } - function _getLotFees(uint96 lotId_) internal view returns (IAuctionHouse.FeeData memory) { + function _getLotFees( + uint96 lotId_ + ) internal view returns (IAuctionHouse.FeeData memory) { ( address curator_, bool curated_, @@ -566,7 +606,9 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSa }); } - function _getLotData(uint96 lotId_) internal view returns (IAuction.Lot memory) { + function _getLotData( + uint96 lotId_ + ) internal view returns (IAuction.Lot memory) { return _auctionModule.getLot(lotId_); } } diff --git a/test/AtomicAuctionHouse/cancelAuction.t.sol b/test/AtomicAuctionHouse/cancelAuction.t.sol index cb0dcbee4..bcf8614bc 100644 --- a/test/AtomicAuctionHouse/cancelAuction.t.sol +++ b/test/AtomicAuctionHouse/cancelAuction.t.sol @@ -14,7 +14,9 @@ contract AtomicCancelAuctionTest is AtomicAuctionHouseTest { bytes internal _purchaseAuctionData = abi.encode(""); - modifier givenPayoutMultiplier(uint256 multiplier_) { + modifier givenPayoutMultiplier( + uint256 multiplier_ + ) { _atomicAuctionModule.setPayoutMultiplier(_lotId, multiplier_); _; } diff --git a/test/AtomicAuctionHouse/purchase.t.sol b/test/AtomicAuctionHouse/purchase.t.sol index af3c7465d..588d5f93b 100644 --- a/test/AtomicAuctionHouse/purchase.t.sol +++ b/test/AtomicAuctionHouse/purchase.t.sol @@ -49,7 +49,9 @@ contract AtomicPurchaseTest is AtomicAuctionHouseTest { _; } - modifier whenPayoutMultiplierIsSet(uint256 multiplier_) { + modifier whenPayoutMultiplierIsSet( + uint256 multiplier_ + ) { _atomicAuctionModule.setPayoutMultiplier(_lotId, multiplier_); uint256 amountInLessFees = _scaleQuoteTokenAmount(_AMOUNT_IN) @@ -79,13 +81,17 @@ contract AtomicPurchaseTest is AtomicAuctionHouseTest { _; } - modifier givenFeesAreCalculated(uint256 amountIn_) { + modifier givenFeesAreCalculated( + uint256 amountIn_ + ) { _expectedReferrerFeesAllocated = (amountIn_ * _referrerFeePercentActual) / 100e2; _expectedProtocolFeesAllocated = (amountIn_ * _protocolFeePercentActual) / 100e2; _; } - modifier givenFeesAreCalculatedNoReferrer(uint256 amountIn_) { + modifier givenFeesAreCalculatedNoReferrer( + uint256 amountIn_ + ) { _expectedReferrerFeesAllocated = 0; _expectedProtocolFeesAllocated = (amountIn_ * (_protocolFeePercentActual + _referrerFeePercentActual)) / 100e2; diff --git a/test/AtomicAuctionHouse/setFee.t.sol b/test/AtomicAuctionHouse/setFee.t.sol index 2b1868319..18f9d5116 100644 --- a/test/AtomicAuctionHouse/setFee.t.sol +++ b/test/AtomicAuctionHouse/setFee.t.sol @@ -68,7 +68,9 @@ contract AtomicSetFeeTest is Test, Permit2User { _auctionHouse.setFee(_auctionKeycode, IFeeManager.FeeType.Protocol, _MAX_FEE + 1); } - function test_protocolFee(uint48 fee_) public { + function test_protocolFee( + uint48 fee_ + ) public { uint48 fee = uint48(bound(fee_, 0, _MAX_FEE)); vm.prank(_OWNER); @@ -82,7 +84,9 @@ contract AtomicSetFeeTest is Test, Permit2User { assertEq(maxCuratorFee, 0); } - function test_maxReferrerFee(uint48 fee_) public { + function test_maxReferrerFee( + uint48 fee_ + ) public { uint48 fee = uint48(bound(fee_, 0, _MAX_FEE)); vm.prank(_OWNER); @@ -96,7 +100,9 @@ contract AtomicSetFeeTest is Test, Permit2User { assertEq(maxCuratorFee, 0); } - function test_curatorFee(uint48 fee_) public { + function test_curatorFee( + uint48 fee_ + ) public { uint48 fee = uint48(bound(fee_, 0, _MAX_FEE)); vm.prank(_OWNER); diff --git a/test/AtomicAuctionHouse/setProtocol.t.sol b/test/AtomicAuctionHouse/setProtocol.t.sol index ba81d493c..bf8806880 100644 --- a/test/AtomicAuctionHouse/setProtocol.t.sol +++ b/test/AtomicAuctionHouse/setProtocol.t.sol @@ -17,7 +17,9 @@ contract AtomicSetProtocolTest is AtomicAuctionHouseTest { // ===== Modifiers ===== // - modifier givenProtocolAddressIsSet(address protocol_) { + modifier givenProtocolAddressIsSet( + address protocol_ + ) { vm.prank(_OWNER); _auctionHouse.setProtocol(protocol_); _; diff --git a/test/AuctionHouse/collectPayment.t.sol b/test/AuctionHouse/collectPayment.t.sol index 671ab29ed..4eb3e4c9e 100644 --- a/test/AuctionHouse/collectPayment.t.sol +++ b/test/AuctionHouse/collectPayment.t.sol @@ -45,7 +45,9 @@ contract CollectPaymentTest is Test, Permit2User { _user = vm.addr(_userKey); } - modifier givenUserHasBalance(uint256 amount_) { + modifier givenUserHasBalance( + uint256 amount_ + ) { _quoteToken.mint(_user, amount_); _; } diff --git a/test/AuctionHouse/sendPayment.t.sol b/test/AuctionHouse/sendPayment.t.sol index 603ab59c4..4af2a14e1 100644 --- a/test/AuctionHouse/sendPayment.t.sol +++ b/test/AuctionHouse/sendPayment.t.sol @@ -71,7 +71,9 @@ contract SendPaymentTest is Test, Permit2User, WithSalts { _; } - modifier givenRouterHasBalance(uint256 amount_) { + modifier givenRouterHasBalance( + uint256 amount_ + ) { _quoteToken.mint(address(_auctionHouse), amount_); _; } diff --git a/test/AuctionHouse/sendPayout.t.sol b/test/AuctionHouse/sendPayout.t.sol index d22587766..737bc0585 100644 --- a/test/AuctionHouse/sendPayout.t.sol +++ b/test/AuctionHouse/sendPayout.t.sol @@ -102,7 +102,9 @@ contract SendPayoutTest is Test, Permit2User { _; } - modifier givenAuctionHouseHasBalance(uint256 amount_) { + modifier givenAuctionHouseHasBalance( + uint256 amount_ + ) { _payoutToken.mint(address(_auctionHouse), amount_); _; } diff --git a/test/BatchAuctionHouse/AuctionHouseTest.sol b/test/BatchAuctionHouse/AuctionHouseTest.sol index 6552921f6..170616ebb 100644 --- a/test/BatchAuctionHouse/AuctionHouseTest.sol +++ b/test/BatchAuctionHouse/AuctionHouseTest.sol @@ -150,11 +150,15 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal // ===== Helper Functions ===== // - function _scaleQuoteTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleQuoteTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return FixedPointMathLib.mulDivDown(amount_, 10 ** _quoteToken.decimals(), _BASE_SCALE); } - function _scaleBaseTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleBaseTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return FixedPointMathLib.mulDivDown(amount_, 10 ** _baseToken.decimals(), _BASE_SCALE); } @@ -186,12 +190,16 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal // ===== Modifiers ===== // - modifier givenLotHasCapacity(uint96 capacity_) { + modifier givenLotHasCapacity( + uint96 capacity_ + ) { _auctionParams.capacity = capacity_; _; } - function _setBaseTokenDecimals(uint8 decimals_) internal { + function _setBaseTokenDecimals( + uint8 decimals_ + ) internal { _baseToken = new MockFeeOnTransferERC20("Base Token", "BASE", decimals_); uint256 lotCapacity = _scaleBaseTokenAmount(_LOT_CAPACITY); @@ -203,19 +211,25 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _auctionParams.capacity = lotCapacity; } - modifier givenBaseTokenHasDecimals(uint8 decimals_) { + modifier givenBaseTokenHasDecimals( + uint8 decimals_ + ) { _setBaseTokenDecimals(decimals_); _; } - function _setQuoteTokenDecimals(uint8 decimals_) internal { + function _setQuoteTokenDecimals( + uint8 decimals_ + ) internal { _quoteToken = new MockFeeOnTransferERC20("Quote Token", "QUOTE", decimals_); // Update routing params _routingParams.quoteToken = address(_quoteToken); } - modifier givenQuoteTokenHasDecimals(uint8 decimals_) { + modifier givenQuoteTokenHasDecimals( + uint8 decimals_ + ) { _setQuoteTokenDecimals(decimals_); _; } @@ -346,7 +360,9 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _; } - modifier whenPermit2ApprovalIsProvided(uint256 amount_) { + modifier whenPermit2ApprovalIsProvided( + uint256 amount_ + ) { // Approve the Permit2 contract to spend the quote token vm.prank(_bidder); _quoteToken.approve(_permit2Address, type(uint256).max); @@ -373,22 +389,30 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _quoteToken.approve(address(_auctionHouse), amount_); } - modifier givenUserHasQuoteTokenBalance(uint256 amount_) { + modifier givenUserHasQuoteTokenBalance( + uint256 amount_ + ) { _sendUserQuoteTokenBalance(_bidder, amount_); _; } - modifier givenUserHasQuoteTokenAllowance(uint256 amount_) { + modifier givenUserHasQuoteTokenAllowance( + uint256 amount_ + ) { _approveUserQuoteTokenAllowance(_bidder, amount_); _; } - modifier givenSellerHasBaseTokenBalance(uint256 amount_) { + modifier givenSellerHasBaseTokenBalance( + uint256 amount_ + ) { _baseToken.mint(_SELLER, amount_); _; } - modifier givenSellerHasBaseTokenAllowance(uint256 amount_) { + modifier givenSellerHasBaseTokenAllowance( + uint256 amount_ + ) { vm.prank(_SELLER); _baseToken.approve(address(_auctionHouse), amount_); _; @@ -429,12 +453,16 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _; } - modifier givenCallbackHasBaseTokenBalance(uint256 amount_) { + modifier givenCallbackHasBaseTokenBalance( + uint256 amount_ + ) { _baseToken.mint(address(_callback), amount_); _; } - modifier givenCallbackHasBaseTokenAllowance(uint256 amount_) { + modifier givenCallbackHasBaseTokenAllowance( + uint256 amount_ + ) { vm.prank(address(_callback)); _baseToken.approve(address(_auctionHouse), amount_); _; @@ -517,7 +545,9 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _; } - function _setMaxReferrerFee(uint24 fee_) internal { + function _setMaxReferrerFee( + uint24 fee_ + ) internal { vm.prank(_OWNER); _auctionHouse.setFee(_auctionModuleKeycode, IFeeManager.FeeType.MaxReferrer, fee_); _maxReferrerFeePercentActual = fee_; @@ -528,12 +558,16 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _; } - function _setReferrerFee(uint24 fee_) internal { + function _setReferrerFee( + uint24 fee_ + ) internal { _referrerFeePercentActual = fee_; _routingParams.referrerFee = fee_; } - modifier givenReferrerFee(uint24 fee_) { + modifier givenReferrerFee( + uint24 fee_ + ) { _setReferrerFee(fee_); _; } @@ -543,7 +577,9 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _; } - function _setCuratorFee(uint24 fee_) internal { + function _setCuratorFee( + uint24 fee_ + ) internal { vm.prank(_CURATOR); _auctionHouse.setCuratorFee(_auctionModuleKeycode, fee_); _curatorFeePercentActual = fee_; @@ -562,7 +598,9 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _; } - function _setProtocolFee(uint24 fee_) internal { + function _setProtocolFee( + uint24 fee_ + ) internal { vm.prank(_OWNER); _auctionHouse.setFee(_auctionModuleKeycode, IFeeManager.FeeType.Protocol, fee_); _protocolFeePercentActual = fee_; @@ -573,7 +611,9 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _; } - modifier givenBidIsClaimed(uint64 bidId_) { + modifier givenBidIsClaimed( + uint64 bidId_ + ) { uint64[] memory bids = new uint64[](1); bids[0] = bidId_; @@ -587,7 +627,9 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _; } - modifier givenRecipientIsOnBaseTokenBlacklist(address recipient_) { + modifier givenRecipientIsOnBaseTokenBlacklist( + address recipient_ + ) { _baseToken.setBlacklist(recipient_, true); _; } @@ -597,14 +639,18 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal _; } - modifier givenRecipientIsOnQuoteTokenBlacklist(address recipient_) { + modifier givenRecipientIsOnQuoteTokenBlacklist( + address recipient_ + ) { _quoteToken.setBlacklist(recipient_, true); _; } // ===== Helpers ===== // - function _getLotRouting(uint96 lotId_) internal view returns (IAuctionHouse.Routing memory) { + function _getLotRouting( + uint96 lotId_ + ) internal view returns (IAuctionHouse.Routing memory) { ( address seller_, address baseToken_, @@ -630,7 +676,9 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal }); } - function _getLotFees(uint96 lotId_) internal view returns (IAuctionHouse.FeeData memory) { + function _getLotFees( + uint96 lotId_ + ) internal view returns (IAuctionHouse.FeeData memory) { ( address curator_, bool curated_, @@ -648,7 +696,9 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSal }); } - function _getLotData(uint96 lotId_) internal view returns (IAuction.Lot memory) { + function _getLotData( + uint96 lotId_ + ) internal view returns (IAuction.Lot memory) { return _auctionModule.getLot(lotId_); } } diff --git a/test/BatchAuctionHouse/claimBids.t.sol b/test/BatchAuctionHouse/claimBids.t.sol index 8536cc546..a3461c4d4 100644 --- a/test/BatchAuctionHouse/claimBids.t.sol +++ b/test/BatchAuctionHouse/claimBids.t.sol @@ -257,12 +257,16 @@ contract BatchClaimBidsTest is BatchAuctionHouseTest { _; } - modifier givenBidderTwoHasQuoteTokenBalance(uint256 amount_) { + modifier givenBidderTwoHasQuoteTokenBalance( + uint256 amount_ + ) { _quoteToken.mint(_BIDDER_TWO, amount_); _; } - modifier givenBidderTwoHasQuoteTokenAllowance(uint256 amount_) { + modifier givenBidderTwoHasQuoteTokenAllowance( + uint256 amount_ + ) { vm.prank(_BIDDER_TWO); _quoteToken.approve(address(_auctionHouse), amount_); _; diff --git a/test/BatchAuctionHouse/settle.t.sol b/test/BatchAuctionHouse/settle.t.sol index 1c6b8d9d2..d79a92307 100644 --- a/test/BatchAuctionHouse/settle.t.sol +++ b/test/BatchAuctionHouse/settle.t.sol @@ -314,7 +314,9 @@ contract BatchSettleTest is BatchAuctionHouseTest { _; } - modifier givenAuctionHouseHasQuoteTokenBalance(uint256 amount_) { + modifier givenAuctionHouseHasQuoteTokenBalance( + uint256 amount_ + ) { _quoteToken.mint(address(_auctionHouse), amount_); _; } diff --git a/test/callbacks/MockCallback.sol b/test/callbacks/MockCallback.sol index 8cc282189..1cda4931b 100644 --- a/test/callbacks/MockCallback.sol +++ b/test/callbacks/MockCallback.sol @@ -120,8 +120,7 @@ contract MockCallback is BaseCallback { if (prefunded_) { // Do nothing, as tokens have already been transferred - } - else { + } else { if (onPurchaseMultiplier > 0) { payout_ = uint96(uint256(payout_) * onPurchaseMultiplier / 100e2); } @@ -159,43 +158,63 @@ contract MockCallback is BaseCallback { lotSettled[lotId_] = true; } - function setOnCreateReverts(bool reverts_) external { + function setOnCreateReverts( + bool reverts_ + ) external { onCreateReverts = reverts_; } - function setOnCancelReverts(bool reverts_) external { + function setOnCancelReverts( + bool reverts_ + ) external { onCancelReverts = reverts_; } - function setOnCurateReverts(bool reverts_) external { + function setOnCurateReverts( + bool reverts_ + ) external { onCurateReverts = reverts_; } - function setOnPurchaseReverts(bool reverts_) external { + function setOnPurchaseReverts( + bool reverts_ + ) external { onPurchaseReverts = reverts_; } - function setOnBidReverts(bool reverts_) external { + function setOnBidReverts( + bool reverts_ + ) external { onBidReverts = reverts_; } - function setOnSettleReverts(bool reverts_) external { + function setOnSettleReverts( + bool reverts_ + ) external { onSettleReverts = reverts_; } - function setOnCreateMultiplier(uint48 multiplier_) external { + function setOnCreateMultiplier( + uint48 multiplier_ + ) external { onCreateMultiplier = multiplier_; } - function setOnCurateMultiplier(uint48 multiplier_) external { + function setOnCurateMultiplier( + uint48 multiplier_ + ) external { onCurateMultiplier = multiplier_; } - function setOnPurchaseMultiplier(uint48 multiplier_) external { + function setOnPurchaseMultiplier( + uint48 multiplier_ + ) external { onPurchaseMultiplier = multiplier_; } - function setAllowlistEnabled(bool enabled_) external { + function setAllowlistEnabled( + bool enabled_ + ) external { allowlistEnabled = enabled_; } diff --git a/test/lib/BidEncoding.t.sol b/test/lib/BidEncoding.t.sol index 8230ec5a3..881da743f 100644 --- a/test/lib/BidEncoding.t.sol +++ b/test/lib/BidEncoding.t.sol @@ -34,7 +34,9 @@ contract BidEncodingTest is Test { // ========== decode ========== // - function testFuzz_decode(bytes32 key) public { + function testFuzz_decode( + bytes32 key + ) public { (uint64 bidId, uint96 amountIn, uint96 amountOut) = key.decode(); uint64 eId = uint64(uint256(key >> 192)); diff --git a/test/lib/ECIES/decrypt.t.sol b/test/lib/ECIES/decrypt.t.sol index db265703b..4ca035965 100644 --- a/test/lib/ECIES/decrypt.t.sol +++ b/test/lib/ECIES/decrypt.t.sol @@ -33,7 +33,9 @@ contract ECIESDecryptTest is Test { ECIES.decrypt(ciphertext, ciphertextPubKey, recipientPrivateKey, salt); } - function testRevert_privateKeyTooLarge(uint256 privateKey_) public { + function testRevert_privateKeyTooLarge( + uint256 privateKey_ + ) public { vm.assume(privateKey_ >= ECIES.GROUP_ORDER); // Setup encryption parameters diff --git a/test/lib/ECIES/encrypt.t.sol b/test/lib/ECIES/encrypt.t.sol index 49da37227..179ed8649 100644 --- a/test/lib/ECIES/encrypt.t.sol +++ b/test/lib/ECIES/encrypt.t.sol @@ -33,7 +33,9 @@ contract ECIESEncryptTest is Test { ECIES.encrypt(message, recipientPubKey, privateKey, salt); } - function testRevert_privateKeyTooLarge(uint256 privateKey_) public { + function testRevert_privateKeyTooLarge( + uint256 privateKey_ + ) public { vm.assume(privateKey_ >= ECIES.GROUP_ORDER); // Setup encryption parameters diff --git a/test/lib/ECIES/isValid.t.sol b/test/lib/ECIES/isValid.t.sol index 728e1142f..3cc913ad4 100644 --- a/test/lib/ECIES/isValid.t.sol +++ b/test/lib/ECIES/isValid.t.sol @@ -59,22 +59,30 @@ contract ECIESisValidTest is Test { _; } - modifier whenXLessThanFieldModulus(uint256 x) { + modifier whenXLessThanFieldModulus( + uint256 x + ) { if (x >= FIELD_MODULUS) return; _; } - modifier whenYLessThanFieldModulus(uint256 y) { + modifier whenYLessThanFieldModulus( + uint256 y + ) { if (y >= FIELD_MODULUS) return; _; } - modifier whenXGreaterThanOrEqualToFieldModulus(uint256 x) { + modifier whenXGreaterThanOrEqualToFieldModulus( + uint256 x + ) { if (x < FIELD_MODULUS) return; _; } - modifier whenYGreaterThanOrEqualToFieldModulus(uint256 y) { + modifier whenYGreaterThanOrEqualToFieldModulus( + uint256 y + ) { if (y < FIELD_MODULUS) return; _; } diff --git a/test/lib/mocks/MockFeeOnTransferERC20.sol b/test/lib/mocks/MockFeeOnTransferERC20.sol index d57153894..6789d01e6 100644 --- a/test/lib/mocks/MockFeeOnTransferERC20.sol +++ b/test/lib/mocks/MockFeeOnTransferERC20.sol @@ -15,11 +15,15 @@ contract MockFeeOnTransferERC20 is MockERC20 { uint8 decimals_ ) MockERC20(name_, symbol_, decimals_) {} - function setTransferFee(uint256 transferFee_) external { + function setTransferFee( + uint256 transferFee_ + ) external { transferFee = transferFee_; } - function setRevertOnZero(bool revertOnZero_) external { + function setRevertOnZero( + bool revertOnZero_ + ) external { revertOnZero = revertOnZero_; } diff --git a/test/modules/Auction/MockAtomicAuctionModule.sol b/test/modules/Auction/MockAtomicAuctionModule.sol index 368a5b2d1..086d88243 100644 --- a/test/modules/Auction/MockAtomicAuctionModule.sol +++ b/test/modules/Auction/MockAtomicAuctionModule.sol @@ -18,7 +18,9 @@ contract MockAtomicAuctionModule is AtomicAuctionModule { mapping(uint96 lotId => bool isCancelled) public cancelled; - constructor(address _owner) AuctionModule(_owner) { + constructor( + address _owner + ) AuctionModule(_owner) { minAuctionDuration = 1 days; } @@ -28,7 +30,9 @@ contract MockAtomicAuctionModule is AtomicAuctionModule { function _auction(uint96, Lot memory, bytes memory) internal virtual override {} - function _cancelAuction(uint96 id_) internal override { + function _cancelAuction( + uint96 id_ + ) internal override { cancelled[id_] = true; } @@ -64,7 +68,9 @@ contract MockAtomicAuctionModule is AtomicAuctionModule { payoutData[lotId_] = multiplier_; } - function setPurchaseReverts(bool reverts_) external virtual { + function setPurchaseReverts( + bool reverts_ + ) external virtual { purchaseReverts = reverts_; } @@ -72,7 +78,11 @@ contract MockAtomicAuctionModule is AtomicAuctionModule { function priceFor(uint96 lotId_, uint256 payout_) external view override returns (uint256) {} - function maxPayout(uint96 lotId_) external view override returns (uint256) {} + function maxPayout( + uint96 lotId_ + ) external view override returns (uint256) {} - function maxAmountAccepted(uint96 lotId_) external view override returns (uint256) {} + function maxAmountAccepted( + uint96 lotId_ + ) external view override returns (uint256) {} } diff --git a/test/modules/Auction/MockBatchAuctionModule.sol b/test/modules/Auction/MockBatchAuctionModule.sol index 2f8ef5aa7..18840d055 100644 --- a/test/modules/Auction/MockBatchAuctionModule.sol +++ b/test/modules/Auction/MockBatchAuctionModule.sol @@ -49,7 +49,9 @@ contract MockBatchAuctionModule is BatchAuctionModule { mapping(uint96 => bool) public settlementFinished; - constructor(address _owner) AuctionModule(_owner) { + constructor( + address _owner + ) AuctionModule(_owner) { minAuctionDuration = 1 days; dedicatedSettlePeriod = 1 days; } @@ -60,7 +62,9 @@ contract MockBatchAuctionModule is BatchAuctionModule { function _auction(uint96, Lot memory, bytes memory) internal virtual override {} - function _cancelAuction(uint96 id_) internal override {} + function _cancelAuction( + uint96 id_ + ) internal override {} function _bid( uint96 lotId_, @@ -175,7 +179,9 @@ contract MockBatchAuctionModule is BatchAuctionModule { return (lotData[lotId_].purchased, lotData[lotId_].sold, settlementFinished[lotId_], ""); } - function _abort(uint96 lotId_) internal override { + function _abort( + uint96 lotId_ + ) internal override { // Update status lotStatus[lotId_] = LotStatus.Settled; } @@ -209,21 +215,27 @@ contract MockBatchAuctionModule is BatchAuctionModule { } } - function _revertIfLotSettled(uint96 lotId_) internal view virtual override { + function _revertIfLotSettled( + uint96 lotId_ + ) internal view virtual override { // Check that the lot has not been settled if (lotStatus[lotId_] == LotStatus.Settled) { revert IAuction.Auction_LotNotActive(lotId_); } } - function _revertIfLotNotSettled(uint96 lotId_) internal view virtual override { + function _revertIfLotNotSettled( + uint96 lotId_ + ) internal view virtual override { // Check that the lot has been settled if (lotStatus[lotId_] != LotStatus.Settled) { revert IAuction.Auction_InvalidParams(); } } - function getNumBids(uint96) external view override returns (uint256) { + function getNumBids( + uint96 + ) external view override returns (uint256) { return bidIds.length; } diff --git a/test/modules/Auction/auction.t.sol b/test/modules/Auction/auction.t.sol index b9722c7ad..4bcbd8024 100644 --- a/test/modules/Auction/auction.t.sol +++ b/test/modules/Auction/auction.t.sol @@ -83,7 +83,9 @@ contract AuctionTest is Test, Permit2User { // [X] creates the auction lot with a custom duration // [X] creates the auction lot when the start time is in the future - function testReverts_whenStartTimeIsInThePast(uint48 timestamp_) external { + function testReverts_whenStartTimeIsInThePast( + uint48 timestamp_ + ) external { console2.log("block.timestamp", block.timestamp); uint48 start = uint48(bound(timestamp_, 1, block.timestamp - 1)); @@ -99,7 +101,9 @@ contract AuctionTest is Test, Permit2User { _auctionHouse.auction(_routingParams, _auctionParams, _infoHash); } - function testReverts_whenDurationIsLessThanMinimum(uint48 duration_) external { + function testReverts_whenDurationIsLessThanMinimum( + uint48 duration_ + ) external { uint48 duration = uint48(bound(duration_, 0, _mockAuctionModule.minAuctionDuration() - 1)); // Update auction params @@ -151,7 +155,9 @@ contract AuctionTest is Test, Permit2User { assertEq(lot.conclusion, lot.start + _auctionParams.duration); } - function test_success_withCustomDuration(uint48 duration_) external { + function test_success_withCustomDuration( + uint48 duration_ + ) external { uint48 duration = uint48(bound(duration_, _mockAuctionModule.minAuctionDuration(), 1 days)); // Update auction params @@ -164,7 +170,9 @@ contract AuctionTest is Test, Permit2User { assertEq(lot.conclusion, lot.start + _auctionParams.duration); } - function test_success_withFutureStartTime(uint48 timestamp_) external { + function test_success_withFutureStartTime( + uint48 timestamp_ + ) external { uint48 start = uint48(bound(timestamp_, block.timestamp + 1, block.timestamp + 1 days)); // Update auction params diff --git a/test/modules/Auction/cancel.t.sol b/test/modules/Auction/cancel.t.sol index aac435d7b..961d925ce 100644 --- a/test/modules/Auction/cancel.t.sol +++ b/test/modules/Auction/cancel.t.sol @@ -113,7 +113,9 @@ contract CancelTest is Test, Permit2User { _mockAuctionModule.cancelAuction(_lotId); } - function testReverts_conclusion(uint48 conclusionElapsed_) external whenLotIsCreated { + function testReverts_conclusion( + uint48 conclusionElapsed_ + ) external whenLotIsCreated { uint48 conclusionElapsed = uint48(bound(conclusionElapsed_, 0, 1 days)); // Warp to the conclusion diff --git a/test/modules/Condenser/MockCondenserModule.sol b/test/modules/Condenser/MockCondenserModule.sol index 005f498d0..8335c5b4f 100644 --- a/test/modules/Condenser/MockCondenserModule.sol +++ b/test/modules/Condenser/MockCondenserModule.sol @@ -11,7 +11,9 @@ import {MockDerivativeModule} from "../derivatives/mocks/MockDerivativeModule.so import {CondenserModule} from "../../../src/modules/Condenser.sol"; contract MockCondenserModule is CondenserModule { - constructor(address _owner) Module(_owner) {} + constructor( + address _owner + ) Module(_owner) {} function VEECODE() public pure virtual override returns (Veecode) { return wrapVeecode(toKeycode("COND"), 1); diff --git a/test/modules/Modules/Keycode.t.sol b/test/modules/Modules/Keycode.t.sol index 3a85c114d..d7040b6e1 100644 --- a/test/modules/Modules/Keycode.t.sol +++ b/test/modules/Modules/Keycode.t.sol @@ -120,7 +120,9 @@ contract KeycodeTest is Test { ensureValidVeecode(t1Veecode); } - function testRevert_ensureValidVeecode_invalidVersion(uint8 version_) external { + function testRevert_ensureValidVeecode_invalidVersion( + uint8 version_ + ) external { // Restrict the version to outside of 0-99 vm.assume(!(version_ >= 0 && version_ <= 99)); diff --git a/test/modules/Modules/MockModule.sol b/test/modules/Modules/MockModule.sol index 4c145daa0..04f04363e 100644 --- a/test/modules/Modules/MockModule.sol +++ b/test/modules/Modules/MockModule.sol @@ -5,7 +5,9 @@ pragma solidity 0.8.19; import {Module, Veecode, toKeycode, wrapVeecode} from "../../../src/modules/Modules.sol"; contract MockModuleV1 is Module { - constructor(address _owner) Module(_owner) {} + constructor( + address _owner + ) Module(_owner) {} function VEECODE() public pure override returns (Veecode) { return wrapVeecode(toKeycode("MOCK"), 1); @@ -23,7 +25,9 @@ contract MockModuleV1 is Module { } contract MockModuleV2 is Module { - constructor(address _owner) Module(_owner) {} + constructor( + address _owner + ) Module(_owner) {} function VEECODE() public pure override returns (Veecode) { return wrapVeecode(toKeycode("MOCK"), 2); @@ -31,7 +35,9 @@ contract MockModuleV2 is Module { } contract MockModuleV3 is Module { - constructor(address _owner) Module(_owner) {} + constructor( + address _owner + ) Module(_owner) {} function VEECODE() public pure override returns (Veecode) { return wrapVeecode(toKeycode("MOCK"), 3); @@ -39,7 +45,9 @@ contract MockModuleV3 is Module { } contract MockModuleV0 is Module { - constructor(address _owner) Module(_owner) {} + constructor( + address _owner + ) Module(_owner) {} function VEECODE() public pure override returns (Veecode) { return wrapVeecode(toKeycode("MOCK"), 0); @@ -47,7 +55,9 @@ contract MockModuleV0 is Module { } contract MockInvalidModule is Module { - constructor(address _owner) Module(_owner) {} + constructor( + address _owner + ) Module(_owner) {} function VEECODE() public pure override returns (Veecode) { return wrapVeecode(toKeycode("INVA_"), 100); diff --git a/test/modules/Modules/MockWithModules.sol b/test/modules/Modules/MockWithModules.sol index a22f6a034..fac45a010 100644 --- a/test/modules/Modules/MockWithModules.sol +++ b/test/modules/Modules/MockWithModules.sol @@ -7,9 +7,13 @@ import {WithModules, Veecode} from "../../../src/modules/Modules.sol"; import {MockModuleV1} from "./MockModule.sol"; contract MockWithModules is WithModules { - constructor(address _owner) WithModules(_owner) {} + constructor( + address _owner + ) WithModules(_owner) {} - function callProhibited(Veecode veecode_) external view returns (bool) { + function callProhibited( + Veecode veecode_ + ) external view returns (bool) { MockModuleV1 module = MockModuleV1(_getModuleIfInstalled(veecode_)); return module.prohibited(); diff --git a/test/modules/auctions/EMP/EMPTest.sol b/test/modules/auctions/EMP/EMPTest.sol index 872e8047a..cabc79930 100644 --- a/test/modules/auctions/EMP/EMPTest.sol +++ b/test/modules/auctions/EMP/EMPTest.sol @@ -85,7 +85,9 @@ abstract contract EmpTest is Test, Permit2User { // ======== Modifiers ======== // - function _setQuoteTokenDecimals(uint8 decimals_) internal { + function _setQuoteTokenDecimals( + uint8 decimals_ + ) internal { _quoteTokenDecimals = decimals_; _auctionDataParams.minPrice = _scaleQuoteTokenAmount(_MIN_PRICE); @@ -94,28 +96,38 @@ abstract contract EmpTest is Test, Permit2User { _auctionParams.implParams = abi.encode(_auctionDataParams); } - modifier givenQuoteTokenDecimals(uint8 decimals_) { + modifier givenQuoteTokenDecimals( + uint8 decimals_ + ) { _setQuoteTokenDecimals(decimals_); _; } - function _setBaseTokenDecimals(uint8 decimals_) internal { + function _setBaseTokenDecimals( + uint8 decimals_ + ) internal { _baseTokenDecimals = decimals_; _auctionParams.capacity = _scaleBaseTokenAmount(_LOT_CAPACITY); } - modifier givenBaseTokenDecimals(uint8 decimals_) { + modifier givenBaseTokenDecimals( + uint8 decimals_ + ) { _setBaseTokenDecimals(decimals_); _; } - modifier givenLotCapacity(uint256 capacity_) { + modifier givenLotCapacity( + uint256 capacity_ + ) { _auctionParams.capacity = capacity_; _; } - modifier givenMinimumPrice(uint256 price_) { + modifier givenMinimumPrice( + uint256 price_ + ) { _auctionDataParams.minPrice = price_; _auctionParams.implParams = abi.encode(_auctionDataParams); @@ -123,24 +135,32 @@ abstract contract EmpTest is Test, Permit2User { _; } - modifier givenStartTimestamp(uint48 start_) { + modifier givenStartTimestamp( + uint48 start_ + ) { _auctionParams.start = start_; _; } - modifier givenDuration(uint48 duration_) { + modifier givenDuration( + uint48 duration_ + ) { _auctionParams.duration = duration_; _; } - modifier givenMinimumFillPercentage(uint24 percentage_) { + modifier givenMinimumFillPercentage( + uint24 percentage_ + ) { _auctionDataParams.minFillPercent = percentage_; _auctionParams.implParams = abi.encode(_auctionDataParams); _; } - modifier givenMinimumBidSize(uint256 amount_) { + modifier givenMinimumBidSize( + uint256 amount_ + ) { _auctionDataParams.minBidSize = amount_; _auctionParams.implParams = abi.encode(_auctionDataParams); @@ -174,7 +194,9 @@ abstract contract EmpTest is Test, Permit2User { _; } - function _formatBid(uint256 amountOut_) internal pure returns (uint256) { + function _formatBid( + uint256 amountOut_ + ) internal pure returns (uint256) { uint256 formattedAmountOut; { uint128 subtracted; @@ -258,7 +280,9 @@ abstract contract EmpTest is Test, Permit2User { _; } - modifier givenBidIsRefunded(uint64 bidId_) { + modifier givenBidIsRefunded( + uint64 bidId_ + ) { // Find bid index // Get number of bids from module @@ -283,7 +307,9 @@ abstract contract EmpTest is Test, Permit2User { _; } - modifier givenBidIsClaimed(uint64 bidId_) { + modifier givenBidIsClaimed( + uint64 bidId_ + ) { uint64[] memory bidIds = new uint64[](1); bidIds[0] = bidId_; @@ -367,11 +393,15 @@ abstract contract EmpTest is Test, Permit2User { // ======== Internal Functions ======== // - function _scaleQuoteTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleQuoteTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return Math.fullMulDiv(amount_, 10 ** _quoteTokenDecimals, _BASE_SCALE); } - function _scaleBaseTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleBaseTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return Math.fullMulDiv(amount_, 10 ** _baseTokenDecimals, _BASE_SCALE); } @@ -406,7 +436,9 @@ abstract contract EmpTest is Test, Permit2User { }); } - function _getAuctionLot(uint96 lotId_) internal view returns (IAuction.Lot memory) { + function _getAuctionLot( + uint96 lotId_ + ) internal view returns (IAuction.Lot memory) { return _module.getLot(lotId_); } diff --git a/test/modules/auctions/EMP/cancelAuction.t.sol b/test/modules/auctions/EMP/cancelAuction.t.sol index a84890b4a..28886be43 100644 --- a/test/modules/auctions/EMP/cancelAuction.t.sol +++ b/test/modules/auctions/EMP/cancelAuction.t.sol @@ -44,7 +44,9 @@ contract EmpCancelAuctionTest is EmpTest { _cancelAuctionLot(); } - function test_auctionConcluded_reverts(uint48 conclusionElapsed_) public givenLotIsCreated { + function test_auctionConcluded_reverts( + uint48 conclusionElapsed_ + ) public givenLotIsCreated { uint48 conclusionElapsed = uint48(bound(conclusionElapsed_, 0, 1 days)); // Warp to the conclusion diff --git a/test/modules/auctions/EMP/settle.t.sol b/test/modules/auctions/EMP/settle.t.sol index b6cd76c37..484befddc 100644 --- a/test/modules/auctions/EMP/settle.t.sol +++ b/test/modules/auctions/EMP/settle.t.sol @@ -1072,7 +1072,9 @@ contract EmpSettleTest is EmpTest { _; } - function _setSettlementComplete(bool complete_) internal { + function _setSettlementComplete( + bool complete_ + ) internal { _expectedSettlementComplete = complete_; } diff --git a/test/modules/auctions/FPB/FPBTest.sol b/test/modules/auctions/FPB/FPBTest.sol index 3727843a2..415b528a1 100644 --- a/test/modules/auctions/FPB/FPBTest.sol +++ b/test/modules/auctions/FPB/FPBTest.sol @@ -61,7 +61,9 @@ abstract contract FpbTest is Test, Permit2User { // ========== MODIFIERS ========== // - function _setQuoteTokenDecimals(uint8 decimals_) internal { + function _setQuoteTokenDecimals( + uint8 decimals_ + ) internal { _quoteTokenDecimals = decimals_; _fpbParams.price = _scaleQuoteTokenAmount(_PRICE); @@ -73,12 +75,16 @@ abstract contract FpbTest is Test, Permit2User { } } - modifier givenQuoteTokenDecimals(uint8 decimals_) { + modifier givenQuoteTokenDecimals( + uint8 decimals_ + ) { _setQuoteTokenDecimals(decimals_); _; } - function _setBaseTokenDecimals(uint8 decimals_) internal { + function _setBaseTokenDecimals( + uint8 decimals_ + ) internal { _baseTokenDecimals = decimals_; if (!_auctionParams.capacityInQuote) { @@ -86,26 +92,36 @@ abstract contract FpbTest is Test, Permit2User { } } - modifier givenBaseTokenDecimals(uint8 decimals_) { + modifier givenBaseTokenDecimals( + uint8 decimals_ + ) { _setBaseTokenDecimals(decimals_); _; } - function _setCapacity(uint256 capacity_) internal { + function _setCapacity( + uint256 capacity_ + ) internal { _auctionParams.capacity = capacity_; } - modifier givenLotCapacity(uint256 capacity_) { + modifier givenLotCapacity( + uint256 capacity_ + ) { _setCapacity(capacity_); _; } - modifier givenStartTimestamp(uint48 start_) { + modifier givenStartTimestamp( + uint48 start_ + ) { _auctionParams.start = start_; _; } - modifier givenDuration(uint48 duration_) { + modifier givenDuration( + uint48 duration_ + ) { _auctionParams.duration = duration_; _; } @@ -120,22 +136,30 @@ abstract contract FpbTest is Test, Permit2User { _; } - function _setPrice(uint256 price_) internal { + function _setPrice( + uint256 price_ + ) internal { _fpbParams.price = price_; _auctionParams.implParams = abi.encode(_fpbParams); } - modifier givenPrice(uint256 price_) { + modifier givenPrice( + uint256 price_ + ) { _setPrice(price_); _; } - function _setMinFillPercent(uint24 minFillPercent_) internal { + function _setMinFillPercent( + uint24 minFillPercent_ + ) internal { _fpbParams.minFillPercent = minFillPercent_; _auctionParams.implParams = abi.encode(_fpbParams); } - modifier givenMinFillPercent(uint24 minFillPercent_) { + modifier givenMinFillPercent( + uint24 minFillPercent_ + ) { _setMinFillPercent(minFillPercent_); _; } @@ -168,12 +192,16 @@ abstract contract FpbTest is Test, Permit2User { _; } - function _createBid(uint256 amount_) internal { + function _createBid( + uint256 amount_ + ) internal { vm.prank(address(_auctionHouse)); _module.bid(_lotId, _BIDDER, _REFERRER, amount_, abi.encode("")); } - modifier givenBidIsCreated(uint256 amount_) { + modifier givenBidIsCreated( + uint256 amount_ + ) { _createBid(amount_); _; } @@ -208,12 +236,16 @@ abstract contract FpbTest is Test, Permit2User { _; } - function _refundBid(uint64 bidId_) internal returns (uint256 refundAmount) { + function _refundBid( + uint64 bidId_ + ) internal returns (uint256 refundAmount) { vm.prank(address(_auctionHouse)); return _module.refundBid(_lotId, bidId_, 0, _BIDDER); } - modifier givenBidIsRefunded(uint64 bidId_) { + modifier givenBidIsRefunded( + uint64 bidId_ + ) { _refundBid(bidId_); _; } @@ -228,7 +260,9 @@ abstract contract FpbTest is Test, Permit2User { return _module.claimBids(_lotId, bidIds); } - modifier givenBidIsClaimed(uint64 bidId_) { + modifier givenBidIsClaimed( + uint64 bidId_ + ) { uint64[] memory bidIds = new uint64[](1); bidIds[0] = bidId_; @@ -239,11 +273,15 @@ abstract contract FpbTest is Test, Permit2User { // ======== Internal Functions ======== // - function _scaleQuoteTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleQuoteTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return Math.mulDivDown(amount_, 10 ** _quoteTokenDecimals, _BASE_SCALE); } - function _scaleBaseTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleBaseTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return Math.mulDivDown(amount_, 10 ** _baseTokenDecimals, _BASE_SCALE); } } diff --git a/test/modules/auctions/FPB/bid.t.sol b/test/modules/auctions/FPB/bid.t.sol index f4943e9bf..9369f5e1f 100644 --- a/test/modules/auctions/FPB/bid.t.sol +++ b/test/modules/auctions/FPB/bid.t.sol @@ -528,7 +528,9 @@ contract FpbBidTest is FpbTest { ); } - function test_partialFill_auctionPriceFuzz(uint256 price_) public { + function test_partialFill_auctionPriceFuzz( + uint256 price_ + ) public { // Given that the capacity is set, there is a maximum value to the price before the bidAmount hits uint96 // 11e18 * price / 1e18 <= type(uint96).max // price <= type(uint96).max / 10e18 diff --git a/test/modules/auctions/FPB/cancelAuction.t.sol b/test/modules/auctions/FPB/cancelAuction.t.sol index ea91d5b16..8ad00054a 100644 --- a/test/modules/auctions/FPB/cancelAuction.t.sol +++ b/test/modules/auctions/FPB/cancelAuction.t.sol @@ -42,7 +42,9 @@ contract FpbCancelAuctionTest is FpbTest { _cancelAuctionLot(); } - function test_auctionConcluded_reverts(uint48 conclusionElapsed_) public givenLotIsCreated { + function test_auctionConcluded_reverts( + uint48 conclusionElapsed_ + ) public givenLotIsCreated { uint48 conclusionElapsed = uint48(bound(conclusionElapsed_, 0, 1 days)); // Warp to the conclusion diff --git a/test/modules/auctions/FPS/FPSTest.sol b/test/modules/auctions/FPS/FPSTest.sol index c201d6875..106e18726 100644 --- a/test/modules/auctions/FPS/FPSTest.sol +++ b/test/modules/auctions/FPS/FPSTest.sol @@ -62,7 +62,9 @@ abstract contract FpsTest is Test, Permit2User { // ========== MODIFIERS ========== // - function _setQuoteTokenDecimals(uint8 decimals_) internal { + function _setQuoteTokenDecimals( + uint8 decimals_ + ) internal { _quoteTokenDecimals = decimals_; _fpaParams.price = _scaleQuoteTokenAmount(_PRICE); @@ -74,12 +76,16 @@ abstract contract FpsTest is Test, Permit2User { } } - modifier givenQuoteTokenDecimals(uint8 decimals_) { + modifier givenQuoteTokenDecimals( + uint8 decimals_ + ) { _setQuoteTokenDecimals(decimals_); _; } - function _setBaseTokenDecimals(uint8 decimals_) internal { + function _setBaseTokenDecimals( + uint8 decimals_ + ) internal { _baseTokenDecimals = decimals_; if (!_auctionParams.capacityInQuote) { @@ -87,22 +93,30 @@ abstract contract FpsTest is Test, Permit2User { } } - modifier givenBaseTokenDecimals(uint8 decimals_) { + modifier givenBaseTokenDecimals( + uint8 decimals_ + ) { _setBaseTokenDecimals(decimals_); _; } - modifier givenLotCapacity(uint256 capacity_) { + modifier givenLotCapacity( + uint256 capacity_ + ) { _auctionParams.capacity = capacity_; _; } - modifier givenStartTimestamp(uint48 start_) { + modifier givenStartTimestamp( + uint48 start_ + ) { _auctionParams.start = start_; _; } - modifier givenDuration(uint48 duration_) { + modifier givenDuration( + uint48 duration_ + ) { _auctionParams.duration = duration_; _; } @@ -122,18 +136,24 @@ abstract contract FpsTest is Test, Permit2User { _; } - modifier givenPrice(uint256 price_) { + modifier givenPrice( + uint256 price_ + ) { _fpaParams.price = price_; _auctionParams.implParams = abi.encode(_fpaParams); _; } - function _setMaxPayout(uint24 maxPayout_) internal { + function _setMaxPayout( + uint24 maxPayout_ + ) internal { _fpaParams.maxPayoutPercent = maxPayout_; _auctionParams.implParams = abi.encode(_fpaParams); } - modifier givenMaxPayout(uint24 maxPayout_) { + modifier givenMaxPayout( + uint24 maxPayout_ + ) { _setMaxPayout(maxPayout_); _; } @@ -177,15 +197,21 @@ abstract contract FpsTest is Test, Permit2User { // ======== Internal Functions ======== // - function _scaleQuoteTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleQuoteTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return Math.mulDivDown(amount_, 10 ** _quoteTokenDecimals, _BASE_SCALE); } - function _scaleBaseTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleBaseTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return Math.mulDivDown(amount_, 10 ** _baseTokenDecimals, _BASE_SCALE); } - function _getAuctionLot(uint96 lotId_) internal view returns (IAuction.Lot memory) { + function _getAuctionLot( + uint96 lotId_ + ) internal view returns (IAuction.Lot memory) { return _module.getLot(lotId_); } diff --git a/test/modules/auctions/FPS/auction.t.sol b/test/modules/auctions/FPS/auction.t.sol index 84771e550..9818db714 100644 --- a/test/modules/auctions/FPS/auction.t.sol +++ b/test/modules/auctions/FPS/auction.t.sol @@ -71,7 +71,9 @@ contract FpsCreateAuctionTest is FpsTest { _createAuctionLot(); } - function test_maxPayoutPercentIsLessThanMinimum_reverts(uint24 maxPayout_) public { + function test_maxPayoutPercentIsLessThanMinimum_reverts( + uint24 maxPayout_ + ) public { uint24 maxPayout = uint24(bound(maxPayout_, 0, 1e2 - 1)); _setMaxPayout(maxPayout); @@ -83,7 +85,9 @@ contract FpsCreateAuctionTest is FpsTest { _createAuctionLot(); } - function test_maxPayoutPercentIsGreaterThanMaximum_reverts(uint24 maxPayout_) public { + function test_maxPayoutPercentIsGreaterThanMaximum_reverts( + uint24 maxPayout_ + ) public { uint24 maxPayout = uint24(bound(maxPayout_, 100e2 + 1, type(uint24).max)); _setMaxPayout(maxPayout); @@ -95,7 +99,9 @@ contract FpsCreateAuctionTest is FpsTest { _createAuctionLot(); } - function test_maxPayoutPercent_fuzz(uint24 maxPayout_) public { + function test_maxPayoutPercent_fuzz( + uint24 maxPayout_ + ) public { uint24 maxPayout = uint24(bound(maxPayout_, 1e2, 100e2)); _setMaxPayout(maxPayout); diff --git a/test/modules/auctions/FPS/cancelAuction.t.sol b/test/modules/auctions/FPS/cancelAuction.t.sol index 4596decf1..1405e22e8 100644 --- a/test/modules/auctions/FPS/cancelAuction.t.sol +++ b/test/modules/auctions/FPS/cancelAuction.t.sol @@ -37,7 +37,9 @@ contract FpsCancelAuctionTest is FpsTest { _cancelAuctionLot(); } - function test_auctionConcluded_reverts(uint48 conclusionElapsed_) public givenLotIsCreated { + function test_auctionConcluded_reverts( + uint48 conclusionElapsed_ + ) public givenLotIsCreated { uint48 conclusionElapsed = uint48(bound(conclusionElapsed_, 0, 1 days)); // Warp to the conclusion diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index 38c484d9f..f1a3fac71 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -72,7 +72,9 @@ abstract contract GdaTest is Test, Permit2User { // ========== MODIFIERS ========== // - function _setQuoteTokenDecimals(uint8 decimals_) internal { + function _setQuoteTokenDecimals( + uint8 decimals_ + ) internal { _quoteTokenDecimals = decimals_; _gdaParams.equilibriumPrice = _scaleQuoteTokenAmount(_INITIAL_PRICE); @@ -81,33 +83,45 @@ abstract contract GdaTest is Test, Permit2User { _auctionParams.implParams = abi.encode(_gdaParams); } - modifier givenQuoteTokenDecimals(uint8 decimals_) { + modifier givenQuoteTokenDecimals( + uint8 decimals_ + ) { _setQuoteTokenDecimals(decimals_); _; } - function _setBaseTokenDecimals(uint8 decimals_) internal { + function _setBaseTokenDecimals( + uint8 decimals_ + ) internal { _baseTokenDecimals = decimals_; _auctionParams.capacity = _scaleBaseTokenAmount(_LOT_CAPACITY); } - modifier givenBaseTokenDecimals(uint8 decimals_) { + modifier givenBaseTokenDecimals( + uint8 decimals_ + ) { _setBaseTokenDecimals(decimals_); _; } - modifier givenLotCapacity(uint256 capacity_) { + modifier givenLotCapacity( + uint256 capacity_ + ) { _auctionParams.capacity = capacity_; _; } - modifier givenStartTimestamp(uint48 start_) { + modifier givenStartTimestamp( + uint48 start_ + ) { _auctionParams.start = start_; _; } - modifier givenDuration(uint48 duration_) { + modifier givenDuration( + uint48 duration_ + ) { _auctionParams.duration = duration_; _; } @@ -127,19 +141,25 @@ abstract contract GdaTest is Test, Permit2User { _; } - modifier givenEquilibriumPrice(uint128 price_) { + modifier givenEquilibriumPrice( + uint128 price_ + ) { _gdaParams.equilibriumPrice = uint256(price_); _auctionParams.implParams = abi.encode(_gdaParams); _; } - modifier givenMinPrice(uint128 minPrice_) { + modifier givenMinPrice( + uint128 minPrice_ + ) { _gdaParams.minimumPrice = uint256(minPrice_); _auctionParams.implParams = abi.encode(_gdaParams); _; } - modifier givenMinIsHalfPrice(uint128 price_) { + modifier givenMinIsHalfPrice( + uint128 price_ + ) { _gdaParams.minimumPrice = (uint256(price_) / 2) + (price_ % 2 == 0 ? 0 : 1); _auctionParams.implParams = abi.encode(_gdaParams); _; @@ -187,13 +207,17 @@ abstract contract GdaTest is Test, Permit2User { _; } - modifier givenDecayTarget(uint256 decayTarget_) { + modifier givenDecayTarget( + uint256 decayTarget_ + ) { _gdaParams.decayTarget = decayTarget_; _auctionParams.implParams = abi.encode(_gdaParams); _; } - modifier givenDecayPeriod(uint256 decayPeriod_) { + modifier givenDecayPeriod( + uint256 decayPeriod_ + ) { _gdaParams.decayPeriod = decayPeriod_; _auctionParams.implParams = abi.encode(_gdaParams); _; @@ -235,15 +259,21 @@ abstract contract GdaTest is Test, Permit2User { // ======== Internal Functions ======== // - function _scaleQuoteTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleQuoteTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return amount_.mulDiv(10 ** _quoteTokenDecimals, _BASE_SCALE); } - function _scaleBaseTokenAmount(uint256 amount_) internal view returns (uint256) { + function _scaleBaseTokenAmount( + uint256 amount_ + ) internal view returns (uint256) { return amount_.mulDiv(10 ** _baseTokenDecimals, _BASE_SCALE); } - function _getAuctionLot(uint96 lotId_) internal view returns (IAuction.Lot memory) { + function _getAuctionLot( + uint96 lotId_ + ) internal view returns (IAuction.Lot memory) { return _module.getLot(lotId_); } diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 3c2d23699..225417adf 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -6,14 +6,7 @@ import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import { - UD60x18, - ud, - convert, - UNIT, - uUNIT, - EXP_MAX_INPUT -} from "prb-math-4.0-axis/UD60x18.sol"; +import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "prb-math-4.0-axis/UD60x18.sol"; import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; @@ -102,7 +95,9 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_equilibriumPriceGreaterThanMax_reverts(uint256 price_) public { + function test_equilibriumPriceGreaterThanMax_reverts( + uint256 price_ + ) public { vm.assume(price_ > type(uint128).max); _gdaParams.equilibriumPrice = price_; _auctionParams.implParams = abi.encode(_gdaParams); @@ -138,7 +133,9 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_capacityGreaterThanMax_reverts(uint256 capacity_) public { + function test_capacityGreaterThanMax_reverts( + uint256 capacity_ + ) public { vm.assume(capacity_ > type(uint128).max); _auctionParams.capacity = capacity_; diff --git a/test/modules/auctions/GDA/cancelAuction.t.sol b/test/modules/auctions/GDA/cancelAuction.t.sol index 47fdc51be..e85633c8a 100644 --- a/test/modules/auctions/GDA/cancelAuction.t.sol +++ b/test/modules/auctions/GDA/cancelAuction.t.sol @@ -37,7 +37,9 @@ contract GdaCancelAuctionTest is GdaTest { _cancelAuctionLot(); } - function test_auctionConcluded_reverts(uint48 conclusionElapsed_) public givenLotIsCreated { + function test_auctionConcluded_reverts( + uint48 conclusionElapsed_ + ) public givenLotIsCreated { uint48 conclusionElapsed = uint48(bound(conclusionElapsed_, 0, 1 days)); // Warp to the conclusion diff --git a/test/modules/auctions/GDA/maxAmountAccepted.t.sol b/test/modules/auctions/GDA/maxAmountAccepted.t.sol index 08cec560c..297ea556f 100644 --- a/test/modules/auctions/GDA/maxAmountAccepted.t.sol +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -14,7 +14,9 @@ contract GdaMaxAmountAcceptedTest is GdaTest { // [X] it reverts // [X] it returns the price for the remaining capacity of the lot - function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { + function testFuzz_lotIdInvalid_reverts( + uint96 lotId_ + ) public { // No lots have been created so all lots are invalid bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, lotId_); vm.expectRevert(err); diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol index 76dc4c5d4..41f86a71b 100644 --- a/test/modules/auctions/GDA/maxPayout.t.sol +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -13,7 +13,9 @@ contract GdaMaxPayoutTest is GdaTest { // [X] it reverts // [X] it returns the remaining capacity of the lot - function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { + function testFuzz_lotIdInvalid_reverts( + uint96 lotId_ + ) public { // No lots have been created so all lots are invalid bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, lotId_); vm.expectRevert(err); diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index 3c3d588f2..645902a6d 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -7,13 +7,7 @@ import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; import { - UD60x18, - ud, - convert, - UNIT, - uUNIT, - ZERO, - EXP_MAX_INPUT + UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT } from "prb-math-4.0-axis/UD60x18.sol"; import "prb-math-4.0-axis/Common.sol" as PRBMath; @@ -45,7 +39,9 @@ contract GdaPayoutForTest is GdaTest { // [X] it does not overflow // TODO can we fuzz this better? - function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { + function testFuzz_lotIdInvalid_reverts( + uint96 lotId_ + ) public { // No lots have been created so all lots are invalid bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, lotId_); vm.expectRevert(err); diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 5588188cd..c63f01859 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -6,14 +6,7 @@ import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import { - UD60x18, - ud, - convert, - UNIT, - uUNIT, - EXP_MAX_INPUT -} from "prb-math-4.0-axis/UD60x18.sol"; +import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "prb-math-4.0-axis/UD60x18.sol"; import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; @@ -43,7 +36,9 @@ contract GdaPriceForTest is GdaTest { // TODO can we fuzz this better? maybe use some external calculations to compare the values? // Otherwise, we're just recreating the same calculations here and not really validating anything - function testFuzz_lotIdInvalid_reverts(uint96 lotId_) public { + function testFuzz_lotIdInvalid_reverts( + uint96 lotId_ + ) public { // No lots have been created so all lots are invalid bytes memory err = abi.encodeWithSelector(IAuction.Auction_InvalidLotId.selector, lotId_); vm.expectRevert(err); diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index 0e010da9d..c5444e177 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -7,13 +7,7 @@ import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; import { - UD60x18, - ud, - convert, - UNIT, - uUNIT, - ZERO, - EXP_MAX_INPUT + UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT } from "prb-math-4.0-axis/UD60x18.sol"; import "prb-math-4.0-axis/Common.sol" as PRBMath; @@ -26,7 +20,9 @@ contract GdaPurchaseTest is GdaTest { uint256 internal _purchaseAmount = 5e18; uint256 internal _purchaseAmountOut; - modifier setPurchaseAmount(uint256 amount) { + modifier setPurchaseAmount( + uint256 amount + ) { _purchaseAmount = amount; _; } diff --git a/test/modules/derivatives/LinearVesting.t.sol b/test/modules/derivatives/LinearVesting.t.sol index 6d3adebe2..ba4e0e137 100644 --- a/test/modules/derivatives/LinearVesting.t.sol +++ b/test/modules/derivatives/LinearVesting.t.sol @@ -162,7 +162,9 @@ contract LinearVestingTest is Test, Permit2User { _; } - modifier givenParentHasUnderlyingTokenBalance(uint256 balance_) { + modifier givenParentHasUnderlyingTokenBalance( + uint256 balance_ + ) { _underlyingToken.mint(address(_auctionHouse), balance_); vm.prank(address(_auctionHouse)); @@ -203,7 +205,9 @@ contract LinearVestingTest is Test, Permit2User { ); } - modifier givenAliceHasDerivativeTokens(uint256 amount_) { + modifier givenAliceHasDerivativeTokens( + uint256 amount_ + ) { _mintDerivativeTokens(_ALICE, amount_); _; } @@ -221,7 +225,9 @@ contract LinearVestingTest is Test, Permit2User { _linearVesting.mint(recipient_, _underlyingTokenAddress, _vestingParamsBytes, amount_, true); } - modifier givenAliceHasWrappedDerivativeTokens(uint256 amount_) { + modifier givenAliceHasWrappedDerivativeTokens( + uint256 amount_ + ) { _mintWrappedDerivativeTokens(_ALICE, amount_); _; } @@ -1720,7 +1726,9 @@ contract LinearVestingTest is Test, Permit2User { assertEq(SoulboundCloneERC20(_derivativeWrappedAddress).totalSupply(), 0); } - function test_redeemMax(uint48 elapsed_) public givenWrappedDerivativeIsDeployed { + function test_redeemMax( + uint48 elapsed_ + ) public givenWrappedDerivativeIsDeployed { // Mint both wrapped and unwrapped _mintDerivativeTokens(_ALICE, _AMOUNT); _mintWrappedDerivativeTokens(_ALICE, _AMOUNT); @@ -2418,7 +2426,9 @@ contract LinearVestingTest is Test, Permit2User { assertEq(balance, 0); } - function test_balanceOf(uint256 amount_) public givenWrappedDerivativeIsDeployed { + function test_balanceOf( + uint256 amount_ + ) public givenWrappedDerivativeIsDeployed { uint256 amount = bound(amount_, 0, _AMOUNT); // Mint @@ -2433,7 +2443,9 @@ contract LinearVestingTest is Test, Permit2User { assertEq(balance, amount); } - function test_balanceOf_wrapped(uint256 amount_) public givenWrappedDerivativeIsDeployed { + function test_balanceOf_wrapped( + uint256 amount_ + ) public givenWrappedDerivativeIsDeployed { uint256 amount = bound(amount_, 0, _AMOUNT); // Mint diff --git a/test/modules/derivatives/LinearVestingEMPAIntegration.t.sol b/test/modules/derivatives/LinearVestingEMPAIntegration.t.sol index c1882ece4..436a17a7f 100644 --- a/test/modules/derivatives/LinearVestingEMPAIntegration.t.sol +++ b/test/modules/derivatives/LinearVestingEMPAIntegration.t.sol @@ -84,7 +84,9 @@ contract LinearVestingEMPAIntegrationTest is BatchAuctionHouseTest { _; } - function _formatBid(uint256 amountOut_) internal pure returns (uint256) { + function _formatBid( + uint256 amountOut_ + ) internal pure returns (uint256) { uint256 formattedAmountOut; { uint128 subtracted; diff --git a/test/modules/derivatives/mocks/MockDerivativeModule.sol b/test/modules/derivatives/mocks/MockDerivativeModule.sol index 729394cc1..4358785bb 100644 --- a/test/modules/derivatives/mocks/MockDerivativeModule.sol +++ b/test/modules/derivatives/mocks/MockDerivativeModule.sol @@ -30,7 +30,9 @@ contract MockDerivativeModule is DerivativeModule { uint256 multiplier; } - constructor(address _owner) Module(_owner) { + constructor( + address _owner + ) Module(_owner) { derivativeToken = new MockERC6909(); } @@ -113,7 +115,9 @@ contract MockDerivativeModule is DerivativeModule { function exercise(uint256 tokenId_, uint256 amount) external virtual override {} - function reclaim(uint256 tokenId_) external virtual override {} + function reclaim( + uint256 tokenId_ + ) external virtual override {} function transform( uint256 tokenId_, @@ -141,11 +145,15 @@ contract MockDerivativeModule is DerivativeModule { return true; } - function setValidateFails(bool validateFails_) external { + function setValidateFails( + bool validateFails_ + ) external { _validateFails = validateFails_; } - function setWrappedImplementation(MockWrappedDerivative implementation_) external { + function setWrappedImplementation( + MockWrappedDerivative implementation_ + ) external { _wrappedImplementation = implementation_; } @@ -200,20 +208,32 @@ contract MockDerivativeModule is DerivativeModule { return (tokenId, token.wrapped); } - function redeemMax(uint256 tokenId_) external virtual override {} + function redeemMax( + uint256 tokenId_ + ) external virtual override {} function redeemable( address owner_, uint256 tokenId_ ) external view virtual override returns (uint256) {} - function name(uint256 tokenId_) public view virtual override returns (string memory) {} + function name( + uint256 tokenId_ + ) public view virtual override returns (string memory) {} - function symbol(uint256 tokenId_) public view virtual override returns (string memory) {} + function symbol( + uint256 tokenId_ + ) public view virtual override returns (string memory) {} - function decimals(uint256 tokenId_) public view virtual override returns (uint8) {} + function decimals( + uint256 tokenId_ + ) public view virtual override returns (uint8) {} - function tokenURI(uint256 tokenId_) public view virtual override returns (string memory) {} + function tokenURI( + uint256 tokenId_ + ) public view virtual override returns (string memory) {} - function totalSupply(uint256 tokenId_) public view virtual override returns (uint256) {} + function totalSupply( + uint256 tokenId_ + ) public view virtual override returns (uint256) {} } From a47af6b6bbd0af442bac8499aed332a93ad550ca Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 19 Sep 2024 14:24:01 -0500 Subject: [PATCH 54/69] chore: minor cleanup --- src/modules/auctions/atomic/GDA.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/auctions/atomic/GDA.sol b/src/modules/auctions/atomic/GDA.sol index 81de2f4ad..abafe06b7 100644 --- a/src/modules/auctions/atomic/GDA.sol +++ b/src/modules/auctions/atomic/GDA.sol @@ -337,8 +337,8 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // In the auction creation, we checked that the equilibrium price is greater than the minimum price // Scale the result to 18 decimals uint256 quoteTokenScale = 10 ** lot.quoteTokenDecimals; - UD60x18 priceDiff = ud( - (auction.equilibriumPrice - auction.minimumPrice).mulDiv(uUNIT, quoteTokenScale) + UD60x18 priceDiff = ud(auction.equilibriumPrice - auction.minimumPrice).div( + ud(quoteTokenScale) ).mul(auction.emissionsRate); // Calculate the second numerator factor: e^((k*P)/r) - 1 @@ -416,6 +416,8 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // Check that payout does not exceed remaining capacity if (payout > lotData[lotId_].capacity) { + console2.log("payout:", payout); + console2.log("capacity:", lotData[lotId_].capacity); revert Auction_InsufficientCapacity(); } From 40a9d88f713399488842a10c8b8bbf6d1f508ae4 Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 19 Sep 2024 14:56:51 -0500 Subject: [PATCH 55/69] test: gda test updates to catch edge cases --- test/modules/auctions/GDA/GDATest.sol | 2 +- .../auctions/GDA/maxAmountAccepted.t.sol | 61 +++++++++++++++++++ test/modules/auctions/GDA/purchase.t.sol | 38 ++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index f1a3fac71..280a5fedb 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -39,7 +39,7 @@ abstract contract GdaTest is Test, Permit2User { // Input parameters (modified by modifiers) uint48 internal _start; - uint96 internal _lotId = type(uint96).max; + uint96 internal _lotId = 0; IAuction.AuctionParams internal _auctionParams; GradualDutchAuction.GDAParams internal _gdaParams; diff --git a/test/modules/auctions/GDA/maxAmountAccepted.t.sol b/test/modules/auctions/GDA/maxAmountAccepted.t.sol index 297ea556f..0156b44da 100644 --- a/test/modules/auctions/GDA/maxAmountAccepted.t.sol +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -42,6 +42,67 @@ contract GdaMaxAmountAcceptedTest is GdaTest { assertEq(expectedAmount, maxAmountAccepted); } + function testFuzz_maxAmountAccepted_minPriceNonZero_success_smallerQuoteDecimals( + uint128 capacity_, + uint128 price_ + ) + public + givenQuoteTokenDecimals(6) + givenDuration(1 days) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + + function testFuzz_maxAmountAccepted_minPriceNonZero_success_smallerBaseDecimals( + uint128 capacity_, + uint128 price_ + ) + public + givenBaseTokenDecimals(6) + givenDuration(1 days) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + + function testFuzz_maxAmountAccepted_minPriceNonZero_success_bothSmallerDecimals( + uint96 capacity_, + uint96 price_ + ) + public + givenQuoteTokenDecimals(9) + givenBaseTokenDecimals(9) + givenDuration(1 days) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + function testFuzz_maxAmountAccepted_minPriceZero_success( uint128 capacity_, uint128 price_ diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index c5444e177..ed7d54b7a 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -247,6 +247,7 @@ contract GdaPurchaseTest is GdaTest { // Normalize the amount uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + vm.assume(maxAmountAccepted > 100); // Tiny maxAmountAccepted values cause large rounding errors which make the capacity insufficient uint256 amount = amount_ % (maxAmountAccepted + 1); // Calculate expected values @@ -334,6 +335,43 @@ contract GdaPurchaseTest is GdaTest { assertEq(lot.sold, expectedPayout, "sold"); } + function testFuzz_minPriceNonZero_varyingSetup_bothSmallerDecimals( + uint256 amount_, + uint96 capacity_, + uint96 price_ + ) + public + givenQuoteTokenDecimals(9) + givenBaseTokenDecimals(9) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted + { + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + // Normalize the amount + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 amount = amount_ % (maxAmountAccepted + 1); + + // Calculate expected values + uint256 expectedPayout = _module.payoutFor(_lotId, amount); + + // Call the function + _createPurchase(amount, expectedPayout); + + // Assert the capacity, purchased and sold + IAuction.Lot memory lot = _getAuctionLot(_lotId); + assertEq(lot.capacity, uint256(capacity_) - expectedPayout, "capacity"); + assertEq(lot.purchased, amount, "purchased"); + assertEq(lot.sold, expectedPayout, "sold"); + } + function testFuzz_minPriceZero_success( uint256 amount_ ) public givenMinPrice(0) givenLotIsCreated givenLotHasStarted { From a4781f54a7d70dbb85d8dba03b0d3c47fcdba9ff Mon Sep 17 00:00:00 2001 From: Oighty Date: Thu, 19 Sep 2024 14:59:03 -0500 Subject: [PATCH 56/69] test: update test salts --- script/salts/salts.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/script/salts/salts.json b/script/salts/salts.json index d9486f558..849ab7f33 100644 --- a/script/salts/salts.json +++ b/script/salts/salts.json @@ -24,23 +24,23 @@ "0xf877dc218ee7538ffb3ad6f8fd1b4694cbb6f38d9e0b7a4d9a19ace3c24830a2": "0xad2a1f39589d55fd4fb667bda19d168106901d33100bb9c50fc57b1735024bcd" }, "Test_MockCallback": { - "0x00b0cb85b4a448d535c32b432ca3414a5bb88ba6295022c61fdb112299cc701d": "0x5f53a77536a8d4f9e16b72259f09284cf342cf413cb5f073c1d79bfd619ce98d", - "0x07427eb7abc3eb9f407cfbf47f97cf1c35f3cdd5f521864a047093d94ce25510": "0x7a7e32ccbccc4a2ee06552a5e3c5071b83b3c6c15c1345c08d32904f015403a0", - "0x0a229fd95c9ed1b1000092d0e2fd7d2114c75f6123a48c2199c81c37c14099b2": "0xc0ef466b3aa33c839780835ba2ff0a6b6d426cb9d89bbe9d05891cc77cd49c21", - "0x2e292a56408fa2febc4cd23c7a9961fa2b0c5dbae9fa7d397e9fb3153d0fa900": "0x933eb8d879ae2f9c4e6d76de8af9b32391850785f9196fc6be65cd98fff09088", - "0x31fa4132f7f76799e69cb97cef1e2db702cbdea7be5458f218342727ebdcd5bb": "0x748cd4ca6d05a1a804976062b1bb4340d07c3f9b6716b473ded094da9ec864c4", - "0x4bbe022c8c8d02e1be60027230a3fe65db72afc7c1780a57b36f0c1ebf522b60": "0x1ab83d0dbea03e6fb34be7c5dbcd434c6c8e65b9a6b48a5217e84de21f687e7b", - "0x60154bba2453aa6282ea1fb356f6abdd06f5b1149803cbe3bf26f0b31e4441bc": "0x9a5918b99a142d1b979adad8415e3847e06e3952773a150aad8aac8f6497a85d", - "0x71f82fd68d0bfcc6b481a9abfc5280dcc2532165835cec2dbf20c29cec1d67e0": "0x62a6926256c2a6f7285588034cfeaeed7abc2a09b21276ddeeb88b054939ad86", - "0x72db56dad49dcb5ef9e7c49cec5ce30ce0d986455f9261a4fc515318234aead6": "0x27a470eff5b98bd0df8c14fd624181c0e0d824e19417b87e842cdfa4644da71d", - "0x863dabd6458602bcfda9857fd7f18fcba01a21fd9f5a354478cc985b67f61697": "0x860d83d790926910c430e8d558e549f51a035e38f6f01b01cecd78dbfaf7ab7e", - "0x86c6cd765dda545f407561cf4711a3c886e1d92df7ea02ba63c1b0fe9480a65b": "0xd4a83aa6339eb819af0df77f7a3a853b744b2492c28835bc832546074879c266", - "0xba556994a94dc0bc511025a60195ff669936387b02e813c7a2bd943ee6fc0784": "0x072b4ee85615319565c77e2e1d52bafe7ef31082e07aaba7b1c48f645266d1a2", - "0xc01403f7e03e367510127d5ec7007c74074edb2ea58754acbdb0e570acac396b": "0x738f5120c6bdc8da3e45acb0bb376535b9c0e22cffa905654e2e6779801b226b", - "0xc2266a1cf2d2af88754600907c74f277aa78f4da5a902c464a391ab24d974d34": "0xea1f5d40d1aa0b00d398252a1c39bd7b28742b568df3eaf84135c9d7f3169dfc", - "0xcc541a55e310b47c815e818b7b70a10e2fed5b9a5756d2b72ebc6714118b2e70": "0x453dae5149ffc7c23b172f17b042754f4a148e40d5ae57a2350eb49b36d53c1c", - "0xd119416b475accd9e59a97021c088b1b5df55fa29d22e231520638641a44c433": "0x8c13dd001be1740014577ba60df4d903f45bc9dd06be2203518cb77e3980be95", - "0xf74f63cef50f9e1e8a2505f72c60421fb8ffd911104dc5122701db403a394092": "0x2ba0ca02a645e0479bd1deb0bfdb59ab04d3dcbdafeecf3c024f5f619d89df5b", - "0xfef7d294b1e9ad2c2d69e2a5f683407a1103571b78d6b77d256102ec2dc82bde": "0x86c8ceb1eaeea01e55511361df1fac06cc02aa88b0a79cd07f81eb795d3e2ebc" + "0x05662b360bb437a8da46dbd4b6f1c8ad4e96b41c82a9d982eeee6f442448aa33": "0xaac1c06c2871c6f9c54ba800b67b6cd797a21e21e344040030e1553a68ffc246", + "0x236e5b0adc291ed39df047aa041acafc4c7377836dc857792a4359f064a96263": "0xce7fc338fab65f9c3d2a17ee2b4940c73a4c87358c6bd26d99d60bc5d4540f80", + "0x296c7b9d63083392fd74f6736c712a3623cbeac46f07784f9b40060d90997d44": "0x98814813448cfffe4642aa00d8696fbacc4f72ded33a8dea0ae91cd63608c5a1", + "0x37481b19c488a3bb6f8516ea20902d44096979e6bb9c9576e17ec2684b6cf9fa": "0x19c7a216d906697eee994275960e6f599ebc8cbfc21060088a7e36a559744b52", + "0x44eead61882149c458d5411dc86629d38654c579323ebb4ff3654bde9bdf7283": "0x45d5d05323e167b1f220bfd39f36fb7eb5c47b448f586cd704b1adf827fd23b5", + "0x5dd07d0b7975bb7b537a4a0e780653a4a6ff6de298854476bef16e94ea4dad5b": "0xd916509c74b78637e327db42b39132d2fc5e1d0cbe4f30a388832bb7ff69c9bb", + "0x6e2d8574962498aeaf59cc2bfb3fe3095f4f08707919ce386178fd4191d181a1": "0xd9618431d228344fd0cdb25b36a57749a1c6467be532f24935db7a3575591b13", + "0x71b2402cf81ed9c6e134205233af0197d4624a783e34fd5df6ade10d992853a0": "0x66bed19d35ae8ec83c3fca8c3db45e4042f1dbd080fc2763c860b069fe8036e5", + "0x7323ea3924a4612c64a00684193dc3e3c0774723775bf387b90e2e78a0c560c3": "0x8d0fb7314601c3201227ddcc8712ab4446daa445b7fb487b77f962e2b621076a", + "0x881853ad29e13aa1637a69214c02e4903406bc6aae8eb67dfadeafef367686d5": "0xe32ed45dfa02c20ecfcb7a990c5c13432049d0c36a4fbafa215b2897501b54bc", + "0x8e6ec1074c82f1b5f45555a29e701aab391555ed7ca727135cc92d3bf18d05f4": "0x91364a9641427d334a58f95187e34ddda763ac3ed18349413d8dcb40ee0ca757", + "0xa03fd061a75e5647894d8dbb5cfefc9b3491f051668e33c25a8261ddfa1ec6d7": "0x47a08350ccdb7265d66cb5682b40c599d5b8312acf91aea7f9d7754de3048c55", + "0xa572a481bf2e738c72f97fed5a5d70349d994a6988de2e562a20774772652a2a": "0xb5f0e78c25a8585be3510b84b3eb1e9acb97be68f573cd14949d9abe2b5dab4a", + "0xa7699d5da4123ac041cd9cc6dca6bb0254ea2b10e7bccbf345cfdf9c13910270": "0x4f1a24ac0c3eeae6ef0a531995607c253ffb238ad9fb0be8bd1d84e00c67c78c", + "0xb9c8f06241efec1228b542d0b4063e945be9909e8f9b01e974f4217e75ccebe2": "0x0adee3adf6bb766c114c821b03e3cafe036bf658127e1de95ff5f63bbf0f2f11", + "0xbfa5a7404ebe20d5b9afd1a03117daa7f5c1f30414dd3536b265f1d2db97c456": "0xd9796c5b5d8dde50d52ddca536258ab52a300379ed7956eb821fcc18b02b0daf", + "0xd6d77347d98ea199311c3647d48376eecf15cee25172b04c57de0f20dd81af5d": "0x8dc39e482f4a7fcc04d99fd28ed23ea7ce3d834ea2d5df2738c99b32f67f7842", + "0xd7b0b180626d94ada1049690f108313bca78129840208fc566bdaf4840d147c1": "0x1e90318d111b165c32315d2f0f7b31af82e520e5b4e598073a40cf10c972e0b2" } } From d8109fd75554ff22e3b479de9f7cf986265f831c Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Tue, 24 Sep 2024 15:44:41 +0400 Subject: [PATCH 57/69] NatSpec for error --- src/interfaces/modules/auctions/IGradualDutchAuction.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/interfaces/modules/auctions/IGradualDutchAuction.sol b/src/interfaces/modules/auctions/IGradualDutchAuction.sol index 957a7868e..e04ce2ec3 100644 --- a/src/interfaces/modules/auctions/IGradualDutchAuction.sol +++ b/src/interfaces/modules/auctions/IGradualDutchAuction.sol @@ -8,7 +8,10 @@ import {IAtomicAuction} from "src/interfaces/modules/IAtomicAuction.sol"; interface IGradualDutchAuction is IAtomicAuction { // ========== ERRORS ========== // - error GDA_InvalidParams(uint256 step); // the step tells you where the error occurred + /// @notice Thrown when the auction parameters are invalid + /// + /// @param step Indicates where the error occurred + error GDA_InvalidParams(uint256 step); // ========== DATA STRUCTURES ========== // From cad6b71d9c208e494493532842f6340ada59377e Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Tue, 24 Sep 2024 15:47:01 +0400 Subject: [PATCH 58/69] Cleanup --- .../modules/auctions/IGradualDutchAuction.sol | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/interfaces/modules/auctions/IGradualDutchAuction.sol b/src/interfaces/modules/auctions/IGradualDutchAuction.sol index e04ce2ec3..a1785c7a5 100644 --- a/src/interfaces/modules/auctions/IGradualDutchAuction.sol +++ b/src/interfaces/modules/auctions/IGradualDutchAuction.sol @@ -16,11 +16,11 @@ interface IGradualDutchAuction is IAtomicAuction { // ========== DATA STRUCTURES ========== // /// @notice Auction pricing data - /// @param equilibriumPrice The initial price of one base token, where capacity and time are balanced - /// @param minimumPrice The minimum price for one base token - /// @param lastAuctionStart The time that the last un-purchased auction started, may be in the future - /// @param decayConstant The speed at which the price decays, as UD60x18 - /// @param emissionsRate The number of tokens released per day, as UD60x18. Calculated as capacity / duration (in days) + /// @param equilibriumPrice The initial price of one base token, where capacity and time are balanced + /// @param minimumPrice The minimum price for one base token + /// @param lastAuctionStart The time that the last un-purchased auction started, may be in the future + /// @param decayConstant The speed at which the price decays, as UD60x18 + /// @param emissionsRate The number of tokens released per day, as UD60x18. Calculated as capacity / duration (in days) struct AuctionData { uint256 equilibriumPrice; uint256 minimumPrice; @@ -30,15 +30,15 @@ interface IGradualDutchAuction is IAtomicAuction { } /// @notice Parameters to create a GDA - /// @param equilibriumPrice The initial price of one base token, where capacity and time are balanced - /// @param minimumPrice The minimum price for one base token - /// @param decayTarget The target decay percent over the first decay period of an auction (steepest part of the curve) - /// @param decayPeriod The period over which the target decay percent is reached, in seconds + /// @param equilibriumPrice The initial price of one base token, where capacity and time are balanced + /// @param minimumPrice The minimum price for one base token + /// @param decayTarget The target decay percent over the first decay period of an auction (steepest part of the curve) + /// @param decayPeriod The period over which the target decay percent is reached, in seconds struct GDAParams { - uint256 equilibriumPrice; // initial price of one base token, where capacity and time are balanced - uint256 minimumPrice; // minimum price for one base token - uint256 decayTarget; // target decay percent over the first decay period of an auction (steepest part of the curve) - uint256 decayPeriod; // period over which the target decay percent is reached, in seconds + uint256 equilibriumPrice; + uint256 minimumPrice; + uint256 decayTarget; + uint256 decayPeriod; } // ========== STATE VARIABLES ========== // From 43338b8efeab40a72e3180d19b5c718dde36d3bc Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Tue, 24 Sep 2024 16:01:15 +0400 Subject: [PATCH 59/69] Improve NatSpec output --- src/modules/auctions/atomic/GDA.sol | 45 +++++++++++++++++------------ 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/modules/auctions/atomic/GDA.sol b/src/modules/auctions/atomic/GDA.sol index abafe06b7..f5aaae5b2 100644 --- a/src/modules/auctions/atomic/GDA.sol +++ b/src/modules/auctions/atomic/GDA.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.19; import {Module, Veecode, toVeecode} from "../../Modules.sol"; import {AuctionModule} from "../../Auction.sol"; import {AtomicAuctionModule} from "../AtomicAuctionModule.sol"; +import {IAtomicAuction} from "../../../interfaces/modules/IAtomicAuction.sol"; import {IGradualDutchAuction} from "../../../interfaces/modules/auctions/IGradualDutchAuction.sol"; // External libraries @@ -114,6 +115,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // ========== AUCTION ========== // + /// @inheritdoc AuctionModule function _auction(uint96 lotId_, Lot memory lot_, bytes memory params_) internal override { // Decode implementation parameters GDAParams memory params = abi.decode(params_, (GDAParams)); @@ -271,13 +273,15 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { data.lastAuctionStart = uint256(lot_.start); } - // Do not need to do anything extra here + /// @inheritdoc AuctionModule + /// @dev Do not need to do anything extra here function _cancelAuction( uint96 lotId_ ) internal override {} // ========== PURCHASE ========== // + /// @inheritdoc AtomicAuctionModule function _purchase( uint96 lotId_, uint256 amount_, @@ -298,24 +302,24 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { // ========== VIEW FUNCTIONS ========== // - // For Continuous GDAs with exponential decay, the price of a given token t seconds after being emitted is: - // q(t) = r * (q0 - qm) * e^(-k*t) + qm - // where k is the decay constant, q0 is the initial price, and qm is the minimum price - // Integrating this function from the last auction start time for a particular number of tokens, - // gives the multiplier for the token price to determine amount of quote tokens required to purchase: - // Q(T) = (r * (q0 - qm) * (e^((k*P)/r) - 1)) / ke^(k*T) + (qm * P) - // where T is the time since the last auction start, P is the number of payout tokens to purchase, - // and r is the emissions rate (number of tokens released per second). - // - // If qm is 0, then the equation simplifies to: - // q(t) = r * q0 * e^(-k*t) - // Integrating this function from the last auction start time for a particular number of tokens, - // gives the multiplier for the token price to determine amount of quote tokens required to purchase: - // Q(T) = (r * q0 * (e^((k*P)/r) - 1)) / ke^(k*T) - // where T is the time since the last auction start, P is the number of payout tokens to purchase. - // - // Note: this function is an estimate. The actual price returned will vary some due to the precision of the calculations. - // Numerator mulDiv operations are rounded up and denominator mulDiv operations are rounded down to ensure the total rounding is conservative (up). + /// @inheritdoc IAtomicAuction + /// @dev For Continuous GDAs with exponential decay, the price of a given token t seconds after being emitted is: + /// q(t) = r * (q0 - qm) * e^(-k*t) + qm + /// where k is the decay constant, q0 is the initial price, and qm is the minimum price + /// Integrating this function from the last auction start time for a particular number of tokens, + /// gives the multiplier for the token price to determine amount of quote tokens required to purchase: + /// Q(T) = (r * (q0 - qm) * (e^((k*P)/r) - 1)) / ke^(k*T) + (qm * P) + /// where T is the time since the last auction start, P is the number of payout tokens to purchase, + /// and r is the emissions rate (number of tokens released per second). + /// If qm is 0, then the equation simplifies to: + /// q(t) = r * q0 * e^(-k*t) + /// Integrating this function from the last auction start time for a particular number of tokens, + /// gives the multiplier for the token price to determine amount of quote tokens required to purchase: + /// Q(T) = (r * q0 * (e^((k*P)/r) - 1)) / ke^(k*T) + /// where T is the time since the last auction start, P is the number of payout tokens to purchase. + /// + /// Note: this function is an estimate. The actual price returned will vary some due to the precision of the calculations. + /// Numerator mulDiv operations are rounded up and denominator mulDiv operations are rounded down to ensure the total rounding is conservative (up). function priceFor(uint96 lotId_, uint256 payout_) public view override returns (uint256) { // Lot ID must be valid _revertIfLotInvalid(lotId_); @@ -406,6 +410,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { return amount; } + /// @inheritdoc IAtomicAuction function payoutFor(uint96 lotId_, uint256 amount_) public view override returns (uint256) { // Lot ID must be valid _revertIfLotInvalid(lotId_); @@ -569,6 +574,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { return (payout.intoUint256().mulDiv(10 ** lot.baseTokenDecimals, uUNIT), secondsOfEmissions); } + /// @inheritdoc IAtomicAuction function maxPayout( uint96 lotId_ ) external view override returns (uint256) { @@ -579,6 +585,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { return lotData[lotId_].capacity; } + /// @inheritdoc IAtomicAuction function maxAmountAccepted( uint96 lotId_ ) public view override returns (uint256) { From 131581ec2fe59633a0ec0233998b9ecf40ed9f63 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Wed, 25 Sep 2024 14:22:15 +0400 Subject: [PATCH 60/69] Typos --- src/modules/auctions/atomic/GDA.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/auctions/atomic/GDA.sol b/src/modules/auctions/atomic/GDA.sol index f5aaae5b2..063583465 100644 --- a/src/modules/auctions/atomic/GDA.sol +++ b/src/modules/auctions/atomic/GDA.sol @@ -346,7 +346,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { ).mul(auction.emissionsRate); // Calculate the second numerator factor: e^((k*P)/r) - 1 - // This cannot exceed the max exponential input due to the bounds imbosed on auction creation + // This cannot exceed the max exponential input due to the bounds imposed on auction creation // emissions rate = initial capacity / duration // payout must be less then or equal to initial capacity // therefore, the resulting exponent is at most decay constant * duration @@ -362,7 +362,7 @@ contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { if (block.timestamp >= auction.lastAuctionStart) { // T is positive // Calculate the denominator: ke^(k*T) - // This cannot exceed the max exponential input due to the bounds imbosed on auction creation + // This cannot exceed the max exponential input due to the bounds imposed on auction creation // Current time - last auction start is guaranteed to be < duration. If not, the auction is over. // We round down here (the default behavior) since it is a component of the denominator. UD60x18 kekt = auction.decayConstant.mul( From b47143335f5a82bb3932b28bb915ee5d3ab97b78 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Wed, 25 Sep 2024 14:22:34 +0400 Subject: [PATCH 61/69] Test TODOs --- test/modules/auctions/GDA/auction.t.sol | 20 +++++++++++- test/modules/auctions/GDA/cancelAuction.t.sol | 2 +- .../auctions/GDA/maxAmountAccepted.t.sol | 31 +++++++++++++------ test/modules/auctions/GDA/maxPayout.t.sol | 9 ++++-- test/modules/auctions/GDA/payoutFor.t.sol | 1 - test/modules/auctions/GDA/priceFor.t.sol | 3 ++ 6 files changed, 50 insertions(+), 16 deletions(-) diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 225417adf..a299a8234 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -148,6 +148,8 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } + // TODO: capacity within bounds + function test_minPriceGreaterThanDecayTargetPrice_reverts() public givenMinPrice(4e18) @@ -176,7 +178,9 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_minPriceGreaterThanMin_reverts() + // TODO: min price within bounds of decay target + + function test_minPriceGreaterThanMax_reverts() public givenDecayTarget(20e16) // 20% decay from 5e18 is 4e18 givenMinPrice(35e17 + 1) // 30% decrease (10% more than decay) from 5e18 is 3.5e18, we go slightly higher @@ -190,6 +194,8 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } + // TODO: min price within bounds of equilibrium price + function test_decayTargetLessThanMinimum_reverts() public givenDecayTarget(1e16 - 1) // slightly less than 1% @@ -216,6 +222,8 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } + // TODO: decay target within bounds + function test_decayPeriodLessThanMinimum_reverts() public givenDecayPeriod(uint48(6 hours) - 1) @@ -242,6 +250,8 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } + // TODO: decay period within bounds + function testFuzz_minPriceNonZero_durationGreaterThanLimit_reverts( uint8 decayTarget_, uint8 decayHours_ @@ -287,6 +297,8 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } + // TODO: min price zero, duration greater than log + function testFuzz_minPriceNonZero_durationEqualToLimit_succeeds( uint8 decayTarget_, uint8 decayHours_ @@ -372,6 +384,8 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } + // TODO: min price not zero, duration greater than exponent + function testFuzz_minPriceZero_durationEqualToLimit_succeeds( uint8 decayTarget_, uint8 decayHours_ @@ -431,6 +445,8 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } + // TODO: min price not zero, eq price times emissions zero + function test_minPriceNonZero_MinPriceTimesEmissionsZero_reverts() public givenMinPrice(1e9) // Smallest value for min price is 10^(quoteDecimals / 2) @@ -451,6 +467,8 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } + // TODO: min price zero, min price times emissions zero + function _assertAuctionData() internal { // Calculate the decay constant from the input parameters uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; diff --git a/test/modules/auctions/GDA/cancelAuction.t.sol b/test/modules/auctions/GDA/cancelAuction.t.sol index e85633c8a..7b892f2b0 100644 --- a/test/modules/auctions/GDA/cancelAuction.t.sol +++ b/test/modules/auctions/GDA/cancelAuction.t.sol @@ -16,7 +16,7 @@ contract GdaCancelAuctionTest is GdaTest { // [X] when the auction has been cancelled // [X] it reverts // [X] when the auction has started - // [X] it reverts + // [X] it updates the conclusion, capacity and status // [X] it updates the conclusion, capacity and status function test_notParent_reverts() public { diff --git a/test/modules/auctions/GDA/maxAmountAccepted.t.sol b/test/modules/auctions/GDA/maxAmountAccepted.t.sol index 0156b44da..dde54930c 100644 --- a/test/modules/auctions/GDA/maxAmountAccepted.t.sol +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -12,7 +12,18 @@ contract GdaMaxAmountAcceptedTest is GdaTest { using {PRBMath.mulDiv} for uint256; // [X] when the lot ID is invalid // [X] it reverts - // [X] it returns the price for the remaining capacity of the lot + // [X] given the minimum price is not zero + // [X] given the quote token decimals are smaller than the base token decimals + // [X] it correctly handles the scaling + // [X] given the quote token decimals are larger than the base token decimals + // [X] it correctly handles the scaling + // [X] it returns the price for the remaining capacity of the lot + // [X] given the minimum price is zero + // [X] given the quote token decimals are smaller than the base token decimals + // [X] it correctly handles the scaling + // [X] given the quote token decimals are larger than the base token decimals + // [X] it correctly handles the scaling + // [X] it returns the price for the remaining capacity of the lot function testFuzz_lotIdInvalid_reverts( uint96 lotId_ @@ -23,7 +34,7 @@ contract GdaMaxAmountAcceptedTest is GdaTest { _module.maxAmountAccepted(lotId_); } - function testFuzz_maxAmountAccepted_minPriceNonZero_success( + function testFuzz_minPriceNonZero_success( uint128 capacity_, uint128 price_ ) @@ -42,7 +53,7 @@ contract GdaMaxAmountAcceptedTest is GdaTest { assertEq(expectedAmount, maxAmountAccepted); } - function testFuzz_maxAmountAccepted_minPriceNonZero_success_smallerQuoteDecimals( + function testFuzz_minPriceNonZero_success_smallerQuoteDecimals( uint128 capacity_, uint128 price_ ) @@ -62,7 +73,7 @@ contract GdaMaxAmountAcceptedTest is GdaTest { assertEq(expectedAmount, maxAmountAccepted); } - function testFuzz_maxAmountAccepted_minPriceNonZero_success_smallerBaseDecimals( + function testFuzz_minPriceNonZero_success_smallerBaseDecimals( uint128 capacity_, uint128 price_ ) @@ -82,7 +93,7 @@ contract GdaMaxAmountAcceptedTest is GdaTest { assertEq(expectedAmount, maxAmountAccepted); } - function testFuzz_maxAmountAccepted_minPriceNonZero_success_bothSmallerDecimals( + function testFuzz_minPriceNonZero_success_bothSmallerDecimals( uint96 capacity_, uint96 price_ ) @@ -103,7 +114,7 @@ contract GdaMaxAmountAcceptedTest is GdaTest { assertEq(expectedAmount, maxAmountAccepted); } - function testFuzz_maxAmountAccepted_minPriceZero_success( + function testFuzz_minPriceZero_success( uint128 capacity_, uint128 price_ ) @@ -122,7 +133,7 @@ contract GdaMaxAmountAcceptedTest is GdaTest { assertEq(expectedAmount, maxAmountAccepted); } - function testFuzz_maxAmountAccepted_minPriceNonZero_quoteDecimalsSmaller_success( + function testFuzz_minPriceNonZero_quoteDecimalsSmaller_success( uint128 capacity_, uint128 price_ ) @@ -142,7 +153,7 @@ contract GdaMaxAmountAcceptedTest is GdaTest { assertEq(expectedAmount, maxAmountAccepted); } - function testFuzz_maxAmountAccepted_minPriceZero_quoteDecimalsSmaller_success( + function testFuzz_minPriceZero_quoteDecimalsSmaller_success( uint128 capacity_, uint128 price_ ) @@ -162,7 +173,7 @@ contract GdaMaxAmountAcceptedTest is GdaTest { assertEq(expectedAmount, maxAmountAccepted); } - function testFuzz_maxAmountAccepted_minPriceNonZero_quoteDecimalsLarger_success( + function testFuzz_minPriceNonZero_quoteDecimalsLarger_success( uint128 capacity_, uint128 price_ ) @@ -182,7 +193,7 @@ contract GdaMaxAmountAcceptedTest is GdaTest { assertEq(expectedAmount, maxAmountAccepted); } - function testFuzz_maxAmountAccepted_minPriceZero_quoteDecimalsLarger_success( + function testFuzz_minPriceZero_quoteDecimalsLarger_success( uint128 capacity_, uint128 price_ ) diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol index 41f86a71b..00cbccce2 100644 --- a/test/modules/auctions/GDA/maxPayout.t.sol +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -11,7 +11,10 @@ contract GdaMaxPayoutTest is GdaTest { using {PRBMath.mulDiv} for uint256; // [X] when the lot ID is invalid // [X] it reverts - // [X] it returns the remaining capacity of the lot + // [X] given the minimum price is zero + // [X] it returns the remaining capacity of the lot + // [X] given the minimum price is not zero + // [X] it returns the remaining capacity of the lot function testFuzz_lotIdInvalid_reverts( uint96 lotId_ @@ -22,7 +25,7 @@ contract GdaMaxPayoutTest is GdaTest { _module.maxPayout(lotId_); } - function testFuzz_maxPayout_success( + function testFuzz_minPriceNotZero_success( uint128 capacity_ ) public givenLotCapacity(capacity_) validateCapacity givenLotIsCreated { uint256 maxPayout = _module.maxPayout(_lotId); @@ -30,7 +33,7 @@ contract GdaMaxPayoutTest is GdaTest { assertEq(expectedMaxPayout, maxPayout); } - function testFuzz_maxPayout_minPriceZero_success( + function testFuzz_minPriceZero_success( uint128 capacity_ ) public givenLotCapacity(capacity_) validateCapacity givenMinPrice(0) givenLotIsCreated { uint256 maxPayout = _module.maxPayout(_lotId); diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index 645902a6d..b47ac0323 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -37,7 +37,6 @@ contract GdaPayoutForTest is GdaTest { // [X] it returns zero // [X] when large, reasonable values are used // [X] it does not overflow - // TODO can we fuzz this better? function testFuzz_lotIdInvalid_reverts( uint96 lotId_ diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index c63f01859..d31982ca7 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -36,6 +36,9 @@ contract GdaPriceForTest is GdaTest { // TODO can we fuzz this better? maybe use some external calculations to compare the values? // Otherwise, we're just recreating the same calculations here and not really validating anything + // TODO: handles quote token decimals greater than base token decimals + // TODO: handles quote token decimals less than base token decimals + function testFuzz_lotIdInvalid_reverts( uint96 lotId_ ) public { From 858a21634d584045e7aa8ab78f3f1240f12e19bd Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Thu, 26 Sep 2024 13:28:56 +0400 Subject: [PATCH 62/69] Additional test coverage --- test/modules/auctions/GDA/GDATest.sol | 30 +++++-- test/modules/auctions/GDA/auction.t.sol | 108 +++++++++++++++++++----- 2 files changed, 110 insertions(+), 28 deletions(-) diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index 280a5fedb..e297de06f 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -149,11 +149,17 @@ abstract contract GdaTest is Test, Permit2User { _; } - modifier givenMinPrice( + function _setMinPrice( uint128 minPrice_ - ) { + ) internal { _gdaParams.minimumPrice = uint256(minPrice_); _auctionParams.implParams = abi.encode(_gdaParams); + } + + modifier givenMinPrice( + uint128 minPrice_ + ) { + _setMinPrice(minPrice_); _; } @@ -207,19 +213,31 @@ abstract contract GdaTest is Test, Permit2User { _; } - modifier givenDecayTarget( + function _setDecayTarget( uint256 decayTarget_ - ) { + ) internal { _gdaParams.decayTarget = decayTarget_; _auctionParams.implParams = abi.encode(_gdaParams); + } + + modifier givenDecayTarget( + uint256 decayTarget_ + ) { + _setDecayTarget(decayTarget_); _; } - modifier givenDecayPeriod( + function _setDecayPeriod( uint256 decayPeriod_ - ) { + ) internal { _gdaParams.decayPeriod = decayPeriod_; _auctionParams.implParams = abi.encode(_gdaParams); + } + + modifier givenDecayPeriod( + uint256 decayPeriod_ + ) { + _setDecayPeriod(decayPeriod_); _; } diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index a299a8234..f8a5f9ccd 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -121,7 +121,7 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_capacityLessThanMin_reverts( + function test_capacity_lessThanMin_reverts( uint128 capacity_ ) public givenLotCapacity(capacity_ % 1e9) { // Expect revert @@ -133,7 +133,7 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_capacityGreaterThanMax_reverts( + function test_capacity_greaterThanMax_reverts( uint256 capacity_ ) public { vm.assume(capacity_ > type(uint128).max); @@ -148,12 +148,25 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - // TODO: capacity within bounds + function test_capacity_withinBounds( + uint128 capacity_ + ) public { + // Set the bounds + uint128 capacity = uint128(bound(capacity_, 1e9 + 1, type(uint128).max)); + + _auctionParams.capacity = capacity; + + // Call the function + _createAuctionLot(); - function test_minPriceGreaterThanDecayTargetPrice_reverts() + // Check the auction data + _assertAuctionData(); + } + + function test_minPrice_equalToDecayTargetPrice_reverts() public givenMinPrice(4e18) - givenDecayTarget(25e16) // 25% decay from 5e18 is 3.75e18 + givenDecayTarget(20e16) // 20% decay from 5e18 is 4e18 { // Expect revert bytes memory err = @@ -164,11 +177,15 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_minPriceEqualToDecayTargetPrice_reverts() - public - givenMinPrice(4e18) - givenDecayTarget(20e16) // 20% decay from 5e18 is 4e18 - { + function test_minPrice_belowHalfEquilibriumPrice_reverts( + uint128 minPrice_ + ) public givenDecayTarget(20e16) { + // Set the bounds + // Minimum price needs to be above 0.5 * equilibrium price + // 0.5 * equilibrium price = 0.5 * 5e18 = 2.5e18 + uint128 minPrice = uint128(bound(minPrice_, 1, 25e17 - 1)); + _setMinPrice(minPrice); + // Expect revert bytes memory err = abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 4); @@ -178,13 +195,33 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - // TODO: min price within bounds of decay target + function test_minPrice_aboveHalfEquilibriumPrice_belowDecayTargetPrice( + uint128 minPrice_ + ) public givenDecayTarget(20e16) { + // Set the bounds + // Minimum price needs to be above 0.5 * equilibrium price and below the decay target price + // 0.5 * equilibrium price = 0.5 * 5e18 = 2.5e18 + // decay target price = equilibrium price * (1 - decay target - 10%) = 5e18 * (1 - 0.2 - 0.1) = 3.5e18 + uint128 minPrice = uint128(bound(minPrice_, 25e17, 35e17)); + _setMinPrice(minPrice); + + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } + + function test_minPrice_greaterThanDecayTargetPrice_reverts( + uint128 minPrice_ + ) public givenDecayTarget(20e16) { + // Set the bounds + // Minimum price needs to be above 0.5 * equilibrium price and below the decay target price + // 0.5 * equilibrium price = 0.5 * 5e18 = 2.5e18 + // decay target price = equilibrium price * (1 - decay target - 10%) = 5e18 * (1 - 0.2 - 0.1) = 3.5e18 + uint128 minPrice = uint128(bound(minPrice_, 35e17 + 1, type(uint128).max)); + _setMinPrice(minPrice); - function test_minPriceGreaterThanMax_reverts() - public - givenDecayTarget(20e16) // 20% decay from 5e18 is 4e18 - givenMinPrice(35e17 + 1) // 30% decrease (10% more than decay) from 5e18 is 3.5e18, we go slightly higher - { // Expect revert bytes memory err = abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 4); @@ -194,11 +231,10 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - // TODO: min price within bounds of equilibrium price - function test_decayTargetLessThanMinimum_reverts() public givenDecayTarget(1e16 - 1) // slightly less than 1% + givenDuration(1 days) { // Expect revert bytes memory err = @@ -212,6 +248,7 @@ contract GdaCreateAuctionTest is GdaTest { function test_decayTargetGreaterThanMaximum_reverts() public givenDecayTarget(40e16 + 1) // slightly more than 40% + givenDuration(1 days) { // Expect revert bytes memory err = @@ -222,9 +259,24 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - // TODO: decay target within bounds + function test_decayTarget_withinBounds( + uint8 decayTarget_ + ) public { + // Set the bounds + uint256 decayTarget = bound(decayTarget_, 1e16, 40e16); + _setDecayTarget(decayTarget); + + // Set the auction duration to avoid breaking invariants + _auctionParams.duration = uint48(1 days); + + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } - function test_decayPeriodLessThanMinimum_reverts() + function test_decayPeriod_lessThanMinimum_reverts() public givenDecayPeriod(uint48(6 hours) - 1) { @@ -237,7 +289,7 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - function test_decayPeriodGreaterThanMaximum_reverts() + function test_decayPeriod_greaterThanMaximum_reverts() public givenDecayPeriod(uint48(1 weeks) + 1) { @@ -250,7 +302,19 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - // TODO: decay period within bounds + function test_decayPeriod_withinBounds( + uint256 decayPeriod_ + ) public { + // Set the bounds + uint48 decayPeriod = uint48(bound(decayPeriod_, 6 hours, 1 weeks)); + _setDecayPeriod(decayPeriod); + + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } function testFuzz_minPriceNonZero_durationGreaterThanLimit_reverts( uint8 decayTarget_, From 3c08fdf2ff8b242ad4c198ce63ebfb40b019a1bf Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Thu, 26 Sep 2024 13:29:07 +0400 Subject: [PATCH 63/69] Update soldeer.lock --- soldeer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soldeer.lock b/soldeer.lock index fd976d0a1..bc487092a 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -16,7 +16,7 @@ name = "forge-std" version = "1.9.1" source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_9_1_03-07-2024_14:44:59_forge-std-v1.9.1.zip" checksum = "110b35ad3604d91a919c521c71206c18cd07b29c750bd90b5cbbaf37288c9636" -integrity = "389f8bfe6b6aad01915b1e38e6d4839f8189e8d4792b42be4e10d0a96a358e3f" +integrity = "229fada41d3af5734ba5e8b2715734b99c7d70335f3dd5a103d2a2e1cae831ba" [[dependencies]] name = "prb-math" From 8a91becedddb7b831e35ee276d52aee1ca3f42b3 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Thu, 26 Sep 2024 15:36:29 +0400 Subject: [PATCH 64/69] Additional fuzz tests --- test/modules/auctions/GDA/GDATest.sol | 8 +- test/modules/auctions/GDA/auction.t.sol | 139 ++++++++++++++---------- 2 files changed, 86 insertions(+), 61 deletions(-) diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index e297de06f..ac0548046 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -119,10 +119,16 @@ abstract contract GdaTest is Test, Permit2User { _; } + function _setDuration( + uint48 duration_ + ) internal { + _auctionParams.duration = duration_; + } + modifier givenDuration( uint48 duration_ ) { - _auctionParams.duration = duration_; + _setDuration(duration_); _; } diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index f8a5f9ccd..92b29a06f 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -316,21 +316,14 @@ contract GdaCreateAuctionTest is GdaTest { _assertAuctionData(); } - function testFuzz_minPriceNonZero_durationGreaterThanLimit_reverts( - uint8 decayTarget_, - uint8 decayHours_ + function testFuzz_minPriceNonZero_duration( + uint48 duration_ ) public { - // Normalize the inputs - uint256 decayTarget = uint256(decayTarget_ % 40 == 0 ? 40 : decayTarget_ % 40) * 1e16; - uint256 decayPeriod = uint256(decayHours_ % 163) * 1 hours + 6 hours; - console2.log("Decay target:", decayTarget); - console2.log("Decay period:", decayPeriod); - // Calculate the decay constant // q1 > qm here because qm = 0.5 * q0, and the max decay target is 0.4 uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); - UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); + UD60x18 q1 = q0.mul(UNIT - ud(_DECAY_TARGET)).div(UNIT); UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); console2.log("q0:", q0.unwrap()); @@ -338,32 +331,27 @@ contract GdaCreateAuctionTest is GdaTest { console2.log("qm:", qm.unwrap()); // Calculate the decay constant - UD60x18 decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(decayPeriod).div(_ONE_DAY)); + UD60x18 decayConstant = + (q0 - qm).div(q1 - qm).ln().div(convert(_DECAY_PERIOD).div(_ONE_DAY)); console2.log("Decay constant:", decayConstant.unwrap()); // Calculate the maximum duration in seconds - uint256 maxDuration = convert(EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); + uint256 maxDuration = convert(LN_OF_PRODUCT_LN_MAX.div(decayConstant).mul(_ONE_DAY)); console2.log("Max duration:", maxDuration); - // Set the decay target and decay period to the fuzzed values - // Set duration to the max duration plus 1 - _gdaParams.decayTarget = decayTarget; - _gdaParams.decayPeriod = decayPeriod; - _auctionParams.implParams = abi.encode(_gdaParams); - _auctionParams.duration = uint48(maxDuration + 1); - - // Expect revert - bytes memory err = - abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 6); - vm.expectRevert(err); + // Set the bounds + uint48 duration = uint48(bound(duration_, 1 days, maxDuration)); + console2.log("Duration:", duration); + _setDuration(duration); // Call the function _createAuctionLot(); - } - // TODO: min price zero, duration greater than log + // Check the auction data + _assertAuctionData(); + } - function testFuzz_minPriceNonZero_durationEqualToLimit_succeeds( + function testFuzz_minPriceNonZero_duration_aboveMaximum_reverts( uint8 decayTarget_, uint8 decayHours_ ) public { @@ -389,35 +377,33 @@ contract GdaCreateAuctionTest is GdaTest { console2.log("Decay constant:", decayConstant.unwrap()); // Calculate the maximum duration in seconds - uint256 maxDuration = convert(LN_OF_PRODUCT_LN_MAX.div(decayConstant).mul(_ONE_DAY)); + uint256 maxDuration = convert(EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); console2.log("Max duration:", maxDuration); // Set the decay target and decay period to the fuzzed values - // Set duration to the max duration + // Set duration to the max duration plus 1 _gdaParams.decayTarget = decayTarget; _gdaParams.decayPeriod = decayPeriod; _auctionParams.implParams = abi.encode(_gdaParams); - _auctionParams.duration = uint48(maxDuration); + _auctionParams.duration = uint48(maxDuration + 1); + + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 6); + vm.expectRevert(err); // Call the function _createAuctionLot(); } - function testFuzz_minPriceZero_durationGreaterThanLimit_reverts( - uint8 decayTarget_, - uint8 decayHours_ + function testFuzz_minPriceZero_duration( + uint48 duration_ ) public givenMinPrice(0) { - // Normalize the inputs - uint256 decayTarget = uint256(decayTarget_ % 40 == 0 ? 40 : decayTarget_ % 40) * 1e16; - uint256 decayPeriod = uint256(decayHours_ % 163) * 1 hours + 6 hours; - console2.log("Decay target:", decayTarget); - console2.log("Decay period:", decayPeriod); - // Calculate the decay constant - // q1 > qm here because qm < q0 * 0.50, which is the max decay target + // q1 > qm here because qm = 0.5 * q0, and the max decay target is 0.4 uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); - UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); + UD60x18 q1 = q0.mul(UNIT - ud(_DECAY_TARGET)).div(UNIT); UD60x18 qm = ud(_gdaParams.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); console2.log("q0:", q0.unwrap()); @@ -425,32 +411,27 @@ contract GdaCreateAuctionTest is GdaTest { console2.log("qm:", qm.unwrap()); // Calculate the decay constant - UD60x18 decayConstant = (q0 - qm).div(q1 - qm).ln().div(convert(decayPeriod).div(_ONE_DAY)); + UD60x18 decayConstant = + (q0 - qm).div(q1 - qm).ln().div(convert(_DECAY_PERIOD).div(_ONE_DAY)); console2.log("Decay constant:", decayConstant.unwrap()); // Calculate the maximum duration in seconds uint256 maxDuration = convert(EXP_MAX_INPUT.div(decayConstant).mul(_ONE_DAY)); console2.log("Max duration:", maxDuration); - // Set the decay target and decay period to the fuzzed values - // Set duration to the max duration plus 1 - _gdaParams.decayTarget = decayTarget; - _gdaParams.decayPeriod = decayPeriod; - _auctionParams.implParams = abi.encode(_gdaParams); - _auctionParams.duration = uint48(maxDuration + 1); - - // Expect revert - bytes memory err = - abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 5); - vm.expectRevert(err); + // Set the bounds + uint48 duration = uint48(bound(duration_, 1 days, maxDuration)); + console2.log("Duration:", duration); + _setDuration(duration); // Call the function _createAuctionLot(); - } - // TODO: min price not zero, duration greater than exponent + // Check the auction data + _assertAuctionData(); + } - function testFuzz_minPriceZero_durationEqualToLimit_succeeds( + function testFuzz_minPriceZero_duration_aboveMaximum_reverts( uint8 decayTarget_, uint8 decayHours_ ) public givenMinPrice(0) { @@ -461,6 +442,7 @@ contract GdaCreateAuctionTest is GdaTest { console2.log("Decay period:", decayPeriod); // Calculate the decay constant + // q1 > qm here because qm < q0 * 0.50, which is the max decay target uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); UD60x18 q1 = q0.mul(UNIT - ud(decayTarget)).div(UNIT); @@ -479,11 +461,16 @@ contract GdaCreateAuctionTest is GdaTest { console2.log("Max duration:", maxDuration); // Set the decay target and decay period to the fuzzed values - // Set duration to the max duration + // Set duration to the max duration plus 1 _gdaParams.decayTarget = decayTarget; _gdaParams.decayPeriod = decayPeriod; _auctionParams.implParams = abi.encode(_gdaParams); - _auctionParams.duration = uint48(maxDuration); + _auctionParams.duration = uint48(maxDuration + 1); + + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 5); + vm.expectRevert(err); // Call the function _createAuctionLot(); @@ -509,7 +496,23 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - // TODO: min price not zero, eq price times emissions zero + function test_minPriceZero_EqPriceTimesEmissionsNotZero() + public + givenMinPrice(0) + givenLotCapacity(1e9) // Smallest value for capacity is 10^(baseDecimals / 2). We divide the by the duration to get the emissions rate during creation. + givenEquilibriumPrice(2e9) // Smallest value for equilibrium price is 10^(quoteDecimals / 2) + { + // Should revert with the standard duration of 2 days, since: + // 2e9 * (1e9 * 1e18 / 2e18) / 1e18 + // = 2e9 * 5e8 / 1e18 + // = 1e18 / 1e18 = 1 + + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } function test_minPriceNonZero_MinPriceTimesEmissionsZero_reverts() public @@ -531,9 +534,25 @@ contract GdaCreateAuctionTest is GdaTest { _createAuctionLot(); } - // TODO: min price zero, min price times emissions zero + function test_minPriceNonZero_MinPriceTimesEmissionsNotZero_reverts() + public + givenMinPrice(1e9) // Smallest value for min price is 10^(quoteDecimals / 2) + givenEquilibriumPrice(2e9) // Must be no more than 2x the min price + givenLotCapacity(2e9) // Smallest value for capacity is 10^(baseDecimals / 2). We divide the by the duration to get the emissions rate during creation. + { + // Should revert with the standard duration of 2 days, since: + // 2e9 * (1e9 * 1e18 / 2e18) / 1e18 + // = 2e9 * 5e8 / 1e18 + // = 1e18 / 1e18 = 1 + + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } - function _assertAuctionData() internal { + function _assertAuctionData() internal view { // Calculate the decay constant from the input parameters uint256 quoteTokenScale = 10 ** _quoteTokenDecimals; UD60x18 q0 = ud(_gdaParams.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); From fe5037d9378423c17bb501ba8efc472cbda3f2e0 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Thu, 26 Sep 2024 15:41:58 +0400 Subject: [PATCH 65/69] chore: linting --- test/modules/auctions/GDA/GDATest.sol | 2 +- test/modules/auctions/GDA/auction.t.sol | 2 +- test/modules/auctions/GDA/maxAmountAccepted.t.sol | 2 -- test/modules/auctions/GDA/maxPayout.t.sol | 1 - test/modules/auctions/GDA/payoutFor.t.sol | 5 +---- test/modules/auctions/GDA/priceFor.t.sol | 3 +-- test/modules/auctions/GDA/purchase.t.sol | 5 ----- 7 files changed, 4 insertions(+), 16 deletions(-) diff --git a/test/modules/auctions/GDA/GDATest.sol b/test/modules/auctions/GDA/GDATest.sol index ac0548046..8d8d97088 100644 --- a/test/modules/auctions/GDA/GDATest.sol +++ b/test/modules/auctions/GDA/GDATest.sol @@ -32,7 +32,7 @@ abstract contract GdaTest is Test, Permit2User { uint256 internal constant _DECAY_TARGET = 10e16; // 10% uint256 internal constant _DECAY_PERIOD = 12 hours; UD60x18 internal constant _ONE_DAY = UD60x18.wrap(1 days * uUNIT); - UD60x18 internal constant LN_OF_PRODUCT_LN_MAX = UD60x18.wrap(4_883_440_042_183_455_484); + UD60x18 internal constant _LN_OF_PRODUCT_LN_MAX = UD60x18.wrap(4_883_440_042_183_455_484); AtomicAuctionHouse internal _auctionHouse; GradualDutchAuction internal _module; diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol index 92b29a06f..660cd9c09 100644 --- a/test/modules/auctions/GDA/auction.t.sol +++ b/test/modules/auctions/GDA/auction.t.sol @@ -336,7 +336,7 @@ contract GdaCreateAuctionTest is GdaTest { console2.log("Decay constant:", decayConstant.unwrap()); // Calculate the maximum duration in seconds - uint256 maxDuration = convert(LN_OF_PRODUCT_LN_MAX.div(decayConstant).mul(_ONE_DAY)); + uint256 maxDuration = convert(_LN_OF_PRODUCT_LN_MAX.div(decayConstant).mul(_ONE_DAY)); console2.log("Max duration:", maxDuration); // Set the bounds diff --git a/test/modules/auctions/GDA/maxAmountAccepted.t.sol b/test/modules/auctions/GDA/maxAmountAccepted.t.sol index dde54930c..f9b2fe0ee 100644 --- a/test/modules/auctions/GDA/maxAmountAccepted.t.sol +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -2,11 +2,9 @@ pragma solidity 0.8.19; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {uUNIT} from "prb-math-4.0-axis/UD60x18.sol"; import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; -import {console2} from "@forge-std-1.9.1/console2.sol"; contract GdaMaxAmountAcceptedTest is GdaTest { using {PRBMath.mulDiv} for uint256; diff --git a/test/modules/auctions/GDA/maxPayout.t.sol b/test/modules/auctions/GDA/maxPayout.t.sol index 00cbccce2..4dc0ce9f0 100644 --- a/test/modules/auctions/GDA/maxPayout.t.sol +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.19; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {uUNIT} from "prb-math-4.0-axis/UD60x18.sol"; import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; diff --git a/test/modules/auctions/GDA/payoutFor.t.sol b/test/modules/auctions/GDA/payoutFor.t.sol index b47ac0323..6baeb2478 100644 --- a/test/modules/auctions/GDA/payoutFor.t.sol +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {Module} from "../../../../src/modules/Modules.sol"; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import { - UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT -} from "prb-math-4.0-axis/UD60x18.sol"; +import {UD60x18, ud, convert, uUNIT} from "prb-math-4.0-axis/UD60x18.sol"; import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index d31982ca7..04adcfba1 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {Module} from "../../../../src/modules/Modules.sol"; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; import {IGradualDutchAuction} from "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import {UD60x18, ud, convert, UNIT, uUNIT, EXP_MAX_INPUT} from "prb-math-4.0-axis/UD60x18.sol"; +import {UD60x18, ud, convert, uUNIT} from "prb-math-4.0-axis/UD60x18.sol"; import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol index ed7d54b7a..83fd23567 100644 --- a/test/modules/auctions/GDA/purchase.t.sol +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -3,12 +3,7 @@ pragma solidity 0.8.19; import {Module} from "../../../../src/modules/Modules.sol"; import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; -import {IGradualDutchAuction} from - "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.sol"; -import { - UD60x18, ud, convert, UNIT, uUNIT, ZERO, EXP_MAX_INPUT -} from "prb-math-4.0-axis/UD60x18.sol"; import "prb-math-4.0-axis/Common.sol" as PRBMath; import {GdaTest} from "./GDATest.sol"; From c9f4790309ab1e451d507c90cd957895938284da Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Fri, 27 Sep 2024 11:05:29 +0400 Subject: [PATCH 66/69] Decimal tests --- test/modules/auctions/GDA/priceFor.t.sol | 159 ++++++++++++++++++++++- 1 file changed, 156 insertions(+), 3 deletions(-) diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 04adcfba1..f15e3dbcf 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -32,12 +32,19 @@ contract GdaPriceForTest is GdaTest { // [X] it calculates the price correctly // [X] when large, reasonable values are used // [X] it does not overflow + // [X] when the quote token decimals are larger than the base token decimals + // [X] when the minimum price is zero + // [X] it calculates the price correctly + // [X] when the minimum price is non-zero + // [X] it calculates the price correctly + // [X] when the quote token decimals are smaller than the base token decimals + // [X] when the minimum price is zero + // [X] it calculates the price correctly + // [X] when the minimum price is non-zero + // [X] it calculates the price correctly // TODO can we fuzz this better? maybe use some external calculations to compare the values? // Otherwise, we're just recreating the same calculations here and not really validating anything - // TODO: handles quote token decimals greater than base token decimals - // TODO: handles quote token decimals less than base token decimals - function testFuzz_lotIdInvalid_reverts( uint96 lotId_ ) public { @@ -293,4 +300,150 @@ contract GdaPriceForTest is GdaTest { assertGe(price, expectedPrice); assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% } + + function test_givenQuoteTokenDecimalsLarger_minPriceNonZero() + public + givenBaseTokenDecimals(9) + givenLotIsCreated + givenLotHasStarted + { + uint256 baseTokenScale = 10 ** 9; + uint256 quoteTokenScale = 10 ** 18; + uint256 initialPrice = _INITIAL_PRICE * quoteTokenScale / 10 ** 18; + + // The timestamp is the start time so current time == last auction start. + // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. + uint256 payout = (_LOT_CAPACITY * baseTokenScale / 10 ** 18) / _auctionParams.duration; // 1 seconds worth of tokens + console2.log("1 second of token emissions:", payout); + + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + uint256 expectedPrice = initialPrice.mulDiv(payout, baseTokenScale); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% + + // Warp to the end of the decay period + vm.warp(_start + _DECAY_PERIOD + 1); + // The first auction has concluded. The price should be the target price for the decay period. + price = _module.priceFor(_lotId, payout); + console2.log("Price for payout end of decay period:", price); + + expectedPrice = + initialPrice.mulDiv(1e18 - _DECAY_TARGET, 1e18).mulDiv(payout, baseTokenScale); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1%, TODO is this good enough? Seems like it slightly underestimates + } + + function test_givenQuoteTokenDecimalsSmaller_minPriceNonZero() + public + givenQuoteTokenDecimals(9) + givenLotIsCreated + givenLotHasStarted + { + uint256 baseTokenScale = 10 ** 18; + uint256 quoteTokenScale = 10 ** 9; + uint256 initialPrice = _INITIAL_PRICE * quoteTokenScale / 10 ** 18; + + // The timestamp is the start time so current time == last auction start. + // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. + uint256 payout = (_LOT_CAPACITY * baseTokenScale / 10 ** 18) / _auctionParams.duration; // 1 seconds worth of tokens + console2.log("1 second of token emissions:", payout); + + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + uint256 expectedPrice = initialPrice.mulDiv(payout, baseTokenScale); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% + + // Warp to the end of the decay period + vm.warp(_start + _DECAY_PERIOD + 1); + // The first auction has concluded. The price should be the target price for the decay period. + price = _module.priceFor(_lotId, payout); + console2.log("Price for payout end of decay period:", price); + + expectedPrice = + initialPrice.mulDiv(1e18 - _DECAY_TARGET, 1e18).mulDiv(payout, baseTokenScale); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1%, TODO is this good enough? Seems like it slightly underestimates + } + + function test_givenQuoteTokenDecimalsLarger_minPriceZero() + public + givenBaseTokenDecimals(9) + givenMinPrice(0) + givenLotIsCreated + givenLotHasStarted + { + uint256 baseTokenScale = 10 ** 9; + uint256 quoteTokenScale = 10 ** 18; + uint256 initialPrice = _INITIAL_PRICE * quoteTokenScale / 10 ** 18; + + // The timestamp is the start time so current time == last auction start. + // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. + uint256 payout = (_LOT_CAPACITY * baseTokenScale / 10 ** 18) / _auctionParams.duration; // 1 seconds worth of tokens + console2.log("1 second of token emissions:", payout); + + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + uint256 expectedPrice = initialPrice.mulDiv(payout, baseTokenScale); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% + + // Warp to the end of the decay period + vm.warp(_start + _DECAY_PERIOD + 1); + // The first auction has concluded. The price should be the target price for the decay period. + price = _module.priceFor(_lotId, payout); + console2.log("Price for payout end of decay period:", price); + + expectedPrice = + initialPrice.mulDiv(1e18 - _DECAY_TARGET, 1e18).mulDiv(payout, baseTokenScale); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1%, TODO is this good enough? Seems like it slightly underestimates + } + + function test_givenQuoteTokenDecimalsSmaller_minPriceZero() + public + givenQuoteTokenDecimals(9) + givenMinPrice(0) + givenLotIsCreated + givenLotHasStarted + { + uint256 baseTokenScale = 10 ** 18; + uint256 quoteTokenScale = 10 ** 9; + uint256 initialPrice = _INITIAL_PRICE * quoteTokenScale / 10 ** 18; + + // The timestamp is the start time so current time == last auction start. + // The first auction is now starting. 1 seconds worth of tokens should be at the initial price. + uint256 payout = (_LOT_CAPACITY * baseTokenScale / 10 ** 18) / _auctionParams.duration; // 1 seconds worth of tokens + console2.log("1 second of token emissions:", payout); + + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + uint256 expectedPrice = initialPrice.mulDiv(payout, baseTokenScale); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% + + // Warp to the end of the decay period + vm.warp(_start + _DECAY_PERIOD + 1); + // The first auction has concluded. The price should be the target price for the decay period. + price = _module.priceFor(_lotId, payout); + console2.log("Price for payout end of decay period:", price); + + expectedPrice = + initialPrice.mulDiv(1e18 - _DECAY_TARGET, 1e18).mulDiv(payout, baseTokenScale); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1%, TODO is this good enough? Seems like it slightly underestimates + } } From 7e2b780b971afaf02dc3ae64a17654fd44333da3 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Fri, 27 Sep 2024 14:50:24 +0400 Subject: [PATCH 67/69] Attempt at pre-calculated tests --- test/modules/auctions/GDA/priceFor.t.sol | 59 ++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index f15e3dbcf..8538767e8 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -446,4 +446,63 @@ contract GdaPriceForTest is GdaTest { assertApproxEqRel(price, expectedPrice, 1e15); // 0.1%, TODO is this good enough? Seems like it slightly underestimates } + + function test_minPriceNonZero_initialTimestep_largeAmount() + public + givenLotIsCreated + givenLotHasStarted + { + // Set desired payout to be close to the lot capacity + uint256 payout = 9e18; + + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + console2.log("Emissions rate:", data.emissionsRate.unwrap()); + console2.log("Decay constant:", data.decayConstant.unwrap()); + + // Calculate the expected price + // Given: Q(T) = (r * (q0 - qm) * (e^((k*P)/r) - 1)) / ke^(k*T) + (qm * P) + // We know: + // r = 5e18 + // q0 = 5e18 + // qm = 2.5e18 + // k = 446287102628419492 + // T = 0 + // P = 9e18 + // Q(T) = (5e18 * (5e18 - 2.5e18) * (e^((446287102628419492*9e18)/5e18) - 1)) / (446287102628419492e18 * e^(446287102628419492e18*0)) + (2.5e18 * 9e18) + // Should fail with division by zero + vm.expectRevert("divide by zero"); + + // Calculate the price + _module.priceFor(_lotId, payout); + } + + function test_minPriceZero_initialTimestep_largeAmount() + public + givenMinPrice(0) + givenLotIsCreated + givenLotHasStarted + { + // Set desired payout to be close to the lot capacity + uint256 payout = 9e18; + + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + console2.log("Emissions rate:", data.emissionsRate.unwrap()); + console2.log("Decay constant:", data.decayConstant.unwrap()); + + // Calculate the expected price + // Given: Q(T) = (r * q0 * (e^((k*P)/r) - 1)) / ke^(k*T) + // We know: + // r = 5e18 + // q0 = 5e18 + // qm = 2.5e18 + // k = 446287102628419492 + // T = 0 + // P = 9e18 + // Q(T) = (5e18 * 5e18 * (e^((446287102628419492*9e18)/5e18) - 1)) / 446287102628419492e18 * e^(446287102628419492e18*0) + // Should fail with division by zero + vm.expectRevert("divide by zero"); + + // Calculate the price + _module.priceFor(_lotId, payout); + } } From 1f79e10adf19247b96ca320f3742283c661a5d16 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Mon, 30 Sep 2024 13:10:06 +0400 Subject: [PATCH 68/69] Amendments to calculations in tests --- test/modules/auctions/GDA/priceFor.t.sol | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index 8538767e8..ea6fd9e5a 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -468,12 +468,16 @@ contract GdaPriceForTest is GdaTest { // k = 446287102628419492 // T = 0 // P = 9e18 - // Q(T) = (5e18 * (5e18 - 2.5e18) * (e^((446287102628419492*9e18)/5e18) - 1)) / (446287102628419492e18 * e^(446287102628419492e18*0)) + (2.5e18 * 9e18) - // Should fail with division by zero - vm.expectRevert("divide by zero"); + // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((446287102628419492*9e18)/5e18) - 1e18)) / (446287102628419492 * e^(446287102628419492*0)) + (2.5e18 * 9e18 / 1e18) + // Q(T) = 1.6991124264×10^19 + uint256 expectedPrice = 1.6991124264e19; // Calculate the price - _module.priceFor(_lotId, payout); + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + // TODO figure out why this is 57033118271917796895 + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% } function test_minPriceZero_initialTimestep_largeAmount() @@ -498,11 +502,15 @@ contract GdaPriceForTest is GdaTest { // k = 446287102628419492 // T = 0 // P = 9e18 - // Q(T) = (5e18 * 5e18 * (e^((446287102628419492*9e18)/5e18) - 1)) / 446287102628419492e18 * e^(446287102628419492e18*0) - // Should fail with division by zero - vm.expectRevert("divide by zero"); + // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((446287102628419492*9e18)/5e18) - 1e18)) / (446287102628419492 * e^(446287102628419492*0)) + // Q(T) = -5.5088757358×10^18 + + // Expect underflow + vm.expectRevert("arithmetic overflow/underflow"); // Calculate the price _module.priceFor(_lotId, payout); + + // TODO figure out why this is 54723799175963968489 } } From 58eaf950adabab9aa2eb8902cb663a663e7678f8 Mon Sep 17 00:00:00 2001 From: Jem <0x0xjem@gmail.com> Date: Mon, 30 Sep 2024 13:42:18 +0400 Subject: [PATCH 69/69] Adjustments to calculations. New test cases for days 1 and 2. --- test/modules/auctions/GDA/priceFor.t.sol | 158 ++++++++++++++++++++++- 1 file changed, 154 insertions(+), 4 deletions(-) diff --git a/test/modules/auctions/GDA/priceFor.t.sol b/test/modules/auctions/GDA/priceFor.t.sol index ea6fd9e5a..71f8c3ecd 100644 --- a/test/modules/auctions/GDA/priceFor.t.sol +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -468,7 +468,7 @@ contract GdaPriceForTest is GdaTest { // k = 446287102628419492 // T = 0 // P = 9e18 - // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((446287102628419492*9e18)/5e18) - 1e18)) / (446287102628419492 * e^(446287102628419492*0)) + (2.5e18 * 9e18 / 1e18) + // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((446287102628419492 * 9e18)/5e18) - 1e18)) / (446287102628419492 * e^(446287102628419492 * 0)) + (2.5e18 * 9e18 / 1e18) // Q(T) = 1.6991124264×10^19 uint256 expectedPrice = 1.6991124264e19; @@ -499,11 +499,11 @@ contract GdaPriceForTest is GdaTest { // r = 5e18 // q0 = 5e18 // qm = 2.5e18 - // k = 446287102628419492 + // k = 210721031315652584 // T = 0 // P = 9e18 - // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((446287102628419492*9e18)/5e18) - 1e18)) / (446287102628419492 * e^(446287102628419492*0)) - // Q(T) = -5.5088757358×10^18 + // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((210721031315652584 * 9e18)/5e18) - 1e18)) / (210721031315652584 * e^(210721031315652584 * 0)) + // Q(T) = -3.6820134881×10^19 // Expect underflow vm.expectRevert("arithmetic overflow/underflow"); @@ -513,4 +513,154 @@ contract GdaPriceForTest is GdaTest { // TODO figure out why this is 54723799175963968489 } + + function test_minPriceNonZero_dayTwo_largeAmount() + public + givenLotIsCreated + givenLotHasStarted + { + // Warp to the second day + uint48 timestamp = _start + 1 days; + vm.warp(timestamp); + + // Set desired payout to be close to the lot capacity + uint256 payout = 9e18; + + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + console2.log("Emissions rate:", data.emissionsRate.unwrap()); + console2.log("Decay constant:", data.decayConstant.unwrap()); + + // Calculate the expected price + // Given: Q(T) = (r * (q0 - qm) * (e^((k*P)/r) - 1)) / ke^(k*T) + (qm * P) + // We know: + // r = 5e18 + // q0 = 5e18 + // qm = 2.5e18 + // k = 446287102628419492 + // T = 1 (day) + // P = 9e18 + // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((446287102628419492 * 9e18)/5e18) - 1e18)) / (446287102628419492 * e^(446287102628419492 * 1)) + (2.5e18 * 9e18 / 1e18) + // Q(T) = 2.25e19 + uint256 expectedPrice = 2.25e19; + + // Calculate the price + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + // TODO figure out why this is 44601195694027390496 + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% + } + + function test_minPriceZero_dayTwo_largeAmount() + public + givenMinPrice(0) + givenLotIsCreated + givenLotHasStarted + { + // Warp to the second day + uint48 timestamp = _start + 1 days; + vm.warp(timestamp); + + // Set desired payout to be close to the lot capacity + uint256 payout = 9e18; + + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + console2.log("Emissions rate:", data.emissionsRate.unwrap()); + console2.log("Decay constant:", data.decayConstant.unwrap()); + + // Calculate the expected price + // Given: Q(T) = (r * q0 * (e^((k*P)/r) - 1)) / ke^(k*T) + // We know: + // r = 5e18 + // q0 = 5e18 + // qm = 2.5e18 + // k = 210721031315652584 + // T = 1 (day) + // P = 9e18 + // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((210721031315652584 * 9e18)/5e18) - 1e18)) / (210721031315652584 * e^(210721031315652584 * 1)) + // Q(T) = -174.7340294016 + + // Expect underflow + vm.expectRevert("arithmetic overflow/underflow"); + + // Calculate the price + _module.priceFor(_lotId, payout); + + // TODO figure out why this is 44326277332530815351 + } + + function test_minPriceNonZero_lastDay_largeAmount() + public + givenLotIsCreated + givenLotHasStarted + { + // Warp to the last day + uint48 timestamp = _start + 2 days; + vm.warp(timestamp); + + // Set desired payout to be close to the lot capacity + uint256 payout = 9e18; + + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + console2.log("Emissions rate:", data.emissionsRate.unwrap()); + console2.log("Decay constant:", data.decayConstant.unwrap()); + + // Calculate the expected price + // Given: Q(T) = (r * (q0 - qm) * (e^((k*P)/r) - 1)) / ke^(k*T) + (qm * P) + // We know: + // r = 5e18 + // q0 = 5e18 + // qm = 2.5e18 + // k = 446287102628419492 + // T = 2 (day) + // P = 9e18 + // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((446287102628419492 * 9e18)/5e18) - 1e18)) / (446287102628419492 * e^(446287102628419492 * 2)) + (2.5e18 * 9e18 / 1e18) + // Q(T) = 2.25e19 + uint256 expectedPrice = 2.25e19; + + // Calculate the price + uint256 price = _module.priceFor(_lotId, payout); + console2.log("Price for payout at beginning:", price); + + // TODO figure out why this is 36644765244177530185 + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% + } + + function test_minPriceZero_lastDay_largeAmount() + public + givenMinPrice(0) + givenLotIsCreated + givenLotHasStarted + { + // Warp to the last day + uint48 timestamp = _start + 2 days; + vm.warp(timestamp); + + // Set desired payout to be close to the lot capacity + uint256 payout = 9e18; + + IGradualDutchAuction.AuctionData memory data = _getAuctionData(_lotId); + console2.log("Emissions rate:", data.emissionsRate.unwrap()); + console2.log("Decay constant:", data.decayConstant.unwrap()); + + // Calculate the expected price + // Given: Q(T) = (r * q0 * (e^((k*P)/r) - 1)) / ke^(k*T) + // We know: + // r = 5e18 + // q0 = 5e18 + // qm = 2.5e18 + // k = 210721031315652584 + // T = 2 (day) + // P = 9e18 + // Q(T) = (5e18 * ((5e18 - 2.5e18)/1e18) * (e^((210721031315652584 * 9e18)/5e18) - 1e18)) / (210721031315652584 * e^(210721031315652584 * 2)) + // Q(T) = -87.3670147008 + + // Expect underflow + vm.expectRevert("arithmetic overflow/underflow"); + + // Calculate the price + _module.priceFor(_lotId, payout); + + // TODO figure out why this is 35904284639349961078 + } }