Skip to content

Commit

Permalink
feat: add assumeNoPrecompiles (#195)
Browse files Browse the repository at this point in the history
* refactor: use fully-qualified paths instead of relative paths

* chore: fix typo

* feat: start adding StdChains

* feat: start adding assumeNoPrecompiles

* feat: add chains

* feat: add precompiles/predeploys

* Revert "refactor: use fully-qualified paths instead of relative paths"

This reverts commit bb2579e.

* refactor: use relative paths for compatibility with solc <0.6.9 (no --base-path flag)

* refactor: make assumeNoPrecompiles virtual

* refactor: no more constructor warning from StdChains

* fix: move stdChains into StdCheats, fix constructor initalization order, move cheats into VmSafe that can be safely used
  • Loading branch information
mds1 committed Oct 28, 2022
1 parent d369d2a commit 72cdd70
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 18 deletions.
7 changes: 7 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
[profile.default]
fs_permissions = [{ access = "read-write", path = "./"}]

[rpc_endpoints]
# We intentionally use both dashes and underscores in the key names to ensure both are supported.
# The RPC URLs below match the StdChains URLs but append a trailing slash for testing.
mainnet = "https://api.mycryptoapi.com/eth/"
optimism_goerli = "https://goerli.optimism.io/"
arbitrum-one-goerli = "https://goerli-rollup.arbitrum.io/rpc/"

[fmt]
# These are all the `forge fmt` defaults.
line_length = 120
Expand Down
129 changes: 129 additions & 0 deletions src/StdCheats.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,45 @@ import "./Vm.sol";
abstract contract StdCheatsSafe {
VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));

/// @dev To hide constructor warnings across solc versions due to different constructor visibility requirements and
/// syntaxes, we put the constructor in a private method and assign an unused return value to a variable. This
/// forces the method to run during construction, but without declaring an explicit constructor.
uint256 private CONSTRUCTOR = _constructor();

struct Chain {
// The chain name, using underscores as the separator to match `foundry.toml` conventions.
string name;
// The chain's Chain ID.
uint256 chainId;
// A default RPC endpoint for this chain.
// NOTE: This default RPC URL is included for convenience to facilitate quick tests and
// experimentation. Do not use this RPC URL for production test suites, CI, or other heavy
// usage as you will be throttled and this is a disservice to others who need this endpoint.
string rpcUrl;
}

struct Chains {
Chain Anvil;
Chain Hardhat;
Chain Mainnet;
Chain Goerli;
Chain Sepolia;
Chain Optimism;
Chain OptimismGoerli;
Chain ArbitrumOne;
Chain ArbitrumOneGoerli;
Chain ArbitrumNova;
Chain Polygon;
Chain PolygonMumbai;
Chain Avalanche;
Chain AvalancheFuji;
Chain BnbSmartChain;
Chain BnbSmartChainTestnet;
Chain GnosisChain;
}

Chains stdChains;

// Data structures to parse Transaction objects from the broadcast artifact
// that conform to EIP1559. The Raw structs is what is parsed from the JSON
// and then converted to the one that is used by the user for better UX.
Expand Down Expand Up @@ -186,6 +225,96 @@ abstract contract StdCheatsSafe {
string value;
}

function _constructor() private returns (uint256) {
// Initialize `stdChains` with the defaults.
stdChains = Chains({
Anvil: Chain("Anvil", 31337, "http://127.0.0.1:8545"),
Hardhat: Chain("Hardhat", 31337, "http://127.0.0.1:8545"),
Mainnet: Chain("Mainnet", 1, "https://api.mycryptoapi.com/eth"),
Goerli: Chain("Goerli", 5, "https://goerli.infura.io/v3/84842078b09946638c03157f83405213"), // Default Infura key from ethers.js: https://github.com/ethers-io/ethers.js/blob/c80fcddf50a9023486e9f9acb1848aba4c19f7b6/packages/providers/src.ts/infura-provider.ts
Sepolia: Chain("Sepolia", 11155111, "https://rpc.sepolia.dev"),
Optimism: Chain("Optimism", 10, "https://mainnet.optimism.io"),
OptimismGoerli: Chain("OptimismGoerli", 420, "https://goerli.optimism.io"),
ArbitrumOne: Chain("ArbitrumOne", 42161, "https://arb1.arbitrum.io/rpc"),
ArbitrumOneGoerli: Chain("ArbitrumOneGoerli", 421613, "https://goerli-rollup.arbitrum.io/rpc"),
ArbitrumNova: Chain("ArbitrumNova", 42170, "https://nova.arbitrum.io/rpc"),
Polygon: Chain("Polygon", 137, "https://polygon-rpc.com"),
PolygonMumbai: Chain("PolygonMumbai", 80001, "https://rpc-mumbai.matic.today"),
Avalanche: Chain("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"),
AvalancheFuji: Chain("AvalancheFuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc"),
BnbSmartChain: Chain("BnbSmartChain", 56, "https://bsc-dataseed1.binance.org"),
BnbSmartChainTestnet: Chain("BnbSmartChainTestnet", 97, "https://data-seed-prebsc-1-s1.binance.org:8545"),
GnosisChain: Chain("GnosisChain", 100, "https://rpc.gnosischain.com")
});

// Loop over RPC URLs in the config file to replace the default RPC URLs
(string[2][] memory rpcs) = vm.rpcUrls();
for (uint256 i = 0; i < rpcs.length; i++) {
(string memory name, string memory rpcUrl) = (rpcs[i][0], rpcs[i][1]);
// forgefmt: disable-start
if (isEqual(name, "anvil")) stdChains.Anvil.rpcUrl = rpcUrl;
else if (isEqual(name, "hardhat")) stdChains.Hardhat.rpcUrl = rpcUrl;
else if (isEqual(name, "mainnet")) stdChains.Mainnet.rpcUrl = rpcUrl;
else if (isEqual(name, "goerli")) stdChains.Goerli.rpcUrl = rpcUrl;
else if (isEqual(name, "sepolia")) stdChains.Sepolia.rpcUrl = rpcUrl;
else if (isEqual(name, "optimism")) stdChains.Optimism.rpcUrl = rpcUrl;
else if (isEqual(name, "optimism_goerli", "optimism-goerli")) stdChains.OptimismGoerli.rpcUrl = rpcUrl;
else if (isEqual(name, "arbitrum_one", "arbitrum-one")) stdChains.ArbitrumOne.rpcUrl = rpcUrl;
else if (isEqual(name, "arbitrum_one_goerli", "arbitrum-one-goerli")) stdChains.ArbitrumOneGoerli.rpcUrl = rpcUrl;
else if (isEqual(name, "arbitrum_nova", "arbitrum-nova")) stdChains.ArbitrumNova.rpcUrl = rpcUrl;
else if (isEqual(name, "polygon")) stdChains.Polygon.rpcUrl = rpcUrl;
else if (isEqual(name, "polygon_mumbai", "polygon-mumbai")) stdChains.PolygonMumbai.rpcUrl = rpcUrl;
else if (isEqual(name, "avalanche")) stdChains.Avalanche.rpcUrl = rpcUrl;
else if (isEqual(name, "avalanche_fuji", "avalanche-fuji")) stdChains.AvalancheFuji.rpcUrl = rpcUrl;
else if (isEqual(name, "bnb_smart_chain", "bnb-smart-chain")) stdChains.BnbSmartChain.rpcUrl = rpcUrl;
else if (isEqual(name, "bnb_smart_chain_testnet", "bnb-smart-chain-testnet")) stdChains.BnbSmartChainTestnet.rpcUrl = rpcUrl;
else if (isEqual(name, "gnosis_chain", "gnosis-chain")) stdChains.GnosisChain.rpcUrl = rpcUrl;
// forgefmt: disable-end
}
return 0;
}

function isEqual(string memory a, string memory b) private pure returns (bool) {
return keccak256(abi.encode(a)) == keccak256(abi.encode(b));
}

function isEqual(string memory a, string memory b, string memory c) private pure returns (bool) {
return keccak256(abi.encode(a)) == keccak256(abi.encode(b))
|| keccak256(abi.encode(a)) == keccak256(abi.encode(c));
}

function assumeNoPrecompiles(address addr) internal virtual {
// Assembly required since `block.chainid` was introduced in 0.8.0.
uint256 chainId;
assembly {
chainId := chainid()
}
assumeNoPrecompiles(addr, chainId);
}

function assumeNoPrecompiles(address addr, uint256 chainId) internal virtual {
// Note: For some chains like Optimism these are technically predeploys (i.e. bytecode placed at a specific
// address), but the same rationale for excluding them applies so we include those too.

// These should be present on all EVM-compatible chains.
vm.assume(addr < address(0x1) || addr > address(0x9));

// forgefmt: disable-start
if (chainId == stdChains.Optimism.chainId || chainId == stdChains.OptimismGoerli.chainId) {
// https://github.com/ethereum-optimism/optimism/blob/eaa371a0184b56b7ca6d9eb9cb0a2b78b2ccd864/op-bindings/predeploys/addresses.go#L6-L21
vm.assume(addr < address(0x4200000000000000000000000000000000000000) || addr > address(0x4200000000000000000000000000000000000800));
} else if (chainId == stdChains.ArbitrumOne.chainId || chainId == stdChains.ArbitrumOneGoerli.chainId) {
// https://developer.arbitrum.io/useful-addresses#arbitrum-precompiles-l2-same-on-all-arb-chains
vm.assume(addr < address(0x0000000000000000000000000000000000000064) || addr > address(0x0000000000000000000000000000000000000068));
} else if (chainId == stdChains.Avalanche.chainId || chainId == stdChains.AvalancheFuji.chainId) {
// https://github.com/ava-labs/subnet-evm/blob/47c03fd007ecaa6de2c52ea081596e0a88401f58/precompile/params.go#L18-L59
vm.assume(addr < address(0x0100000000000000000000000000000000000000) || addr > address(0x01000000000000000000000000000000000000ff));
vm.assume(addr < address(0x0200000000000000000000000000000000000000) || addr > address(0x02000000000000000000000000000000000000FF));
vm.assume(addr < address(0x0300000000000000000000000000000000000000) || addr > address(0x03000000000000000000000000000000000000Ff));
}
// forgefmt: disable-end
}

function readEIP1559ScriptArtifact(string memory path) internal virtual returns (EIP1559ScriptArtifact memory) {
string memory data = vm.readFile(path);
bytes memory parsedData = vm.parseJson(data);
Expand Down
14 changes: 8 additions & 6 deletions src/Vm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ interface VmSafe {
// struct json = { uint256 a; address b; }
// If we defined a json struct with the opposite order, meaning placing the address b first, it would try to
// decode the tuple in that order, and thus fail.

// Returns the RPC url for the given alias
function rpcUrl(string calldata) external returns (string memory);
// Returns all rpc urls and their aliases `[alias, url][]`
function rpcUrls() external returns (string[2][] memory);

// If the condition is false, discard this run's fuzz inputs and generate new ones.
function assume(bool) external;
}

interface Vm is VmSafe {
Expand Down Expand Up @@ -187,8 +195,6 @@ interface Vm is VmSafe {
function expectCall(address, bytes calldata) external;
// Expects a call to an address with the specified msg.value and calldata
function expectCall(address, uint256, bytes calldata) external;
// If the condition is false, discard this run's fuzz inputs and generate new ones
function assume(bool) external;
// Sets block.coinbase (who)
function coinbase(address) external;
// Snapshot the current state of the evm.
Expand Down Expand Up @@ -243,8 +249,4 @@ interface Vm is VmSafe {
function transact(bytes32 txHash) external;
// Fetches the given transaction from the given fork and executes it on the current state
function transact(uint256 forkId, bytes32 txHash) external;
// Returns the RPC url for the given alias
function rpcUrl(string calldata) external returns (string memory);
// Returns all rpc urls and their aliases `[alias, url][]`
function rpcUrls() external returns (string[2][] memory);
}
2 changes: 1 addition & 1 deletion src/console2.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

/// @dev The orignal console.sol uses `int` and `uint` for computing function selectors, but it should
/// @dev The original console.sol uses `int` and `uint` for computing function selectors, but it should
/// use `int256` and `uint256`. This modified version fixes that. This version is recommended
/// over `console.sol` if you don't need compatibility with Hardhat as the logs will show up in
/// forge stack traces. If you do need compatibility with Hardhat, you must use `console.sol`.
Expand Down
2 changes: 1 addition & 1 deletion test/StdAssertions.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

import "src/Test.sol";
import "../src/Test.sol";

contract StdAssertionsTest is Test {
string constant CUSTOM_ERROR = "guh!";
Expand Down
27 changes: 24 additions & 3 deletions test/StdCheats.t.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

import "src/StdCheats.sol";
import "src/Test.sol";
import "src/StdJson.sol";
import "../src/StdCheats.sol";
import "../src/Test.sol";
import "../src/StdJson.sol";

contract StdCheatsTest is Test {
Bar test;
Expand Down Expand Up @@ -223,6 +223,27 @@ contract StdCheatsTest is Test {
}
return number;
}

function testChainRpcInitialization() public {
// RPCs specified in `foundry.toml` should be updated.
assertEq(stdChains.Mainnet.rpcUrl, "https://api.mycryptoapi.com/eth/");
assertEq(stdChains.OptimismGoerli.rpcUrl, "https://goerli.optimism.io/");
assertEq(stdChains.ArbitrumOneGoerli.rpcUrl, "https://goerli-rollup.arbitrum.io/rpc/");

// Other RPCs should remain unchanged.
assertEq(stdChains.Anvil.rpcUrl, "http://127.0.0.1:8545");
assertEq(stdChains.Hardhat.rpcUrl, "http://127.0.0.1:8545");
assertEq(stdChains.Sepolia.rpcUrl, "https://rpc.sepolia.dev");
}

// Ensure we can connect to the default RPC URL for each chain.
function testRpcs() public {
(string[2][] memory rpcs) = vm.rpcUrls();
for (uint256 i = 0; i < rpcs.length; i++) {
( /* string memory name */ , string memory rpcUrl) = (rpcs[i][0], rpcs[i][1]);
vm.createSelectFork(rpcUrl);
}
}
}

contract Bar {
Expand Down
4 changes: 2 additions & 2 deletions test/StdError.t.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "src/StdError.sol";
import "src/Test.sol";
import "../src/StdError.sol";
import "../src/Test.sol";

contract StdErrorsTest is Test {
ErrorsTest test;
Expand Down
4 changes: 2 additions & 2 deletions test/StdMath.t.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "src/StdMath.sol";
import "src/Test.sol";
import "../src/StdMath.sol";
import "../src/Test.sol";

contract StdMathTest is Test {
function testGetAbs() external {
Expand Down
4 changes: 2 additions & 2 deletions test/StdStorage.t.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

import "src/StdStorage.sol";
import "src/Test.sol";
import "../src/StdStorage.sol";
import "../src/Test.sol";

contract StdStorageTest is Test {
using stdStorage for StdStorage;
Expand Down
7 changes: 6 additions & 1 deletion test/StdUtils.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

import "src/Test.sol";
import "../src/Test.sol";

contract StdUtilsTest is Test {
function testBound() public {
Expand Down Expand Up @@ -88,4 +88,9 @@ contract StdUtilsTest is Test {
address create2Address = computeCreate2Address(salt, initcodeHash, deployer);
assertEq(create2Address, 0xB147a5d25748fda14b463EB04B111027C290f4d3);
}

function testAssumeNoPrecompilesL1(address addr) external {
assumeNoPrecompiles(addr, stdChains.Mainnet.chainId);
assertTrue(addr < address(1) || addr > address(9));
}
}

0 comments on commit 72cdd70

Please sign in to comment.