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

feat: automated rKP3R and ibEUR reward distribution #4

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
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
Prev Previous commit
Next Next commit
chore: added test scaffolding (#2)
* chore: added hardhat boilerplate

* chore: run linter

* fix: rm legacy code

* feat: added vyper compiler

* feat: added github workflows

* fix: linter error

* feat: adding basic test structure

* fix: typings bug

* feat: added tests scaffolding

* fix: revert linter

* fix: run prettier
wei3erHase authored May 11, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 1f9cd619ca14e0bd71e8da46e416dc54331b7827
26 changes: 7 additions & 19 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -10,37 +10,25 @@ import 'hardhat-gas-reporter';
import 'hardhat-deploy';
import 'solidity-coverage';
import { HardhatUserConfig, MultiSolcUserConfig, NetworksUserConfig } from 'hardhat/types';
import * as env from './utils/env';
import 'tsconfig-paths/register';
import { getNodeUrl, accounts } from './utils/network';

const networks: NetworksUserConfig = process.env.TEST
? {}
: {
hardhat: {
forking: {
enabled: process.env.FORK ? true : false,
url: getNodeUrl('mainnet'),
url: env.getNodeUrl('ethereum'),
},
},
localhost: {
url: getNodeUrl('localhost'),
accounts: accounts('localhost'),
},
kovan: {
url: getNodeUrl('kovan'),
accounts: accounts('kovan'),
},
rinkeby: {
url: getNodeUrl('rinkeby'),
accounts: accounts('rinkeby'),
},
ropsten: {
url: getNodeUrl('ropsten'),
accounts: accounts('ropsten'),
url: env.getNodeUrl('kovan'),
accounts: env.getAccounts('kovan'),
},
mainnet: {
url: getNodeUrl('mainnet'),
accounts: accounts('mainnet'),
ethereum: {
url: env.getNodeUrl('ethereum'),
accounts: env.getAccounts('ethereum'),
},
};

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -55,7 +55,7 @@
"eth-gas-reporter/colors": "1.4.0"
},
"dependencies": {
"@nomiclabs/hardhat-vyper": "^3.0.0",
"@nomiclabs/hardhat-vyper": "3.0.0",
"@openzeppelin/contracts": "4.6.0",
"solhint-plugin-wonderland": "0.0.1"
},
@@ -81,7 +81,7 @@
"cross-env": "7.0.3",
"dotenv": "16.0.0",
"ethereum-waffle": "3.4.4",
"ethers": "5.6.4",
"ethers": "5.6.5",
"hardhat": "2.9.3",
"hardhat-deploy": "0.11.4",
"hardhat-gas-reporter": "1.0.8",
42 changes: 42 additions & 0 deletions test/e2e/fixed-forex.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { getMainnetSdk } from '@dethcrypto/eth-sdk-client';
import { Keep3rV1 } from '@eth-sdk-types';
import { FixedForex, FixedForex__factory } from '@typechained';
import { ethers } from 'hardhat';
import { evm } from '@utils';
import { expect } from 'chai';
import { getNodeUrl } from 'utils/env';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';

describe('FixedForex @skip-on-coverage', () => {
let deployer: SignerWithAddress;
let keep3rV1: Keep3rV1;
let snapshotId: string;
let fixedForex: FixedForex;

before(async () => {
[deployer] = await ethers.getSigners();

await evm.reset({
jsonRpcUrl: getNodeUrl('ethereum'),
blockNumber: 14750000,
});

const sdk = getMainnetSdk(deployer);
keep3rV1 = sdk.keep3rV1;

const fixedForexFactory = (await ethers.getContractFactory('FixedForex')) as FixedForex__factory;
fixedForex = await fixedForexFactory.connect(deployer).deploy(keep3rV1.address);

snapshotId = await evm.snapshot.take();
});

beforeEach(async () => {
await evm.snapshot.revert(snapshotId);
});

describe('fixed-forex', () => {
it('should be deployed', async () => {
expect(await fixedForex.deployed());
});
});
});
26 changes: 26 additions & 0 deletions test/unit/GaugeProxy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import chai, { expect } from 'chai';
import { MockContract, MockContractFactory, smock } from '@defi-wonderland/smock';
import { GaugeProxy, GaugeProxy__factory } from '@typechained';
import { evm } from '@utils';

chai.use(smock.matchers);

describe('GaugeProxy', () => {
let gauge: MockContract<GaugeProxy>;
let gaugeFactory: MockContractFactory<GaugeProxy__factory>;
let snapshotId: string;

before(async () => {
gaugeFactory = await smock.mock<GaugeProxy__factory>('GaugeProxy');
gauge = await gaugeFactory.deploy();
snapshotId = await evm.snapshot.take();
});

beforeEach(async () => {
await evm.snapshot.revert(snapshotId);
});

it('should be deployed', async () => {
expect(await gauge.deployed());
});
});
9 changes: 9 additions & 0 deletions test/utils/bdd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Suite, SuiteFunction } from 'mocha';

export const then = it;
export const given = beforeEach;
export const when: SuiteFunction = <SuiteFunction>function (title: string, fn: (this: Suite) => void) {
context('when ' + title, fn);
};
when.only = (title: string, fn?: (this: Suite) => void) => context.only('when ' + title, fn!);
when.skip = (title: string, fn: (this: Suite) => void) => context.skip('when ' + title, fn);
49 changes: 49 additions & 0 deletions test/utils/behaviours.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { smock } from '@defi-wonderland/smock';
import { Provider } from '@ethersproject/providers';
import chai, { expect } from 'chai';
import { Signer } from 'ethers';
import { contracts, wallet } from '.';
import { toUnit } from './bn';

chai.use(smock.matchers);

export type Impersonator = Signer | Provider | string;

export const onlyMaker = createOnlyCallableCheck(['maker'], 'OnlyMaker()');
export const onlyKeeper = createOnlyCallableCheck(['keeper'], 'OnlyKeeper()');
export const onlyGovernor = createOnlyCallableCheck(['governance'], 'OnlyGovernor()');
export const onlyPendingGovernor = createOnlyCallableCheck(['pending governance'], 'OnlyPendingGovernor()');

export function createOnlyCallableCheck(allowedLabels: string[], error: string) {
return (
delayedContract: () => any,
fnName: string,
allowedWallet: Impersonator | Impersonator[] | (() => Impersonator | Impersonator[]),
args: unknown[] | (() => unknown[])
) => {
allowedLabels.forEach((allowedLabel, index) => {
it(`should be callable by ${allowedLabel}`, async () => {
let impersonator = allowedWallet;
if (typeof allowedWallet === 'function') impersonator = allowedWallet();
if (Array.isArray(impersonator)) impersonator = impersonator[index];

return expect(callFunction(impersonator as Impersonator)).not.to.be.revertedWith(error);
});
});

it('should not be callable by any address', async () => {
const any = await wallet.generateRandom();
await wallet.setBalance({ account: any.address, balance: toUnit(1) });
return expect(callFunction(any)).to.be.revertedWith(error);
});

function callFunction(impersonator: Impersonator) {
const argsArray: unknown[] = typeof args === 'function' ? args() : args;
const fn = delayedContract().connect(impersonator)[fnName] as (...args: unknown[]) => unknown;
return fn(...argsArray, {
gasLimit: 1e6,
gasPrice: 500e9,
});
}
};
}
28 changes: 28 additions & 0 deletions test/utils/bn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { BigNumber, utils } from 'ethers';
import { expect } from 'chai';

export const expectToEqualWithThreshold = ({
value,
to,
threshold,
}: {
value: BigNumber | number | string;
to: BigNumber | number | string;
threshold: BigNumber | number | string;
}): void => {
value = toBN(value);
to = toBN(to);
threshold = toBN(threshold);
expect(
to.sub(threshold).lte(value) && to.add(threshold).gte(value),
`Expected ${value.toString()} to be between ${to.sub(threshold).toString()} and ${to.add(threshold).toString()}`
).to.be.true;
};

export const toBN = (value: string | number | BigNumber): BigNumber => {
return BigNumber.isBigNumber(value) ? value : BigNumber.from(value);
};

export const toUnit = (value: number): BigNumber => {
return utils.parseUnits(value.toString());
};
2 changes: 2 additions & 0 deletions test/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
export const ETH_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
18 changes: 18 additions & 0 deletions test/utils/contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Contract, ContractFactory } from '@ethersproject/contracts';
import { TransactionResponse } from '@ethersproject/abstract-provider';
import { ContractInterface, Signer } from 'ethers';
import { getStatic } from 'ethers/lib/utils';

export const deploy = async (contract: ContractFactory, args: any[]): Promise<{ tx: TransactionResponse; contract: Contract }> => {
const deploymentTransactionRequest = await contract.getDeployTransaction(...args);
const deploymentTx = await contract.signer.sendTransaction(deploymentTransactionRequest);
const contractAddress = getStatic<(deploymentTx: TransactionResponse) => string>(contract.constructor, 'getContractAddress')(deploymentTx);
const deployedContract = getStatic<(contractAddress: string, contractInterface: ContractInterface, signer?: Signer) => Contract>(
contract.constructor,
'getContract'
)(contractAddress, contract.interface, contract.signer);
return {
tx: deploymentTx,
contract: deployedContract,
};
};
20 changes: 20 additions & 0 deletions test/utils/erc20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { BigNumber, Contract } from 'ethers';
import { ethers } from 'hardhat';

export const deploy = async ({
name,
symbol,
decimals,
initialAccount,
initialAmount,
}: {
name: string;
symbol: string;
decimals?: BigNumber | number;
initialAccount: string;
initialAmount: BigNumber;
}): Promise<Contract> => {
const erc20MockContract = await ethers.getContractFactory('contracts/mocks/ERC20Mock.sol:ERC20Mock');
const deployedContract = await erc20MockContract.deploy(name, symbol, decimals || 18, initialAccount, initialAmount);
return deployedContract;
};
36 changes: 36 additions & 0 deletions test/utils/event-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { TransactionResponse, TransactionReceipt } from '@ethersproject/abstract-provider';
import { expect } from 'chai';

export async function expectNoEventWithName(response: TransactionResponse, eventName: string) {
const receipt = await response.wait();
for (const event of getEvents(receipt)) {
expect(event.event).not.to.equal(eventName);
}
}

export async function readArgFromEvent<T>(response: TransactionResponse, eventName: string, paramName: string): Promise<T | undefined> {
const receipt = await response.wait();
for (const event of getEvents(receipt)) {
if (event.event === eventName) {
return event.args[paramName];
}
}
}

export async function readArgFromEventOrFail<T>(response: TransactionResponse, eventName: string, paramName: string): Promise<T> {
const result = await readArgFromEvent<T>(response, eventName, paramName);
if (result) {
return result;
}
throw new Error(`Failed to find event with name ${eventName}`);
}

function getEvents(receipt: TransactionReceipt): Event[] {
// @ts-ignore
return receipt.events;
}

type Event = {
event: string; // Event name
args: any;
};
73 changes: 73 additions & 0 deletions test/utils/evm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { BigNumber, BigNumberish } from 'ethers';
import { network } from 'hardhat';

export const advanceTimeAndBlock = async (time: number): Promise<void> => {
await advanceTime(time);
await advanceBlocks(1);
};

export const advanceToTimeAndBlock = async (time: number): Promise<void> => {
await advanceToTime(time);
await advanceBlocks(1);
};

export const advanceTime = async (time: number): Promise<void> => {
await network.provider.request({
method: 'evm_increaseTime',
params: [time],
});
};

export const advanceToTime = async (time: number): Promise<void> => {
await network.provider.request({
method: 'evm_setNextBlockTimestamp',
params: [time],
});
};

export const advanceBlocks = async (blocks: BigNumberish) => {
blocks = !BigNumber.isBigNumber(blocks) ? BigNumber.from(`${blocks}`) : blocks;
await network.provider.request({
method: 'hardhat_mine',
params: [blocks.toHexString().replace('0x0', '0x')],
});
};

export const reset = async (forking?: { [key: string]: any }) => {
const params = forking ? [{ forking }] : [];
await network.provider.request({
method: 'hardhat_reset',
params,
});
};

class SnapshotManager {
snapshots: { [id: string]: string } = {};

async take(): Promise<string> {
const id = await this.takeSnapshot();
this.snapshots[id] = id;
return id;
}

async revert(id: string): Promise<void> {
await this.revertSnapshot(this.snapshots[id]);
this.snapshots[id] = await this.takeSnapshot();
}

private async takeSnapshot(): Promise<string> {
return (await network.provider.request({
method: 'evm_snapshot',
params: [],
})) as string;
}

private async revertSnapshot(id: string) {
await network.provider.request({
method: 'evm_revert',
params: [id],
});
}
}

export const snapshot = new SnapshotManager();
8 changes: 8 additions & 0 deletions test/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as behaviours from './behaviours';
import * as contracts from './contracts';
import * as erc20 from './erc20';
import * as evm from './evm';
import * as bn from './bn';
import * as wallet from './wallet';

export { contracts, behaviours, bn, erc20, evm, wallet };
Loading