import { BigNumber , Signer } from "ethers" ; import { Cloner , GenericERC20WithGovernance , Vesting , } from "../build/typechain/" ; import { ZERO_ADDRESS , getCurrentBlockTimestamp , setTimestamp , } from "./testUtils" ; import chai from "chai" ; import { deployments } from "hardhat" ; import { solidity } from "ethereum-waffle" ; chai . use ( solidity ) ; const { expect } = chai ; describe ( "Vesting" , ( ) => { let signers ; let deployer ; let deployerAddress ; let beneficiary ; let beneficiaryAddress ; let governance ; let governanceAddress ; let malActor ; let vestingClone ; let vesting ; let dummyToken ; let cloner ; const setupTest = deployments . createFixture ( async ( { deployments , ethers } ) => { const { deploy } = deployments ; await deployments . fixture ( ) ; signers = await ethers . getSigners ( ) ; deployer = signers [ 0 ] ; deployerAddress = await deployer . getAddress ( ) ; beneficiary = signers [ 1 ] ; beneficiaryAddress = await beneficiary . getAddress ( ) ; governance = signers [ 0 ] ; governanceAddress = deployerAddress ; malActor = signers [ 10 ] ; await deploy ( "DummyToken" , { contract : "GenericERC20WithGovernance" , args : [ "DummyToken" , "TOKEN" , 18 ] , from : deployerAddress , } ) ; dummyToken = await ethers . getContract ( "DummyToken" ) ; await dummyToken . mint ( deployerAddress , BigNumber . from ( 10 ) . pow ( 18 ) . mul ( 10000 ) ) ; await deploy ( "Cloner" , { from : deployerAddress , log : true , skipIfAlreadyDeployed : true , } ) ; await deploy ( "Vesting" , { from : deployerAddress , log : true , skipIfAlreadyDeployed : true , } ) ; cloner = await ethers . getContract ( "Cloner" ) ; vesting = await ethers . getContract ( "Vesting" ) ; const cloneAddress = await cloner . callStatic . clone ( vesting . address ) ; await cloner . clone ( vesting . address ) ; vestingClone = ( await ethers . getContractAt ( "Vesting" , cloneAddress ) ) as Vesting ; } ) ; beforeEach ( async ( ) => { await setupTest ( ) ; } ) ; describe ( "initialize" , ( ) => { let startTimestamp ; beforeEach ( async ( ) => { startTimestamp = ( await getCurrentBlockTimestamp ( ) ) - 1000 ; } ) ; it ( "Fails to initialize the logic contract" , async ( ) => { await expect ( vesting . initialize ( dummyToken . address , beneficiaryAddress , startTimestamp , 3600 , 7200 ) ) . to . be . revertedWith ( "cannot initialize logic contract" ) ; } ) ; it ( "Fails to initialize a clone with empty beneficiary" , async ( ) => { await expect ( vestingClone . initialize ( dummyToken . address , ZERO_ADDRESS , startTimestamp , 3600 , 7200 ) ) . to . be . revertedWith ( "beneficiary cannot be empty" ) ; } ) ; it ( "Fails to initialize a clone with longer cliff than duration" , async ( ) => { await expect ( vestingClone . initialize ( dummyToken . address , beneficiaryAddress , startTimestamp , 7201 , 7200 ) ) . to . be . revertedWith ( "cliff is greater than duration" ) ; } ) ; it ( "Successfully initializes a clone" , async ( ) => { await vestingClone . initialize ( dummyToken . address , beneficiaryAddress , startTimestamp , 3600 , 7200 ) ; expect ( await vestingClone . beneficiary ( ) ) . to . eq ( beneficiaryAddress ) ; expect ( await vestingClone . governance ( ) ) . to . eq ( deployerAddress ) ; } ) ; } ) ; describe ( "vestedAmount" , ( ) => { const totalVestedAmount = BigNumber . from ( 10 ) . pow ( 18 ) . mul ( 10000 ) ; beforeEach ( async ( ) => { const startTimestamp = ( await getCurrentBlockTimestamp ( ) ) - 1000 ; await vestingClone . initialize ( dummyToken . address , beneficiaryAddress , startTimestamp , 3600 , 7200 ) ; } ) ; describe ( "contract is initialized but NOT filled with tokens" , ( ) => { it ( "Successfully returns 0 when contract is empty" , async ( ) => { expect ( await vestingClone . vestedAmount ( ) ) . to . eq ( 0 ) ; const startTimestamp = await vestingClone . startTimestamp ( ) ; await setTimestamp ( startTimestamp . add ( 3600 ) . toNumber ( ) ) ; await expect ( vestingClone . connect ( beneficiary ) . release ( ) ) . to . be . revertedWith ( "No tokens to release" ) ; } ) ; } ) ; describe ( "contract is initialized and filled with some tokens" , ( ) => { beforeEach ( async ( ) => { await dummyToken . transfer ( vestingClone . address , totalVestedAmount ) ; } ) ; it ( "Successfully calculates the vested amounts" , async ( ) => { const startTimestamp = await vestingClone . startTimestamp ( ) ; expect ( await vestingClone . vestedAmount ( ) ) . to . eq ( 0 ) ; await setTimestamp ( startTimestamp . add ( 1800 ) . toNumber ( ) ) ; expect ( await vestingClone . vestedAmount ( ) ) . to . eq ( 0 ) ; await setTimestamp ( startTimestamp . add ( 3600 ) . toNumber ( ) ) ; expect ( await vestingClone . vestedAmount ( ) ) . to . eq ( totalVestedAmount . div ( 2 ) ) ; await setTimestamp ( startTimestamp . add ( 5400 ) . toNumber ( ) ) ; expect ( await vestingClone . vestedAmount ( ) ) . to . eq ( totalVestedAmount . mul ( 3 ) . div ( 4 ) ) ; await setTimestamp ( startTimestamp . add ( 7200 ) . toNumber ( ) ) ; expect ( await vestingClone . vestedAmount ( ) ) . to . eq ( totalVestedAmount ) ; } ) ; it ( "Successfully returns 0 when there are no more tokens left in the contract" , async ( ) => { const startTimestamp = await vestingClone . startTimestamp ( ) ; await setTimestamp ( startTimestamp . add ( 7200 ) . toNumber ( ) ) ; expect ( await vestingClone . vestedAmount ( ) ) . to . eq ( totalVestedAmount ) ; await vestingClone . connect ( beneficiary ) . release ( ) ; expect ( await vestingClone . vestedAmount ( ) ) . to . eq ( 0 ) ; } ) ; } ) ; } ) ; describe ( "release" , ( ) => { const totalVestedAmount = BigNumber . from ( 10 ) . pow ( 18 ) . mul ( 10000 ) ; beforeEach ( async ( ) => { const startTimestamp = ( await getCurrentBlockTimestamp ( ) ) - 1000 ; await vestingClone . initialize ( dummyToken . address , beneficiaryAddress , startTimestamp , 3600 , 7200 ) ; await dummyToken . transfer ( vestingClone . address , totalVestedAmount ) ; } ) ; it ( "Fails when there are no tokens to claim" , async ( ) => { await expect ( vestingClone . connect ( beneficiary ) . release ( ) ) . to . be . revertedWith ( "No tokens to release" ) ; } ) ; it ( "Successfully releases the vested amounts" , async ( ) => { const startTimestamp = await vestingClone . startTimestamp ( ) ; await setTimestamp ( startTimestamp . add ( 3600 ) . toNumber ( ) ) ; expect ( await vestingClone . vestedAmount ( ) ) . to . eq ( totalVestedAmount . div ( 2 ) ) ; await vestingClone . connect ( beneficiary ) . release ( ) ; expect ( await dummyToken . balanceOf ( beneficiaryAddress ) ) . gte ( totalVestedAmount . div ( 2 ) ) . and . lte ( "5001388888888888888888" ) ; await setTimestamp ( startTimestamp . add ( 5400 ) . toNumber ( ) ) ; await vestingClone . connect ( beneficiary ) . release ( ) ; expect ( await dummyToken . balanceOf ( beneficiaryAddress ) ) . gte ( totalVestedAmount . mul ( 3 ) . div ( 4 ) ) . and . lte ( "7501388888888888888888" ) ; await setTimestamp ( startTimestamp . add ( 7200 ) . toNumber ( ) ) ; await vestingClone . connect ( beneficiary ) . release ( ) ; expect ( await dummyToken . balanceOf ( beneficiaryAddress ) ) . eq ( totalVestedAmount ) ; } ) ; } ) ; describe ( "changeBeneficiary" , ( ) => { const totalVestedAmount = BigNumber . from ( 10 ) . pow ( 18 ) . mul ( 10000 ) ; beforeEach ( async ( ) => { const startTimestamp = ( await getCurrentBlockTimestamp ( ) ) - 1000 ; await vestingClone . initialize ( dummyToken . address , beneficiaryAddress , startTimestamp , 3600 , 7200 ) ; await dummyToken . transfer ( vestingClone . address , totalVestedAmount ) ; } ) ; it ( "Fails when called by other than the governance" , async ( ) => { await expect ( vestingClone . connect ( malActor ) . changeBeneficiary ( await malActor . getAddress ( ) ) ) . to . be . revertedWith ( "only governance can perform this action" ) ; } ) ; it ( "Successfully changes beneficiary" , async ( ) => { await vestingClone . connect ( governance ) . changeBeneficiary ( deployerAddress ) ; expect ( await vestingClone . beneficiary ( ) ) . to . be . eq ( deployerAddress ) ; } ) ; } ) ; } ) ;