diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ebc9b5b --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +DEPLOYER_PRIVATE_KEY= + +RPC_URL_MAINNET= +RPC_URL_GOERLI= + +ETHERSCAN_API_KEY= diff --git a/.gasestimates.md b/.gasestimates.md new file mode 100644 index 0000000..4775a53 --- /dev/null +++ b/.gasestimates.md @@ -0,0 +1,34 @@ +# Gas Estimates +Generated via `bash utils/inspect.sh`. + +--- + +`forge test --gas-report --no-match-path "test/script/**/*"` +| src/Counter.sol:Counter contract | | | | | | +|----------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 77935 | 417 | | | | | +| Function Name | min | avg | median | max | # calls | +| increment | 22334 | 22334 | 22334 | 22334 | 1 | +| number | 269 | 269 | 269 | 269 | 2 | +| setNumber | 2338 | 8971 | 2338 | 22238 | 3 | + + + +`forge inspect src/Counter.sol:Counter gasestimates` +```json +{ + "creation": { + "codeDepositCost": "77800", + "executionCost": "135", + "totalCost": "77935" + }, + "external": { + "increment()": "24434", + "number()": "2269", + "setNumber(uint256)": "22238" + }, + "internal": {} +} +``` + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..0e144d2 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ + + +## Motivation + + + +## Solution + + \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..6ec014b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,63 @@ +name: sc-template Test CI + +on: [pull_request, workflow_dispatch] + +concurrency: + group: ${{github.workflow}}-${{github.ref}} + cancel-in-progress: true + +# Runs linter, tests, and inspection checker in parallel +jobs: + lint: + name: Run Linter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + - run: forge install + + - run: forge fmt --check + + check-inspect: + name: Verify Inspections + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - run: forge install + - run: bash ./utils/inspect.sh + + - run: git status --untracked-files=no --porcelain + - run: git --no-pager diff + + - name: Check Inspections + run: if [[ -n "$(git status --untracked-files=no --porcelain)" ]]; then echo "Inspection difference detected, verify tests are passing and run \`bash ./utils/inspect.sh\` to fix." && exit 1; fi + + test: + name: Run Forge Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Install forge dependencies + run: forge install + + - name: Build project + run: forge build + + - name: Run tests + run: forge test -vvv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b224905 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Foundry build and cache directories +out/ +cache/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b2061c7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std + branch = v1.3.0 diff --git a/.storagelayout.md b/.storagelayout.md new file mode 100644 index 0000000..003a109 --- /dev/null +++ b/.storagelayout.md @@ -0,0 +1,10 @@ +# Storage Layouts +Generated via `bash utils/inspect.sh`. + +--- + +`forge inspect --pretty src/Counter.sol:Counter storage-layout` +| Name | Type | Slot | Offset | Bytes | Contract | +|--------|---------|------|--------|-------|-------------------------| +| number | uint256 | 0 | 0 | 32 | src/Counter.sol:Counter | + diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bb4b958 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "solidity.packageDefaultDependenciesContractsDirectory": "src", + "solidity.packageDefaultDependenciesDirectory": "lib", + "solidity.compileUsingRemoteVersion": "v0.8.14", + "editor.formatOnSave": true, + "[solidity]": { + "editor.defaultFormatter": "JuanBlanco.solidity" + }, + "solidity.formatter": "forge", + "search.exclude": { "lib": true } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..dbeb81d --- /dev/null +++ b/foundry.toml @@ -0,0 +1,22 @@ +[profile.default] +solc = '0.8.14' +via_ir = true +src = 'src' +out = 'out' +test = 'test' +libs = ['lib'] +optimizer = true +optimizer_runs = 10_000 + +[fuzz] +runs = 5000 + +[rpc_endpoints] +mainnet = "${RPC_URL_MAINNET}" +goerli = "${RPC_URL_GOERLI}" + +[etherscan] +mainnet = { key = "${ETHERSCAN_API_KEY}" } +goerli = { key = "${ETHERSCAN_API_KEY}" } + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..066ff16 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 066ff16c5c03e6f931cd041fd366bc4be1fae82a diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..1076a42 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,2 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ \ No newline at end of file diff --git a/script/Counter.s.sol b/script/Counter.s.sol new file mode 100644 index 0000000..fb4224a --- /dev/null +++ b/script/Counter.s.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.14; + +import "forge-std/Script.sol"; + +contract CounterScript is Script { + function setUp() public {} + + function run() public { + vm.broadcast(); + } +} diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 0000000..fd69b55 --- /dev/null +++ b/slither.config.json @@ -0,0 +1,3 @@ +{ + "filter_paths": "lib" +} \ No newline at end of file diff --git a/src/Counter.sol b/src/Counter.sol new file mode 100644 index 0000000..44816e0 --- /dev/null +++ b/src/Counter.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.14; + +/// @title A public counter for anyone to use. +contract Counter { + uint256 public number; + + /// @notice Set the counter's number to a new value. + /// @param newNumber The new number for the counter. + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + /// @notice Increase the counter's value by one. + /// @dev The number is not in an unchecked block, so overflows will revert. + function increment() public { + number++; + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol new file mode 100644 index 0000000..c155e42 --- /dev/null +++ b/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.14; + +import "forge-std/Test.sol"; +import "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testIncrement() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testSetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/utils/inspect.sh b/utils/inspect.sh new file mode 100644 index 0000000..1f31cf9 --- /dev/null +++ b/utils/inspect.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Generate inspection files in MD format for all primary contracts in src. + +CONTRACT_FILES=($(find src -iname '*.sol' | sort)) + +rm .storagelayout.md +rm .gasestimates.md + +echo "# Storage Layouts" >> .storagelayout.md +echo "Generated via \`bash utils/inspect.sh\`." >> .storagelayout.md +echo "" >> .storagelayout.md +echo "---" >> .storagelayout.md +echo "" >> .storagelayout.md + +echo "# Gas Estimates" >> .gasestimates.md +echo "Generated via \`bash utils/inspect.sh\`." >> .gasestimates.md +echo "" >> .gasestimates.md +echo "---" >> .gasestimates.md +echo "" >> .gasestimates.md +echo "\`forge test --gas-report --no-match-path \"test/script/**/*\"\`" >> .gasestimates.md +# Sed strings to strip color data and only start printing after the first '|' character, to exclude previous contents (compilation, test results, etc) +forge test --gas-report --no-match-path "test/script/**/*" | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" | sed -nr '/\|/,$p' >> .gasestimates.md + +for index in ${!CONTRACT_FILES[*]}; do + # echo "${CONTRACT_NAMES[$index]} is in ${CONTRACT_FILES[$index]}" + CONTRACT_NAME=$(basename -s ".sol" ${CONTRACT_FILES[${index}]}) + # echo ${CONTRACT_NAME} + # If file does not contain a contract named the same as the filename, discard from inspection (e.g. libraries). + if ! grep -q "contract ${CONTRACT_NAME}" ${CONTRACT_FILES[$index]}; then + # echo "Skipping ${CONTRACT_NAME}" + continue + fi + + # Show command names in files + echo "\`forge inspect --pretty ${CONTRACT_FILES[$index]}:${CONTRACT_NAME} storage-layout\`" >> .storagelayout.md + forge inspect --pretty ${CONTRACT_FILES[$index]}:${CONTRACT_NAME} storage-layout >> .storagelayout.md + echo "" >> .storagelayout.md + + echo "\`forge inspect ${CONTRACT_FILES[$index]}:${CONTRACT_NAME} gasestimates\`" >> .gasestimates.md + echo "\`\`\`json" >> .gasestimates.md + forge inspect ${CONTRACT_FILES[$index]}:${CONTRACT_NAME} gasestimates >> .gasestimates.md + echo "\`\`\`" >> .gasestimates.md + echo "" >> .gasestimates.md + +done