Skip to content

Commit

Permalink
Add Scenarios for Asteroid Goerli (compound-finance#39)
Browse files Browse the repository at this point in the history
* Add Scenarios for Asteroid Goerli

This patch adds real running scenarios for Asteroid Goerli. We work to make sure contracts are idempotently deployed and referenced correctly in scenarios. Additionally, we:

 * Add colored diffs for assertion errors
 * Bind all Context methods to make them destructurable
 * Use the provider from Hardhat instead of Etherscan provider for Spider JSON-RPC queries

* Don't use alias syntax
  • Loading branch information
hayesgm authored Dec 18, 2021
1 parent b34e9a0 commit a7df47b
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 40 deletions.
5 changes: 5 additions & 0 deletions deployments/goerli/pointers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"raffle": "0xbc8e8f5bD66D90C5D7334708E8502A3233b73B21",
"oracle": "0xf1460F5157e7EcA9BF74A264FB6e78Afd6aA73a1",
"token": "0xF283e236a6EeFC70836B801a26B72d85D9e14276",
}
5 changes: 5 additions & 0 deletions deployments/goerli/relations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"AsteroidRaffle": {
"relations": ["token", "oracle"]
}
}
3 changes: 3 additions & 0 deletions deployments/goerli/roots.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"AsteroidRaffle": "0xbc8e8f5bD66D90C5D7334708E8502A3233b73B21"
}
5 changes: 0 additions & 5 deletions deployments/mainnet/relations.json

This file was deleted.

1 change: 0 additions & 1 deletion deployments/mainnet/roots.json

This file was deleted.

4 changes: 0 additions & 4 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,6 @@ function setupDefaultNetworkProviders(hardhatConfig: HardhatUserConfig) {

scenario: {
bases: [
{
name: "mainnet",
url: `https://eth-mainnet.alchemyapi.io/v2/-lH3DVZ5yNTgaJjsituB9PssBzM3SN-R`
},
{
name: "goerli",
url: "https://eth-goerli.alchemyapi.io/v2/Xs9F4EHXAb1wg_PvxlKu3HaXglyPkc2E"
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.7",
"chai": "^4.3.4",
"chalk": "^5.0.0",
"dotenv": "^10.0.0",
"ethereum-waffle": "^3.4.0",
"ethers": "^5.5.1",
"fast-glob": "^3.2.7",
"hardhat": "^2.6.7",
"hardhat-gas-reporter": "^1.0.4",
"jest-diff": "^27.4.2",
"solhint": "^3.3.6",
"solidity-coverage": "^0.7.17",
"ts-node": "^10.4.0",
Expand Down
10 changes: 10 additions & 0 deletions plugins/scenario/Runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ function *combos(choices: object[][]) {
yield [option, ...combo];
}
}

function bindFunctions(obj: any) {
for (let property of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
obj[property] = obj[property].bind(obj);
}
}

export class Runner<T> {
config: Config<T>;

Expand Down Expand Up @@ -67,6 +74,9 @@ export class Runner<T> {
await constraint.check(scenario.requirements, ctx, world);
}

// Prebind all functions on object
bindFunctions(ctx);

// requirements met, run the property
await scenario.property(ctx, world);
// XXX add scenario failure on ctx, world to report
Expand Down
1 change: 1 addition & 0 deletions plugins/scenario/worker/Parent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface Result {
elapsed?: number,
error?: Error,
trace?: string,
diff?: { actual: any, expected: any },
skipped?: boolean
}

Expand Down
12 changes: 8 additions & 4 deletions plugins/scenario/worker/Report.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Result } from './Parent';
import { diff as showDiff } from 'jest-diff';

export type Format = "console";

Expand All @@ -24,25 +25,28 @@ function showReportConsole(results: Result[]) {
let errCount = 0;
let skipCount = 0;
let totalTime = 0;
let errors: Map<string, [Error, string]> = new Map();
let errors: Map<string, { error: Error, trace?: string, diff?: { actual: any, expected: any } }> = new Map();

for (let {scenario, elapsed, error, trace, skipped} of results) {
for (let {scenario, elapsed, error, trace, diff, skipped} of results) {
if (skipped) {
skipCount++;
} else {
testCount++;
totalTime += elapsed;
if (error) {
errCount++;
errors[scenario] = [error, trace];
errors[scenario] = { error, trace, diff };
} else {
succCount++;
}
}
}

for (let [scenario, [error, trace]] of Object.entries(errors)) {
for (let [scenario, { error, trace, diff }] of Object.entries(errors)) {
console.error(`❌ ${scenario}: Error ${trace || error.message}`);
if (diff) {
console.error(showDiff(diff.expected, diff.actual));
}
}

let prefix = errCount === 0 ? "✅" : "❌";
Expand Down
10 changes: 9 additions & 1 deletion plugins/scenario/worker/Worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getEthersContractsForDeployment } from "../../spider";
import { HardhatConfig, HardhatArguments, createContext, setConfig, getContext } from './HardhatContext';
import * as util from 'util';
import { ScenarioConfig } from '../types';
import { AssertionError } from 'chai';

interface Message {
config?: [HardhatConfig, HardhatArguments],
Expand Down Expand Up @@ -44,8 +45,15 @@ export async function run<T>(scenarioConfig: ScenarioConfig) {
// Add timeout for flush
eventually(() => parentPort.postMessage({result: { scenario: scenario.name, elapsed: Date.now() - startTime, error: null, trace: null }}));
} catch (error) {
let diff = null;
if (error instanceof AssertionError) {
let { actual, expected } = <any>error; // Types unclear
if (actual !== expected) {
diff = { actual, expected };
}
}
// Add timeout for flush
eventually(() => parentPort.postMessage({result: { scenario: scenario.name, elapsed: Date.now() - startTime, error, trace: error.stack.toString() }}));
eventually(() => parentPort.postMessage({result: { scenario: scenario.name, elapsed: Date.now() - startTime, error, trace: error.stack.toString(), diff }}));
}
} else {
throw new Error(`Unknown or invalid worker message: ${JSON.stringify(message)}`);
Expand Down
3 changes: 1 addition & 2 deletions plugins/spider/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,10 @@ async function expand(
const key = Object.keys(loadedContract.contracts)[0]; // TODO: assert contracts length is 1
const abi = loadedContract.contracts[key].abi;
const contractName = loadedContract.contracts[key].name;
const provider = new hre.ethers.providers.EtherscanProvider(network, process.env.ETHERSCAN_KEY);
let contract = new hre.ethers.Contract(
currentProxy ?? address,
abi,
provider
hre.ethers.provider
);
visited.set(address, contractName);

Expand Down
53 changes: 33 additions & 20 deletions scenario/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ async function getUntilEmpty<T>(emptyVal: T, fn: (index: number) => Promise<T>):
try {
curr = await fn(index++);
} catch (e) {
return acc;
if (e.message.includes("Transaction reverted without a reason string")) {
return acc;
} else {
throw e;
}
}

if (curr === emptyVal) {
Expand Down Expand Up @@ -38,26 +42,32 @@ export class CometContext {
}
}

let contractDeployers: {[name: string]: ((world: World, contracts: ContractMap, signers: Signer[]) => Promise<Contract>)} = {
token: async (world, contracts, signers) => {
console.log("Deploying FaucetToken", 100000, "DAI", 18, "DAI");
const FaucetToken = await world.hre.ethers.getContractFactory('FaucetToken');
const token = await FaucetToken.deploy(100000, "DAI", 18, "DAI");
return await token.deployed();
let contractDeployers: {[name: string]: { contract: string, deployer: ((world: World, contracts: ContractMap, signers: Signer[]) => Promise<Contract>) }} = {
token: {
contract: "DAIFaucetToken", // TODO: This should be handled by pointers.json
deployer: async (world, contracts, signers) => {
const FaucetToken = await world.hre.ethers.getContractFactory('FaucetToken');
const token = await FaucetToken.deploy(100000, "DAI", 18, "DAI");
return await token.deployed();
},
},

oracle: async (world, contracts, signers) => {
console.log("Deploying Oracle", (<any>signers[1]).address);
const Oracle = await world.hre.ethers.getContractFactory('MockedOracle');
const oracle = await Oracle.connect(signers[1]).deploy();
return await oracle.deployed();
oracle: {
contract: "AsteroidRaffleMockedOracle", // TODO: This should be handled by pointers.json
deployer: async (world, contracts, signers) => {
const Oracle = await world.hre.ethers.getContractFactory('MockedOracle');
const oracle = await Oracle.connect(signers[1]).deploy();
return await oracle.deployed();
},
},

raffle: async (world, contracts, signers) => {
console.log("Deploying Raffle", '100000000000000000', contracts.token.address, contracts.oracle.address);
const AsteroidRaffle = await world.hre.ethers.getContractFactory('AsteroidRaffle');
const raffle = await AsteroidRaffle.deploy('100000000000000000', contracts.token.address, contracts.oracle.address);
return await raffle.deployed();
raffle: {
contract: "AsteroidRaffle", // TODO: This should be handled by pointers.json
deployer: async (world, contracts, signers) => {
const AsteroidRaffle = await world.hre.ethers.getContractFactory('AsteroidRaffle');
const raffle = await AsteroidRaffle.deploy('100000000000000000', contracts.token.address, contracts.oracle.address);
return await raffle.deployed();
},
},
}

Expand All @@ -66,11 +76,14 @@ const getInitialContext = async (world: World, base: ForkSpec): Promise<CometCon
let signers = await world.hre.ethers.getSigners();

// Deploy missing contracts
for (let [name, deployer] of Object.entries(contractDeployers)) {
if (!contracts[name]) {
for (let [name, {contract, deployer}] of Object.entries(contractDeployers)) {
let contractInst = contracts[contract];

if (contractInst) {
contracts[name] = contractInst;
} else {
console.log("Deploying " + name);
contracts[name] = await deployer(world, contracts, signers);
console.log("Deployed " + name);
}
}

Expand Down
5 changes: 2 additions & 3 deletions scenario/SimpleScenario.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { scenario } from './Context';
import { expect } from 'chai';

scenario.only("my scenario", {}, async (ctx, world) => {
expect(await ctx.players()).to.eql([]);
console.log("Roof said " + ctx.dog);
scenario.only("my scenario", {}, async ({players}, world) => {
expect(await players()).to.eql(["0x29e31E1eE143a76039F00860d3Bd25804357f0b3"]);
});

scenario("scen 2", {}, async (context, world) => {
Expand Down
Loading

0 comments on commit a7df47b

Please sign in to comment.