diff --git a/.gas-snapshot b/.gas-snapshot index f309fb6..ab16f3c 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1 +1 @@ -UpgradeArbOSVersionAtTimestampActionTest:test_1() (gas: 165) \ No newline at end of file +UpgradeArbOSVersionAtTimestampActionTest:test_1() (gas: 165) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb1cf56..2600f09 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -84,3 +84,16 @@ jobs: - run: yarn minimal-install - run: yarn test:storage + + test-unused-errors: + name: Test Unused Errors + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Execute check + uses: OffchainLabs/actions/check-unused-errors@main + with: + directory: './contracts' + exceptions_file: './test/unused-errors/exceptions.txt' diff --git a/audit-ci.jsonc b/audit-ci.jsonc index 0967ef4..ea00c84 100644 --- a/audit-ci.jsonc +++ b/audit-ci.jsonc @@ -1 +1,16 @@ -{} +{ + "$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json", + "low": true, + "allowlist": [ + // ws affected by a DoS when handling a request with many HTTP headers + "GHSA-3h5v-q93c-6h6q", + // follow-redirects' Proxy-Authorization header kept across hosts + "GHSA-cxjh-pqwp-8mfp", + // Undici's Proxy-Authorization header not cleared on cross-origin redirect for dispatch, request, stream, pipeline + "GHSA-m4v8-wqvr-p9f7", + // Undici's fetch with integrity option is too lax when algorithm is specified but hash value is in incorrect + "GHSA-9qxr-qj54-h672", + // Uncontrolled resource consumption in braces + "GHSA-grv7-fg5c-xmjg" + ] +} diff --git a/foundry.toml b/foundry.toml index f5e41c0..f363a94 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,3 +8,19 @@ solc_version = '0.8.16' optimizer_runs = 2000 fs_permissions = [{ access = "read", path = "node_modules/@arbitrum/"}, { access = "read", path = "node_modules/@openzeppelin/"},{ access = "read-write", path = "./scripts/foundry"}] script = 'scripts' + +[fmt] +line_length = 120 +tab_width = 4 +bracket_spacing = false +int_types = "long" +multiline_func_header = "attributes_first" +quote_style = "double" +number_underscore = "preserve" +hex_underscore = "remove" +single_line_statement_blocks = "preserve" +override_spacing = false +wrap_comments = false +ignore = [] +contract_new_lines = false +sort_imports = false diff --git a/hardhat.config.ts b/hardhat.config.ts index 95abc97..2c60b29 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,41 +3,37 @@ import '@nomicfoundation/hardhat-toolbox' import '@nomicfoundation/hardhat-foundry' import dotenv from 'dotenv' dotenv.config() +import 'hardhat-contract-sizer' -import { SolidityUserConfig } from 'hardhat/types' +import { SolcUserConfig } from 'hardhat/types' import toml from 'toml' import fs from 'fs' const config: HardhatUserConfig = { - solidity: getSolidityConfigFromFoundryToml( - process.env.FOUNDRY_PROFILE || 'default' - ), + solidity: { + ...getSolidityConfigFromFoundryToml(process.env.FOUNDRY_PROFILE), + // overrides here + // overrides: { + // 'contracts/MyContract.sol': { + // version: '0.8.0', + // settings: { + // optimizer: { + // enabled: false, + // }, + // }, + // }, + // }, + }, networks: { fork: { url: process.env.FORK_URL || 'http://localhost:8545', }, - arb1: { - url: 'https://arb1.arbitrum.io/rpc', - }, - mainnet: { - url: 'https://mainnet.infura.io/v3/' + process.env['INFURA_KEY'], - }, - sepolia: { - url: 'https://sepolia.infura.io/v3/' + process.env['INFURA_KEY'], - }, - arbSepolia: { - url: 'https://sepolia-rollup.arbitrum.io/rpc', - }, - nova: { - url: 'https://nova.arbitrum.io/rpc', - }, - holesky: { - url: 'https://1rpc.io/holesky', - }, }, } -function getSolidityConfigFromFoundryToml(profile: string): SolidityUserConfig { +function getSolidityConfigFromFoundryToml( + profile: string | undefined +): SolcUserConfig { const data = toml.parse(fs.readFileSync('foundry.toml', 'utf-8')) const defaultConfig = data.profile['default'] diff --git a/package.json b/package.json index f225d7f..9b5c04a 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "license": "Apache 2.0", "scripts": { "prepare": "forge install && cd lib/arbitrum-sdk && yarn", - "minimal-publish": "./scripts/publish.bash", "minimal-install": "yarn --ignore-scripts && forge install", + "minimal-publish": "./scripts/template/publish.bash", "audit:ci": "audit-ci --config ./audit-ci.jsonc", "upload-all-selectors": "forge build && find ./out -name \"*.json\" -exec cast upload-signature {} +", "format": "cat .prettierignore .gitignore > .pignore && prettier --ignore-path .pignore \"**/*.{js,ts,json}\" --write && rm .pignore && forge fmt", @@ -16,7 +16,7 @@ "test:unit": "./test/unit/test-unit.bash", "test:fork": "./test/fork/test-fork.bash", "test:e2e": "./test/e2e/test-e2e.bash", - "test:sizes": "forge build --sizes", + "test:sizes": "STRICT=true yarn hardhat size-contracts", "test:gas-check": "forge snapshot --check --tolerance 1 --match-path \"test/unit/**/*.t.sol\"", "test:sigs": "./test/signatures/test-sigs.bash", "test:storage": "./test/storage/test-storage.bash", @@ -52,6 +52,7 @@ "ethers": "^6.4.0", "ethers-v5": "npm:ethers@^5.7.2", "hardhat": "^2.19.5", + "hardhat-contract-sizer": "^2.10.0", "hardhat-gas-reporter": "^1.0.8", "prettier": "^3.2.5", "solidity-coverage": "^0.8.0", diff --git a/scripts/prepublish.ts b/scripts/template/prepublish.ts similarity index 96% rename from scripts/prepublish.ts rename to scripts/template/prepublish.ts index 9e66c88..0f61f98 100644 --- a/scripts/prepublish.ts +++ b/scripts/template/prepublish.ts @@ -1,5 +1,5 @@ import * as fs from 'fs' -import hardhatConfig from '../hardhat.config' +import hardhatConfig from '../../hardhat.config' /* Generates a minimal package.json and hardhat.config.js for publishing to npm diff --git a/scripts/print-contracts.bash b/scripts/template/print-contracts.bash similarity index 100% rename from scripts/print-contracts.bash rename to scripts/template/print-contracts.bash diff --git a/scripts/publish.bash b/scripts/template/publish.bash similarity index 94% rename from scripts/publish.bash rename to scripts/template/publish.bash index b33a4f5..b684d61 100755 --- a/scripts/publish.bash +++ b/scripts/template/publish.bash @@ -8,7 +8,7 @@ # prepublish # must run with hardhat to generate hardhat.config.js -yarn hardhat run scripts/prepublish.ts +yarn hardhat run scripts/template/prepublish.ts if [ $? -ne 0 ]; then echo "Prepublish failed" diff --git a/scripts/template/util.ts b/scripts/template/util.ts new file mode 100644 index 0000000..aa7d882 --- /dev/null +++ b/scripts/template/util.ts @@ -0,0 +1,38 @@ +import { JsonRpcProvider, Provider, Wallet } from 'ethers' +import { ethers as ethersv5 } from 'ethers-v5' + +export type Unwrap = T extends Promise ? U : T + +export function getEnv(name: string): string { + const value = process.env[name] || '' + if (value === '') { + throw new Error(`Environment variable ${name} is not defined`) + } + return value +} + +export class DoubleProvider extends JsonRpcProvider { + public readonly v5: ethersv5.providers.JsonRpcProvider + constructor(public readonly url: string) { + super(url) + this.v5 = new ethersv5.providers.JsonRpcProvider(url) + } +} + +export class DoubleWallet extends Wallet { + public readonly provider!: Provider + public readonly v5: ethersv5.Wallet & { + provider: ethersv5.providers.JsonRpcProvider + } + + constructor( + privateKey: string, + public readonly doubleProvider: DoubleProvider + ) { + super(privateKey, doubleProvider) + this.v5 = new ethersv5.Wallet( + privateKey, + doubleProvider.v5 + ) as ethersv5.Wallet & { provider: ethersv5.providers.JsonRpcProvider } + } +} diff --git a/test/e2e/test-e2e.bash b/test/e2e/test-e2e.bash index 0f0f104..c7187b7 100755 --- a/test/e2e/test-e2e.bash +++ b/test/e2e/test-e2e.bash @@ -12,14 +12,12 @@ else export ORBIT_TEST=1 fi -cd lib/arbitrum-sdk && yarn gen:network && cd - +set -e -# if the above command fails, exit -if [ $? -ne 0 ]; then - echo "Failed to generate network" - exit 1 -fi +cd lib/arbitrum-sdk && yarn gen:network && cd - -yarn hardhat compile +yarn build yarn mocha test/e2e/ --timeout 30000000 --bail + +exit $? diff --git a/test/e2e/testSetup.ts b/test/e2e/testSetup.ts index a97ef9f..7b81c7b 100644 --- a/test/e2e/testSetup.ts +++ b/test/e2e/testSetup.ts @@ -1,24 +1,27 @@ -import { JsonRpcProvider, Wallet } from 'ethers' import { testSetup as sdkTestSetup } from '../../lib/arbitrum-sdk/scripts/testSetup' import { L1Network, L2Network, getL1Network } from '../../lib/arbitrum-sdk/src' -import { getEnv } from '../util/util' +import { + DoubleProvider, + DoubleWallet, + getEnv, +} from '../../scripts/template/util' export const isTestingOrbit = process.env.ORBIT_TEST === '1' type BaseTestSetup = { l1Network: L1Network l2Network: L2Network - l1Signer: Wallet - l2Signer: Wallet - l1Provider: JsonRpcProvider - l2Provider: JsonRpcProvider + l1Signer: DoubleWallet + l2Signer: DoubleWallet + l1Provider: DoubleProvider + l2Provider: DoubleProvider } export type OrbitTestSetup = BaseTestSetup & { isTestingOrbit: true l3Network: L2Network - l3Provider: JsonRpcProvider - l3Signer: Wallet + l3Provider: DoubleProvider + l3Signer: DoubleWallet } export type NonOrbitTestSetup = BaseTestSetup & { @@ -28,16 +31,16 @@ export type NonOrbitTestSetup = BaseTestSetup & { export type TestSetup = OrbitTestSetup | NonOrbitTestSetup export async function testSetup(): Promise { - const l1Provider = new JsonRpcProvider(getEnv('LOCAL_L1_URL')) - const l2Provider = new JsonRpcProvider(getEnv('LOCAL_L2_URL')) - const l1Signer = new Wallet(getEnv('LOCAL_L1_KEY'), l1Provider) - const l2Signer = new Wallet(getEnv('LOCAL_L2_KEY'), l2Provider) + const l1Provider = new DoubleProvider(getEnv('LOCAL_L1_URL')) + const l2Provider = new DoubleProvider(getEnv('LOCAL_L2_URL')) + const l1Signer = new DoubleWallet(getEnv('LOCAL_L1_KEY'), l1Provider) + const l2Signer = new DoubleWallet(getEnv('LOCAL_L2_KEY'), l2Provider) const setup = await sdkTestSetup() if (isTestingOrbit) { - const l3Provider = new JsonRpcProvider(getEnv('LOCAL_L3_URL')) - const l3Signer = new Wallet(getEnv('LOCAL_L3_KEY'), l3Provider) + const l3Provider = new DoubleProvider(getEnv('LOCAL_L3_URL')) + const l3Signer = new DoubleWallet(getEnv('LOCAL_L3_KEY'), l3Provider) const l1Network = await getL1Network( (setup.l1Network as L2Network).partnerChainID diff --git a/test/fork/test-fork.bash b/test/fork/test-fork.bash index 52b54c9..3398161 100755 --- a/test/fork/test-fork.bash +++ b/test/fork/test-fork.bash @@ -1,48 +1,10 @@ #!/bin/bash -# run fork tests, tests in fork// will be run against _FORK_URL -# if there is a chain dir without a corresponding env var, the script will fail +# early exit on failure +set -e -shopt -s globstar +# run hardhat tests if there are any +test $(find test/fork -name '*.test.ts' | wc -l) -eq 0 || yarn hardhat test $(find test/fork -name '*.test.ts') -chains=$(ls -d ./test/fork/*/ 2>/dev/null) - -if [ -z "$chains" ]; then - echo "No directories found in ./test/fork/" - exit 0 -fi - -for dir in $chains; do - dirName=$(basename "$dir") - forkUrlName="${dirName^^}_FORK_URL" - forkUrl="${!forkUrlName}" - - if [ -z "$forkUrl" ]; then - echo "No value found for $forkUrlName" - exit 1 - fi - - code=0 - - hardhatFiles=$(find "$dir" -name "*.test.ts") - if [ -z "$hardhatFiles" ]; then - echo "No .test.ts files found in $dir" - else - echo "Running hardhat tests against \$$forkUrlName ..." - FORK_URL=$forkUrl yarn run hardhat test $hardhatFiles --network fork - CODE=$? - fi - [ "$code" -ne 0 ] && exit $code - - foundryFiles=$(find "$dir" -name "*.t.sol") - if [ -z "$foundryFiles" ]; then - echo "No .t.sol files found in $dir" - else - echo "Running foundry tests against \$$forkUrlName ..." - forge test --fork-url $forkUrl --match-path "$dir**/*.t.sol" - code=$? - fi - [ "$code" -ne 0 ] && exit $code -done - -exit 0 \ No newline at end of file +# run foundry tests if there are any +test $(find test/fork -name '*.t.sol' | wc -l) -eq 0 || forge test --match-path 'test/fork/**/*.t.sol' \ No newline at end of file diff --git a/test/mocks/SampleMock.sol b/test/mocks/SampleMock.sol new file mode 100644 index 0000000..afc6768 --- /dev/null +++ b/test/mocks/SampleMock.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +contract SampleMock { + function arbOSVersion() external pure returns (uint256) { + return 1; + } +} diff --git a/test/unit/test-unit.bash b/test/unit/test-unit.bash index 03887ba..8e13860 100755 --- a/test/unit/test-unit.bash +++ b/test/unit/test-unit.bash @@ -1,21 +1,10 @@ #!/bin/bash -shopt -s globstar +# early exit on failure +set -e -code=0 +# run hardhat tests if there are any +test $(find test/unit -name '*.test.ts' | wc -l) -eq 0 || yarn hardhat test $(find test/unit -name '*.test.ts') -hardhatFiles=$(ls ./test/unit/**/*.test.ts 2>/dev/null) -if [ -n "$hardhatFiles" ]; then - yarn run hardhat test $hardhatFiles - code=$? -fi - -[ "$code" -ne 0 ] && exit $code - -foundryFiles=$(ls ./test/unit/**/*.t.sol 2>/dev/null) -if [ -n "$foundryFiles" ]; then - forge test --match-path "test/unit/*.t.sol" - code=$? -fi - -exit $code +# run foundry tests if there are any +test $(find test/unit -name '*.t.sol' | wc -l) -eq 0 || forge test --match-path 'test/unit/**/*.t.sol' \ No newline at end of file diff --git a/test/unused-errors/exceptions.txt b/test/unused-errors/exceptions.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/util/forge-inspect.bash b/test/util/forge-inspect.bash index a4640ea..0237e19 100755 --- a/test/util/forge-inspect.bash +++ b/test/util/forge-inspect.bash @@ -2,7 +2,7 @@ # usage: ./test/util/forge-inspect.bash -contracts=$(./scripts/print-contracts.bash) +contracts=$(./scripts/template/print-contracts.bash) if [[ $? != "0" ]]; then echo "Failed to get contracts" exit 1 diff --git a/test/util/util.ts b/test/util/util.ts deleted file mode 100644 index a7898fc..0000000 --- a/test/util/util.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type Unwrap = T extends Promise ? U : T - -export function getEnv(name: string): string { - const value = process.env[name] || '' - if (value === '') { - throw new Error(`Environment variable ${name} is not defined`) - } - return value -} diff --git a/yarn.lock b/yarn.lock index 61489f8..7e20384 100644 --- a/yarn.lock +++ b/yarn.lock @@ -101,6 +101,11 @@ "@chainsafe/persistent-merkle-tree" "^0.4.2" case "^1.6.3" +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -1976,6 +1981,15 @@ cli-table3@^0.5.0: optionalDependencies: colors "^1.1.2" +cli-table3@^0.6.0: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -3171,6 +3185,15 @@ handlebars@^4.0.1: optionalDependencies: uglify-js "^3.1.4" +hardhat-contract-sizer@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/hardhat-contract-sizer/-/hardhat-contract-sizer-2.10.0.tgz#72646f43bfe50e9a5702c9720c9bc3e77d93a2c9" + integrity sha512-QiinUgBD5MqJZJh1hl1jc9dNnpJg7eE/w4/4GEnrcmZJJTDbVFNe3+/3Ep24XqISSkYxRz36czcPHKHd/a0dwA== + dependencies: + chalk "^4.0.0" + cli-table3 "^0.6.0" + strip-ansi "^6.0.0" + hardhat-gas-reporter@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz#ebe5bda5334b5def312747580cd923c2b09aef1b"