Skip to content

Commit

Permalink
Merge pull request #338 from whetstoneresearch/script/token-deployment
Browse files Browse the repository at this point in the history
Script/token deployment
  • Loading branch information
clemlak authored Feb 11, 2025
2 parents bf678a4 + aab71c4 commit 6c1219d
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 22 deletions.
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

0 comments on commit 6c1219d

Please sign in to comment.