|
| 1 | +--- |
| 2 | +id: example-configuration |
| 3 | +title: Example configuration |
| 4 | +sidebar_position: 3 |
| 5 | +--- |
| 6 | + |
| 7 | +# Configuring a CCA auction |
| 8 | +This section will walk you through each parameter of a CCA auction and an example configuration. For more details please refer to the [technical reference](/docs/contracts/liquidity-launchpad/05-auction-mechanism.md). |
| 9 | + |
| 10 | +### Prerequisites |
| 11 | +Basic understanding of the CCA auction mechanism and Solidity is assumed. This guide continues from the [previous section](/docs/contracts/liquidity-launchpad/quickstart/local-deployment.md). |
| 12 | + |
| 13 | +## Auction Parameters |
| 14 | +The `AuctionParameters` struct parameterizes a new CCA auction. It is encoded and passed to the `ContinuousClearingAuctionFactory` contract when deploying a new auction. The struct definition is as follows: |
| 15 | + |
| 16 | +```solidity |
| 17 | +/// from: https://github.com/Uniswap/continuous-clearing-auction/blob/main/src/interfaces/IContinuousClearingAuction.sol |
| 18 | +struct AuctionParameters { |
| 19 | + address currency; // token to raise funds in. Use address(0) for ETH |
| 20 | + address tokensRecipient; // address to receive leftover tokens |
| 21 | + address fundsRecipient; // address to receive all raised funds |
| 22 | + uint64 startBlock; // Block which the first step starts |
| 23 | + uint64 endBlock; // When the auction finishes |
| 24 | + uint64 claimBlock; // Block when the auction can claimed |
| 25 | + uint256 tickSpacing; // Fixed granularity for prices |
| 26 | + address validationHook; // Optional hook called before a bid |
| 27 | + uint256 floorPrice; // Starting floor price for the auction |
| 28 | + uint128 requiredCurrencyRaised; // Amount of currency required to be raised for the auction to graduate |
| 29 | + bytes auctionStepsData; // Packed bytes describing token issuance schedule |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +We'll cover each parameter in detail below. |
| 34 | + |
| 35 | +### currency |
| 36 | +The `currency` parameter is the address of the token that will be used to raise funds for the auction. This can be any ERC20 token or the native token of the chain (address(0)). |
| 37 | + |
| 38 | +### tokensRecipient |
| 39 | +The `tokensRecipient` parameter is the address that will receive the leftover tokens after the auction is complete. Depending on the implementation, this may be a trusted EOA address or a strategy contract address if the auction is being used to bootstrap a liquidity pool. |
| 40 | + |
| 41 | +### fundsRecipient |
| 42 | +The `fundsRecipient` parameter is the address that will receive the funds raised during the auction. Again, depending on the implementation, this may be a trusted EOA address or a strategy contract address if the auction is being used to bootstrap a liquidity pool. |
| 43 | + |
| 44 | +### startBlock |
| 45 | +The `startBlock` parameter is the block number at which the auction will start. Once the auction starts, the supply schedule will begin and bids will be accepted. Note that `startBlock` is inclusive so the auction will start exactly on the block specified. |
| 46 | + |
| 47 | +### endBlock |
| 48 | +The `endBlock` parameter is the block number at which the auction will end. Note that `endBlock` is exclusive so the auction will end on the block specified. No more bids are accepted at and after the end block. |
| 49 | + |
| 50 | +### claimBlock |
| 51 | +The `claimBlock` parameter is the block number at which purchased tokens can be claimed. It must be at or after the `endBlock`. |
| 52 | + |
| 53 | +### tickSpacing |
| 54 | +The `tickSpacing` parameter denotes the **minimum** price increment for bids. It is used to prevent users from being outbid by others by infinitesimally small amounts and for gas efficiency in finding new clearing prices. Generally integrators should choose a tick spacing of AT LEAST 1 basis point of the floor price. 1% or 10% is also reasonable. |
| 55 | + |
| 56 | +Bids can only be placed at increments of tickSpacing, but the auction may clear at any price. |
| 57 | + |
| 58 | +### validationHook |
| 59 | +The `validationHook` parameter is an optional contract that can be used to validate bids before they are accepted. It is called before a bid is accepted and can be used to reject bids that do not meet certain criteria. It must implement the `IValidationHook` interface. Use `address(0)` to opt-out of validation. |
| 60 | + |
| 61 | +### floorPrice |
| 62 | +The `floorPrice` parameter is the starting floor price for the auction. It is the minimum price at which bids will be accepted. |
| 63 | + |
| 64 | +All prices in the auction are represented as the ratio of `currency` to `token`. For example, a floor price with an integer component of 1000 means that 1000 `currency` is required to purchase 1 `token`. Additionally, the price is represented as a Q96 fixed-point number to allow for fractional prices. For more details about the Q-number format please refer to this wikipedia [article](https://en.wikipedia.org/wiki/Q_(number_format)). |
| 65 | + |
| 66 | +Using the above example, a floor price of 1000 would be represented as `1000 << 96` or `1000 * 2^96`. |
| 67 | + |
| 68 | +### requiredCurrencyRaised |
| 69 | +The `requiredCurrencyRaised` parameter is the amount of `currency` required to be raised for the auction to graduate. If the auction does not raise this amount, the auction will not graduate and all bidders will be able to withdraw their initial bid amounts. No tokens will be sold and the `totalSupply` will be swept back to the `tokensRecipient`. |
| 70 | + |
| 71 | +### auctionStepsData |
| 72 | +The `auctionStepsData` parameter is a packed bytes array that describes the token issuance schedule. It is used to determine the amount of tokens that will be sold in each block. It is a series of `uint64` values that represent the per-block issuance rate in MPS (milli-bips), and the number of blocks to sell over. |
| 73 | + |
| 74 | +For more details about the auction steps please refer to the [technical reference](/docs/contracts/liquidity-launchpad/05-auction-mechanism.md#auction-steps-supply-issuance-schedule). |
| 75 | + |
| 76 | +## Example configuration |
| 77 | +Let's create an example configuration for a CCA auction. We'll use the script we started in the [previous section](/docs/contracts/liquidity-launchpad/quickstart/local-deployment.md). |
| 78 | + |
| 79 | +Here's the script copy and pasted for convenience: |
| 80 | + |
| 81 | +```solidity |
| 82 | +// SPDX-License-Identifier: MIT |
| 83 | +pragma solidity ^0.8.0; |
| 84 | +
|
| 85 | +import {Script} from "forge-std/Script.sol"; |
| 86 | +import {ContinuousClearingAuctionFactory} from "../src/ContinuousClearingAuctionFactory.sol"; |
| 87 | +import {IDistributionContract} from "../src/interfaces/external/IDistributionContract.sol"; |
| 88 | +import {console2} from "forge-std/console2.sol"; |
| 89 | +
|
| 90 | +contract ExampleCCADeploymentScript is Script { |
| 91 | + function setUp() public {} |
| 92 | +
|
| 93 | + function run() public { |
| 94 | + vm.startBroadcast(); |
| 95 | + ContinuousClearingAuctionFactory factory = new ContinuousClearingAuctionFactory(); |
| 96 | + console2.log("Factory deployed to:", address(factory)); |
| 97 | +
|
| 98 | + // TODO: configure the auction and deploy it via `initializeDistribution()` |
| 99 | +
|
| 100 | + vm.stopBroadcast(); |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +First, make sure to import the `AuctionParameters` struct from the `IContinuousClearingAuction` interface: |
| 106 | + |
| 107 | +```solidity |
| 108 | +import {AuctionParameters} from "../src/interfaces/IContinuousClearingAuction.sol"; |
| 109 | +``` |
| 110 | + |
| 111 | +Then, we can configure the auction parameters: |
| 112 | + |
| 113 | +```solidity |
| 114 | +address deployer = vm.envAddress("DEPLOYER"); |
| 115 | +
|
| 116 | +AuctionParameters memory parameters = AuctionParameters({ |
| 117 | + currency: address(0), // We'll use the native token for this example |
| 118 | + tokensRecipient: deployer, |
| 119 | + fundsRecipient: deployer, |
| 120 | + startBlock: uint64(block.number), // Start the auction on the current block |
| 121 | + endBlock: uint64(block.number + 100), // End the auction after 100 blocks |
| 122 | + claimBlock: uint64(block.number + 100), // Allow claims at the end of the auction |
| 123 | + tickSpacing: 79228162514264334008320, // Use a tick spacing equal to the floor price |
| 124 | + validationHook: address(0), // Use no validation hook |
| 125 | + floorPrice: 79228162514264334008320, // Use a floor price representing a ratio of 1:1,000,000 (1 ETH for 1 million tokens) |
| 126 | + requiredCurrencyRaised: 0, // No graduation threshold |
| 127 | + auctionStepsData: bytes("") // Leave this blank for now |
| 128 | +}); |
| 129 | +``` |
| 130 | + |
| 131 | +Let's build the auction steps data. For simplicity, we'll sell tokens following a monotonically increasing schedule. We'll sell 10% over 50 blocks, 49% over 49 blocks, and the final 41% in the last block. See the [note about auction steps](/docs/contracts/liquidity-launchpad/05-auction-mechanism.md#note-on-auction-steps) for more details about the rationale behind this example schedule. |
| 132 | + |
| 133 | +To derive the steps: |
| 134 | +- First tranche: 10% over 50 blocks |
| 135 | +Express 10% in MPS as 1e6 (1,000,000). Over 50 blocks this is 1e6 / 50 = 20,000 MPS per block. |
| 136 | +Pack this into a bytes8 value: |
| 137 | + |
| 138 | +```solidity |
| 139 | +bytes8 firstTranche = uint64(20_000) | (uint64(50) << 24); |
| 140 | +``` |
| 141 | + |
| 142 | +Repeat this for the rest of the tranches to get the final auction steps data: |
| 143 | +```solidity |
| 144 | +bytes8 secondTranche = uint64(100_000) | (uint64(49) << 24); // 49e6 / 49 = 100_000 MPS per block |
| 145 | +bytes8 thirdTranche = uint64(4_100_000) | (uint64(1) << 24); // 41e6 / 1 = 4_100_000 MPS per block |
| 146 | +``` |
| 147 | + |
| 148 | +Finally, pack the auction steps data into a bytes array: |
| 149 | +```solidity |
| 150 | +bytes memory auctionStepsData = abi.encodePacked(firstTranche, secondTranche, thirdTranche); |
| 151 | +
|
| 152 | +// Set the auction steps data |
| 153 | +parameters.auctionStepsData = auctionStepsData; |
| 154 | +``` |
| 155 | + |
| 156 | +You can leverage the [AuctionStepsBuilder](https://github.com/Uniswap/continuous-clearing-auction/blob/main/test/utils/AuctionStepsBuilder.sol) helper library to build the auction steps data. |
| 157 | + |
| 158 | +Before we can finish the script we need to deploy a MockERC20 token to use in the auction. You can use the `ERC20Mock` contract in `@openzeppelin/contracts/mocks/token/ERC20Mock.sol`, or any other MockERC20 token you prefer. |
| 159 | + |
| 160 | +```solidity |
| 161 | +import {ERC20Mock} from '@openzeppelin/contracts/mocks/token/ERC20Mock.sol'; |
| 162 | +``` |
| 163 | + |
| 164 | +Now let's finish the script: |
| 165 | + |
| 166 | +```solidity |
| 167 | + using AuctionStepsBuilder for bytes; |
| 168 | +
|
| 169 | + function run() public { |
| 170 | + address deployer = vm.envAddress("DEPLOYER"); |
| 171 | +
|
| 172 | + vm.startBroadcast(); |
| 173 | + ContinuousClearingAuctionFactory factory = new ContinuousClearingAuctionFactory(); |
| 174 | + console2.log("Factory deployed to:", address(factory)); |
| 175 | +
|
| 176 | + ERC20Mock token = new ERC20Mock(); |
| 177 | + uint256 totalSupply = 1_000_000_000e18; // 1 billion tokens |
| 178 | +
|
| 179 | + bytes memory auctionStepsData = AuctionStepsBuilder.init().addStep(20_000, 50).addStep(100_000, 49).addStep(4_100_000, 1); |
| 180 | +
|
| 181 | + AuctionParameters memory parameters = AuctionParameters({ |
| 182 | + currency: address(0), |
| 183 | + tokensRecipient: deployer, |
| 184 | + fundsRecipient: deployer, |
| 185 | + startBlock: uint64(block.number), |
| 186 | + endBlock: uint64(block.number + 100), |
| 187 | + claimBlock: uint64(block.number + 100), |
| 188 | + tickSpacing: 79228162514264334008320, |
| 189 | + validationHook: address(0), |
| 190 | + floorPrice: 79228162514264334008320, |
| 191 | + requiredCurrencyRaised: 0, |
| 192 | + auctionStepsData: auctionStepsData |
| 193 | + }); |
| 194 | +
|
| 195 | + IDistributionContract auction = IDistributionContract(factory.initializeDistribution(address(token), totalSupply, abi.encode(parameters), bytes32(0))); |
| 196 | + token.mint(address(auction), totalSupply); |
| 197 | + console2.log("Auction deployed to:", address(auction)); |
| 198 | +
|
| 199 | + vm.stopBroadcast(); |
| 200 | + } |
| 201 | +``` |
| 202 | + |
| 203 | +This will deploy the mock token, deploy the auction contract, and mint the total supply (1 billion tokens) to the auction contract. |
| 204 | + |
| 205 | +The final step in the script is to ensure that we call `onTokensReceived()` on the auction contract to register the receipt of the tokens. |
| 206 | + |
| 207 | +```solidity |
| 208 | + token.mint(address(auction), totalSupply); |
| 209 | + auction.onTokensReceived(); |
| 210 | +``` |
| 211 | + |
| 212 | +The complete script should look like this: |
| 213 | + |
| 214 | +```solidity |
| 215 | +// SPDX-License-Identifier: MIT |
| 216 | +pragma solidity ^0.8.0; |
| 217 | +
|
| 218 | +import {Script} from "forge-std/Script.sol"; |
| 219 | +import {ContinuousClearingAuctionFactory} from "../src/ContinuousClearingAuctionFactory.sol"; |
| 220 | +import {AuctionParameters} from "../src/interfaces/IContinuousClearingAuction.sol"; |
| 221 | +import {IDistributionContract} from "../src/interfaces/external/IDistributionContract.sol"; |
| 222 | +import {AuctionStepsBuilder} from "../test/utils/AuctionStepsBuilder.sol"; |
| 223 | +import {ERC20Mock} from '@openzeppelin/contracts/mocks/token/ERC20Mock.sol'; |
| 224 | +import {console2} from "forge-std/console2.sol"; |
| 225 | +
|
| 226 | +contract ExampleCCADeploymentScript is Script { |
| 227 | + using AuctionStepsBuilder for bytes; |
| 228 | + function setUp() public {} |
| 229 | +
|
| 230 | + function run() public { |
| 231 | + address deployer = vm.envAddress("DEPLOYER"); |
| 232 | +
|
| 233 | + vm.startBroadcast(); |
| 234 | + ContinuousClearingAuctionFactory factory = new ContinuousClearingAuctionFactory(); |
| 235 | + console2.log("Factory deployed to:", address(factory)); |
| 236 | +
|
| 237 | + ERC20Mock token = new ERC20Mock(); |
| 238 | + uint256 totalSupply = 1_000_000_000e18; // 1 billion tokens |
| 239 | +
|
| 240 | + bytes memory auctionStepsData = AuctionStepsBuilder.init().addStep(20_000, 50).addStep(100_000, 49).addStep(4_100_000, 1); |
| 241 | +
|
| 242 | + AuctionParameters memory parameters = AuctionParameters({ |
| 243 | + currency: address(0), |
| 244 | + tokensRecipient: deployer, |
| 245 | + fundsRecipient: deployer, |
| 246 | + startBlock: uint64(block.number), |
| 247 | + endBlock: uint64(block.number + 100), |
| 248 | + claimBlock: uint64(block.number + 100), |
| 249 | + tickSpacing: 79228162514264334008320, |
| 250 | + validationHook: address(0), |
| 251 | + floorPrice: 79228162514264334008320, |
| 252 | + requiredCurrencyRaised: 0, |
| 253 | + auctionStepsData: auctionStepsData |
| 254 | + }); |
| 255 | +
|
| 256 | + IDistributionContract auction = IDistributionContract(factory.initializeDistribution(address(token), totalSupply, abi.encode(parameters), bytes32(0))); |
| 257 | +
|
| 258 | + token.mint(address(auction), totalSupply); |
| 259 | + auction.onTokensReceived(); |
| 260 | +
|
| 261 | + console2.log("Auction deployed to:", address(auction)); |
| 262 | + vm.stopBroadcast(); |
| 263 | + } |
| 264 | +} |
| 265 | +``` |
| 266 | + |
| 267 | +Let's run the script: |
| 268 | +```bash |
| 269 | +forge script scripts/ExampleCCADeploymentScript.s.sol:ExampleCCADeploymentScript \ |
| 270 | +--rpc-url http://localhost:8545 --private-key <your-private-key> --broadcast |
| 271 | +``` |
| 272 | + |
| 273 | +The deployment should be successful and you should see the factory and auction contract addresses logged to the console. |
| 274 | + |
| 275 | +### Next steps |
| 276 | +In the next section we'll write some scripts to interact with the deployed auction contract. |
0 commit comments