Skip to content

Commit 2e11105

Browse files
authored
chore: finish quickstart section for cca (#1084)
1 parent 39e9a59 commit 2e11105

7 files changed

Lines changed: 946 additions & 11 deletions

File tree

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
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

Comments
 (0)