Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Script/token deployment #338

Merged
merged 7 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -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=
128 changes: 108 additions & 20 deletions script/V1DeploymentScript.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,45 +25,38 @@ 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));

console.log("uniswapV2Router02 %s", addresses.uniswapV2Router02);
console.log("uniswapV2Factory %s", addresses.uniswapV2Factory);
console.log("uniswapV3Factory %s", addresses.uniswapV3Factory);

// 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);

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);
Expand All @@ -68,6 +71,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:");
Expand All @@ -85,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
Expand All @@ -100,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");
}
}
9 changes: 9 additions & 0 deletions src/Airlock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/DERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions src/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ contract Governance is
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
/// @notice Timestamp at which the proposal period starts
uint32 public immutable proposalPeriodStart;

constructor(
Expand Down