diff --git a/README.md b/README.md index 8d8bb9ae..3a935687 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. + #### Address Mismatch Many of the contracts (e.g. callbacks) require a specific address prefix or have a deterministic address. If tests are failing for this reason, the cause is usually one of: diff --git a/foundry.toml b/foundry.toml index aac35063..563d696f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,9 +5,11 @@ fs_permissions = [{access = "read-write", path = "./bytecode/"}, {access = "read ffi = true solc_version = "0.8.19" 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 @@ -27,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", 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 = "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 03efc5cb..d916e50d 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/src diff --git a/script/deploy/Deploy.s.sol b/script/deploy/Deploy.s.sol index 95a35504..b520baa1 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 a53686b4..d127173a 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 98a572b7..4dd0fc8c 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 2b8f0a7d..c1c9654c 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 f9121568..fbf65621 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 4459e8e4..8cc58c5c 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/TestSaltConstants.sol b/script/salts/TestSaltConstants.sol new file mode 100644 index 00000000..cda646c3 --- /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(0x7F1bb763E9acE13Ad3488E3d48D4AE1cde6E9604); + address internal constant _GUNI_FACTORY = address(0x2Bc907bF89D72479e6b6A38CC89fdE71672ba90e); +} diff --git a/script/salts/WithSalts.s.sol b/script/salts/WithSalts.s.sol index f03c0b2a..2f5035f2 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 03fe81eb..446debc5 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 6c4135b4..252defd6 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/salts.json b/script/salts/salts.json index e4f1e49e..849ab7f3 100644 --- a/script/salts/salts.json +++ b/script/salts/salts.json @@ -1,6 +1,6 @@ { "AtomicAuctionHouse": { - "0xc34e46cfceb1e62d804e8197ba829a150e216e433ee21a68b96f1edd3abd4dd9": "0x87bc14fda5bd97c7e883ae1227f30462125ebcf6800a34d10f39af4ac46e84b9" + "0xc33a32af6cec8e866f5ee293e333e7c8f7b721b1f69f8a8d9e20a1f3b5135538": "0x0875d97e8c58e2cf11bb3ae95350381f3d4a4c81e598e28a1a7120c846509427" }, "BatchAuctionHouse": { "0x1d8b7b9cfbd8610a556d5e2e85dcdb25a17d6b5407574aef07970a86e4b0e2c3": "0x06374b73456c869d550c6cb45aff40113752ac3c4dd8efb8185b6da5c898aa82", @@ -12,7 +12,7 @@ "0xf77b6d9024091bb6924c65ea67fb047db889c8f626975bc7222df6d0de23612e": "0xe6da5ed268cad156b2f5f63fb1d5a3a86a8c508e669f385d4a772fd22b87c684" }, "BlastAtomicAuctionHouse": { - "0x3d41c1d7705983200681645a648d410d545bba492ad801c3938664a6bf58d882": "0x11f6c4c27a20cd6e6925ddd316451417e2cdf68d28a8bd2be486f9709f89a301" + "0x51bc087e65e71bfc5ad0c2b79d088d131eb8772e04b2dc2f8110931bd570460e": "0xf7e06e4c18040172fe87ec7bef926bf1cee3e23ef8f62307bb0fc53e37e0285a" }, "BlastBatchAuctionHouse": { "0x19f1cca625d728962b50c0f27f6463bb237c5620f334343a132594a6d701f5e9": "0x4437b0b8eb051772d75ad55abc7d73adc986149a8deb2d43e74fe3b83add64d0", @@ -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" + "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" } } diff --git a/script/salts/test/TestSalts.s.sol b/script/salts/test/TestSalts.s.sol index c12717c3..533296aa 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/soldeer.lock b/soldeer.lock index a6863b27..bc487092 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -1,30 +1,38 @@ +[[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 = "229fada41d3af5734ba5e8b2715734b99c7d70335f3dd5a103d2a2e1cae831ba" [[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://github.com/transmissions11/solmate.git" +checksum = "c892309933b25c03d32b1b0d674df7ae292ba925" diff --git a/src/AtomicCatalogue.sol b/src/AtomicCatalogue.sol index f04dfbb3..d28e54fe 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 fc2b4f82..4386d4c8 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 b188ed92..0dddc0f7 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 6831c89c..1ab5009e 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 d885718e..486f0530 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 b5e32538..ad45e8a6 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 3153a468..92818950 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 bc9a5c84..14e81c82 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 abd23593..7c1fddc8 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 ccc32811..d405ac13 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 e0c1eed1..458cbda8 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 338d5b8d..f831473e 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 95a2f132..e9dc6ad0 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 6b6371a2..e8649f50 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 802cd46a..c67215e2 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 7a0f59b9..dd768ea3 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 304ae6df..9a25f99f 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 d566f06b..33ba8d8f 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 32f880a0..e40066c4 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 35b5c048..1ada6921 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 1679cf0e..845b84d6 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/interfaces/modules/auctions/IGradualDutchAuction.sol b/src/interfaces/modules/auctions/IGradualDutchAuction.sol new file mode 100644 index 00000000..a1785c7a --- /dev/null +++ b/src/interfaces/modules/auctions/IGradualDutchAuction.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +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 +interface IGradualDutchAuction is IAtomicAuction { + // ========== ERRORS ========== // + + /// @notice Thrown when the auction parameters are invalid + /// + /// @param step Indicates where the error occurred + error GDA_InvalidParams(uint256 step); + + // ========== 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; + uint256 minimumPrice; + uint256 decayTarget; + uint256 decayPeriod; + } + + // ========== 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/lib/Callbacks.sol b/src/lib/Callbacks.sol index f091c40c..446fda4f 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 8d239add..2ae90d82 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 3d83cd01..35a92fe1 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 9d73ba25..6ea257f0 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 417bfe94..91fd99ad 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 a28d399d..66bdc774 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 72f5f888..3d56bb8f 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 5a4c2e42..6d782bb2 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 6082e693..7761b091 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 bd94dd6a..0c156a09 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 b0e438c7..6349617e 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 0fbaf27a..242a2150 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 new file mode 100644 index 00000000..06358346 --- /dev/null +++ b/src/modules/auctions/atomic/GDA.sol @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: BSL-1.1 +pragma solidity 0.8.19; + +// Protocol dependencies +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 +import { + UD60x18, + ud, + convert, + UNIT, + uUNIT, + EXP_MAX_INPUT, + ZERO, + HALF_UNIT +} 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"; + +/// @notice Continuous Gradual Dutch Auction (GDA) module with exponential decay and a minimum price. +contract GradualDutchAuction is IGradualDutchAuction, AtomicAuctionModule { + using {PRBMath.mulDiv, PRBMath.mulDivUp} for uint256; + + // ========== STATE VARIABLES ========== // + /* 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 + uint256 internal constant MIN_DECAY_TARGET = 1e16; // 1% + 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 + // and calculate the decay target for that point as your input + uint48 internal constant MIN_DECAY_PERIOD = 6 hours; + uint48 internal constant MAX_DECAY_PERIOD = 1 weeks; + + // 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 */ + + mapping(uint96 lotId => AuctionData data) public auctionData; + + // ========== SETUP ========== // + + constructor( + address auctionHouse_ + ) AuctionModule(auctionHouse_) { + // Initially setting the minimum GDA duration to 1 hour + minAuctionDuration = 1 hours; + } + + /// @inheritdoc Module + function VEECODE() public pure override returns (Veecode) { + return toVeecode("01GDA"); + } + + // ========== 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)); + + // Validate parameters + // 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. + // + // 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) + || params.equilibriumPrice > type(uint128).max + ) { + revert GDA_InvalidParams(0); + } + + // 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); + } + + // Minimum price can be zero, but the equations default back to the basic GDA implementation + // If non-zero, its bounds are validated below. + } + + // 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 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 GDA_InvalidParams(3); + } + + UD60x18 decayConstant; + UD60x18 q0; + UD60x18 qm; + { + uint256 quoteTokenScale = 10 ** lot_.quoteTokenDecimals; + q0 = ud(params.equilibriumPrice.mulDiv(uUNIT, quoteTokenScale)); + UD60x18 q1 = q0.mul(UNIT - ud(params.decayTarget)).div(UNIT); + qm = ud(params.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + console2.log("q0:", q0.unwrap()); + console2.log("q1:", q1.unwrap()); + console2.log("qm:", qm.unwrap()); + + // 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 + // 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 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)); + 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 + // constant calculations for more information. + if (qm == ZERO && duration > EXP_MAX_INPUT.div(decayConstant)) { + 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_PRODUCT_LN_MAX.div(decayConstant)) { + revert GDA_InvalidParams(6); + } + + // 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()); + + // 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 GDA_InvalidParams(7); + } + + if (qm > ZERO && qm.mul(emissionsRate) == ZERO) { + revert GDA_InvalidParams(8); + } + + // Store auction data + AuctionData storage data = auctionData[lotId_]; + data.equilibriumPrice = params.equilibriumPrice; + data.minimumPrice = params.minimumPrice; + data.decayConstant = decayConstant; + data.emissionsRate = emissionsRate; + data.lastAuctionStart = uint256(lot_.start); + } + + /// @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_, + bytes calldata + ) internal override returns (uint256 payout, bytes memory auctionOutput) { + // Calculate the payout and emissions + uint256 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 ========== // + + /// @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_); + + // Get lot and auction data + 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 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; + UD60x18 priceDiff = ud(auction.equilibriumPrice - auction.minimumPrice).div( + ud(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 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 + // 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; + 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 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( + convert(block.timestamp - auction.lastAuctionStart).div(ONE_DAY) + ).exp().mul(auction.decayConstant); + + // Calculate the first term in the formula + // 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. + // 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. + // 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 + // 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) { + uint256 minPrice = auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale); + result = result + ud(minPrice.mulDivUp(payout.intoUint256(), uUNIT)); + } + + // Scale price back to quote token decimals and return + uint256 amount = result.intoUint256().mulDiv(quoteTokenScale, uUNIT); + return amount; + } + + /// @inheritdoc IAtomicAuction + 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_); + + // 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(); + } + + return payout; + } + + // Two cases: + // + // 1. Minimum price is zero + // 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) / (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, + // 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_ + ) internal view returns (uint256, uint256) { + 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)); + + // 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) { + // Auction does not have a minimum price + UD60x18 logFactor; + 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).div(ONE_DAY) + ).exp(); + + // Calculate the logarithm + // Operand is guaranteed to be >= 1, so the result is positive + 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 + + // 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(); + + // Calculate the logarithm + // Operand is guaranteed to be >= 1, so the result is positive + logFactor = amount.mul(auction.decayConstant).div( + ekt.mul(auction.emissionsRate).mul(q0) + ).add(UNIT).ln(); + } + + // Calculate the payout + payout = auction.emissionsRate.mul(logFactor).div(auction.decayConstant); + } else { + // Auction has a minimum price + + // Convert minimum price to 18 decimals + // Can't overflow because quoteTokenScale <= uUNIT + UD60x18 qm = ud(auction.minimumPrice.mulDiv(uUNIT, quoteTokenScale)); + + // Calculate first term aka F: (k * Q) / (r * qm) + UD60x18 f = auction.decayConstant.mul(amount).div(auction.emissionsRate.mul(qm)); + + // Calculate second term aka C: (q0 - qm)/(qm * e^(k * T)) + 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. + // 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).div(ONE_DAY) + ).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. + // 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).div(ONE_DAY) + ).exp() + ); + } + + // Calculate the third term: W(C e^(F + C)) + // 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_PRODUCT_LN_MAX / decayConstant check. + // This means that e^(f + c) will always be < EXP_MAX_INPUT / c. + // 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); + + // 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, 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 + // 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 + // + // 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); + } + + // Calculate seconds of emissions from payout + 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); + } + + /// @inheritdoc IAtomicAuction + function maxPayout( + uint96 lotId_ + ) external 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; + } + + /// @inheritdoc IAtomicAuction + 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/src/modules/auctions/batch/EMP.sol b/src/modules/auctions/batch/EMP.sol index 2a17255a..3378fbc9 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 9a2b6e7f..ced95861 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 726d29cb..d9e59353 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 c7956d24..27c6698b 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 94156315..e5f9b702 100644 --- a/test/AtomicAuctionHouse/AuctionHouseTest.sol +++ b/test/AtomicAuctionHouse/AuctionHouseTest.sol @@ -31,8 +31,9 @@ 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 { +abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts, TestSaltConstants { MockFeeOnTransferERC20 internal _baseToken; MockFeeOnTransferERC20 internal _quoteToken; @@ -51,7 +52,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); @@ -106,7 +106,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 @@ -149,17 +149,23 @@ abstract contract AtomicAuctionHouseTest is Test, Permit2User, WithSalts { // ===== 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 { _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 { _; } - 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 { _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 { _; } - 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 { _; } - 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 { _; } - 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 { _; } - 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 { _; } - 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 { // ===== 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 { }); } - 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 { }); } - 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 cb0dcbee..bcf8614b 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 af3c7465..588d5f93 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 2b186831..18f9d511 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 ba81d493..bf880688 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 671ab29e..4eb3e4c9 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 603ab59c..4af2a14e 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 d2258776..737bc058 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 8c4daa3b..170616eb 100644 --- a/test/BatchAuctionHouse/AuctionHouseTest.sol +++ b/test/BatchAuctionHouse/AuctionHouseTest.sol @@ -31,8 +31,9 @@ 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 { +abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts, TestSaltConstants { MockFeeOnTransferERC20 internal _baseToken; MockFeeOnTransferERC20 internal _quoteToken; @@ -50,7 +51,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); @@ -106,7 +106,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 @@ -150,11 +150,15 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts { // ===== 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 { // ===== 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 { _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 { _; } - 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 { _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 { _; } - 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 { _; } - 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 { _; } - 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 { _; } - 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 { _; } - 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 { _; } - 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 { _; } - modifier givenRecipientIsOnBaseTokenBlacklist(address recipient_) { + modifier givenRecipientIsOnBaseTokenBlacklist( + address recipient_ + ) { _baseToken.setBlacklist(recipient_, true); _; } @@ -597,14 +639,18 @@ abstract contract BatchAuctionHouseTest is Test, Permit2User, WithSalts { _; } - 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 { }); } - 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 { }); } - 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 8536cc54..a3461c4d 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 1c6b8d9d..d79a9230 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 8cc28218..1cda4931 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 8230ec5a..881da743 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 db265703..4ca03596 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 49da3722..179ed864 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 728e1142..3cc913ad 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 d5715389..6789d01e 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 368a5b2d..086d8824 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 2f8ef5aa..18840d05 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 b9722c7a..4bcbd802 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 aac435d7..961d925c 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 005f498d..8335c5b4 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 3a85c114..d7040b6e 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 4c145daa..04f04363 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 a22f6a03..fac45a01 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 872e8047..cabc7993 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 a84890b4..28886be4 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 b6cd76c3..484befdd 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 3727843a..415b528a 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 f4943e9b..9369f5e1 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 ea91d5b1..8ad00054 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 c201d687..106e1872 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 84771e55..9818db71 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 4596decf..1405e22e 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 new file mode 100644 index 00000000..8d8d9708 --- /dev/null +++ b/test/modules/auctions/GDA/GDATest.sol @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +// Libraries +import {Test} from "@forge-std-1.9.1/Test.sol"; +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"; + +// 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/atomic/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 = 2 days; + uint256 internal constant _INITIAL_PRICE = 5e18; + 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_PRODUCT_LN_MAX = UD60x18.wrap(4_883_440_042_183_455_484); + + AtomicAuctionHouse internal _auctionHouse; + GradualDutchAuction internal _module; + + // Input parameters (modified by modifiers) + uint48 internal _start; + uint96 internal _lotId = 0; + 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 = IGradualDutchAuction.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_; + _; + } + + function _setDuration( + uint48 duration_ + ) internal { + _auctionParams.duration = duration_; + } + + modifier givenDuration( + uint48 duration_ + ) { + _setDuration(duration_); + _; + } + + modifier givenCapacityInQuote() { + _auctionParams.capacityInQuote = true; + _; + } + + function _createAuctionLot() internal { + vm.prank(address(_auctionHouse)); + _module.auction(_lotId, _auctionParams, _quoteTokenDecimals, _baseTokenDecimals); + } + + modifier givenLotIsCreated() { + _createAuctionLot(); + _; + } + + modifier givenEquilibriumPrice( + uint128 price_ + ) { + _gdaParams.equilibriumPrice = uint256(price_); + _auctionParams.implParams = abi.encode(_gdaParams); + _; + } + + function _setMinPrice( + uint128 minPrice_ + ) internal { + _gdaParams.minimumPrice = uint256(minPrice_); + _auctionParams.implParams = abi.encode(_gdaParams); + } + + modifier givenMinPrice( + uint128 minPrice_ + ) { + _setMinPrice(minPrice_); + _; + } + + 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); + } + _; + } + + function _setDecayTarget( + uint256 decayTarget_ + ) internal { + _gdaParams.decayTarget = decayTarget_; + _auctionParams.implParams = abi.encode(_gdaParams); + } + + modifier givenDecayTarget( + uint256 decayTarget_ + ) { + _setDecayTarget(decayTarget_); + _; + } + + function _setDecayPeriod( + uint256 decayPeriod_ + ) internal { + _gdaParams.decayPeriod = decayPeriod_; + _auctionParams.implParams = abi.encode(_gdaParams); + } + + modifier givenDecayPeriod( + uint256 decayPeriod_ + ) { + _setDecayPeriod(decayPeriod_); + _; + } + + function _concludeLot() internal { + vm.warp(_start + _DURATION + 1); + } + + modifier givenLotHasConcluded() { + _concludeLot(); + _; + } + + modifier givenLotHasStarted() { + vm.warp(_start); + _; + } + + 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 (IGradualDutchAuction.AuctionData memory) { + ( + uint256 eqPrice, + uint256 minPrice, + uint256 lastAuctionStart, + UD60x18 decayConstant, + UD60x18 emissionsRate + ) = _module.auctionData(lotId_); + + return IGradualDutchAuction.AuctionData({ + equilibriumPrice: eqPrice, + minimumPrice: minPrice, + lastAuctionStart: lastAuctionStart, + decayConstant: decayConstant, + emissionsRate: emissionsRate + }); + } +} diff --git a/test/modules/auctions/GDA/auction.t.sol b/test/modules/auctions/GDA/auction.t.sol new file mode 100644 index 00000000..660cd9c0 --- /dev/null +++ b/test/modules/auctions/GDA/auction.t.sol @@ -0,0 +1,601 @@ +// 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 "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 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 less than 10^(quotetoken decimals / 2) + // [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 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 + // [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 + // [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 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 + // [X] when the token decimals differ + // [X] 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_equilibriumPriceIsLessThanMin_reverts( + uint128 price_ + ) public givenEquilibriumPrice(price_ % 1e9) { + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 0); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + 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 = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 0); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + 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_capacity_lessThanMin_reverts( + uint128 capacity_ + ) public givenLotCapacity(capacity_ % 1e9) { + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 1); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_capacity_greaterThanMax_reverts( + uint256 capacity_ + ) public { + vm.assume(capacity_ > type(uint128).max); + _auctionParams.capacity = capacity_; + + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 1); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + 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(); + + // Check the auction data + _assertAuctionData(); + } + + function test_minPrice_equalToDecayTargetPrice_reverts() + public + givenMinPrice(4e18) + givenDecayTarget(20e16) // 20% decay from 5e18 is 4e18 + { + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 4); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + 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); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + 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); + + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 4); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_decayTargetLessThanMinimum_reverts() + public + givenDecayTarget(1e16 - 1) // slightly less than 1% + givenDuration(1 days) + { + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 2); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_decayTargetGreaterThanMaximum_reverts() + public + givenDecayTarget(40e16 + 1) // slightly more than 40% + givenDuration(1 days) + { + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 2); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + 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_decayPeriod_lessThanMinimum_reverts() + public + givenDecayPeriod(uint48(6 hours) - 1) + { + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 3); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + function test_decayPeriod_greaterThanMaximum_reverts() + public + givenDecayPeriod(uint48(1 weeks) + 1) + { + // Expect revert + bytes memory err = + abi.encodeWithSelector(IGradualDutchAuction.GDA_InvalidParams.selector, 3); + vm.expectRevert(err); + + // Call the function + _createAuctionLot(); + } + + 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_duration( + uint48 duration_ + ) public { + // 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(_DECAY_TARGET)).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(_DECAY_PERIOD).div(_ONE_DAY)); + 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)); + console2.log("Max duration:", maxDuration); + + // Set the bounds + uint48 duration = uint48(bound(duration_, 1 days, maxDuration)); + console2.log("Duration:", duration); + _setDuration(duration); + + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } + + function testFuzz_minPriceNonZero_duration_aboveMaximum_reverts( + uint8 decayTarget_, + uint8 decayHours_ + ) 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 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 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); + + // Call the function + _createAuctionLot(); + } + + function testFuzz_minPriceZero_duration( + uint48 duration_ + ) public givenMinPrice(0) { + // 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(_DECAY_TARGET)).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(_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 bounds + uint48 duration = uint48(bound(duration_, 1 days, maxDuration)); + console2.log("Duration:", duration); + _setDuration(duration); + + // Call the function + _createAuctionLot(); + + // Check the auction data + _assertAuctionData(); + } + + function testFuzz_minPriceZero_duration_aboveMaximum_reverts( + 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 + // 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 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 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_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 + 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 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 view { + // 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 + IGradualDutchAuction.AuctionData memory auctionData = _getAuctionData(_lotId); + assertEq(auctionData.equilibriumPrice, _gdaParams.equilibriumPrice); + assertEq(auctionData.minimumPrice, _gdaParams.minimumPrice); + assertEq(auctionData.lastAuctionStart, _auctionParams.start); + assertEq(auctionData.decayConstant.unwrap(), decayConstant.unwrap()); + assertEq(auctionData.emissionsRate.unwrap(), emissionsRate.unwrap()); + } + + 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(); + } +} diff --git a/test/modules/auctions/GDA/cancelAuction.t.sol b/test/modules/auctions/GDA/cancelAuction.t.sol new file mode 100644 index 00000000..7b892f2b --- /dev/null +++ b/test/modules/auctions/GDA/cancelAuction.t.sol @@ -0,0 +1,84 @@ +// 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 "./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 updates the conclusion, capacity and status + // [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 00000000..f9b2fe0e --- /dev/null +++ b/test/modules/auctions/GDA/maxAmountAccepted.t.sol @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; + +import {GdaTest} from "./GDATest.sol"; + +contract GdaMaxAmountAcceptedTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + // [X] when the lot ID is invalid + // [X] it reverts + // [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_ + ) 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_minPriceNonZero_success( + uint128 capacity_, + uint128 price_ + ) + public + 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_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_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_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_minPriceZero_success( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + + function testFuzz_minPriceNonZero_quoteDecimalsSmaller_success( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenQuoteTokenDecimals(6) + 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_minPriceZero_quoteDecimalsSmaller_success( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenQuoteTokenDecimals(6) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + { + uint256 maxAmountAccepted = _module.maxAmountAccepted(_lotId); + uint256 expectedAmount = _module.priceFor(_lotId, capacity_); + assertEq(expectedAmount, maxAmountAccepted); + } + + function testFuzz_minPriceNonZero_quoteDecimalsLarger_success( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenBaseTokenDecimals(6) + 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_minPriceZero_quoteDecimalsLarger_success( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenBaseTokenDecimals(6) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + { + 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 00000000..4dc0ce9f --- /dev/null +++ b/test/modules/auctions/GDA/maxPayout.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import "prb-math-4.0-axis/Common.sol" as PRBMath; + +import {GdaTest} from "./GDATest.sol"; + +contract GdaMaxPayoutTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + // [X] when the lot ID is invalid + // [X] it reverts + // [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_ + ) 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_minPriceNotZero_success( + uint128 capacity_ + ) public givenLotCapacity(capacity_) validateCapacity givenLotIsCreated { + uint256 maxPayout = _module.maxPayout(_lotId); + uint256 expectedMaxPayout = capacity_; + assertEq(expectedMaxPayout, maxPayout); + } + + function testFuzz_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 new file mode 100644 index 00000000..6baeb247 --- /dev/null +++ b/test/modules/auctions/GDA/payoutFor.t.sol @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from + "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.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"; +import {console2} from "@forge-std-1.9.1/console2.sol"; + +contract GdaPayoutForTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + + // [X] when the lot ID is invalid + // [X] it reverts + // [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 + // [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 + // [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 + + 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_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 + 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_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. + 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. + // 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(capacity_) + givenMinPrice(0) + validateCapacity + givenLotIsCreated + givenLotHasStarted + { + vm.assume(amount_ <= _module.maxAmountAccepted(_lotId)); + + _module.payoutFor(_lotId, amount_); + } + + function testFuzz_minPriceNonZero_noOverflows( + uint128 capacity_, + uint128 amount_ + ) public givenLotCapacity(capacity_) validateCapacity givenLotIsCreated givenLotHasStarted { + vm.assume(amount_ <= _module.maxAmountAccepted(_lotId)); + + _module.payoutFor(_lotId, amount_); + } + + function testFuzz_minPriceZero_varyingSetup( + uint128 capacity_, + uint128 price_ + ) + public + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted + { + 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); + + 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); + } + + function testFuzz_minPriceNonZero_varyingSetup( + uint128 capacity_, + uint128 price_, + uint128 minPrice_ + ) + public + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(minPrice_) + validateCapacity + validatePrice + validateMinPrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted + { + 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); + + 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); + } + + 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 new file mode 100644 index 00000000..71f8c3ec --- /dev/null +++ b/test/modules/auctions/GDA/priceFor.t.sol @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IAuction} from "../../../../src/interfaces/modules/IAuction.sol"; +import {IGradualDutchAuction} from + "../../../../src/interfaces/modules/auctions/IGradualDutchAuction.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"; +import {console2} from "@forge-std-1.9.1/console2.sol"; + +contract GdaPriceForTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + + // [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 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 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 + // [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 + + 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 / _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 = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + assertApproxEqRel(price, expectedPrice, 1e15); // 0.1% + + // 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, 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 / _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 = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + 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 = + _INITIAL_PRICE.mulDiv(1e18 - _DECAY_TARGET, 1e18).mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + 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 / _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 = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + assertGe(price, expectedPrice); + } + + function test_minPriceNonZero_lastAuctionStartInPast() public givenLotIsCreated { + 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 / _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 = _INITIAL_PRICE.mulDiv(payout, _BASE_SCALE); + console2.log("Expected price:", expectedPrice); + + assertLe(price, expectedPrice); + } + + function testFuzz_minPriceZero_noOverflows( + uint128 capacity_, + uint128 payout_ + ) + public + givenLotCapacity(capacity_) + givenMinPrice(0) + validateCapacity + givenLotIsCreated + givenLotHasStarted + { + vm.assume(payout_ <= capacity_); + + _module.priceFor(_lotId, payout_); + } + + function testFuzz_minPriceNonZero_noOverflows( + uint128 capacity_, + uint128 payout_ + ) public givenLotCapacity(capacity_) validateCapacity givenLotIsCreated givenLotHasStarted { + vm.assume(payout_ <= capacity_); + + _module.priceFor(_lotId, payout_); + } + + function testFuzz_minPriceZero_varyingSetup( + uint128 capacity_, + uint128 price_ + ) + public + givenDuration(1 days) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(0) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted + { + console2.log("Capacity:", capacity_); + console2.log("Price:", price_); + + 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); + assertGe(price, expectedPrice); + + vm.warp(_start + _DECAY_PERIOD); + price = _module.priceFor(_lotId, payout); + expectedPrice = expectedPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); + assertGe(price, expectedPrice); + } + + function testFuzz_minPriceNonZero_varyingSetup( + uint128 capacity_, + uint128 price_ + ) + public + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinIsHalfPrice(price_) + validateCapacity + validatePrice + validatePriceTimesEmissionsRate + givenLotIsCreated + givenLotHasStarted + { + // // 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 / _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); + assertGe(price, expectedPrice); + + vm.warp(_start + _gdaParams.decayPeriod); + price = _module.priceFor(_lotId, payout); + expectedPrice = expectedPrice.mulDiv(uUNIT - _gdaParams.decayTarget, uUNIT); + assertGe(price, expectedPrice); + } + + 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); + 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 / _auctionParams.duration; + + // Calculate the price + uint256 price = _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, 1e15); // 0.1% + } + + function testFuzz_minPriceNonZero_varyingTimesteps( + uint48 timestep_ + ) public givenLotIsCreated givenLotHasStarted { + // Warp to the timestep + uint48 timestep = timestep_ % _auctionParams.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 / _auctionParams.duration; + + // Calculate the price + uint256 price = _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, 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 + } + + 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)/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 + 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() + 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 = 210721031315652584 + // T = 0 + // P = 9e18 + // 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"); + + // Calculate the price + _module.priceFor(_lotId, payout); + + // 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 + } +} diff --git a/test/modules/auctions/GDA/purchase.t.sol b/test/modules/auctions/GDA/purchase.t.sol new file mode 100644 index 00000000..83fd2356 --- /dev/null +++ b/test/modules/auctions/GDA/purchase.t.sol @@ -0,0 +1,571 @@ +// 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 "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 GdaPurchaseTest is GdaTest { + using {PRBMath.mulDiv} for uint256; + + 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 + // [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 + + 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_varyingSetup( + uint256 amount_, + uint128 capacity_, + 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); + vm.assume(maxAmountAccepted > 100); // Tiny maxAmountAccepted values cause large rounding errors which make the capacity insufficient + 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 + givenQuoteTokenDecimals(6) + 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_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_); + + // 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_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 { + 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_varyingSetup( + uint256 amount_, + uint128 capacity_, + uint128 price_ + ) + public + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(0) + 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_varyingSetup_quoteDecimalsSmaller( + uint256 amount_, + uint128 capacity_, + uint128 price_ + ) + public + givenQuoteTokenDecimals(6) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(0) + 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_varyingSetup_quoteDecimalsLarger( + uint256 amount_, + uint128 capacity_, + uint128 price_ + ) + public + givenBaseTokenDecimals(6) + givenLotCapacity(capacity_) + givenEquilibriumPrice(price_) + givenMinPrice(0) + 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"); + } +} diff --git a/test/modules/derivatives/LinearVesting.t.sol b/test/modules/derivatives/LinearVesting.t.sol index 6d3adebe..ba4e0e13 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 c1882ece..436a17a7 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 729394cc..4358785b 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) {} }