Skip to content

Commit

Permalink
Add final airdrop amount and merkle tree, modded deploy scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
HickupHH3 committed Jan 11, 2022
1 parent 97a9991 commit a22a8b5
Show file tree
Hide file tree
Showing 6 changed files with 1,347 additions and 72 deletions.
4 changes: 4 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '@nomiclabs/hardhat-waffle';
import 'hardhat-gas-reporter';
import 'solidity-coverage';
import './scripts/deploy';
import './scripts/deployTokenSale';
import './scripts/verify';
import './scripts/proposals';

Expand All @@ -27,6 +28,9 @@ const config: HardhatUserConfig = {
},
},
networks: {
develop: {
url: 'http://127.0.0.1:8545/'
},
rinkeby: {
chainId: 4,
url: process.env.RINKEBY_URL || '',
Expand Down
1,184 changes: 1,184 additions & 0 deletions scripts/airdrop/mainnetMerkle.json

Large diffs are not rendered by default.

31 changes: 18 additions & 13 deletions scripts/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ type Config = {
VEST_DURATION: number;
MERKLE_ROOT: string;
TIMELOCK_DELAY: number;
EXPORT_FILENAME: string;
};

type TokenSaleConfig = {
TOKEN_SALE_START: number;
TOKEN_SALE_DURATION: number;
TOKEN_SALE_USDC: string;
Expand Down Expand Up @@ -51,29 +55,30 @@ export const allConfigs: {[key: number]: Config} = {
VEST_DURATION: 4 * ONE_DAY,
MERKLE_ROOT: '0xd97c9a423833d78e0562b8ed2d14752b54e7ef9b52314cafb197e3a339299901',
TIMELOCK_DELAY: 1800, // 30 mins
TOKEN_SALE_START: Math.floor(new Date(`2021-12-27T13:16:00.000Z`).getTime() / 1000),
TOKEN_SALE_DURATION: 14 * ONE_DAY,
TOKEN_SALE_USDC: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', // USDC 6 decimals
TOKEN_SALE_ARENA_PRICE: BN.from(30_000).mul(ONE_18).div(ONE_18), // 0.03 USDC * 1e18 / 1.0 ARENA
TOKEN_SALE_RECIPIENT: '0x670f9e8B37d5816c2eB93A1D94841C66652a8E26', // TODO: change this to real recipient
TOKEN_SALE_WHITELIST,
EXPORT_FILENAME: 'rinkebyAddresses.json',
},
// polygon mainnet
137: {
FREE_SUPPLY: BN.from(900).mul(1_000_000).mul(constants.WeiPerEther), // 900M
AIRDROP_SUPPLY: BN.from(100).mul(1_000_000).mul(constants.WeiPerEther), // 100M
FREE_SUPPLY: BN.from(640_826_767).mul(constants.WeiPerEther), // 1B - mainnet markle tokenTotal
AIRDROP_SUPPLY: BN.from(359_173_233).mul(constants.WeiPerEther), // mainnet merkle tokenTotal
CLAIMABLE_PROPORTION: 2000, // 20%
CLAIM_END_DATE: '2022-12-25', // TODO: edit value
CLAIM_END_DATE: '2023-1-11',
VEST_DURATION: 4 * ONE_YEAR,
MERKLE_ROOT: '0x0', // TODO: edit value
MERKLE_ROOT: '0xb86e0dced055310e26ce11e69d47b6e6064be988564fb002d6ba5a29e7eee713',
TIMELOCK_DELAY: 2 * ONE_DAY, // 2 days (same as ENS)
TOKEN_SALE_START: Math.floor(new Date(`2021-12-27T13:50:00.000Z`).getTime() / 1000),
EXPORT_FILENAME: 'polygonAddresses.json',
},
};

export const tokenSaleConfigs: {[key: number]: TokenSaleConfig} = {
// polygon mainnet
137: {
TOKEN_SALE_START: Math.floor(new Date(`2022-01-12T00:00:00.000Z`).getTime() / 1000),
TOKEN_SALE_DURATION: 14 * ONE_DAY,
TOKEN_SALE_USDC: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
TOKEN_SALE_ARENA_PRICE: BN.from(30_000).mul(ONE_18).div(ONE_18), // 0.03 USDC * 1e18 / 1.0 ARENA
TOKEN_SALE_RECIPIENT: '0x670f9e8B37d5816c2eB93A1D94841C66652a8E26',
TOKEN_SALE_RECIPIENT: '0x670f9e8B37d5816c2eB93A1D94841C66652a8E26', // TODO: change to intended recipient
TOKEN_SALE_WHITELIST,
EXPORT_FILENAME: 'polygonAddresses.json',
EXPORT_FILENAME: 'polygonTokenSaleAddress.json',
},
};
52 changes: 4 additions & 48 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {task} from 'hardhat/config';
import {BigNumber as BN} from 'ethers';
import {expect} from 'chai';
import fs from 'fs';

Expand All @@ -12,8 +11,6 @@ import {
TimelockController,
ArenaGovernor__factory,
ArenaGovernor,
TokenSale__factory,
TokenSale,
} from '../typechain';

import {allConfigs} from './config';
Expand All @@ -23,7 +20,6 @@ let token: ArenaToken;
let revokableTokenLock: RevokableTokenLock;
let timelock: TimelockController;
let governor: ArenaGovernor;
let tokenSale: TokenSale;

// see OZ docs: https://docs.openzeppelin.com/contracts/4.x/api/governance#timelock-roles
const ADMIN_ROLE = '0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5';
Expand Down Expand Up @@ -81,20 +77,8 @@ task('deploy', 'deploy contracts').setAction(async (taskArgs, hre) => {
await governor.deployed();
console.log(`governor address: ${governor.address}`);

console.log(`deploying tokensale...`);
const TokenSaleFactory = (await hre.ethers.getContractFactory('TokenSale')) as TokenSale__factory;
tokenSale = await TokenSaleFactory.deploy(
config.TOKEN_SALE_USDC,
token.address,
config.TOKEN_SALE_START,
config.TOKEN_SALE_DURATION,
config.TOKEN_SALE_ARENA_PRICE,
config.TOKEN_SALE_RECIPIENT,
revokableTokenLock.address,
config.VEST_DURATION
);
await tokenSale.deployed();
console.log(`tokensale address: ${tokenSale.address}`);
console.log(`transfer remaining tokens to timelock`);
await token.transfer(timelock.address, config.FREE_SUPPLY);

// give governor proposer role
// https://docs.openzeppelin.com/contracts/4.x/api/governance#timelock-proposer
Expand All @@ -111,26 +95,10 @@ task('deploy', 'deploy contracts').setAction(async (taskArgs, hre) => {

// set revoker role in TokenLock to timelock
await revokableTokenLock.setRevoker(timelock.address);
// set token sale in TokenLock
await revokableTokenLock.setTokenSale(tokenSale.address);

// transfer tokenlock admin role to timelock
await revokableTokenLock.transferOwnership(timelock.address);

// set up token sale whitelist
await tokenSale.changeWhiteList(
config.TOKEN_SALE_WHITELIST.map(({buyer}) => buyer),
config.TOKEN_SALE_WHITELIST.map(({arenaAmount}) => arenaAmount)
);
// transfer token sale admin role to timelock
await tokenSale.transferOwnership(timelock.address);

// transfer all tokens held by deployer to token sale and timelock
const TOKEN_SALE_SUPPLY = config.TOKEN_SALE_WHITELIST.reduce((sum, el) => sum.add(el.arenaAmount), BN.from(`0`));
console.log(`transferring ${TOKEN_SALE_SUPPLY.toString()} ARENA to TokenSale. Remaining back to Timelock`);
await token.transfer(tokenSale.address, TOKEN_SALE_SUPPLY);
await token.transfer(timelock.address, config.FREE_SUPPLY.sub(TOKEN_SALE_SUPPLY));

// transfer token admin role to timelock
await token.transferOwnership(timelock.address);

Expand All @@ -141,7 +109,6 @@ task('deploy', 'deploy contracts').setAction(async (taskArgs, hre) => {
tokenLock: revokableTokenLock.address,
timelock: timelock.address,
governor: governor.address,
tokenSale: tokenSale.address,
};
let exportJson = JSON.stringify(addressesToExport, null, 2);
fs.writeFileSync(config.EXPORT_FILENAME, exportJson);
Expand Down Expand Up @@ -169,9 +136,6 @@ task('deploy', 'deploy contracts').setAction(async (taskArgs, hre) => {
// TokenLock revoker should be timelock
expect(await revokableTokenLock.revoker()).to.be.eq(timelock.address);

// TokenLock token sale should be set
expect(await revokableTokenLock.tokenSale()).to.be.eq(tokenSale.address);

// TokenLock owner should be timelock
expect(await revokableTokenLock.owner()).to.be.eq(timelock.address);

Expand All @@ -181,11 +145,6 @@ task('deploy', 'deploy contracts').setAction(async (taskArgs, hre) => {
// check Token's tokenlock has been set
expect(await token.tokenLock()).to.be.eq(revokableTokenLock.address);

// check TokenSale's tokenlock has been set
expect(await tokenSale.tokenLock()).to.be.eq(revokableTokenLock.address);
// Token's owner should be timelock
expect(await tokenSale.owner()).to.be.eq(timelock.address);

/////////////////////////
// CONFIG VERIFICATION //
/////////////////////////
Expand All @@ -194,11 +153,8 @@ task('deploy', 'deploy contracts').setAction(async (taskArgs, hre) => {
// check ArenaToken's token balance == AIRDROP_SUPPLY
expect(await token.balanceOf(token.address)).to.be.eq(config.AIRDROP_SUPPLY);

// check timelock's token balance == TOKEN_SALE_SUPPLY
expect(await token.balanceOf(tokenSale.address)).to.be.eq(TOKEN_SALE_SUPPLY);

// check timelock's token balance == FREE_SUPPLY - TOKEN_SALE_SUPPLY (rest of it)
expect(await token.balanceOf(timelock.address)).to.be.eq(config.FREE_SUPPLY.sub(TOKEN_SALE_SUPPLY));
// check timelock's token balance == FREE_SUPPLY
expect(await token.balanceOf(timelock.address)).to.be.eq(config.FREE_SUPPLY);

// check timelock's minDelay
expect(await timelock.getMinDelay()).to.be.eq(config.TIMELOCK_DELAY);
Expand Down
126 changes: 126 additions & 0 deletions scripts/deployTokenSale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {task} from 'hardhat/config';
import {BigNumber as BN, Signer} from 'ethers';
import {expect} from 'chai';
import fs from 'fs';
import path from 'path';

import {
ArenaToken__factory,
TimelockController__factory,
ArenaGovernor__factory,
TokenSale__factory,
TokenSale,
TokenLock__factory,
} from '../typechain';

import {allConfigs, tokenSaleConfigs} from './config';

let proposerAddress: string;
let tokenSale: TokenSale;

const getContracts = (signer: Signer, config: typeof allConfigs[0]) => {
const deploymentFilePath = path.join(`deployments`, config.EXPORT_FILENAME);
if (!fs.existsSync(deploymentFilePath)) throw new Error(`File '${path.resolve(deploymentFilePath)}' does not exist.`);

const contents = fs.readFileSync(deploymentFilePath, `utf8`);
let governorAddress;
let arenaAddress;
let timelockAddress;
let tokenLockAddress;
try {
({
governor: governorAddress,
token: arenaAddress,
tokenLock: tokenLockAddress,
timelock: timelockAddress,
} = JSON.parse(contents));
} catch (error) {
throw new Error(`Cannot parse deployment config at '${path.resolve(deploymentFilePath)}'.`);
}
if (!governorAddress) throw new Error(`Deployment file did not include governor address '${deploymentFilePath}'.`);
if (!arenaAddress) throw new Error(`Deployment file did not include arena token address '${deploymentFilePath}'.`);
if (!timelockAddress) throw new Error(`Deployment file did not include timelock address '${deploymentFilePath}'.`);
if (!tokenLockAddress) throw new Error(`Deployment file did not include tokenLock address '${deploymentFilePath}'.`);

return {
governor: ArenaGovernor__factory.connect(governorAddress, signer),
arenaToken: ArenaToken__factory.connect(arenaAddress, signer),
timelock: TimelockController__factory.connect(timelockAddress, signer),
tokenLock: TokenLock__factory.connect(tokenLockAddress, signer),
};
};

task('deployTokenSale', 'deploy token sale and make proposal for relevant actions').setAction(async (taskArgs, hre) => {
const networkId = hre.network.config.chainId as number;
const [proposer] = await hre.ethers.getSigners();
proposerAddress = await proposer.getAddress();
console.log(`Proposer: ${proposerAddress}`);

let config = tokenSaleConfigs[networkId];
if (!config) throw new Error(`Unknown network ${hre.network.name} (${networkId})`);
const {governor, arenaToken, timelock, tokenLock} = getContracts(proposer, allConfigs[networkId]);

console.log(`deploying tokensale...`);
const TokenSaleFactory = (await hre.ethers.getContractFactory('TokenSale')) as TokenSale__factory;

tokenSale = await TokenSaleFactory.deploy(
config.TOKEN_SALE_USDC,
arenaToken.address,
config.TOKEN_SALE_START,
config.TOKEN_SALE_DURATION,
config.TOKEN_SALE_ARENA_PRICE,
config.TOKEN_SALE_RECIPIENT,
tokenLock.address,
allConfigs[networkId].VEST_DURATION
);
await tokenSale.deployed();
console.log(`tokenSale address: ${tokenSale.address}`);

// set up token sale whitelist
await tokenSale.changeWhiteList(
config.TOKEN_SALE_WHITELIST.map(({buyer}) => buyer),
config.TOKEN_SALE_WHITELIST.map(({arenaAmount}) => arenaAmount)
);
const TOKEN_SALE_SUPPLY = config.TOKEN_SALE_WHITELIST.reduce((sum, el) => sum.add(el.arenaAmount), BN.from(`0`));
// transfer token sale admin role to timelock
await tokenSale.transferOwnership(timelock.address);

// 1st action: set token sale in TokenLock
// 2nd action: request TOKEN_SALE_SUPPLY tokens from timelock to tokenSale
let targets: string[] = [tokenLock.address, arenaToken.address];
let values: string[] = ['0', '0'];
let calldatas: string[] = [
tokenLock.interface.encodeFunctionData('setTokenSale', [tokenSale.address]),
arenaToken.interface.encodeFunctionData('transfer', [tokenSale.address, TOKEN_SALE_SUPPLY]),
];

const tx = await governor['propose(address[],uint256[],bytes[],string)'](
targets,
values,
calldatas,
`Conduct Arena token sale!`
);
console.log(`proposal submitted: ${tx.hash}`);
console.log(`waiting for block inclusion ...`);
await tx.wait(1);

console.log('exporting addresses...');
let addressesToExport = {
proposer: proposerAddress,
tokenSale: tokenSale.address,
};
let exportJson = JSON.stringify(addressesToExport, null, 2);
fs.writeFileSync(config.EXPORT_FILENAME, exportJson);

/////////////////////////////////
// ACCESS CONTROL VERIFICATION //
/////////////////////////////////
console.log('verifying access control settings...');
// check tokenSale's tokenlock has been set
expect(await tokenSale.tokenLock()).to.be.eq(tokenLock.address);
// tokenSale's owner should be timelock
expect(await tokenSale.owner()).to.be.eq(timelock.address);

console.log('verification complete!');
process.exit(0);
});
22 changes: 11 additions & 11 deletions scripts/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ task('verifyContracts', 'verify deployed contracts')
revokableTokenLock = await hre.ethers.getContractAt('RevokableTokenLock', addresses['tokenLock']);
timelock = await hre.ethers.getContractAt('TimelockController', addresses['timelock']);
governor = await hre.ethers.getContractAt('ArenaGovernor', addresses['governor']);
tokenSale = await hre.ethers.getContractAt('TokenSale', addresses['tokenSale']);
// tokenSale = await hre.ethers.getContractAt('TokenSale', addresses['tokenSale']);

let config = allConfigs[networkId];

Expand All @@ -37,16 +37,16 @@ task('verifyContracts', 'verify deployed contracts')
await verifyContract(hre, revokableTokenLock.address, [token.address, addresses['deployer']]);
await verifyContract(hre, timelock.address, [config.TIMELOCK_DELAY, [], []]);
await verifyContract(hre, governor.address, [token.address, timelock.address]);
await verifyContract(hre, tokenSale.address, [
config.TOKEN_SALE_USDC,
token.address,
config.TOKEN_SALE_START,
config.TOKEN_SALE_DURATION,
config.TOKEN_SALE_ARENA_PRICE,
config.TOKEN_SALE_RECIPIENT,
revokableTokenLock.address,
config.VEST_DURATION,
]);
// await verifyContract(hre, tokenSale.address, [
// config.TOKEN_SALE_USDC,
// token.address,
// config.TOKEN_SALE_START,
// config.TOKEN_SALE_DURATION,
// config.TOKEN_SALE_ARENA_PRICE,
// config.TOKEN_SALE_RECIPIENT,
// revokableTokenLock.address,
// config.VEST_DURATION,
// ]);
process.exit(0);
});

Expand Down

0 comments on commit a22a8b5

Please sign in to comment.