Skip to content

Commit

Permalink
Merge pull request #712 from OpenZeppelin/foundry
Browse files Browse the repository at this point in the history
✨ Port all contracts to Foundry
  • Loading branch information
xaler5 authored Apr 5, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents c8860dc + 3432dca commit ebf7e29
Showing 492 changed files with 9,969 additions and 11,644 deletions.
33 changes: 25 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
name: Test

on:
push:
branches: [master]
pull_request: {}

jobs:
test:
tests:
name: Foundry tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./contracts

steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16.x
- run: yarn
- run: yarn test:contracts
- uses: actions/checkout@v3
with:
submodules: true

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Install dependencies
run: forge install

- name: Check contract sizes
run: forge build --sizes --skip test
id: build

- name: Run tests
run: forge test -v
id: test
9 changes: 3 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -5,14 +5,11 @@ node_modules/
client/.env
contracts/.env

# Contracts
contracts/build/
contracts/cache/
contracts/node_modules/
contracts/.openzeppelin/

# Client
client/build/
client/node_modules/
client/src/gamedata/deploy.local.json
.env

# Local Netlify folder
.netlify
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/lib/openzeppelin-contracts-06"]
path = contracts/lib/openzeppelin-contracts-06
url = https://github.com/openzeppelin/openzeppelin-contracts
[submodule "contracts/lib/openzeppelin-contracts-08"]
path = contracts/lib/openzeppelin-contracts-08
url = https://github.com/openzeppelin/openzeppelin-contracts
[submodule "contracts/lib/openzeppelin-contracts-upgradeable"]
path = contracts/lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## March 2024
- Updated all contracts (challenges, tests, etc.) to use Foundry instead of Hardhat

## August 2021
- Filter out repeated log messages for mined transactions
- Increased Sidebar width
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ If you would like to contribute in another way, please reach out to us via email

*A level is composed of the following elements:*

- A `<Level>Factory.sol` contract, where `<Level>` is replaced by the name of the level, that needs to extend [`Level.sol`](./contracts/contracts/levels/base/Level.sol). This factory contract will be deployed only once and registered on Ethernaut.sol by Ethernaut's owner. Players never interact with the factory directly. The factory is in charge of creating level instances for players to use (1 instance per player) and to check these instances to verify if the player has passed the level. Factories should not have state that can be changed by the player.
- A `<Level>Factory.sol` contract, where `<Level>` is replaced by the name of the level, that needs to extend [`Level.sol`](./contracts/src/levels/base/Level.sol). This factory contract will be deployed only once and registered on Ethernaut.sol by Ethernaut's owner. Players never interact with the factory directly. The factory is in charge of creating level instances for players to use (1 instance per player) and to check these instances to verify if the player has passed the level. Factories should not have state that can be changed by the player.
- A `level instance` contract named `<Level>.sol`, where `<Level>` is replaced by the name of the level, that is emitted by the factory for each player that requests it. Instances need to be completely decouppled from Ethernaut's architecture. Factories will emit them and verify them. That is, level instances don't know anything about their factories or Ethernaut. An instance's state can be completely demolished by players and even destroyed since they are not really part of the architecture, just a challenge for a player to use at will.
- A `description file` in [the descriptions directory](./client/src/gamedata/en/descriptions/levels) that the UI presents to the player and describes the level's objectives with some narrative and tips.
- A `description completion file` also located in [the descriptions directory](./client/src/gamedata/en/descriptions/levels) that the UI presents to the player when the level is passed, further information about the player, historical insights, further explanations or just a congrats message.
@@ -60,8 +60,8 @@ Let's suppose that we are creating the level "King" (which is already created an
2. Use the other levels as a basis, eg. duplicate DummyFactory.sol and Dummy.sol.
3. Rename and modify the contracts to KingFactory.sol and King.sol respectively.
4. Implement the desired instance and factory logic in solidity. See current levels and notes to understand how the game mechanics work.
5. Add the test file `contracts/test/levels/King.test.js`. Use other tests files as reference to see how tests might work.
6. Run `yarn test:contracts` and once all tests pass, register the level in [gamedata.json](client/src/gamedata/gamedata.json).
5. Add the test file `contracts/test/levels/King.t.sol`. Use other tests files as reference to see how tests might work.
6. Run `forge test` and once all tests pass, register the level in [gamedata.json](client/src/gamedata/gamedata.json).
7. The level should now show up in the ui. To start the UI, set the [ACTIVE_NETWORK](client/src/constants.js) to `NETWORKS.LOCAL` and run `yarn start`.
8. Add a description markdown file, in this case client/src/gamedata/levels/king.md (make sure gamedata.json points to it). This content will now be displayed in the ui for the level.
9. Add a completed description markdown file, in this case client/src/gamedata/levels/king_complete.md (make sure gamedata.json points to it). The level will display this as additional info once the level is solved, usually to include historical information related to the level.
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -5,12 +5,12 @@
"private": true,
"dependencies": {
"@sentry/react": "^7.0.0",
"@sentry/tracing": "^7.108.0",
"@truffle/contract": "^4.3.15",
"alchemy-sdk": "^2.2.3",
"axios": "^1.2.4",
"bad-words": "^3.0.4",
"bootstrap": "^5.0.0",
"contracts": "0.1.0",
"cross-env": "7.0.3",
"devtools-detect": "^4.0.0",
"dotenv": "^16.0.3",
1 change: 0 additions & 1 deletion client/public/contracts

This file was deleted.

10 changes: 5 additions & 5 deletions client/scripts/deploy_contracts.mjs
Original file line number Diff line number Diff line change
@@ -7,10 +7,10 @@ import * as ethutil from "../src/utils/ethutil.js";
import * as constants from "../src/constants.js";
import HDWalletProvider from "@truffle/hdwallet-provider";
import * as gamedata from "../src/gamedata/gamedata.json" assert { type: "json" };
import * as EthernautABI from "contracts/build/contracts/Ethernaut.sol/Ethernaut.json" assert { type: "json" };
import * as ProxyAdminABI from "contracts/build/contracts/proxy/ProxyAdmin.sol/ProxyAdmin.json" assert { type: "json" };
import * as ImplementationABI from "contracts/build/contracts/metrics/Statistics.sol/Statistics.json" assert { type: "json" };
import * as ProxyStatsABI from "contracts/build/contracts/proxy/ProxyStats.sol/ProxyStats.json" assert { type: "json" };
import * as EthernautABI from "../src/contracts/out/Ethernaut.sol/Ethernaut.json" assert { type: "json" };
import * as ProxyAdminABI from "../src/contracts/out/ProxyAdmin.sol/ProxyAdmin.json" assert { type: "json" };
import * as ImplementationABI from "../src/contracts/out/Statistics.sol/Statistics.json" assert { type: "json" };
import * as ProxyStatsABI from "../src/contracts/out/ProxyStats.sol/ProxyStats.json" assert { type: "json" };

let web3;
let ethernaut;
@@ -127,7 +127,7 @@ async function deployContracts(deployData) {
// Deploy contract
const LevelABI = JSON.parse(
fs.readFileSync(
`contracts/build/contracts/levels/${
`contracts/out/${
level.levelContract
}/${withoutExtension(level.levelContract)}.json`,
"utf-8"
14 changes: 7 additions & 7 deletions client/scripts/supersede_level.mjs
Original file line number Diff line number Diff line change
@@ -5,11 +5,11 @@ import HDWalletProvider from "@truffle/hdwallet-provider";
import Web3 from "web3";
import * as ethutil from "../src/utils/ethutil.js";
import * as constants from "../src/constants.js";
import * as EthernautABI from "contracts/build/contracts/Ethernaut.sol/Ethernaut.json" assert { type: "json" };
import * as ProxyStatsABI from "contracts/build/contracts/proxy/ProxyStats.sol/ProxyStats.json" assert { type: "json" };
import * as ProxyAdminABI from "contracts/build/contracts/proxy/ProxyAdmin.sol/ProxyAdmin.json" assert { type: "json" };
import * as ImplementationABI from "contracts/build/contracts/metrics/Statistics.sol/Statistics.json" assert { type: "json" };
import * as SupersederImplementationABI from "contracts/build/contracts/metrics/StatisticsLevelSuperseder.sol/StatisticsLevelSuperseder.json" assert { type: "json" };
import * as EthernautABI from "../src/contracts/out/Ethernaut.sol/Ethernaut.json" assert { type: "json" };
import * as ProxyStatsABI from "../src/contracts/out/ProxyStats.sol/ProxyStats.json" assert { type: "json" };
import * as ProxyAdminABI from "../src/contracts/out/ProxyAdmin.sol/ProxyAdmin.json" assert { type: "json" };
import * as ImplementationABI from "../src/contracts/out/Statistics.sol/Statistics.json" assert { type: "json" };
import * as SupersederImplementationABI from "../src/contracts/out/StatisticsLevelSuperseder.sol/StatisticsLevelSuperseder.json" assert { type: "json" };

import gamedata from "../src/gamedata/gamedata.json" assert { type: "json" };
const levels = gamedata.levels;
@@ -201,7 +201,7 @@ async function deployAndUpgradeStatisticsToStatisticsSuperseder() {

const props = {
gasPrice: parseInt(await web3.eth.getGasPrice() * 1.10),
gas: 45000000,
gas: 30000000,
};
let from = constants.ADDRESSES[constants.ACTIVE_NETWORK.name];
if (!from) from = (await web3.eth.getAccounts())[0];
@@ -258,7 +258,7 @@ async function deployLevel(level) {

const LevelABI = JSON.parse(
fs.readFileSync(
`contracts/build/contracts/levels/${level.levelContract}/${
`contracts/out/${level.levelContract}/${
level.levelContract.split(".")[0]
}.json`,
"utf-8"
4 changes: 2 additions & 2 deletions client/scripts/upgrade_proxy.mjs
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@ import fs from 'fs';
import * as ethutil from '../src/utils/ethutil.js';
import * as constants from '../src/constants.js';
import HDWalletProvider from '@truffle/hdwallet-provider';
import * as ProxyAdminABI from 'contracts/build/contracts/proxy/ProxyAdmin.sol/ProxyAdmin.json' assert { type: 'json' };
import * as ImplementationABI from 'contracts/build/contracts/metrics/Statistics.sol/Statistics.json' assert { type: 'json' };
import * as ProxyAdminABI from '../src/contracts/out/ProxyAdmin.sol/ProxyAdmin.json' assert { type: 'json' };
import * as ImplementationABI from '../src/contracts/out/Statistics.sol/Statistics.json' assert { type: 'json' };

let web3;

2 changes: 1 addition & 1 deletion client/src/constants.js
Original file line number Diff line number Diff line change
@@ -148,7 +148,7 @@ export const GOOGLE_ANALYTICS_ID = "UA-85043059-4";

// Owner addresses
export const ADDRESSES = {
[NETWORKS.LOCAL.name]: undefined,
[NETWORKS.LOCAL.name]: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
[NETWORKS.MUMBAI.name]: "0x09902A56d04a9446601a0d451E07459dC5aF0820",
[NETWORKS.SEPOLIA.name]: "0x09902A56d04a9446601a0d451E07459dC5aF0820",
[NETWORKS.OPTIMISM_SEPOLIA.name]: "0x09902A56d04a9446601a0d451E07459dC5aF0820",
2 changes: 1 addition & 1 deletion client/src/containers/Level.js
Original file line number Diff line number Diff line change
@@ -142,7 +142,7 @@ class Level extends React.Component {

let sourcesFile = null;
try {
sourcesFile = require(`contracts/contracts/levels/${level.instanceContract}`);
sourcesFile = require(`../contracts/src/levels/${level.instanceContract}`);
} catch (e) {
console.log(e);
}
1 change: 1 addition & 0 deletions client/src/contracts
2 changes: 1 addition & 1 deletion client/src/middlewares/loadEthernautContract.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ethutil from '../utils/ethutil'
import EthernautABI from 'contracts/build/contracts/Ethernaut.sol/Ethernaut.json'
import EthernautABI from '../contracts/out/Ethernaut.sol/Ethernaut.json'
import * as actions from '../actions';
import { loadTranslations } from '../utils/translations'

4 changes: 2 additions & 2 deletions client/src/middlewares/loadLevelInstance.js
Original file line number Diff line number Diff line change
@@ -79,8 +79,8 @@ const loadLevelInstance = (store) => (next) => (action) => {
if (!instanceAddress) return;
console.info(`=> ${strings.instanceAddressMessage}\n${instanceAddress}`);
const Instance = ethutil.getTruffleContract(
require(`contracts/build/contracts/levels/${action.level.instanceContract
}/${withoutExtension(action.level.instanceContract)}.json`),
require(`../contracts/out/${action.level.instanceContract
}/${withoutExtension(action.level.instanceContract)}.json`),
{
from: state.player.address,
gasPrice: 2 * state.network.gasPrice,
6 changes: 3 additions & 3 deletions client/src/utils/contractutil.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as constants from "../constants";
import { getWeb3, setWeb3, getTruffleContract, getNetworkFromId } from "./ethutil";
import { newGithubIssueUrl } from "./github";
import * as LocalFactoryABI from "contracts/build/contracts/factory/LocalFactory.sol/Factory.json";
import * as LocalFactoryABI from "../contracts/out/LocalFactory.sol/Factory.json";
import { deployAdminContracts, deployAndRegisterLevel } from "./deploycontract";
var levels = require(`../gamedata/gamedata.json`).levels;

@@ -62,7 +62,7 @@ export function getLevelKey(levelAddress) {

export function fetchLevelABI(level) {
const contractName = level.levelContract.split(".")[0];
return require(`contracts/build/contracts/levels/${level.levelContract}/${contractName}.json`);
return require(`../contracts/out/${level.levelContract}/${contractName}.json`);
}

// write windows finction to transfer ownership to a new user
@@ -175,7 +175,7 @@ export const verifyContract = async (contractAddress, level, chainId) => {
if (!network.explorer || !level.verificationDetails)
return;

const contractFile = await fetch(`contracts/levels/${level.instanceContract}`);
const contractFile = await fetch(`../contracts/src/levels/${level.instanceContract}`);
const contractCode = await contractFile.text();

const headers = new Headers();
2 changes: 1 addition & 1 deletion client/src/utils/deploycontract.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import colors from "colors";
import * as ethutil from "../utils/ethutil.js";
import * as LocalFactoryABI from "contracts/build/contracts/factory/LocalFactory.sol/Factory.json";
import * as LocalFactoryABI from "../contracts/out/LocalFactory.sol/Factory.json";
import { getGasFeeDetails } from "../utils/ethutil.js";
import {
cacheContract,
2 changes: 1 addition & 1 deletion client/src/utils/ethutil.js
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ export const getTruffleContract = (jsonABI, defaults = {}) => {
// // With this, MetaMask v9 deprecation warnings are removed.
// const TruffleContract = require('@truffle/contract');

const truffleContract = TruffleContract(jsonABI);
const truffleContract = TruffleContract({ abi: jsonABI.abi, bytecode: jsonABI.bytecode.object });
if (!defaults.gasPrice) defaults.gasPrice = 2000000000;
if (!defaults.gas) defaults.gas = 2000000;
truffleContract.defaults(defaults);
2 changes: 1 addition & 1 deletion client/src/utils/statsContract.js
Original file line number Diff line number Diff line change
@@ -158,7 +158,7 @@ const getProxyStatsContractAddressInNetwork = (networkId) => {
}

const getStatsContract = async (proxyStatsAddress, playerAddress) => {
const statsABI = require("contracts/build/contracts/metrics/Statistics.sol/Statistics.json");
const statsABI = require("../contracts/out/Statistics.sol/Statistics.json");
let statsContract;
if (playerAddress) {
statsContract = getTruffleContract(statsABI, { from: playerAddress });
13 changes: 13 additions & 0 deletions contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Compiler files
cache/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
66 changes: 66 additions & 0 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
Loading

0 comments on commit ebf7e29

Please sign in to comment.