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

feat: StdChains and assumeNoPrecompiles #195

Merged
merged 11 commits into from
Oct 28, 2022
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;
ZeroEkkusu marked this conversation as resolved.
Show resolved Hide resolved
}

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 @@ -72,4 +72,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));
}
}