Skip to content

Commit

Permalink
support via-ir and remove bytecode hash
Browse files Browse the repository at this point in the history
  • Loading branch information
spengrah committed Aug 15, 2023
1 parent f8e67a5 commit 761ad14
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 40 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ jobs:

- name: Check contract sizes
run: forge build --sizes --skip script
# Replace the above line with the below if using pre-compiled ir-optimized contracts
# run: FOUNDRY_PROFILE=optimized forge build --sizes --skip script

- name: Run tests
run: forge test -vvv
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
cache/
out/
optimized-out/
.vscode/
broadcast/
.env
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,17 @@ This repo uses Foundry for development and testing. To get started:

1. Fork the project
2. Install [Foundry](https://book.getfoundry.sh/getting-started/installation)
3. To install dependencies, run `forge install`
3. To compile the contracts, run `forge build`
4. To test, run `forge test`

### IR-Optimized Builds

This repo also supports contracts compiled via IR. Since compiling all contracts via IR would slow down testing workflows, we only want to do this for our target contract(s), not anything in this `test` or `script` stack. We accomplish this by pre-compiled the target contract(s) and then loading the pre-compiled artifacts in the test suite.

First, we compile the target contract(s) via IR by running`FOUNDRY_PROFILE=optimized forge build` (ensuring that FOUNDRY_PROFILE is not in our .env file)

Next, ensure that tests are using the `DeployOptimized` script, and run `forge test` as normal.

See the wonderful [Seaport repo](https://github.com/ProjectOpenSea/seaport/blob/main/README.md#foundry-tests) for more details and options for this approach.

40 changes: 29 additions & 11 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,31 @@ src = 'src'
out = 'out'
libs = ['lib']
optimizer_runs = 1_000_000
# the following setting ensures that deterministically deployed contracts will always be to the same address
bytecode_hash = "none"
gas_reports = []
auto_detect_solc = false
solc = "0.8.18"
solc = "0.8.19"
remappings = [
"ds-test/=lib/forge-std/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/"
"forge-std/=lib/forge-std/src/",
]
# Enable tests to read ir-optimized bytecode precompiled by profile.optimized
fs_permissions = [{ access = "read", path = "./optimized-out" }]

# for pre-compiling ir-optimized bytecode that will be later deployed by tests
[profile.optimized]
via_ir = true
out = "optimized-out"
script = "src"
bytecode_hash = "none"
sizes = true
# no need to compile tests with via-ir since they load optimized bytecode directly by default
test = "src"

# for running tests against pre-compiled ir-optimized deployments
[profile.test]
src = "test"

[profile.ci]
fuzz = { runs = 5000 }
Expand Down Expand Up @@ -42,12 +60,12 @@ polygon = "${POLYGON_RPC}"
sepolia = "https://sepolia.infura.io/v3/${INFURA_KEY}"

[etherscan]
arbitrum = {key = "${ARBISCAN_KEY}", url = "https://api.arbiscan.io/api"}
goerli = {key = "${ETHERSCAN_KEY}", url = "https://api-goerli.etherscan.io/api"}
gnosis = {key = "${GNOSISSCAN_KEY}", url = "https://api.gnosisscan.io/api"}
mainnet = {key = "${ETHERSCAN_KEY}", url = "https://api.etherscan.io/api"}
optimism = {key = "${OPTIMISM_KEY}", url = "https://api-optimistic.etherscan.io/api"}
sepolia = {key = "${ETHERSCAN_KEY}", url = "https://api-sepolia.etherscan.io/api"}
polygon = {key = "${POLYGONSCAN_KEY}", url = "https://api.polygonscan.com/api"}

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
arbitrum = { key = "${ARBISCAN_KEY}", url = "https://api.arbiscan.io/api" }
goerli = { key = "${ETHERSCAN_KEY}", url = "https://api-goerli.etherscan.io/api" }
gnosis = { key = "${GNOSISSCAN_KEY}", url = "https://api.gnosisscan.io/api" }
mainnet = { key = "${ETHERSCAN_KEY}", url = "https://api.etherscan.io/api" }
optimism = { key = "${OPTIMISM_KEY}", url = "https://api-optimistic.etherscan.io/api" }
sepolia = { key = "${ETHERSCAN_KEY}", url = "https://api-sepolia.etherscan.io/api" }
polygon = { key = "${POLYGONSCAN_KEY}", url = "https://api.polygonscan.com/api" }

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
90 changes: 77 additions & 13 deletions script/Counter.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,95 @@ pragma solidity ^0.8.18;
import { Script, console2 } from "forge-std/Script.sol";
import { Counter } from "../src/Counter.sol";

interface ImmutableCreate2Factory {
function create2(bytes32 salt, bytes calldata initializationCode)
external
payable
returns (address deploymentAddress);
}

contract Deploy is Script {
Counter public counter;
bytes32 public SALT = keccak256("lets add some salt to this meal");
bytes32 public SALT = bytes32(abi.encode(0x4a75));

// default values
bool private verbose = true;
bool internal _verbose = true;

/// @notice Override default values, if desired
function prepare(bool _verbose) public {
verbose = _verbose;
/// @dev Override default values, if desired
function prepare(bool verbose) public {
_verbose = verbose;
}

function run() public {
/// @dev Set up the deployer via their private key from the environment
function deployer() public returns (address) {
uint256 privKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.rememberKey(privKey);
vm.startBroadcast(deployer);
return vm.rememberKey(privKey);
}

function _log(string memory prefix) internal view {
if (_verbose) {
console2.log(string.concat(prefix, "Counter:"), address(counter));
}
}

counter = new Counter{ salt: SALT}();
/// @dev Deploy the contract to a deterministic address via forge's create2 deployer factory.
function run() public virtual {
vm.startBroadcast(deployer());

/**
* @dev Deploy the contract to a deterministic address via forge's create2 deployer factory, which is at this
* address on all chains: `0x4e59b44847b379578588920cA78FbF26c0B4956C`.
* The resulting deployment address is determined by only two factors:
* 1. The bytecode hash of the contract to deploy. Setting `bytecode_hash` to "none" in foundry.toml ensures that
* never differs regardless of where its being compiled
* 2. The provided salt, `SALT`
*/
counter = new Counter{ salt: SALT}(/* insert constructor args here */);

vm.stopBroadcast();

if (verbose) {
console2.log("Counter:", address(counter));
}
_log("");
}
}

// forge script script/Deploy.s.sol -f ethereum --broadcast --verify
/// @dev Deploy pre-compiled ir-optimized bytecode to a non-deterministic address
contract DeployPrecompiled is Deploy {
/// @dev Update SALT and default values in Deploy contract

function run() public override {
vm.startBroadcast(deployer());

bytes memory args = abi.encode( /* insert constructor args here */ );

/// @dev Load and deploy pre-compiled ir-optimized bytecode.
counter = Counter(deployCode("optimized-out/Counter.sol/Counter.json", args));

vm.stopBroadcast();

_log("Precompiled ");
}
}

/* FORGE CLI COMMANDS
## A. Simulate the deployment locally
forge script script/Deploy.s.sol -f mainnet
## B. Deploy to real network and verify on etherscan
forge script script/Deploy.s.sol -f mainnet --broadcast --verify
## C. Fix verification issues (replace values in curly braces with the actual values)
forge verify-contract --chain-id 1 --num-of-optimizations 1000000 --watch --constructor-args $(cast abi-encode \
"constructor({args})" "{arg1}" "{arg2}" "{argN}" ) \
--compiler-version v0.8.19 {deploymentAddress} \
src/{Counter}.sol:{Counter} --etherscan-api-key $ETHERSCAN_KEY
## D. To verify ir-optimized contracts on etherscan...
1. Run (C) with the following additional flag: `--show-standard-json-input > etherscan.json`
2. Patch `etherscan.json`: `"optimizer":{"enabled":true,"runs":100}` =>
`"optimizer":{"enabled":true,"runs":100},"viaIR":true`
3. Upload the patched `etherscan.json` to etherscan manually
See this github issue for more: https://github.com/foundry-rs/foundry/issues/3507#issuecomment-1465382107
*/
29 changes: 13 additions & 16 deletions test/Counter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,30 @@ pragma solidity ^0.8.18;

import { Test, console2 } from "forge-std/Test.sol";
import { Counter } from "../src/Counter.sol";
import { Deploy } from "../script/Counter.s.sol";
import { Deploy, DeployPrecompiled } from "../script/Counter.s.sol";

contract CounterTest is Deploy, Test {
// variables inhereted from Deploy script
contract CounterTest is DeployPrecompiled, Test {
/// @dev Inherit from DeployPrecompiled instead of Deploy if working with pre-compiled contracts

/// @dev variables inhereted from Deploy script
// Counter public counter;
// bytes32 public SALT;

uint256 public fork;
uint256 public BLOCK_NUMBER;

function setUp() public virtual {
// create and activate a fork, at BLOCK_NUMBER
// OPTIONAL: create and activate a fork, at BLOCK_NUMBER
// fork = vm.createSelectFork(vm.rpcUrl("mainnet"), BLOCK_NUMBER);

// deploy via the script
Deploy.prepare(false); // set to true to log deployment addresses
Deploy.run();

counter.setNumber(0);
}

function testIncrement() public {
counter.increment();
assertEq(counter.number(), 1);
prepare(true);
run();
}
}

function testSetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
contract UnitTests is CounterTest {
function test_empty() public {
counter.setNumber(2);
}
}

0 comments on commit 761ad14

Please sign in to comment.