From a989ffa188060e01617c501bef05e9b5d2257f87 Mon Sep 17 00:00:00 2001 From: clemlak <39790678+clemlak@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:26:06 +0400 Subject: [PATCH 1/7] build: remove console logs from deployment script --- script/V1DeploymentScript.s.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/script/V1DeploymentScript.s.sol b/script/V1DeploymentScript.s.sol index 93b885cc..91db25d4 100644 --- a/script/V1DeploymentScript.s.sol +++ b/script/V1DeploymentScript.s.sol @@ -30,10 +30,6 @@ contract V1DeploymentScript is Script { bytes memory data = vm.parseJson(json, string.concat(".", vm.toString(block.chainid))); Addresses memory addresses = abi.decode(data, (Addresses)); - console.log("uniswapV2Router02 %s", addresses.uniswapV2Router02); - console.log("uniswapV2Factory %s", addresses.uniswapV2Factory); - console.log("uniswapV3Factory %s", addresses.uniswapV3Factory); - address owner = vm.envOr("PROTOCOL_OWNER", address(0)); require(owner != address(0), "PROTOCOL_OWNER not set! Please edit your .env file."); console.log(unicode"👑 PROTOCOL_OWNER set as %s", owner); From 5dd1aa745ce1af9d9f15158e4cdc1cdc58b5c171 Mon Sep 17 00:00:00 2001 From: clemlak <39790678+clemlak@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:28:50 +0400 Subject: [PATCH 2/7] build: clean up V1 deployment script, add some comments --- script/V1DeploymentScript.s.sol | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/script/V1DeploymentScript.s.sol b/script/V1DeploymentScript.s.sol index 91db25d4..9065bcd6 100644 --- a/script/V1DeploymentScript.s.sol +++ b/script/V1DeploymentScript.s.sol @@ -15,21 +15,16 @@ struct Addresses { } contract V1DeploymentScript is Script { - Airlock airlock; - TokenFactory tokenFactory; - UniswapV3Initializer uniswapV3Initializer; - GovernanceFactory governanceFactory; - UniswapV2Migrator uniswapV2LiquidityMigrator; - function run() public { + // Let's validate that we have the correct addresses for the chain we're targeting string memory path = "./script/addresses.json"; string memory json = vm.readFile(path); bool exists = vm.keyExistsJson(json, string.concat(".", vm.toString(block.chainid))); require(exists, string.concat("Missing Uniswap addresses for chain with id ", vm.toString(block.chainid))); - bytes memory data = vm.parseJson(json, string.concat(".", vm.toString(block.chainid))); Addresses memory addresses = abi.decode(data, (Addresses)); + // Let's check that a valid protocol owner is set address owner = vm.envOr("PROTOCOL_OWNER", address(0)); require(owner != address(0), "PROTOCOL_OWNER not set! Please edit your .env file."); console.log(unicode"👑 PROTOCOL_OWNER set as %s", owner); @@ -37,19 +32,21 @@ contract V1DeploymentScript is Script { vm.startBroadcast(); console.log(unicode"🚀 Deploying contracts..."); - // Owner of the protocol is set as the deployer to allow the whitelisting of modules, ownership - // is then transferred - airlock = new Airlock(msg.sender); - tokenFactory = new TokenFactory(address(airlock)); - uniswapV3Initializer = new UniswapV3Initializer(address(airlock), IUniswapV3Factory(addresses.uniswapV3Factory)); - governanceFactory = new GovernanceFactory(address(airlock)); - uniswapV2LiquidityMigrator = new UniswapV2Migrator( + // Owner of the protocol is first set as the deployer to allow the whitelisting of modules, + // ownership is then transferred to the address defined as the PROTOCOL_OWNER + Airlock airlock = new Airlock(msg.sender); + TokenFactory tokenFactory = new TokenFactory(address(airlock)); + UniswapV3Initializer uniswapV3Initializer = + new UniswapV3Initializer(address(airlock), IUniswapV3Factory(addresses.uniswapV3Factory)); + GovernanceFactory governanceFactory = new GovernanceFactory(address(airlock)); + UniswapV2Migrator uniswapV2LiquidityMigrator = new UniswapV2Migrator( address(airlock), IUniswapV2Factory(addresses.uniswapV2Factory), IUniswapV2Router02(addresses.uniswapV2Router02), owner ); + // Whitelisting the initial modules address[] memory modules = new address[](4); modules[0] = address(tokenFactory); modules[1] = address(uniswapV3Initializer); @@ -64,6 +61,7 @@ contract V1DeploymentScript is Script { airlock.setModuleState(modules, states); + // Transfer ownership to the actual PROTOCOL_OWNER airlock.transferOwnership(owner); console.log(unicode"✨ Contracts were successfully deployed:"); From 79b49125ca947656d2d73689bcff97d5048b039d Mon Sep 17 00:00:00 2001 From: clemlak <39790678+clemlak@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:46:08 +0400 Subject: [PATCH 3/7] build: add token deployment script --- script/V1DeploymentScript.s.sol | 98 ++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/script/V1DeploymentScript.s.sol b/script/V1DeploymentScript.s.sol index 9065bcd6..d452bbbf 100644 --- a/script/V1DeploymentScript.s.sol +++ b/script/V1DeploymentScript.s.sol @@ -2,11 +2,21 @@ pragma solidity ^0.8.24; import { Script, console } from "forge-std/Script.sol"; -import { Airlock, ModuleState } from "src/Airlock.sol"; +import { IUniswapV2Router02 } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Router02.sol"; +import { + Airlock, + ModuleState, + CreateParams, + ITokenFactory, + IGovernanceFactory, + IPoolInitializer, + ILiquidityMigrator +} from "src/Airlock.sol"; import { TokenFactory } from "src/TokenFactory.sol"; import { GovernanceFactory } from "src/GovernanceFactory.sol"; import { UniswapV2Migrator, IUniswapV2Router02, IUniswapV2Factory } from "src/UniswapV2Migrator.sol"; -import { UniswapV3Initializer, IUniswapV3Factory } from "src/UniswapV3Initializer.sol"; +import { InitData, UniswapV3Initializer, IUniswapV3Factory } from "src/UniswapV3Initializer.sol"; +import { DERC20 } from "src/DERC20.sol"; struct Addresses { address uniswapV2Factory; @@ -79,6 +89,16 @@ contract V1DeploymentScript is Script { console.log("| UniswapV2LiquidityMigrator | %s |", address(uniswapV2LiquidityMigrator)); console.log("+----------------------------+--------------------------------------------+"); + // Now it's time to deploy a first token + _deployToken( + airlock, + tokenFactory, + governanceFactory, + uniswapV3Initializer, + uniswapV2LiquidityMigrator, + IUniswapV2Router02(addresses.uniswapV2Router02).WETH() + ); + vm.stopBroadcast(); // Some checks to ensure that the deployment was successful @@ -94,4 +114,78 @@ contract V1DeploymentScript is Script { ); require(airlock.owner() == owner, "Ownership not transferred to PROTOCOL_OWNER"); } + + function _deployToken( + Airlock airlock, + ITokenFactory tokenFactory, + IGovernanceFactory governanceFactory, + IPoolInitializer poolInitializer, + ILiquidityMigrator liquidityMigrator, + address weth + ) internal { + int24 DEFAULT_LOWER_TICK = 167_520; + int24 DEFAULT_UPPER_TICK = 200_040; + uint256 DEFAULT_MAX_SHARE_TO_BE_SOLD = 0.23 ether; + + bool isToken0; + uint256 initialSupply = 100_000_000 ether; + string memory name = "Best Coin"; + string memory symbol = "BEST"; + bytes memory governanceData = abi.encode(name, 7200, 50_400, initialSupply / 1000); + bytes memory tokenFactoryData = abi.encode(name, symbol, 0, 0, new address[](0), new uint256[](0), ""); + + // Compute the asset address that will be created + bytes32 salt; + bytes memory creationCode = type(DERC20).creationCode; + bytes memory create2Args = abi.encode( + name, + symbol, + initialSupply, + address(airlock), + address(airlock), + 0, + 0, + new address[](0), + new uint256[](0), + "" + ); + address predictedAsset = vm.computeCreate2Address( + salt, keccak256(abi.encodePacked(creationCode, create2Args)), address(tokenFactory) + ); + + isToken0 = predictedAsset < address(weth); + + int24 tickLower = isToken0 ? -DEFAULT_UPPER_TICK : DEFAULT_LOWER_TICK; + int24 tickUpper = isToken0 ? -DEFAULT_LOWER_TICK : DEFAULT_UPPER_TICK; + + bytes memory poolInitializerData = abi.encode( + InitData({ + fee: uint24(vm.envOr("V3_FEE", uint256(3000))), + tickLower: tickLower, + tickUpper: tickUpper, + numPositions: 10, + maxShareToBeSold: DEFAULT_MAX_SHARE_TO_BE_SOLD + }) + ); + + (address asset,,,,) = airlock.create( + CreateParams( + initialSupply, + initialSupply, + weth, + tokenFactory, + tokenFactoryData, + governanceFactory, + governanceData, + poolInitializer, + poolInitializerData, + liquidityMigrator, + "", + address(this), + salt + ) + ); + + require(asset == predictedAsset, "Predicted asset address doesn't match actual"); + } } From 572525dddc9654abb8aad3aededdfc25f10877c6 Mon Sep 17 00:00:00 2001 From: clemlak <39790678+clemlak@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:46:29 +0400 Subject: [PATCH 4/7] build: add UNICHAIN_MAINNET_RPC_URL --- .env.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 28f332fd..9d62285d 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,8 @@ IS_TOKEN_0=FALSE USING_ETH=FALSE FEE=30 -MAINNET_RPC_URL="http://..." +MAINNET_RPC_URL="https://..." UNICHAIN_SEPOLIA_RPC_URL="https://sepolia.unichain.org" +UNICHAIN_MAINNET_RPC_URL="https://..." PROTOCOL_OWNER= V3_FEE= \ No newline at end of file From ddcad6a945a6693283df002e1151b8928a7f86ca Mon Sep 17 00:00:00 2001 From: clemlak <39790678+clemlak@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:25:50 +0400 Subject: [PATCH 5/7] chore: add some missing NatSpec to Airlock contract --- src/Airlock.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Airlock.sol b/src/Airlock.sol index 87a24bb1..a5645e37 100644 --- a/src/Airlock.sol +++ b/src/Airlock.sol @@ -54,10 +54,13 @@ struct AssetData { * @notice Data used to create a new asset token * @param initialSupply Total supply of the token (might be increased later on) * @param numTokensToSell Amount of tokens to sell in the Doppler hook + * @param numeraire Address of the numeraire token * @param tokenFactory Address of the factory contract deploying the ERC20 token * @param tokenFactoryData Arbitrary data to pass to the token factory * @param governanceFactory Address of the factory contract deploying the governance * @param governanceFactoryData Arbitrary data to pass to the governance factory + * @param poolInitializer Address of the pool initializer contract + * @param poolInitializerData Arbitrary data to pass to the pool initializer * @param liquidityMigrator Address of the liquidity migrator contract * @param integrator Address of the front-end integrator * @param salt Salt used by the different factories to deploy the contracts using CREATE2 @@ -129,6 +132,12 @@ contract Airlock is Ownable { /** * @notice Deploys a new token with the associated governance, timelock and hook contracts + * @param createData Data used to create the new token (see `CreateParams` struct) + * @return asset Address of the deployed asset token + * @return pool Address of the created liquidity pool + * @return governance Address of the deployed governance contract + * @return timelock Address of the deployed timelock contract + * @return migrationPool Address of the created migration pool */ function create( CreateParams calldata createData From 50fffb8d1c4ab71ae9fc7858bba1b60ded5e92d1 Mon Sep 17 00:00:00 2001 From: clemlak <39790678+clemlak@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:28:23 +0400 Subject: [PATCH 6/7] feat: use custom error instead of string in require --- src/DERC20.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DERC20.sol b/src/DERC20.sol index b1074435..f64d062d 100644 --- a/src/DERC20.sol +++ b/src/DERC20.sol @@ -234,7 +234,9 @@ contract DERC20 is ERC20, ERC20Votes, ERC20Permit, Ownable { uint256 newMintRate ) external onlyOwner { // Inflation can't be more than 2% of token supply per year - require(newMintRate <= MAX_YEARLY_MINT_RATE_WAD, "New mint rate exceeds the maximum allowed"); + require( + newMintRate <= MAX_YEARLY_MINT_RATE_WAD, MaxYearlyMintRateExceeded(newMintRate, MAX_YEARLY_MINT_RATE_WAD) + ); if (currentYearStart != 0 && (block.timestamp - lastMintTimestamp) != 0) { mintInflation(); From aab71c41de0492abc8d01f521f23ef96d587802f Mon Sep 17 00:00:00 2001 From: clemlak <39790678+clemlak@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:37:15 +0400 Subject: [PATCH 7/7] chore: add NatSpec to proposalPeriodStart --- src/Governance.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Governance.sol b/src/Governance.sol index ff3b183f..d129a5e8 100644 --- a/src/Governance.sol +++ b/src/Governance.sol @@ -22,6 +22,7 @@ contract Governance is GovernorVotesQuorumFraction, GovernorTimelockControl { + /// @notice Timestamp at which the proposal period starts uint32 public immutable proposalPeriodStart; constructor(