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

refactor: setup for standardizing benchmarks #2

Merged
merged 5 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export ETH_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/yourAlchemyApiKey
export FORK_BLOCK=13724056
export GAS_LIMIT=30000000

# skip type-checking to avoid its perfomance impact
export TS_NODE_TRANSPILE_ONLY=1
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
all :; dapp build
clean :; dapp clean
test :; dapp test --rpc-url ${ETH_RPC_URL}
deploy :; dapp create Convex
benchmark-all :; bash scripts/benchmark-all.sh
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside: does this repo need to do any results verification? One or more tool's simulation could be totally inaccurate and we wouldn't know!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep true! I was thinking of also reporting the gasUsed of each tool in a future PR, but open to other ideas here also. Not sure if verifying state changes or comparing transaction traces is excessive for a benchmarking repo 🤔

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just thinking... if we're forking at the same block for each tool, just count the logs and make sure all tools report the same number of logs. Seems quick and dirty? Could also do a deep-equals on the JSON, but that's perhaps more prone to minute differences.

Anyway, this also might be something to manually test once and then not worry about until/unless this repo becomes used more seriously.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, just verifying once might be sufficient for now! Either way, now tracking all potential metrics in #3

benchmark-dapptools :; bash scripts/benchmark-dapptools.sh
benchmark-foundry :; bash scripts/benchmark-foundry.sh
benchmark-ganache :; bash scripts/benchmark-ganache.sh
benchmark-hardhat :; bash scripts/benchmark-hardhat.sh
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ This repository benchmarks performance of various Ethereum development
frameworks by simulating a call to Convex's `systemShutdown` method. This method
uses about 16M gas and performs a number of token transfers

To run tests, make sure you have an environment variable called `ETH_RPC_URL`.
Then use the following test commands:
## Usage

- Hardhat: `yarn` then `yarn hardhat test --no-compile`
- Dapptools: `dapp update` then `dapp test --rpc-url $ETH_RPC_URL`
- Foundry: `forge test --rpc-url $ETH_RPC_URL`
- Ganache v7 (@beta): `yarn` then `yarn benchmark:ganache`
1. Run `cp .env.example .env`, and in the resulting `.env` file enter a URL to an Ethereum archive node in the `ETH_RPC_URL` environment variable. ([Alchemy](https://www.alchemy.com/) provides free archive node data)

Installation instructions for forge and dapptools can be found
[here](https://github.com/gakonst/foundry/) and
[here](https://github.com/dapphub/dapptools/) respectively.
2. Run `yarn` to install dependencies for Ganache and Hardhat

3. Install Foundry's forge and Dapptools using the installation instructions[here](https://github.com/gakonst/foundry/) and [here](https://github.com/dapphub/dapptools/) respectively

4. Run `dapp update` to install dependencies for Dapptools and Foundry

5. Run any command in the `Makefile` to benchmark that tool. For example, use `make benchmark-hardhat` to run the simulation against Hardhat. Alternatively, run `make benchmark-all` to run all tools

## Tips

Dapptools seems to fail when run on macOS, as explained in [this](https://github.com/dapphub/dapptools/issues/876), so you may need to skip that script on macOS
20 changes: 0 additions & 20 deletions hardhat.config.js

This file was deleted.

23 changes: 23 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import '@nomiclabs/hardhat-waffle';
import { HardhatUserConfig } from 'hardhat/config';

const config: HardhatUserConfig = {
solidity: '0.8.10',
defaultNetwork: 'hardhat',
networks: {
hardhat: {
accounts: { mnemonic: 'test test test test test test test test test test test junk' },
chainId: 31337,
gas: Number(process.env.GAS_LIMIT), // set to block gas limit, which avoids calls to eth_estimateGas (results in same behavior as dapptools/foundry)
forking: {
url: String(process.env.ETH_RPC_URL),
blockNumber: Number(process.env.FORK_BLOCK),
},
},
},
mocha: {
timeout: 0,
},
};

export default config;
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@
"repository": "https://github.com/mds1/convex-shutdown-simulation",
"author": "Matt Solomon <[email protected]>",
"license": "MIT",
"scripts": {
"benchmark:ganache": "ts-node ./benchmark-ganache.ts"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-ethers": "^2.0.3",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"chai": "^4.3.4",
"chalk": "^4",
Expand Down
11 changes: 11 additions & 0 deletions scripts/benchmark-all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
echo -e "\n--- BENCHMARKING DAPPTOOLS ---"
bash scripts/benchmark-dapptools.sh

echo -e "\n--- BENCHMARKING FOUNDRY ---"
bash scripts/benchmark-foundry.sh

echo -e "\n--- BENCHMARKING GANACHE ---"
bash scripts/benchmark-ganache.sh

echo -e "\n--- BENCHMARKING HARDHAT ---"
bash scripts/benchmark-hardhat.sh
5 changes: 5 additions & 0 deletions scripts/benchmark-dapptools.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
. ./.env

# dapptools has no option to set gas limit, but it's hardcoded by default
# anyway so shouldn't be make a difference
time dapp test --rpc-url $ETH_RPC_URL --rpc-block $FORK_BLOCK
3 changes: 3 additions & 0 deletions scripts/benchmark-foundry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
. ./.env

time forge test --rpc-url $ETH_RPC_URL --fork-block-number $FORK_BLOCK --gas-limit $GAS_LIMIT
4 changes: 4 additions & 0 deletions scripts/benchmark-ganache.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
. ./.env

# Uses Ganache v7 (@beta)
time yarn ts-node ./scripts/convex.ganache.ts
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume we're okay with just running time to measure the whole command?

I was thinking it might be neat to benchmark startup time separately from simulation time, but it'd require that each script output its own metrics broken out. Probably more trouble than it's worth, though.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I'd like to benchmark that as well. I wasn't sure if each tool exposed that easily so figured it's worth looking at in a future PR, with timeing the whole command being a good start for now

3 changes: 3 additions & 0 deletions scripts/benchmark-hardhat.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
. ./.env

time TS_NODE_FILES=true yarn ts-node ./scripts/convex.hardhat.ts
121 changes: 49 additions & 72 deletions benchmark-ganache.ts → scripts/convex.ganache.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,67 @@
import * as assert from "assert";

const chalk: any = require("chalk");
import * as ethers from "ethers";
import Ganache from "ganache";

const url = process.env["ETH_RPC_URL"] || "mainnet";
const deleteCache = !!(
process.env["DELETE_CACHE"] && process.env["DELETE_CACHE"] !== "false"
);
const blockGasLimit = "0x1C9C380"; // 30,000,000
const blockNumber = 13724056;
const defaultBalance = "0xffffffffffffffffffffff";
const targetBalance = "0xffffffffffffffffffff";
const convexAddress = "0xF403C135812408BFbE8713b5A23a04b3D48AAE31";
const convexAbi = [
"function shutdownSystem() external",
"function owner() external view returns (address)"
];

main();
import * as assert from 'assert';

const chalk: any = require('chalk');
import * as ethers from 'ethers';
import Ganache from 'ganache';

const url = process.env['ETH_RPC_URL'] || 'mainnet';
const deleteCache = !!(process.env['DELETE_CACHE'] && process.env['DELETE_CACHE'] !== 'false');
const blockGasLimit = ethers.BigNumber.from(process.env.GAS_LIMIT).toHexString();
const blockNumber = Number(process.env.FORK_BLOCK);
const defaultBalance = '0xffffffffffffffffffffff';
const targetBalance = '0xffffffffffffffffffff';
const convexAddress = '0xF403C135812408BFbE8713b5A23a04b3D48AAE31';
const convexAbi = ['function shutdownSystem() external', 'function owner() external view returns (address)'];

async function main(): Promise<void> {
/*
* setup
*/

console.log(chalk.bold("Setting up Ganache..."));
console.time("setup-ganache");
console.log(chalk.bold('Setting up Ganache...'));
console.time('setup-ganache');
console.group();

const { ganache, provider } = await prepareGanache({
url,
blockNumber,
blockGasLimit,
defaultBalance,
deleteCache
deleteCache,
});

const convex = new ethers.Contract(convexAddress, convexAbi, provider);
const ownerAddress = await convex.owner();

await fundAccounts({
provider,
accounts: [convexAddress, ownerAddress],
amount: targetBalance
});
await fundAccounts({ provider, accounts: [convexAddress, ownerAddress], amount: targetBalance });

const owner = await unlockAddress({
provider,
address: ownerAddress
});
const owner = await unlockAddress({ provider, address: ownerAddress });

console.groupEnd();
console.timeEnd("setup-ganache");
console.log("");
console.timeEnd('setup-ganache');
console.log('');

/*
* simulation
*/

console.log(chalk.bold("Simulating shutdown..."));
console.time("simulate-shutdown");
console.log(chalk.bold('Simulating shutdown...'));
console.time('simulate-shutdown');
console.group();
const tx = await convex.connect(owner).shutdownSystem({
gasLimit: blockGasLimit
});
const tx = await convex.connect(owner).shutdownSystem({ gasLimit: blockGasLimit });

const receipt = await provider.waitForTransaction(tx.hash);
console.groupEnd();
console.timeEnd("simulate-shutdown");
console.log("");
console.timeEnd('simulate-shutdown');
console.log('');

// @ts-ignore looks like ganche has a bad typing
await ganache.disconnect();
}

interface PrepareGanacheOptions {
url: string;
blockNumber: "latest" | number;
blockNumber: 'latest' | number;
blockGasLimit: string;
defaultBalance: string;
deleteCache: boolean;
Expand All @@ -88,7 +72,7 @@ async function prepareGanache({
blockNumber,
blockGasLimit,
defaultBalance,
deleteCache
deleteCache,
}: PrepareGanacheOptions): Promise<{
ganache: ReturnType<typeof Ganache.provider>;
provider: ethers.providers.JsonRpcProvider;
Expand All @@ -97,17 +81,17 @@ async function prepareGanache({
fork: {
url,
blockNumber,
deleteCache
deleteCache,
},
miner: { blockGasLimit },
wallet: {
// ganache expects value in ETH
defaultBalance: ethers.utils.formatEther(defaultBalance)
defaultBalance: ethers.utils.formatEther(defaultBalance),
},
logging: {
quiet: true
quiet: true,
},
legacyInstamine: true
legacyInstamine: true,
});

const provider = new ethers.providers.Web3Provider(ganache);
Expand All @@ -121,25 +105,20 @@ interface FundAccountsOptions {
amount: string;
}

async function fundAccounts({
provider,
accounts,
amount
}: FundAccountsOptions): Promise<void> {
async function fundAccounts({ provider, accounts, amount }: FundAccountsOptions): Promise<void> {
const [from] = await provider.listAccounts();

for (const address of accounts) {
// simple contract that just selfdestructs funds to address constructor arg
const sendBytecode = "0x60806040526040516100c13803806100c18339818101604052810190602391906098565b8073ffffffffffffffffffffffffffffffffffffffff16ff5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000606a826041565b9050919050565b6078816061565b8114608257600080fd5b50565b6000815190506092816071565b92915050565b60006020828403121560ab5760aa603c565b5b600060b7848285016085565b9150509291505056fe";
const sendBytecode = '0x60806040526040516100c13803806100c18339818101604052810190602391906098565b8073ffffffffffffffffffffffffffffffffffffffff16ff5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000606a826041565b9050919050565b6078816061565b8114608257600080fd5b50565b6000815190506092816071565b92915050565b60006020828403121560ab5760aa603c565b5b600060b7848285016085565b9150509291505056fe';

const txHash = await provider.send(
"eth_sendTransaction",
[{
const txHash = await provider.send('eth_sendTransaction', [
{
from,
input: `${sendBytecode}000000000000000000000000${address.slice(2)}`,
value: amount
}]
);
value: amount,
},
]);

const receipt = await provider.waitForTransaction(txHash);
assert.ok(receipt.status);
Expand All @@ -151,18 +130,16 @@ interface UnlockAddressOptions {
address: string;
}

async function unlockAddress({
provider,
address
}: UnlockAddressOptions): Promise<ethers.providers.JsonRpcSigner> {
await provider.send(
"evm_addAccount",
[address, ""]
);


async function unlockAddress({ provider, address }: UnlockAddressOptions): Promise<ethers.providers.JsonRpcSigner> {
await provider.send('evm_addAccount', [address, '']);
const signer = await provider.getUncheckedSigner(address);
await signer.unlock("");

await signer.unlock('');
return signer;
}

main()
.then(() => process.exit(0))
.catch((error: Error) => {
console.error(error);
process.exit(1);
});
25 changes: 25 additions & 0 deletions scripts/convex.hardhat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ethers, network } from 'hardhat';
import '@nomiclabs/hardhat-ethers';

async function main(): Promise<void> {
// Impersonate owner
const abi = ['function shutdownSystem() external', 'function owner() external view returns (address)'];
const convex = new ethers.Contract('0xF403C135812408BFbE8713b5A23a04b3D48AAE31', abi, ethers.provider);
const ownerAddr = await convex.owner();
await network.provider.request({ method: 'hardhat_impersonateAccount', params: [ownerAddr] });
const owner = await ethers.getSigner(ownerAddr);

// Fund owner (it's a contract)
await network.provider.send('hardhat_setBalance', [owner.address, '0xffffffffffffffffffff']);

// Execute transaction
const tx = await convex.connect(owner).shutdownSystem();
const receipt = await ethers.provider.getTransactionReceipt(tx.hash);
}

main()
.then(() => process.exit(0))
.catch((error: Error) => {
console.error(error);
process.exit(1);
});
17 changes: 0 additions & 17 deletions test/convex.test.js

This file was deleted.

8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -507,10 +507,10 @@
"@ethersproject/properties" "^5.5.0"
"@ethersproject/strings" "^5.5.0"

"@nomiclabs/hardhat-ethers@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.2.tgz#c472abcba0c5185aaa4ad4070146e95213c68511"
integrity sha512-6quxWe8wwS4X5v3Au8q1jOvXYEPkS1Fh+cME5u6AwNdnI4uERvPlVjlgRWzpnb+Rrt1l/cEqiNRH9GlsBMSDQg==
"@nomiclabs/hardhat-ethers@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.3.tgz#06e20a57274f6ce3148132910e723948a711edf1"
integrity sha512-IJ0gBotVtO7YyLZyHNgbxzskUtFok+JkRlKPo8YELqj1ms9XL6Qm3vsfsGdZr22wnJeVEF5TQPotKuwQk21Dag==

"@nomiclabs/hardhat-waffle@^2.0.1":
version "2.0.1"
Expand Down