Skip to content

Commit

Permalink
brings devnets to multisig-wallet example
Browse files Browse the repository at this point in the history
  • Loading branch information
nvitorovic committed Sep 4, 2023
1 parent aea5c9d commit 656d43c
Show file tree
Hide file tree
Showing 15 changed files with 22,312 additions and 4,870 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,4 @@ typings/

# macOS
.DS_Store
.devnet
3 changes: 3 additions & 0 deletions multisig-wallet/.tpl.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ TENDERLY_ACCESS_KEY=#
TENDERLY_FORK_URL=#Tenderly fork URL
TENDERLY_FORK_CHAINID=#
TENDERLY_FORK_NETWORK=#
TENDERLY_DEVNET_URL=#
TENDERLY_DEVNET_CHAIN_ID=#


# https://docs.tenderly.co/other/platform-access/how-to-find-the-project-slug-username-and-organization-name
TENDERLY_PROJECT_SLUG=#
Expand Down
38 changes: 24 additions & 14 deletions multisig-wallet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ This project demonstrates deployment and testig of a simple Multisig wallet usin

## Setup

### Step 1. Create a Fork
### Step 1. Create a DevNet

Go to Tenderly dashboard > Forks and click "Create Fork". [Find out more](https://docs.tenderly.co/simulations-and-forks/how-to-create-a-fork).
Go to Tenderly dashboard > DevNets and click "Create a DevNet". [Find out more](https://docs.tenderly.co/devnets/setting-up-devnets-for-local-development).

### Step 2. Install dependencies

Expand All @@ -23,10 +23,27 @@ Run the following command to initialize an **.env** file for storing sensitive i
cp .tpl.env .env
```

1. Copy Fork RPC URL and fill in the `FORK_URL` in `.env` file. Follow this guide to [get the FORK url](https://docs.tenderly.co/simulations-and-forks/how-to-create-a-fork/how-to-get-a-fork-json-rpc-url-and-id).
2. Fill in `TENDERLY_PROJECT_SLUG` and `TENDERLY_USERNAME` in `.env` file. Follow this guide to [get project slug and username](https://docs.tenderly.co/other/platform-access/how-to-find-the-project-slug-username-and-organization-name)
3. Fill in `TENDERLY_ACCESS_KEY` in `.env` file. Follow this gude to [get Tenderly access key](https://docs.tenderly.co/other/platform-access/how-to-generate-api-access-tokens).
4. Optional: If running on a testnet, provide 3 private keys to .env file (`SEPOLIA_PRIVATE_KEY_1`, `SEPOLIA_PRIVATE_KEY_2`, `SEPOLIA_PRIVATE_KEY_3`) and uncomment lines 30..33 in `hardhat.config.ts`.
1. Fill in `TENDERLY_PROJECT_SLUG` and `TENDERLY_USERNAME` in `.env` file. Follow this guide to [get project slug and username](https://docs.tenderly.co/other/platform-access/how-to-find-the-project-slug-username-and-organization-name)
2. Fill in `TENDERLY_ACCESS_KEY` in `.env` file. Follow this gude to [get Tenderly access key](https://docs.tenderly.co/other/platform-access/how-to-generate-api-access-tokens).
3. Optional: If running on a public testnet, provide 3 private keys to .env file (`SEPOLIA_PRIVATE_KEY_1`, `SEPOLIA_PRIVATE_KEY_2`, `SEPOLIA_PRIVATE_KEY_3`) and uncomment lines 30..33 in `hardhat.config.ts`.
4. Leave the rest unchanged for now

## Step 6: Run tests against the DevNet

Use the `tenderly:devnet:new` command to spawn a new devnet based on your template from step 1. It will update the .env file, in particular `TENDERLY_DEVNET_URL`, `TENDERLY_DEVNET_CHAIN_ID`.

1. Create a DevNet template.
2. Copy DevNet slug and chain-id from YAML file
![Alt text](image.png)
3. Run the following command to spin up a new devnet
```bash
npm run tenderly:devnet:new my-project my-testing-devnet 1
# project devnet template chain-id
```
4. Run the tests
```bash
npx hardhat test --network tenderly
```

## Step 4: Run the example

Expand All @@ -36,21 +53,14 @@ To try out the multisig, from deployment to executing an approved transaction, r
npx hardhat run scripts/run-deploy-submit-execute.ts --network tenderly
```

The following script will:
The script will:

- Deploy the Multisig to a Tenderly Fork
- Fund the Multisig smart contract
- Submit a Transaction to the Multisig
- Send 2 confirmations for the submitted transaction
- Execute the submitted transaction

## Step 5: Run tests against the fork

To run tests using a Fork in place of the network run

```bash
npx hardhat test --network tenderly
```

### Granular usage:

Expand Down
3 changes: 3 additions & 0 deletions multisig-wallet/deployments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"latest": []
}
57 changes: 29 additions & 28 deletions multisig-wallet/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,41 @@ const config: HardhatUserConfig = {
solidity: "0.8.17",

networks: {
sepolia: {
url: process.env.SEPOLIA_RPC || "",
chainId: 11155111, // Sepolia Chain ID
accounts: [
/*
TODO: (1) If you're following the tutorial at (LINK HERE)
then paste your Metamask wallet's private key in .env as SEPOLIA_PRIVATE_KEY_1
and uncomment the following line
*/
// process.env.SEPOLIA_PRIVATE_KEY_1 || "", // uncomment me
//
//
//
/*
TODO: (2) If you want to run `scripts/deploy-submit-execute.ts` on Sepolia
you have to specify 4 private keys
*/
process.env.SEPOLIA_PRIVATE_KEY_1 || "",
process.env.SEPOLIA_PRIVATE_KEY_2 || "",
process.env.SEPOLIA_PRIVATE_KEY_3 || "",
process.env.SEPOLIA_PRIVATE_KEY_4 || "",
],
},

tenderly: {
// tenderly network used for running tests
chainId: Number.parseInt(process.env.TENDERLY_FORK_CHAINID || "SET ME"),
url: process.env.TENDERLY_FORK_URL || "SET ME",
chainId: 736031,
url: "https://rpc.vnet.tenderly.co/devnet/mini-safe/fbdd9913-7e93-4d93-88fb-f41dddd9856d",
},

// Sepolia config in case you still want public testnets
// sepolia: {
// url: process.env.SEPOLIA_RPC || "",
// chainId: 11155111, // Sepolia Chain ID
// accounts: [
// /*
// TODO: (1) If you're following the tutorial at (LINK HERE)
// then paste your Metamask wallet's private key in .env as SEPOLIA_PRIVATE_KEY_1
// and uncomment the following line
// */
// // process.env.SEPOLIA_PRIVATE_KEY_1 || "", // uncomment me
// //
// //
// //
// /*
// TODO: (2) If you want to run `scripts/deploy-submit-execute.ts` on Sepolia
// you have to specify 4 private keys
// */
// process.env.SEPOLIA_PRIVATE_KEY_1 || "",
// process.env.SEPOLIA_PRIVATE_KEY_2 || "",
// process.env.SEPOLIA_PRIVATE_KEY_3 || "",
// process.env.SEPOLIA_PRIVATE_KEY_4 || "",
// ],
// },
},

tenderly: {
project: "project",
username: "nenad",
project: process.env.TENDERLY_PROJECT_SLUG || "",
username: process.env.TENDERLY_USERNAME || "",
privateVerification: false,
},
};
Expand Down
Binary file added multisig-wallet/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions multisig-wallet/lib-tenderly/api/forks-clean-recreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { replaceExistingForkwWith } from "../tenderly-infra";
import { aTenderlyFork, removeFork } from "./tenderly-api";

export const deleteAndCreateFork = async () => {
const forkUrl =
process.env.FORK_MODE === "TEST"
? process.env.TENDERLY_TEST_FORK_URL
: process.env.TENDERLY_FORK_URL;

if (!!forkUrl) {
const forkId = forkUrl.split("/").reverse()[0];
console.log("Removing fork", forkId);
await removeFork(forkId).catch((err) => {
// console.error(err);
});
}
console.log("Creating a fork");
console.time("fork");
const fork = await aTenderlyFork({
network_id: process.env.TENDERLY_FORK_NETWORK || "5",
chain_config: {
chain_id: Number.parseInt(process.env.TENDERLY_FORK_CHAINID || "55"),
},
alias: "UI Fork",
description: "",
});
console.timeEnd("fork");

console.log("Created the fork", fork.id);
replaceExistingForkwWith(fork);
};
31 changes: 31 additions & 0 deletions multisig-wallet/lib-tenderly/api/t-forks-clean-recreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { readFileSync, writeFileSync } from "fs";
import { loadInfra, replaceExistingForkwWith } from "../tenderly-infra";
import { aTenderlyFork, removeFork } from "./tenderly-api";

(async () => {
const forkUrl =
process.env.FORK_MODE === "TEST"
? process.env.TENDERLY_TEST_FORK_URL
: process.env.TENDERLY_FORK_URL;

if (!!forkUrl) {
const forkId = forkUrl.split("/").reverse()[0];
console.log("Removing fork", forkId);
await removeFork(forkId).catch((err) => {
// console.error(err);
});
}
console.log("Creating a fork");
console.time("fork");
const fork = await aTenderlyFork({
network_id: process.env.TENDERLY_FORK_NETWORK || "5",
chain_config: {
chain_id: Number.parseInt(process.env.TENDERLY_FORK_CHAINID || "55"),
},
alias: "UI Fork",
});
console.timeEnd("fork");

console.log("Created the fork", fork.id);
replaceExistingForkwWith(fork);
})().catch();
101 changes: 101 additions & 0 deletions multisig-wallet/lib-tenderly/api/tenderly-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { JsonRpcProvider } from "@ethersproject/providers";
import axios, { AxiosResponse } from "axios";
import * as dotenv from "dotenv";

dotenv.config();

type TenderlyForkRequest = {
block_number?: number;
network_id: string;
transaction_index?: number;
initial_balance?: number;
alias?: string;
description?: string;
chain_config?: {
chain_id?: number;
homestead_block?: number;
dao_fork_support?: boolean;
eip_150_block?: number;
eip_150_hash?: string;
eip_155_block?: number;
eip_158_block?: number;
byzantium_block?: number;
constantinople_block?: number;
petersburg_block?: number;
istanbul_block?: number;
berlin_block?: number;
};
};

export type TenderlyForkProvider = {
provider: JsonRpcProvider;
id: number;
blockNumber: number;
chainId: number;
networkId: number;
/**
* map from address to given address' balance
*/
removeFork: () => Promise<AxiosResponse<any, any>>;
};

const somewhereInTenderly = (
where: string = process.env.TENDERLY_PROJECT_SLUG || ""
) =>
axios.create({
baseURL: `https://api.tenderly.co/api/v1/${where}`,
headers: {
"X-Access-Key": process.env.TENDERLY_ACCESS_KEY || "",
"Content-Type": "application/json",
},
});

export const inProject = (...path: any[]) =>
[
`account/${process.env.TENDERLY_USERNAME}/project/${process.env.TENDERLY_PROJECT_SLUG}`,
...path,
]
.join("/")
.replace("//", "");

export const anAxiosOnTenderly = () => somewhereInTenderly("");
const axiosOnTenderly = anAxiosOnTenderly();

// todo: cache these poor axioses
const axiosInProject = somewhereInTenderly(inProject());

export const removeFork = async (forkId: string) => {
console.log("Removing test fork", forkId);
return await axiosOnTenderly.delete(inProject(`fork/${forkId}`));
};

export async function aTenderlyFork(
fork: TenderlyForkRequest
): Promise<TenderlyForkProvider> {
const forkResponse = await axiosInProject.post(`/fork`, fork);
const forkId = forkResponse.data.root_transaction.fork_id;

const forkRpc = `https://rpc.tenderly.co/fork/${forkId}`;
const forkProvider = new JsonRpcProvider(forkRpc);

const blockNumberStr = (
forkResponse.data.root_transaction.receipt.blockNumber as string
).replace("0x", "");
const blockNumber: number = Number.parseInt(blockNumberStr, 16);

console.info(
`\nForked with fork id ${forkId} at block number ${blockNumber}`
);

console.info(`https://dashboard.tenderly.co/${inProject("fork", forkId)}`);
console.info("JSON-RPC:", forkRpc);

return {
provider: forkProvider,
blockNumber,
id: forkId,
removeFork: () => removeFork(forkId),
networkId: fork.network_id as any,
chainId: forkResponse.data.simulation_fork.chain_config.chain_id,
};
}
52 changes: 52 additions & 0 deletions multisig-wallet/lib-tenderly/deployment-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Contract } from "ethers";
import { existsSync, readFileSync, writeFileSync } from "fs";
import { network } from "hardhat";

export type DeploymentRecord = {
name: string;
address: string;
network: string;
timestamp?: Date;
};

function readDeployments() {
type Deployments = Record<string, DeploymentRecord[]>;
if (!existsSync(".tenderly/deployments.json")) {
return {} as Deployments;
}
const deployments = JSON.parse(
readFileSync(".tenderly/deployments.json", "utf8").toString()
) as unknown as Deployments;
return deployments;
}

export function deployed(deployment: DeploymentRecord, tag: string = "latest") {
if (findDeployment(deployment.name, deployment.network, tag)) {
return;
}
deployment.timestamp = new Date();

const deploymentsOn = findDeployment(deployment.name, tag);
const deployments = readDeployments();
if (!deployments[tag]) {
deployments[tag] = [deployment];
} else {
const idx = deployments[tag].findIndex(
(d) => d.network == deployment.network && d.name == deployment.name
);
deployments[tag][idx] = { ...deployments[tag][idx], ...deployment };
}

writeFileSync(
".tenderly/deployments.json",
JSON.stringify(deployments, null, 2)
);
}

export function findDeployment(name: string, network: string, tag = "latest") {
const deployments = readDeployments()[tag];
if (!deployments) {
return undefined;
}
return deployments.filter((d) => d.name === name && d.network == network)[0];
}
Loading

0 comments on commit 656d43c

Please sign in to comment.