-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
221 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import chai, {expect} from 'chai'; | ||
import {BigNumber as BN} from 'ethers'; | ||
import * as fs from 'fs'; | ||
import {ethers, waffle} from 'hardhat'; | ||
import * as path from 'path'; | ||
import {ArenaToken, RevokableTokenLock} from '../typechain'; | ||
import {impersonateAccountWithFunds, stopImpersonateAccount} from './shared/AccountManipulation'; | ||
import {ONE_18, ONE_DAY, ONE_YEAR} from './shared/Constants'; | ||
import {resetNetwork, setNextBlockTimeStamp} from './shared/TimeManipulation'; | ||
import {MerkleDistributorInfo} from '../src/parse-balance-map'; | ||
|
||
const {solidity, loadFixture} = waffle; | ||
chai.use(solidity); | ||
|
||
let tokenLock: RevokableTokenLock; | ||
let token: ArenaToken; | ||
const CLAIMABLE_PROPORTION = 2000; | ||
const VEST_DURATION = 4 * ONE_YEAR; | ||
const NOW = Math.floor(Date.now() / 1000); | ||
const CLAIM_END_TIME = NOW + ONE_YEAR; | ||
|
||
const airdropJson = JSON.parse( | ||
fs.readFileSync(path.resolve(__dirname, `./fixtures/testAirdrop.json`), `utf8`) | ||
) as MerkleDistributorInfo; | ||
const {claims} = airdropJson; | ||
const claimers = Object.keys(claims); | ||
const AIRDROP_SUPPLY = BN.from(airdropJson.tokenTotal); | ||
const FREE_SUPPLY = BN.from(1000_000_000).mul(ONE_18).sub(AIRDROP_SUPPLY); | ||
|
||
describe('TokenSale', async () => { | ||
const [user, admin, other] = waffle.provider.getWallets(); | ||
|
||
async function fixture() { | ||
await setNextBlockTimeStamp(NOW); | ||
let ArenaTokenFactory = await ethers.getContractFactory('ArenaToken'); | ||
let token = (await ArenaTokenFactory.connect(admin).deploy( | ||
FREE_SUPPLY, | ||
AIRDROP_SUPPLY, | ||
CLAIMABLE_PROPORTION, | ||
CLAIM_END_TIME, | ||
VEST_DURATION | ||
)) as ArenaToken; | ||
|
||
let TokenLockFactory = await ethers.getContractFactory('RevokableTokenLock'); | ||
let tokenLock = (await TokenLockFactory.connect(admin).deploy(token.address, admin.address)) as RevokableTokenLock; | ||
|
||
await token.setTokenLock(tokenLock.address); | ||
return {token, tokenLock}; | ||
} | ||
|
||
beforeEach('deploy fixture', async () => { | ||
({token, tokenLock} = await loadFixture(fixture)); | ||
}); | ||
|
||
describe('#claimTokens, no root set', async () => { | ||
describe('when no merkle root is set', async () => { | ||
it('should revert when trying to claim', async () => { | ||
const claim = claims[claimers[0]]; | ||
const claimerSigner = await impersonateAccountWithFunds(claimers[0]); | ||
await expect(token.connect(claimerSigner).claimTokens(claim.amount, claim.proof)).to.be.revertedWith( | ||
'ArenaToken: Valid proof required.' | ||
); | ||
}); | ||
}); | ||
|
||
describe('when merkle root is set', async () => { | ||
beforeEach('set merkle root', async () => { | ||
await token.setMerkleRoot(airdropJson.merkleRoot); | ||
}); | ||
|
||
it('should revert when claimer not in merkle tree', async () => { | ||
const claim = claims[claimers[0]]; | ||
await expect(token.connect(user).claimTokens(claim.amount, claim.proof)).to.be.revertedWith( | ||
'ArenaToken: Valid proof required.' | ||
); | ||
}); | ||
|
||
it('should let everyone in the merkle tree claim exactly once', async () => { | ||
for (let i = 0; i < claimers.length; i++) { | ||
const claimer = claimers[i]; | ||
const claim = claims[claimer]; | ||
const claimerSigner = await impersonateAccountWithFunds(claimer); | ||
const claimAmount = BN.from(claim.amount); | ||
const distributedAmount = claimAmount.mul(CLAIMABLE_PROPORTION).div(1e4); | ||
|
||
const nextBlock = NOW + ONE_DAY + i * 13; | ||
await setNextBlockTimeStamp(nextBlock); | ||
await expect(() => | ||
token.connect(claimerSigner).claimTokens(claim.amount, claim.proof) | ||
).to.changeTokenBalances( | ||
token, | ||
[claimerSigner, tokenLock, token], | ||
[distributedAmount, claimAmount.sub(distributedAmount), claimAmount.mul(-1)] | ||
); | ||
|
||
const vesting = await tokenLock.vesting(claimer); | ||
expect(vesting.unlockBegin).to.eq(nextBlock); | ||
expect(vesting.unlockCliff).to.eq(nextBlock); | ||
expect(vesting.unlockEnd).to.eq(nextBlock + VEST_DURATION); | ||
expect(vesting.lockedAmounts).to.eq(claimAmount.sub(distributedAmount)); | ||
|
||
await expect(token.connect(claimerSigner).claimTokens(claim.amount, claim.proof)).to.be.revertedWith( | ||
'ArenaToken: Tokens already claimed.' | ||
); | ||
expect(await token.isClaimed(claim.index)).to.be.true; | ||
await stopImpersonateAccount(claimer); | ||
} | ||
|
||
// all airdrops distributed => zero balance left | ||
expect(await token.balanceOf(token.address)).to.be.eq(`0`); | ||
}); | ||
}); | ||
}); | ||
|
||
after('reset network', async () => { | ||
await resetNetwork(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
{ | ||
"merkleRoot": "0xf6a3174a6a23755234ca9741160ced7ad3bb030d9beebf34b81c7ccee5521325", | ||
"tokenTotal": "111273456790012345679000", | ||
"claims": { | ||
"0x05fc93DeFFFe436822100E795F376228470FB514": { | ||
"index": 3, | ||
"amount": "99999999999999999999999", | ||
"proof": [ | ||
"0x1df2a81f5faaa0715fbbbd964b7932033e93b2a61812aa54ed683f9437ee63ec", | ||
"0x89507e63fc342ee6910c523c6d48b3ac052f04abdabc5f007f1251fadf52f6c9", | ||
"0x2f9de3881912d3dc316f94ba1178536ec6458f2782e31a9a7ecec6373c6bcdee" | ||
] | ||
}, | ||
"0x57E7c6B647C004CFB7A38E08fDDef09Af5Ea55eD": { | ||
"index": 2, | ||
"amount": "11111111111111111111111", | ||
"proof": [ | ||
"0xee2881e4ec01b555360d3ede4f5aa690f9ad5dd3078b08e93f10cb73ecc97c41", | ||
"0x5a9b114d67427f0fd4f7b4cdc43b72df107b48af378a20969ee3f960e19860dd", | ||
"0xcf25e423924a50e798ed02c7268d38e21ececd0aceb25c18d8304c02a2def553" | ||
] | ||
}, | ||
"0x6A2dE67981CbE91209c1046D67eF7a45631d0666": { | ||
"index": 1, | ||
"amount": "50000000000000000000", | ||
"proof": [ | ||
"0x6151560a7871f491d682080b805ff64e1adcf83500aa7cd92827491b2c73a548", | ||
"0xaeea4a22851fcbae45e8635cbb8a782ea6025077c4c3b7468a0b03697b219a35", | ||
"0x2f9de3881912d3dc316f94ba1178536ec6458f2782e31a9a7ecec6373c6bcdee" | ||
] | ||
}, | ||
"0x7C262baf13794f54e3514539c411f92716996C38": { | ||
"index": 5, | ||
"amount": "12345678901234567890", | ||
"proof": [ | ||
"0x3d027b44082cbb838456d30ef0ccabf187d198065eee704be7d2260ac23de5a1", | ||
"0xaeea4a22851fcbae45e8635cbb8a782ea6025077c4c3b7468a0b03697b219a35", | ||
"0x2f9de3881912d3dc316f94ba1178536ec6458f2782e31a9a7ecec6373c6bcdee" | ||
] | ||
}, | ||
"0xB9CcDD7Bedb7157798e10Ff06C7F10e0F37C6BdD": { | ||
"index": 4, | ||
"amount": "20000000000000000000", | ||
"proof": [ | ||
"0x64842b66d90a8a19c64cd2b849f7927e55c272cc41db22a32f9aa3315ee4d517", | ||
"0x9d8f6b3a0f19d70d95c91a99e814b70ecf6079fd92f2a89eb1a3ddfa36d009de", | ||
"0xcf25e423924a50e798ed02c7268d38e21ececd0aceb25c18d8304c02a2def553" | ||
] | ||
}, | ||
"0xF3c6F5F265F503f53EAD8aae90FC257A5aa49AC1": { | ||
"index": 6, | ||
"amount": "10000000000000000000", | ||
"proof": [ | ||
"0x98563b98ecf6cce9a38e24d970a507dffa69e8eabf8e8e601025dedfabd66d88", | ||
"0x5a9b114d67427f0fd4f7b4cdc43b72df107b48af378a20969ee3f960e19860dd", | ||
"0xcf25e423924a50e798ed02c7268d38e21ececd0aceb25c18d8304c02a2def553" | ||
] | ||
}, | ||
"0xf0591a60b8dBa2420408Acc5eDFA4f8A15d87308": { | ||
"index": 7, | ||
"amount": "40000000000000000000", | ||
"proof": [ | ||
"0x08edf17e374021d7b03b556d44a8401a1bdd615fe861cac9174e04420d4644ca", | ||
"0x89507e63fc342ee6910c523c6d48b3ac052f04abdabc5f007f1251fadf52f6c9", | ||
"0x2f9de3881912d3dc316f94ba1178536ec6458f2782e31a9a7ecec6373c6bcdee" | ||
] | ||
}, | ||
"0xf94DbB18cc2a7852C9CEd052393d517408E8C20C": { | ||
"index": 0, | ||
"amount": "30000000000000000000", | ||
"proof": [ | ||
"0x6d3f403972026170536f6a14f80f6ec59ab727c5158f3d2e8e1729df5e823bf7", | ||
"0x9d8f6b3a0f19d70d95c91a99e814b70ecf6079fd92f2a89eb1a3ddfa36d009de", | ||
"0xcf25e423924a50e798ed02c7268d38e21ececd0aceb25c18d8304c02a2def553" | ||
] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import hre, {ethers} from 'hardhat'; | ||
import {ONE_18} from './Constants'; | ||
|
||
export const impersonateAccountWithFunds = async (address: string) => { | ||
await hre.network.provider.request({ | ||
method: 'hardhat_impersonateAccount', | ||
params: [address], | ||
}); | ||
// these accounts don't start with a balance, give them one | ||
await hre.network.provider.send('hardhat_setBalance', [ | ||
address, | ||
// need to strip leading zeroes here https://github.com/nomiclabs/hardhat/issues/1585 | ||
ONE_18.toHexString().replace(/0x0+/, '0x'), | ||
]); | ||
|
||
return ethers.provider.getSigner(address); | ||
}; | ||
|
||
export const stopImpersonateAccount = async (address: string) => { | ||
await hre.network.provider.request({ | ||
method: 'hardhat_stopImpersonatingAccount', | ||
params: [address], | ||
}); | ||
}; |