├── docs ├── .gitignore ├── README.md ├── src │ ├── modules │ │ ├── console.md │ │ ├── json.md │ │ ├── forks.md │ │ ├── env.md │ │ ├── gas.md │ │ ├── strings.md │ │ ├── accounts.md │ │ ├── config.md │ │ ├── events.md │ │ ├── rpc.md │ │ ├── fs.md │ │ ├── commands.md │ │ ├── fe.md │ │ ├── huff.md │ │ ├── requests.md │ │ ├── context.md │ │ ├── expect.md │ │ ├── utils.md │ │ ├── fmt.md │ │ ├── results.md │ │ ├── README.md │ │ └── watchers.md │ ├── OTHER_MODULES.md │ ├── references │ │ ├── Gas.md │ │ ├── Fmt.md │ │ ├── rpc.md │ │ ├── Config.md │ │ ├── SUMMARY.md │ │ ├── Vulcan.md │ │ ├── Utils.md │ │ ├── Semver.md │ │ ├── Events.md │ │ ├── Huff.md │ │ ├── Fe.md │ │ ├── Pointer.md │ │ ├── Strings.md │ │ ├── Error.md │ │ ├── Watchers.md │ │ ├── Invariants.md │ │ ├── Forks.md │ │ ├── Fs.md │ │ ├── Result.md │ │ └── Env.md │ ├── examples │ │ ├── huff │ │ │ └── example.md │ │ ├── gas │ │ │ └── example.md │ │ ├── forks │ │ │ └── example.md │ │ ├── fe │ │ │ └── example.md │ │ ├── json │ │ │ └── example.md │ │ ├── rpc │ │ │ └── example.md │ │ ├── console │ │ │ └── example.md │ │ ├── events │ │ │ └── example.md │ │ ├── env │ │ │ └── example.md │ │ ├── strings │ │ │ └── example.md │ │ ├── expect │ │ │ └── example.md │ │ ├── utils │ │ │ └── example.md │ │ ├── commands │ │ │ └── example.md │ │ ├── config │ │ │ └── example.md │ │ ├── format │ │ │ └── example.md │ │ ├── context │ │ │ └── example.md │ │ ├── results │ │ │ └── example.md │ │ ├── accounts │ │ │ └── example.md │ │ ├── requests │ │ │ └── example.md │ │ └── fs │ │ │ └── example.md │ ├── guide │ │ └── installation.md │ ├── testing │ │ ├── README.md │ │ └── expect.md │ ├── scripts │ │ └── README.md │ ├── SUMMARY.md │ └── README.md ├── book.css └── book.toml ├── test ├── fixtures │ └── fs │ │ ├── read │ │ └── hello_world.txt │ │ └── write │ │ └── .gitignore ├── mocks │ ├── guest_book.fe │ ├── Sender.sol │ └── Getter.huff ├── examples │ ├── accounts │ │ ├── AccountsExample01.t.sol │ │ ├── AccountsExample02.t.sol │ │ ├── AccountsExample03.t.sol │ │ ├── AccountsExample04.t.sol │ │ └── AccountsExample05.t.sol │ ├── config │ │ ├── ConfigExample01.t.sol │ │ ├── ConfigExample02.t.sol │ │ └── ConfigExample03.t.sol │ ├── utils │ │ ├── UtilsExample02.t.sol │ │ └── UtilsExample01.t.sol │ ├── huff │ │ └── HuffExample01.t.sol │ ├── commands │ │ ├── CommandExample01.t.sol │ │ └── CommandExample02.t.sol │ ├── gas │ │ └── GasExample01.t.sol │ ├── forks │ │ └── ForksExample01.t.sol │ ├── fe │ │ └── FeExample01.t.sol │ ├── format │ │ ├── FormatExample02.t.sol │ │ └── FormatExample01.t.sol │ ├── requests │ │ ├── RequestsExample02.t.sol │ │ ├── RequestsExample03.t.sol │ │ ├── RequestsExample01.t.sol │ │ └── RequestsExample04.t.sol │ ├── json │ │ └── JSONExample01.t.sol │ ├── rpc │ │ └── RpcExample01.t.sol │ ├── console │ │ └── ConsoleExample01.t.sol │ ├── events │ │ └── EventsExample01.t.sol │ ├── fs │ │ ├── FsExample01.t.sol │ │ ├── FsExample03.t.sol │ │ └── FsExample02.t.sol │ ├── context │ │ ├── ContextExample02.t.sol │ │ └── ContextExample01.t.sol │ ├── env │ │ └── EnvExample01.t.sol │ ├── results │ │ ├── ResultsExample02.t.sol │ │ └── ResultsExample01.t.sol │ ├── strings │ │ └── StringsExample01.t.sol │ └── expect │ │ └── ExpectExample01.t.sol ├── modules │ ├── Gas.t.sol │ ├── Pointer.t.sol │ ├── Rpc.t.sol │ ├── Env.t.sol │ ├── Config.t.sol │ ├── Forks.t.sol │ ├── Fmt.t.sol │ ├── Events.t.sol │ ├── Error.t.sol │ ├── Fe.t.sol │ ├── Commands.t.sol │ ├── Strings.t.sol │ ├── Huff.t.sol │ └── Context.t.sol └── ExampleTest.sol ├── commitlint.config.js ├── assets └── landscape.png ├── remappings.txt ├── .gitmodules ├── .husky └── commit-msg ├── src ├── utils.sol ├── script │ ├── Env.sol │ ├── Fe.sol │ ├── Fmt.sol │ ├── Gas.sol │ ├── Rpc.sol │ ├── Error.sol │ ├── Events.sol │ ├── Huff.sol │ ├── Console.sol │ ├── Strings.sol │ ├── Vulcan.sol │ ├── Config.sol │ ├── Context.sol │ ├── Accounts.sol │ ├── Json.sol │ ├── Fs.sol │ ├── Result.sol │ ├── Commands.sol │ └── Request.sol ├── test │ ├── Env.sol │ ├── Fe.sol │ ├── Fmt.sol │ ├── Gas.sol │ ├── Rpc.sol │ ├── Error.sol │ ├── Console.sol │ ├── Events.sol │ ├── Huff.sol │ ├── Strings.sol │ ├── Expect.sol │ ├── Vulcan.sol │ ├── Config.sol │ ├── Context.sol │ ├── Forks.sol │ ├── Json.sol │ ├── Accounts.sol │ ├── Fs.sol │ ├── Watchers.sol │ ├── Result.sol │ ├── Commands.sol │ └── Request.sol ├── _internal │ ├── Console.sol │ ├── Config.sol │ ├── Rpc.sol │ ├── Vulcan.sol │ ├── Gas.sol │ ├── Pointer.sol │ ├── Events.sol │ ├── Fe.sol │ ├── Strings.sol │ ├── Huff.sol │ ├── Error.sol │ ├── Semver.sol │ └── Utils.sol ├── script.sol ├── test.sol └── _imports.sol ├── .gitignore ├── package.json ├── .github └── workflows │ ├── release-please.yml │ ├── forge.yml │ └── deploy.yml ├── foundry.toml ├── Dockerfile ├── LICENSE.md └── README.md /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book/ -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Vulcan Book -------------------------------------------------------------------------------- /test/fixtures/fs/read/hello_world.txt: -------------------------------------------------------------------------------- 1 | Hello, World! 2 | -------------------------------------------------------------------------------- /test/fixtures/fs/write/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /test/mocks/guest_book.fe: -------------------------------------------------------------------------------- 1 | contract MyFeContract { 2 | target: address 3 | } 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']} 2 | -------------------------------------------------------------------------------- /assets/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomoixyz/vulcan/HEAD/assets/landscape.png -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | vulcan/=src/ 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit ${1} 5 | -------------------------------------------------------------------------------- /src/utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import "./_internal/Utils.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Env.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {env} from "../_internal/Env.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Fe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {fe, Fe} from "../_internal/Fe.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Fmt.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {fmt} from "../_internal/Fmt.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Gas.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {gas} from "../_internal/Gas.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Rpc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {rpc} from "../_internal/Rpc.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Env.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {env} from "../_internal/Env.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Fe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {fe, Fe} from "../_internal/Fe.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Fmt.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {fmt} from "../_internal/Fmt.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Gas.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {gas} from "../_internal/Gas.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Rpc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {rpc} from "../_internal/Rpc.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Error.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Error} from "../_internal/Error.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Error.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Error} from "../_internal/Error.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Events.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {events} from "../_internal/Events.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Huff.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {huff, Huffc} from "../_internal/Huff.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Console.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {console} from "../_internal/Console.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Events.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {events} from "../_internal/Events.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Huff.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {huff, Huffc} from "../_internal/Huff.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Strings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {strings} from "../_internal/Strings.sol"; 5 | -------------------------------------------------------------------------------- /src/_internal/Console.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {console2 as console} from "forge-std/console2.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Console.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {console} from "../_internal/Console.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Strings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {strings} from "../_internal/Strings.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Vulcan.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {vulcan, Log} from "../_internal/Vulcan.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Expect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {expect, any} from "../_internal/Expect.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Vulcan.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {vulcan, Log} from "../_internal/Vulcan.sol"; 5 | -------------------------------------------------------------------------------- /docs/src/modules/console.md: -------------------------------------------------------------------------------- 1 | # Console 2 | 3 | Print to the console. Uses `forge-std/console2.sol`. 4 | 5 | {{#include ../examples/console/example.md}} 6 | 7 | -------------------------------------------------------------------------------- /src/script/Config.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {config, RpcConfig} from "../_internal/Config.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Context.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {ctxSafe as ctx} from "../_internal/Context.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Config.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {config, RpcConfig} from "../_internal/Config.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Context.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {ctxUnsafe as ctx} from "../_internal/Context.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Forks.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {forksUnsafe as forks, Fork} from "../_internal/Forks.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Json.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {json, JsonObject, JsonResult} from "../_internal/Json.sol"; 5 | -------------------------------------------------------------------------------- /docs/src/modules/json.md: -------------------------------------------------------------------------------- 1 | # Json 2 | 3 | Manipulate JSON data. 4 | 5 | {{#include ../examples/json/example.md}} 6 | 7 | [**Json API reference**](../references/Json.md) 8 | -------------------------------------------------------------------------------- /src/script/Accounts.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {accountsSafe as accounts} from "../_internal/Accounts.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Json.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {json, JsonObject, JsonResult} from "../_internal/Json.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Accounts.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {accountsUnsafe as accounts} from "../_internal/Accounts.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Fs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {fs, FsMetadata, FsMetadataResult, FsErrors} from "../_internal/Fs.sol"; 5 | -------------------------------------------------------------------------------- /docs/src/modules/forks.md: -------------------------------------------------------------------------------- 1 | # Forks 2 | 3 | Forking functionality. 4 | 5 | {{#include ../examples/forks/example.md}} 6 | 7 | [**Forks API reference**](../references/Forks.md) 8 | -------------------------------------------------------------------------------- /src/script/Fs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {fs, FsMetadata, FsMetadataResult, FsErrors} from "../_internal/Fs.sol"; 5 | -------------------------------------------------------------------------------- /src/test/Watchers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {watchersUnsafe as watchers, Watcher} from "../_internal/Watchers.sol"; 5 | -------------------------------------------------------------------------------- /docs/src/modules/env.md: -------------------------------------------------------------------------------- 1 | # Env 2 | 3 | Set and read environmental variables. 4 | 5 | {{#include ../examples/env/example.md}} 6 | 7 | [**Env API reference**](../references/Env.md) 8 | -------------------------------------------------------------------------------- /docs/src/modules/gas.md: -------------------------------------------------------------------------------- 1 | Provides method to measure gas usage in a more granular way. 2 | 3 | {{#include ../examples/gas/example.md}} 4 | 5 | [**Gas API reference**](../references/Gas.md) 6 | -------------------------------------------------------------------------------- /src/test/Result.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {StringResult, BoolResult, BytesResult, EmptyResult} from "../_internal/Result.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Result.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {StringResult, BoolResult, BytesResult, EmptyResult} from "../_internal/Result.sol"; 5 | -------------------------------------------------------------------------------- /docs/src/modules/strings.md: -------------------------------------------------------------------------------- 1 | # Strings 2 | 3 | Convert basic types from / to strings. 4 | 5 | {{#include ../examples/strings/example.md}} 6 | 7 | [**Strings API reference**](../references/Strings.md) 8 | -------------------------------------------------------------------------------- /src/test/Commands.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {commands, Command, CommandResult, CommandOutput, CommandError} from "../_internal/Commands.sol"; 5 | -------------------------------------------------------------------------------- /src/script/Commands.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {commands, Command, CommandResult, CommandOutput, CommandError} from "../_internal/Commands.sol"; 5 | -------------------------------------------------------------------------------- /docs/book.css: -------------------------------------------------------------------------------- 1 | table { 2 | margin: 0 auto; 3 | border-collapse: collapse; 4 | width: 100%; 5 | } 6 | 7 | table td:first-child { 8 | width: 15%; 9 | } 10 | 11 | table td:nth-child(2) { 12 | width: 25%; 13 | } -------------------------------------------------------------------------------- /docs/src/modules/accounts.md: -------------------------------------------------------------------------------- 1 | # Accounts 2 | 3 | Utilities to operate over accounts (balances, impersonation, etc.) 4 | 5 | {{#include ../examples/accounts/example.md}} 6 | 7 | [**Accounts API reference**](../references/Accounts.md) 8 | -------------------------------------------------------------------------------- /docs/src/modules/config.md: -------------------------------------------------------------------------------- 1 | # Config 2 | 3 | Access the Foundry project configuration. For now it can only read RPC URLs. 4 | 5 | {{#include ../examples/config/example.md}} 6 | 7 | [**Config API reference**](../references/Config.md) 8 | -------------------------------------------------------------------------------- /docs/src/modules/events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | Provides utilities to get logs and transform values into their topic representation. 4 | 5 | {{#include ../examples/events/example.md}} 6 | 7 | [**Events API reference**](../references/Events.md) 8 | -------------------------------------------------------------------------------- /docs/src/modules/rpc.md: -------------------------------------------------------------------------------- 1 | # RPC 2 | 3 | The `rpc` module provides methods to interact with JSON-RPC APIs. The list of official Ethereum RPC methods can 4 | be found [here](https://ethereum.org/en/developers/docs/apis/json-rpc). 5 | 6 | {{#include ../examples/rpc/example.md}} 7 | 8 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | src = 'src' 3 | title = '' 4 | [output.html] 5 | no-section-label = true 6 | additional-js = ['solidity.min.js'] 7 | additional-css = ['book.css'] 8 | git-repository-url = 'https://github.com/nomoixyz/vulcan' 9 | 10 | [output.html.fold] 11 | enable = true 12 | -------------------------------------------------------------------------------- /docs/src/modules/fs.md: -------------------------------------------------------------------------------- 1 | # Fs 2 | 3 | Provides utilities to interact with the filesystem. In order to use this module the 4 | `fs_permissions` setting must be set correctly in `foundry.toml`. 5 | 6 | {{#include ../examples/fs/example.md}} 7 | 8 | [**Fs API reference**](../references/Fs.md) 9 | -------------------------------------------------------------------------------- /docs/src/OTHER_MODULES.md: -------------------------------------------------------------------------------- 1 | # Other modules 2 | 3 | - [Config](./modules/config.md) 4 | - [Console](./modules/console.md) 5 | - [Env](./modules/env.md) 6 | - [Events](./modules/events.md) 7 | - [Forks](./modules/forks.md) 8 | - [RPC](./modules/rpc.md) 9 | - [Strings](./modules/strings.md) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Dotenv file 11 | .env 12 | 13 | .DS_Store 14 | 15 | # Test stuff 16 | test/fixtures/fe 17 | 18 | # Node stuff 19 | node_modules 20 | -------------------------------------------------------------------------------- /src/script/Request.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import { 5 | request, 6 | Headers, 7 | ResponseResult, 8 | RequestResult, 9 | Response, 10 | Request, 11 | RequestClient, 12 | RequestBuilder 13 | } from "../_internal/Request.sol"; 14 | -------------------------------------------------------------------------------- /src/test/Request.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import { 5 | request, 6 | Headers, 7 | ResponseResult, 8 | RequestResult, 9 | Response, 10 | Request, 11 | RequestClient, 12 | RequestBuilder 13 | } from "../_internal/Request.sol"; 14 | -------------------------------------------------------------------------------- /test/mocks/Sender.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.13 <0.9.0; 2 | 3 | contract Sender { 4 | function get() external view returns (address) { 5 | return msg.sender; 6 | } 7 | 8 | function getWithOrigin() external view returns (address, address) { 9 | return (msg.sender, tx.origin); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/references/Gas.md: -------------------------------------------------------------------------------- 1 | # Gas 2 | 3 | ## gas 4 | 5 | 6 | 7 | ### **record(string name)** 8 | 9 | 10 | 11 | ### **stopRecord(string name) → (uint256)** 12 | 13 | 14 | 15 | ### **getRecord(string name) → (uint256, uint256)** 16 | 17 | 18 | 19 | ### **used(string name) → (uint256)** 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/src/references/Fmt.md: -------------------------------------------------------------------------------- 1 | # Fmt 2 | 3 | ## Structs 4 | 5 | ### Placeholder 6 | 7 | ```solidity 8 | struct Placeholder { 9 | uint256 start 10 | uint256 end 11 | Type t 12 | bytes mod 13 | } 14 | ``` 15 | 16 | 17 | 18 | ## fmt 19 | 20 | 21 | 22 | ### **format(string template, bytes args) → (string)** 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/src/modules/commands.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | Execute external commands. The `ffi` setting must be enabled on `foundry.toml` for this module to 4 | work. The `commands` module uses [`Results`](./results.md) when returning values. 5 | 6 | {{#include ../examples/commands/example.md}} 7 | 8 | [**Commands API reference**](../references/Commands.md) 9 | -------------------------------------------------------------------------------- /docs/src/modules/fe.md: -------------------------------------------------------------------------------- 1 | # Fe 2 | 3 | Provides [Fe](https://fe-lang.org/) compiler support. The `ffi` setting must be enabled on `foundry.toml` for this module 4 | to work. This module requires the `fe` binary installed in order to work. 5 | 6 | {{ #include ../examples/fe/example.md }} 7 | 8 | [**Fe API reference**](../references/Fe.md) 9 | 10 | -------------------------------------------------------------------------------- /docs/src/modules/huff.md: -------------------------------------------------------------------------------- 1 | # Huff 2 | 3 | Provides [Huff](https://huff.sh/) compiler support. The `ffi` setting must be enabled on `foundry.toml` for this module 4 | to work. This module requires the `huffc` binary installed in order to work. 5 | 6 | {{ #include ../examples/huff/example.md }} 7 | 8 | [**Huff API reference**](../references/Huff.md) 9 | -------------------------------------------------------------------------------- /src/script.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | // Common imports 5 | import "./_imports.sol"; 6 | 7 | import {accountsSafe as accounts} from "./_internal/Accounts.sol"; 8 | import {ctxSafe as ctx} from "./_internal/Context.sol"; 9 | 10 | contract Script { 11 | bool public IS_SCRIPT = true; 12 | } 13 | -------------------------------------------------------------------------------- /docs/src/modules/requests.md: -------------------------------------------------------------------------------- 1 | # Requests 2 | 3 | The request module is inspired by Rust's `reqwest` crate. It provides a flexible API to interact 4 | with external web services allowing to work with request headers, request authorization, response 5 | headers, parsing a response body as JSON and others. 6 | 7 | {{#include ../examples/requests/example.md}} 8 | 9 | -------------------------------------------------------------------------------- /docs/src/modules/context.md: -------------------------------------------------------------------------------- 1 | # Context 2 | 3 | Functionality to interact with the current runtime context: 4 | - Block data 5 | - Gas metering 6 | - Forge's `expectRevert`, `expectEmit` and `mockCall` (for an alternative, see 7 | [`watchers`](./watchers.md)) 8 | - Vm state snapshots 9 | 10 | {{#include ../examples/context/example.md}} 11 | 12 | [**Context API reference**](../references/Context.md) 13 | -------------------------------------------------------------------------------- /docs/src/references/rpc.md: -------------------------------------------------------------------------------- 1 | # Rpc 2 | 3 | ## rpc 4 | 5 | 6 | 7 | ### **call(string urlOrName, string method, string params) → (bytes data)** 8 | 9 | Calls an JSON-RPC method on a specific RPC endpoint. If there was a previous active fork it will return back to that one once the method is called. 10 | 11 | ### **call(string method, string params) → (bytes data)** 12 | 13 | Calls an JSON-RPC method on the current active fork 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vulcan", 3 | "version": "1.0.0", 4 | "description": "

Vulcan

", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "Nomoi", 8 | "license": "MIT", 9 | "devDependencies": { 10 | "@commitlint/cli": "^17.7.1", 11 | "@commitlint/config-conventional": "^17.7.0", 12 | "husky": "^8.0.3" 13 | }, 14 | "scripts": { 15 | "prepare": "husky install" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/examples/accounts/AccountsExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, accounts} from "vulcan/test.sol"; 5 | 6 | /// @title Create an address 7 | /// @dev How to create a simple address 8 | contract AccountsExample is Test { 9 | function test() external { 10 | address alice = accounts.create(); 11 | 12 | expect(alice).not.toEqual(address(0)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/examples/accounts/AccountsExample02.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, accounts} from "vulcan/test.sol"; 5 | 6 | /// @title Create a labeled address 7 | /// @dev Creating an address labeled as "Alice" 8 | contract AccountsExample is Test { 9 | function test() external { 10 | address alice = accounts.create("Alice"); 11 | 12 | expect(alice).not.toEqual(address(0)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/examples/accounts/AccountsExample03.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, accounts} from "vulcan/test.sol"; 5 | 6 | /// @title Create multiple addresses 7 | /// @dev Creating multiple addresses 8 | contract AccountsExample is Test { 9 | function test() external { 10 | address[] memory addresses = accounts.createMany(10); 11 | 12 | expect(addresses.length).toEqual(10); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: release-please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: google-github-actions/release-please-action@v3 17 | with: 18 | release-type: simple 19 | bump-minor-pre-major: true 20 | bump-patch-for-minor-pre-major: true 21 | -------------------------------------------------------------------------------- /test/examples/config/ConfigExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, config} from "vulcan/test.sol"; 5 | 6 | /// @title Obtain a specific RPC URL 7 | /// @dev Read a specific RPC URL from the foundry configuration 8 | contract ConfigExample is Test { 9 | function test() external { 10 | string memory key = "mainnet"; 11 | 12 | expect(config.rpcUrl(key)).toEqual("https://mainnet.rpc.io"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/examples/accounts/AccountsExample04.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, accounts} from "vulcan/test.sol"; 5 | 6 | /// @title Create multiple labeled addresses with a prefix 7 | /// @dev Creating multiple addresses labeled with the prefix `Account` 8 | contract AccountsExample is Test { 9 | function test() external { 10 | address[] memory addresses = accounts.createMany(10, "Account"); 11 | 12 | expect(addresses.length).toEqual(10); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/examples/utils/UtilsExample02.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, format} from "vulcan/test.sol"; 5 | 6 | /// @title Using format 7 | /// @dev Using the format function to format data 8 | contract FormatExample is Test { 9 | function test() external { 10 | uint256 uno = 1; 11 | 12 | string memory formatted = format("is {u} greater than 0? {bool}", abi.encode(uno, uno > 0)); 13 | 14 | expect(formatted).toEqual("is 1 greater than 0? true"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/examples/huff/HuffExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, huff, CommandOutput} from "vulcan/test.sol"; 5 | 6 | /// @title How to compile `huff` code 7 | /// @dev How to compile `huff` code using the `huff` module (Requires to have `huff` installed) 8 | contract HuffExample is Test { 9 | function test() external { 10 | CommandOutput memory result = huff.create().setFilePath("./test/mocks/Getter.huff").compile().unwrap(); 11 | expect(result.stdout.length).toBeGreaterThan(0); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/src/references/Config.md: -------------------------------------------------------------------------------- 1 | # Config 2 | 3 | ## Structs 4 | 5 | ### RpcConfig 6 | 7 | ```solidity 8 | struct RpcConfig { 9 | string name 10 | string url 11 | } 12 | ``` 13 | 14 | Struct that represents an RPC endpoint 15 | 16 | ## config 17 | 18 | 19 | 20 | ### **rpcUrl(string name) → (string)** 21 | 22 | Obtains a specific RPC from the configuration by name. 23 | 24 | ### **rpcUrls() → (string[2][])** 25 | 26 | Obtains all the RPCs from the configuration. 27 | 28 | ### **rpcUrlStructs() → (RpcConfig[] rpcs)** 29 | 30 | Obtains all the RPCs from the configuration. 31 | 32 | -------------------------------------------------------------------------------- /test/modules/Gas.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "src/test.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {gas} from "src/test/Gas.sol"; 7 | 8 | contract GasTest is Test { 9 | function testItMeasures() public { 10 | string memory name = "test"; 11 | 12 | gas.record(name); 13 | keccak256(bytes(name)); 14 | uint256 measurementValue = gas.stopRecord(name); 15 | 16 | expect(measurementValue).toBeGreaterThan(0); 17 | expect(measurementValue).toEqual(gas.used(name)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/examples/commands/CommandExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, commands, CommandResult, CommandOutput} from "vulcan/test.sol"; 5 | 6 | /// @title Run a simple command 7 | /// @dev Run a simple command and obtain the output 8 | contract RunCommandExample is Test { 9 | function test() external { 10 | // Run the command 11 | CommandOutput memory result = commands.run(["echo", "Hello, World!"]).unwrap(); 12 | 13 | // Check the output 14 | expect(string(result.stdout)).toEqual("Hello, World!"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/examples/gas/GasExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, gas} from "vulcan/test.sol"; 5 | 6 | /// @title Measuring gas 7 | /// @dev Obtain the gas cost of a operation 8 | contract GasExample is Test { 9 | function test() external { 10 | // Start recording gas usage 11 | gas.record("example"); 12 | 13 | payable(0).transfer(1e18); 14 | 15 | // Stop recording and obtain the amount of gas used 16 | uint256 used = gas.stopRecord("example"); 17 | 18 | expect(used).toBeGreaterThan(0); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | ffi = true 6 | fs_permissions = [ 7 | { access = "read", path = "./out"}, 8 | { access = "read", path = "./test/modules" }, 9 | { access = "read", path = "./test/fixtures/fs/read" }, 10 | { access = "read-write", path = "./test/fixtures/fs/write" }, 11 | { access = "read-write", path = "./test/fixtures/fe/output" } 12 | ] 13 | 14 | [rpc_endpoints] 15 | mainnet = "https://mainnet.rpc.io" 16 | arbitrum = "https://arbitrum.rpc.io" 17 | 18 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 19 | -------------------------------------------------------------------------------- /docs/src/examples/huff/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### How to compile `huff` code 3 | 4 | How to compile `huff` code using the `huff` module (Requires to have `huff` installed) 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, huff, CommandOutput} from "vulcan/test.sol"; 11 | 12 | contract HuffExample is Test { 13 | function test() external { 14 | CommandOutput memory result = huff.create().setFilePath("./test/mocks/Getter.huff").compile().unwrap(); 15 | expect(result.stdout.length).toBeGreaterThan(0); 16 | } 17 | } 18 | 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /docs/src/modules/expect.md: -------------------------------------------------------------------------------- 1 | # Expect 2 | 3 | The `Expect` module introduces the `expect` function, designed to validate if specified conditions are satisfied. 4 | Depending on the type of the input parameter, the `expect` function offers a range of matchers tailored to the 5 | context. For instance, with a string like `"Hello World"`, you can use `.toContain("Hello")`, or for numbers, 6 | `expect(1).toBeGreaterThan(0)` can be applied. This adaptability ensures precise and concise condition 7 | checking across various data types. 8 | 9 | {{#include ../examples/expect/example.md}} 10 | 11 | [**Expect API reference**](../references/Expect.md) 12 | -------------------------------------------------------------------------------- /docs/src/guide/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | In an existing Foundry project, use `forge install`: 4 | 5 | ``` 6 | $ forge install nomoixyz/vulcan 7 | ``` 8 | 9 | Or install a specific release with: 10 | 11 | ``` 12 | $ forge install nomoixyz/vulcan@x.y.z 13 | ``` 14 | 15 | Release tags can be found [here](https://github.com/nomoixyz/vulcan/releases) 16 | 17 | To reduce compatibility issues, `vulcan` utilizes a particular version of `forge-std`. To avoid such problems in your project, consider including the following in your remappings: 18 | 19 | ``` 20 | forge-std/=lib/vulcan/lib/forge-std/src/ 21 | vulcan/=lib/vulcan/src/ 22 | ``` 23 | -------------------------------------------------------------------------------- /test/examples/forks/ForksExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, vulcan} from "vulcan/test.sol"; 5 | import {forks, Fork} from "vulcan/test/Forks.sol"; 6 | import {ctx} from "vulcan/test/Context.sol"; 7 | 8 | /// @title How to use forks 9 | /// @dev How to use forks. This example assumes there is a JSON RPC server running at `localhost:8545` 10 | contract ForksExample is Test { 11 | string constant RPC_URL = "http://localhost:8545"; 12 | 13 | function test() external { 14 | forks.create(RPC_URL).select(); 15 | 16 | expect(block.chainid).toEqual(31337); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/src/examples/gas/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Measuring gas 3 | 4 | Obtain the gas cost of a operation 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, gas} from "vulcan/test.sol"; 11 | 12 | contract GasExample is Test { 13 | function test() external { 14 | // Start recording gas usage 15 | gas.record("example"); 16 | 17 | payable(0).transfer(1e18); 18 | 19 | // Stop recording and obtain the amount of gas used 20 | uint256 used = gas.stopRecord("example"); 21 | 22 | expect(used).toBeGreaterThan(0); 23 | } 24 | } 25 | 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /docs/src/references/SUMMARY.md: -------------------------------------------------------------------------------- 1 | - [Accounts](./Accounts.md) 2 | - [Commands](./Commands.md) 3 | - [Config](./Config.md) 4 | - [Context](./Context.md) 5 | - [Env](./Env.md) 6 | - [Error](./Error.md) 7 | - [Events](./Events.md) 8 | - [Expect](./Expect.md) 9 | - [Fe](./Fe.md) 10 | - [Fmt](./Fmt.md) 11 | - [Forks](./Forks.md) 12 | - [Fs](./Fs.md) 13 | - [Gas](./Gas.md) 14 | - [Huff](./Huff.md) 15 | - [Invariants](./Invariants.md) 16 | - [Json](./Json.md) 17 | - [Pointer](./Pointer.md) 18 | - [Result](./Result.md) 19 | - [Rpc](./Rpc.md) 20 | - [Semver](./Semver.md) 21 | - [Strings](./Strings.md) 22 | - [Utils](./Utils.md) 23 | - [Vulcan](./Vulcan.md) 24 | - [Watchers](./Watchers.md) 25 | -------------------------------------------------------------------------------- /test/examples/config/ConfigExample02.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, config} from "vulcan/test.sol"; 5 | 6 | /// @title Obtain all the RPC URLs 7 | /// @dev Read all the RPC URLs from the foundry configuration 8 | contract ConfigExample is Test { 9 | function test() external { 10 | string[2][] memory rpcs = config.rpcUrls(); 11 | 12 | expect(rpcs.length).toEqual(2); 13 | expect(rpcs[0][0]).toEqual("arbitrum"); 14 | expect(rpcs[0][1]).toEqual("https://arbitrum.rpc.io"); 15 | expect(rpcs[1][0]).toEqual("mainnet"); 16 | expect(rpcs[1][1]).toEqual("https://mainnet.rpc.io"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/src/examples/forks/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### How to use forks 3 | 4 | How to use forks. This example assumes there is a JSON RPC server running at `localhost:8545` 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, vulcan} from "vulcan/test.sol"; 11 | import {forks, Fork} from "vulcan/test/Forks.sol"; 12 | import {ctx} from "vulcan/test/Context.sol"; 13 | 14 | contract ForksExample is Test { 15 | string constant RPC_URL = "http://localhost:8545"; 16 | 17 | function test() external { 18 | forks.create(RPC_URL).select(); 19 | 20 | expect(block.chainid).toEqual(31337); 21 | } 22 | } 23 | 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /test/modules/Pointer.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test, expect} from "src/test.sol"; 5 | import {Pointer} from "src/_internal/Pointer.sol"; 6 | 7 | contract PointerTest is Test { 8 | function testAsBytes32(bytes32 value) external { 9 | Pointer ptr; 10 | 11 | assembly { 12 | ptr := value 13 | } 14 | 15 | expect(ptr.asBytes32()).toEqual(value); 16 | } 17 | 18 | function testAsString(string memory value) external { 19 | Pointer ptr; 20 | 21 | assembly { 22 | ptr := value 23 | } 24 | 25 | expect(ptr.asString()).toEqual(value); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/examples/utils/UtilsExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, println} from "vulcan/test.sol"; 5 | 6 | /// @title Using println 7 | /// @dev Using the println function to log formatted data 8 | contract UtilsExample is Test { 9 | function test() external view { 10 | println("Log a simple string"); 11 | 12 | string memory someString = "someString"; 13 | println("This is a string: {s}", abi.encode(someString)); 14 | 15 | uint256 aNumber = 123; 16 | println("This is a uint256: {u}", abi.encode(aNumber)); 17 | 18 | println("A string: {s} and a number: {u}", abi.encode(someString, aNumber)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/src/modules/utils.md: -------------------------------------------------------------------------------- 1 | # Utils 2 | 3 | This module provides a set of utility functions that make use of other modules in Vulcan. 4 | 5 | Some of the utility functions: 6 | - `format`: Formats a string in a similar way to rust [`format!`](https://doc.rust-lang.org/std/macro.format.html) macro. This function uses the [`fmt` module](./fmt.md) underneath meaning that all templating options from the [`fmt` module](./fmt.md) are available. 7 | - `println`: Logs a formatted string in a similar way to rust [`println!`](https://doc.rust-lang.org/std/macro.println.html) macro. This function uses 8 | the `format` function underneath 9 | 10 | {{#include ../examples/utils/example.md}} 11 | 12 | [**Utils API reference**](../references/Utils.md) 13 | -------------------------------------------------------------------------------- /test/examples/fe/FeExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, fe, Fe} from "vulcan/test.sol"; 5 | 6 | /// @title How to compile `fe` code 7 | /// @dev How to compile `fe` using the `fe` module (Requires to have `fe` installed) 8 | contract FeExample is Test { 9 | function test() external { 10 | Fe memory feCmd = fe.create().setFilePath("./test/mocks/guest_book.fe").setOverwrite(true); 11 | 12 | // Compile the bytecode and revert if there is an error 13 | feCmd.build().unwrap(); 14 | 15 | bytes memory bytecode = feCmd.getBytecode("MyFeContract").toValue(); 16 | 17 | expect(bytecode).toEqual("600180600c6000396000f3fe00"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/examples/config/ConfigExample03.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, config, RpcConfig} from "vulcan/test.sol"; 5 | 6 | /// @title Obtain all the RPC URLs using structs 7 | /// @dev Read all the RPC URL from the foundry configuration as structs 8 | contract ConfigExample is Test { 9 | function test() external { 10 | RpcConfig[] memory rpcs = config.rpcUrlStructs(); 11 | 12 | expect(rpcs.length).toEqual(2); 13 | expect(rpcs[0].name).toEqual("arbitrum"); 14 | expect(rpcs[0].url).toEqual("https://arbitrum.rpc.io"); 15 | expect(rpcs[1].name).toEqual("mainnet"); 16 | expect(rpcs[1].url).toEqual("https://mainnet.rpc.io"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/modules/Rpc.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test, expect} from "vulcan/test.sol"; 5 | import {rpc} from "vulcan/test/Rpc.sol"; 6 | import {Fork, forks} from "vulcan/test/Forks.sol"; 7 | 8 | contract RpcTest is Test { 9 | function testNetVersion() external { 10 | string memory rpcUrl = "https://rpc.mevblocker.io/fast"; 11 | string memory method = "eth_chainId"; 12 | string memory params = "[]"; 13 | 14 | bytes memory data = rpc.call(rpcUrl, method, params); 15 | 16 | uint8 chainId; 17 | 18 | assembly { 19 | chainId := mload(add(data, 0x01)) 20 | } 21 | 22 | expect(chainId).toEqual(block.chainid); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/src/references/Vulcan.md: -------------------------------------------------------------------------------- 1 | # Vulcan 2 | 3 | ## Structs 4 | 5 | ### Log 6 | 7 | ```solidity 8 | struct Log { 9 | bytes32[] topics 10 | bytes data 11 | address emitter 12 | } 13 | ``` 14 | 15 | Struct that represent an EVM log 16 | 17 | ## vulcan 18 | 19 | 20 | 21 | ### **init()** 22 | 23 | Initializes the context module 24 | 25 | ### **failed() → (bool)** 26 | 27 | Checks if `fail` was called at some point. 28 | 29 | ### **fail()** 30 | 31 | Signal that an expectation/assertion failed. 32 | 33 | ### **clearFailure()** 34 | 35 | Resets the failed state. 36 | 37 | ### **watch(address _target) → (Watcher)** 38 | 39 | Starts monitoring an address. 40 | 41 | ### **stopWatcher(address _target)** 42 | 43 | Stops monitoring an address. 44 | 45 | -------------------------------------------------------------------------------- /docs/src/examples/fe/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### How to compile `fe` code 3 | 4 | How to compile `fe` using the `fe` module (Requires to have `fe` installed) 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, fe, Fe} from "vulcan/test.sol"; 11 | 12 | contract FeExample is Test { 13 | function test() external { 14 | Fe memory feCmd = fe.create().setFilePath("./test/mocks/guest_book.fe").setOverwrite(true); 15 | 16 | // Compile the bytecode and revert if there is an error 17 | feCmd.build().unwrap(); 18 | 19 | bytes memory bytecode = feCmd.getBytecode("MyFeContract").toValue(); 20 | 21 | expect(bytecode).toEqual("600180600c6000396000f3fe00"); 22 | } 23 | } 24 | 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /test/examples/format/FormatExample02.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, accounts, expect, fmt} from "vulcan/test.sol"; 5 | 6 | /// @title Formatting decimals 7 | /// @dev Use the `{uint:dx}` placeholder to format numbers with decimals 8 | contract FormatExample is Test { 9 | using accounts for address; 10 | 11 | function test() external { 12 | address target = address(1).setBalance(1e17); 13 | uint256 balance = target.balance; 14 | 15 | // Store it as a string 16 | string memory result = fmt.format("The account {address} has {uint:d18} eth", abi.encode(target, balance)); 17 | 18 | expect(result).toEqual("The account 0x0000000000000000000000000000000000000001 has 0.1 eth"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/examples/accounts/AccountsExample05.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, accounts} from "vulcan/test.sol"; 5 | 6 | /// @title Use method chaining on addresses 7 | /// @dev Use method chaining on addresses to call multiple methods 8 | contract AccountsExample05 is Test { 9 | using accounts for address; 10 | 11 | function test() external { 12 | address alice = accounts.create("Alice").setNonce(666).setBalance(100e18); 13 | 14 | address bob = accounts.create("Bob").setBalance(10e18).impersonateOnce(); 15 | 16 | payable(alice).transfer(bob.balance); 17 | 18 | expect(alice.balance).toEqual(110e18); 19 | expect(alice.getNonce()).toEqual(666); 20 | expect(bob.balance).toEqual(0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/examples/requests/RequestsExample02.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, request, Response, RequestClient, JsonObject} from "vulcan/test.sol"; 5 | 6 | /// @title Sending a JSON payload 7 | /// @dev How to send a request with a JSON body 8 | contract RequestExample is Test { 9 | function test() external { 10 | // Create a request client 11 | RequestClient memory client = request.create(); 12 | 13 | Response memory jsonRes = client.post("https://httpbin.org/post").json('{ "foo": "bar" }').send().unwrap(); 14 | 15 | expect(jsonRes.status).toEqual(200); 16 | 17 | JsonObject memory responseBody = jsonRes.json().unwrap(); 18 | 19 | expect(responseBody.getString(".json.foo")).toEqual("bar"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/examples/json/JSONExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, json, JsonObject} from "vulcan/test.sol"; 5 | 6 | /// @title Work with JSON objects 7 | /// @dev Create a JSON object, populate it and read it 8 | contract JSONExample is Test { 9 | function test() external { 10 | // Create an empty JsonObject 11 | JsonObject memory obj = json.create(); 12 | 13 | string memory key = "foo"; 14 | string memory value = "bar"; 15 | 16 | obj.set(key, value); 17 | 18 | expect(obj.getString(".foo")).toEqual(value); 19 | 20 | // Create a populated JsonObject 21 | obj = json.create("{ \"foo\": { \"bar\": \"baz\" } }").unwrap(); 22 | 23 | expect(obj.getString(".foo.bar")).toEqual("baz"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/src/modules/fmt.md: -------------------------------------------------------------------------------- 1 | # Format 2 | 3 | The format function defined under the `fmt` module enables you to format strings dynamically by using a template string and the ABI encoded arguments. 4 | 5 | The accepted placeholders are: 6 | - `{address} or {a}` for the `address` type. 7 | - `{bytes32} or {b32}` for the `bytes32` type. 8 | - `{string} or {s}` for the `string` type. 9 | - `{bytes} or {b}` for the `bytes` type. 10 | - `{uint} or {u}` for the `uint256` type. 11 | - `{int} or {i}` for the `int256` type. 12 | - `{bool}` for the `bool` type. 13 | 14 | For the `uint256` type there is a special placeholder to deal with decimals `{u:dx}` where `x` is 15 | the number of decimals, for example `{u:d18}` to format numbers with `18` decimals. 16 | 17 | {{#include ../examples/format/example.md}} 18 | 19 | [**Format API reference**](../references/Fmt.md) 20 | -------------------------------------------------------------------------------- /test/examples/requests/RequestsExample03.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, request, Response, RequestClient} from "vulcan/test.sol"; 5 | 6 | /// @title Request authentication 7 | /// @dev How to use different methods of authentication 8 | contract RequestExample is Test { 9 | function test() external { 10 | RequestClient memory client = request.create(); 11 | 12 | Response memory basicAuthRes = 13 | client.get("https://httpbin.org/basic-auth/user/pass").basicAuth("user", "pass").send().unwrap(); 14 | 15 | expect(basicAuthRes.status).toEqual(200); 16 | 17 | Response memory bearerAuthRes = client.get("https://httpbin.org/bearer").bearerAuth("token").send().unwrap(); 18 | 19 | expect(bearerAuthRes.status).toEqual(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/examples/rpc/RpcExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect} from "vulcan/test.sol"; 5 | import {rpc} from "vulcan/test/Rpc.sol"; 6 | import {Fork, forks} from "vulcan/test/Forks.sol"; 7 | 8 | /// @title Calling an RPC 9 | /// @dev Calling an rpc using the `eth_chainId` method 10 | contract RpcTest is Test { 11 | function testNetVersion() external { 12 | forks.create("https://rpc.mevblocker.io/fast").select(); 13 | 14 | string memory method = "eth_chainId"; 15 | string memory params = "[]"; 16 | 17 | bytes memory data = rpc.call(method, params); 18 | 19 | uint8 chainId; 20 | 21 | assembly { 22 | chainId := mload(add(data, 0x01)) 23 | } 24 | 25 | expect(chainId).toEqual(block.chainid); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:mantic 2 | 3 | RUN apt update &&\ 4 | apt install -y\ 5 | git\ 6 | curl 7 | 8 | WORKDIR /home/ubuntu/vulcan 9 | 10 | COPY .gitmodules foundry.toml remappings.txt ./ 11 | COPY src/ ./src 12 | COPY test/ ./test 13 | COPY lib/ ./lib 14 | 15 | RUN chown ubuntu:ubuntu -R . 16 | 17 | USER ubuntu 18 | 19 | ENV PATH "${PATH}:/home/ubuntu/.local/bin:/home/ubuntu/.foundry/bin:/home/ubuntu/.huff/bin" 20 | 21 | RUN git init && git add . 22 | RUN curl -L https://foundry.paradigm.xyz | bash 23 | RUN curl -L get.huff.sh | bash 24 | RUN foundryup 25 | RUN huffup 26 | RUN forge install --no-commit 27 | RUN mkdir -p /home/ubuntu/.local/bin && curl -o /home/ubuntu/.local/bin/fe -L https://github.com/ethereum/fe/releases/download/v0.24.0/fe_amd64 28 | RUN chmod +x /home/ubuntu/.local/bin/fe 29 | 30 | ENTRYPOINT [ "forge", "test" ] 31 | 32 | -------------------------------------------------------------------------------- /test/examples/commands/CommandExample02.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, commands, Command, CommandResult, CommandOutput} from "vulcan/test.sol"; 5 | 6 | /// @title Reuse a command 7 | /// @dev Reuse a command with different arguments 8 | contract ReuseACommandExample is Test { 9 | function test() external { 10 | // Create a command 11 | Command memory echo = commands.create("echo"); 12 | 13 | // Run the commands and unwrap the results 14 | CommandOutput memory fooOutput = echo.arg("foo").run().unwrap(); 15 | CommandOutput memory barOutput = echo.arg("bar").run().unwrap(); 16 | 17 | // Check the outputs 18 | expect(string(fooOutput.stdout)).toEqual("foo"); 19 | expect(string(barOutput.stdout)).toEqual("bar"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/modules/Env.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test} from "../../src/test.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {json} from "src/test/Json.sol"; 7 | import {vulcan} from "src/test/Vulcan.sol"; 8 | import {env} from "src/test/Env.sol"; 9 | 10 | contract EnvTest is Test { 11 | using vulcan for *; 12 | 13 | function testSet() external { 14 | env.set("foo", "123"); 15 | expect(env.getString("foo")).toEqual("123"); 16 | expect(env.getUint("foo")).toEqual(123); 17 | } 18 | 19 | function testGetters() external { 20 | env.set("foo", "bar"); 21 | // expect(env.getString("foo")).toEqual("bar"); 22 | // expect(env.getUint("foo")).toEqual(0); 23 | // expect(env.getBool("foo")).toEqual(false); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/src/examples/json/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Work with JSON objects 3 | 4 | Create a JSON object, populate it and read it 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, json, JsonObject} from "vulcan/test.sol"; 11 | 12 | contract JSONExample is Test { 13 | function test() external { 14 | // Create an empty JsonObject 15 | JsonObject memory obj = json.create(); 16 | 17 | string memory key = "foo"; 18 | string memory value = "bar"; 19 | 20 | obj.set(key, value); 21 | 22 | expect(obj.getString(".foo")).toEqual(value); 23 | 24 | // Create a populated JsonObject 25 | obj = json.create("{ \"foo\": { \"bar\": \"baz\" } }").unwrap(); 26 | 27 | expect(obj.getString(".foo.bar")).toEqual("baz"); 28 | } 29 | } 30 | 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /docs/src/references/Utils.md: -------------------------------------------------------------------------------- 1 | # Utils 2 | 3 | ## Functions 4 | 5 | ### **bound(uint256 x, uint256 min, uint256 max) → (uint256 result)** 6 | 7 | 8 | 9 | ### **bound(int256 x, int256 min, int256 max) → (int256 result)** 10 | 11 | 12 | 13 | ### **abs(int256 a) → (uint256)** 14 | 15 | 16 | 17 | ### **delta(uint256 a, uint256 b) → (uint256)** 18 | 19 | 20 | 21 | ### **delta(int256 a, int256 b) → (uint256)** 22 | 23 | 24 | 25 | ### **format(string template, bytes args) → (string)** 26 | 27 | 28 | 29 | ### **formatError(string module, string func, string message) → (string)** 30 | 31 | 32 | 33 | ### **println(string template, bytes args)** 34 | 35 | 36 | 37 | ### **println(string arg)** 38 | 39 | 40 | 41 | ### **rawConsoleLog(string arg)** 42 | 43 | 44 | 45 | ### **removeSelector(bytes data) → (bytes)** 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/src/examples/rpc/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Calling an RPC 3 | 4 | Calling an rpc using the `eth_chainId` method 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect} from "vulcan/test.sol"; 11 | import {rpc} from "vulcan/test/Rpc.sol"; 12 | import {Fork, forks} from "vulcan/test/Forks.sol"; 13 | 14 | contract RpcTest is Test { 15 | function testNetVersion() external { 16 | forks.create("https://rpc.mevblocker.io/fast").select(); 17 | 18 | string memory method = "eth_chainId"; 19 | string memory params = "[]"; 20 | 21 | bytes memory data = rpc.call(method, params); 22 | 23 | uint8 chainId; 24 | 25 | assembly { 26 | chainId := mload(add(data, 0x01)) 27 | } 28 | 29 | expect(chainId).toEqual(block.chainid); 30 | } 31 | } 32 | 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /docs/src/examples/console/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Log values 3 | 4 | Use the `console` function to log values. 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, console} from "vulcan/test.sol"; 11 | 12 | contract ConsoleExample is Test { 13 | function test() external pure { 14 | string memory foo = "foo"; 15 | string memory bar = "bar"; 16 | 17 | uint256 oneTwoThree = 123; 18 | uint256 threeTwoOne = 321; 19 | 20 | bool isTrue = true; 21 | 22 | console.log(foo); 23 | console.log(foo, bar); 24 | console.log(foo, bar, threeTwoOne); 25 | console.log(foo, bar, threeTwoOne, isTrue); 26 | console.log(threeTwoOne, oneTwoThree); 27 | console.log(threeTwoOne + oneTwoThree); 28 | console.log(1 > 0); 29 | } 30 | } 31 | 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /test/examples/console/ConsoleExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, console} from "vulcan/test.sol"; 5 | 6 | /// @title Log values 7 | /// @dev Use the `console` function to log values. 8 | /// NOTE: Prefer `println` over `console.log` for a more flexible API. 9 | contract ConsoleExample is Test { 10 | function test() external pure { 11 | string memory foo = "foo"; 12 | string memory bar = "bar"; 13 | 14 | uint256 oneTwoThree = 123; 15 | uint256 threeTwoOne = 321; 16 | 17 | bool isTrue = true; 18 | 19 | console.log(foo); 20 | console.log(foo, bar); 21 | console.log(foo, bar, threeTwoOne); 22 | console.log(foo, bar, threeTwoOne, isTrue); 23 | console.log(threeTwoOne, oneTwoThree); 24 | console.log(threeTwoOne + oneTwoThree); 25 | console.log(1 > 0); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/examples/events/EventsExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, events, Log} from "vulcan/test.sol"; 5 | 6 | /// @title Logging events 7 | /// @dev Logging events and reading events topics and data 8 | contract EventsExample is Test { 9 | using events for *; 10 | 11 | event SomeEvent(uint256 indexed a, address b); 12 | 13 | function run() external { 14 | uint256 a = 666; 15 | address b = address(333); 16 | 17 | events.recordLogs(); 18 | 19 | emit SomeEvent(a, b); 20 | 21 | Log[] memory logs = events.getRecordedLogs(); 22 | 23 | expect(logs.length).toEqual(1); 24 | expect(logs[0].emitter).toEqual(address(this)); 25 | expect(logs[0].topics[0]).toEqual(SomeEvent.selector); 26 | expect(logs[0].topics[1]).toEqual(a.topic()); 27 | expect(logs[0].data).toEqual(abi.encode(b)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/examples/fs/FsExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, fs, BytesResult, StringResult} from "vulcan/test.sol"; 5 | 6 | /// @title Reading files 7 | /// @dev Read files as string or bytes 8 | contract FsExample is Test { 9 | // These files are available only on the context of vulcan 10 | // You will need to provide your own files and edit the read permissions accordingly 11 | string constant HELLO_WORLD = "./test/fixtures/fs/read/hello_world.txt"; 12 | string constant BINARY_TEST_FILE = "./test/fixtures/fs/write/test_binary.txt"; 13 | 14 | function test() external { 15 | string memory stringResult = fs.readFile(HELLO_WORLD).unwrap(); 16 | expect(stringResult).toEqual("Hello, World!\n"); 17 | 18 | bytes memory bytesResult = fs.readFileBinary(HELLO_WORLD).unwrap(); 19 | expect(bytesResult).toEqual("Hello, World!\n"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/src/modules/results.md: -------------------------------------------------------------------------------- 1 | # Results \& Errors 2 | 3 | The concept of "Results" is inspired by Rust. It centers around using specific types for returning values while also offering mechanisms to handle any potential errors. 4 | 5 | Similar to Rust's Results API, Vulcan implements the following functions for all result types: 6 | - `unwrap()`: if the Result is an Error, reverts with the default error message. If if is `Ok`, it returns the underlying value 7 | - `expect(message)`: same as `unwrap()`, but reverts with the provided error message 8 | - `isError()`: returns `true` if the `Result` is an error, `false` otherwise 9 | - `isOk()`: the oposite of `isError()` 10 | - `toError()`: transforms the Result into an `Error` 11 | - `toValue()`: gets the Result's underlying value (if the Result is Ok) 12 | 13 | {{#include ../examples/results/example.md}} 14 | 15 | [**Results API reference**](../references/Result.md) 16 | 17 | [**Errors API reference**](../references/Error.md) 18 | -------------------------------------------------------------------------------- /test/examples/context/ContextExample02.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, ctx} from "vulcan/test.sol"; 5 | 6 | /// @title Modify chain parameters using method chaining 7 | /// @dev Use the context module to modify chain parameters using method chaining 8 | contract ContextExample is Test { 9 | function test() external { 10 | ctx.setBlockTimestamp(1).setBlockNumber(123).setBlockBaseFee(99999).setBlockPrevrandao(bytes32(uint256(123))) 11 | .setChainId(666).setBlockCoinbase(address(1)).setGasPrice(1e18); 12 | 13 | expect(block.timestamp).toEqual(1); 14 | expect(block.number).toEqual(123); 15 | expect(block.basefee).toEqual(99999); 16 | expect(block.prevrandao).toEqual(uint256(bytes32(uint256(123)))); 17 | expect(block.chainid).toEqual(666); 18 | expect(block.coinbase).toEqual(address(1)); 19 | expect(tx.gasprice).toEqual(1e18); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/forge.yml: -------------------------------------------------------------------------------- 1 | name: Forge 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | FOUNDRY_PROFILE: ci 11 | 12 | jobs: 13 | test: 14 | name: Run tests 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | submodules: recursive 20 | - name: Build image 21 | run: docker build . -t nomoixyz/vulcan-test-suite 22 | - name: Run tests 23 | run: docker run --rm nomoixyz/vulcan-test-suite --no-match-path 'test/examples/**/*' 24 | format: 25 | name: Check format 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v3 29 | 30 | - name: Install Foundry 31 | uses: onbjerg/foundry-toolchain@v1 32 | with: 33 | version: nightly 34 | 35 | - name: Print forge version 36 | run: forge --version 37 | 38 | - name: Check formatting 39 | run: forge fmt --check 40 | -------------------------------------------------------------------------------- /docs/src/examples/events/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Logging events 3 | 4 | Logging events and reading events topics and data 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, events, Log} from "vulcan/test.sol"; 11 | 12 | contract EventsExample is Test { 13 | using events for *; 14 | 15 | event SomeEvent(uint256 indexed a, address b); 16 | 17 | function run() external { 18 | uint256 a = 666; 19 | address b = address(333); 20 | 21 | events.recordLogs(); 22 | 23 | emit SomeEvent(a, b); 24 | 25 | Log[] memory logs = events.getRecordedLogs(); 26 | 27 | expect(logs.length).toEqual(1); 28 | expect(logs[0].emitter).toEqual(address(this)); 29 | expect(logs[0].topics[0]).toEqual(SomeEvent.selector); 30 | expect(logs[0].topics[1]).toEqual(a.topic()); 31 | expect(logs[0].data).toEqual(abi.encode(b)); 32 | } 33 | } 34 | 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /docs/src/modules/README.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | The VM functionality and additional utilities are organized in modules, which are imported either through `vulcan/test.sol` or `vulcan/script.sol`. 4 | 5 | Each module can contain "safe" functions which are available in the imported module for both tests and scripts, and "unsafe" functions that are only meant to be used in tests. Unsafe functions can also be used in a script if needed (when connecting to an appropriate node) by importing the version of the module suffixed with `Unsafe`. 6 | 7 | ```solidity 8 | /** 9 | * The accounts module will contain all safe and unsafe functions when 10 | * imported from `vulcan/test.sol` 11 | */ 12 | import { Test, accounts } from "vulcan/test.sol"; 13 | 14 | /** 15 | * The accounts module will only contain safe functions when imported 16 | * from `vulcan/script.sol`, and unsafe functions can be accessed by 17 | * importing the `unsafe` version of the module. 18 | */ 19 | import { Test, accounts, accountsUnsafe } from "vulcan/script.sol"; 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/src/references/Semver.md: -------------------------------------------------------------------------------- 1 | # Semver 2 | 3 | ## Structs 4 | 5 | ### Semver 6 | 7 | ```solidity 8 | struct Semver { 9 | uint256 major 10 | uint256 minor 11 | uint256 patch 12 | } 13 | ``` 14 | 15 | 16 | 17 | ## semver 18 | 19 | 20 | 21 | ### **create(uint256 major, uint256 minor, uint256 patch) → (Semver)** 22 | 23 | 24 | 25 | ### **create(uint256 major, uint256 minor) → (Semver)** 26 | 27 | 28 | 29 | ### **create(uint256 major) → (Semver)** 30 | 31 | 32 | 33 | ### **parse(string input) → (Semver)** 34 | 35 | 36 | 37 | ### **toString(Semver self) → (string)** 38 | 39 | 40 | 41 | ### **equals(Semver self, Semver other) → (bool)** 42 | 43 | 44 | 45 | ### **greaterThan(Semver self, Semver other) → (bool)** 46 | 47 | 48 | 49 | ### **greaterThanOrEqual(Semver self, Semver other) → (bool)** 50 | 51 | 52 | 53 | ### **lessThan(Semver self, Semver other) → (bool)** 54 | 55 | 56 | 57 | ### **lessThanOrEqual(Semver self, Semver other) → (bool)** 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /test/mocks/Getter.huff: -------------------------------------------------------------------------------- 1 | #define constant SLOT = FREE_STORAGE_POINTER() 2 | 3 | #define macro SET() = takes (0) returns (0) { 4 | 0x04 // [value_offset] 5 | calldataload // [value] 6 | [SLOT] // [slot, value] 7 | sstore // [] 8 | } 9 | 10 | #define macro GET() = takes (0) returns (0) { 11 | [SLOT] // [slot] 12 | sload // [value] 13 | 0x00 // [offset, value] 14 | mload // [] 15 | 0x20 // [size] 16 | 0x00 // [offset, size] 17 | return // [] 18 | } 19 | 20 | #define macro MAIN() = takes (0) returns (0) { 21 | 0x00 // [offset] 22 | calldataload // [value] 23 | 0xe0 // [shift, value] 24 | shr // [selector] 25 | dup1 // [selector, selector] 26 | 0x60fe47b1 // [setselector, selector, selector] 27 | eq // [issetselector, selector] 28 | set // [jumpdest, issetselector selector] 29 | jumpi // [selector] 30 | 0x6d4ce63c // [getselector, selector] 31 | eq // [isgetselector] 32 | get // [jumpdest, isgetselector] 33 | jumpi // [] 34 | 35 | set: 36 | SET() 37 | get: 38 | GET() 39 | } 40 | -------------------------------------------------------------------------------- /test/examples/env/EnvExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, env} from "vulcan/test.sol"; 5 | 6 | /// @title Set and get environment variables 7 | /// @dev Use the `env` module to set and read environment variables 8 | contract EnvExample is Test { 9 | function test() external { 10 | env.set("SomeString", "foo"); 11 | env.set("SomeUint", "100000000000000000000000"); 12 | env.set("SomeBool", "true"); 13 | env.set("SomeArray", "1,2,3,4"); 14 | 15 | expect(env.getString("SomeString")).toEqual("foo"); 16 | expect(env.getUint("SomeUint")).toEqual(100_000_000_000_000_000_000_000); 17 | expect(env.getBool("SomeBool")).toBeTrue(); 18 | expect(env.getUintArray("SomeArray", ",")[0]).toEqual(1); 19 | expect(env.getUintArray("SomeArray", ",")[1]).toEqual(2); 20 | expect(env.getUintArray("SomeArray", ",")[2]).toEqual(3); 21 | expect(env.getUintArray("SomeArray", ",")[3]).toEqual(4); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2023 nomoi.xyz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/examples/context/ContextExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, ctx} from "vulcan/test.sol"; 5 | 6 | /// @title Modify chain parameters 7 | /// @dev Use the context module to modify chain parameters 8 | contract ContextExample is Test { 9 | function test() external { 10 | ctx.setBlockTimestamp(1); 11 | expect(block.timestamp).toEqual(1); 12 | 13 | ctx.setBlockNumber(123); 14 | expect(block.number).toEqual(123); 15 | 16 | ctx.setBlockBaseFee(99999); 17 | expect(block.basefee).toEqual(99999); 18 | 19 | ctx.setBlockPrevrandao(bytes32(uint256(123))); 20 | expect(block.prevrandao).toEqual(uint256(bytes32(uint256(123)))); 21 | 22 | ctx.setChainId(666); 23 | expect(block.chainid).toEqual(666); 24 | 25 | ctx.setBlockCoinbase(address(1)); 26 | expect(block.coinbase).toEqual(address(1)); 27 | 28 | ctx.setGasPrice(1e18); 29 | expect(tx.gasprice).toEqual(1e18); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/src/examples/env/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Set and get environment variables 3 | 4 | Use the `env` module to set and read environment variables 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, env} from "vulcan/test.sol"; 11 | 12 | contract EnvExample is Test { 13 | function test() external { 14 | env.set("SomeString", "foo"); 15 | env.set("SomeUint", "100000000000000000000000"); 16 | env.set("SomeBool", "true"); 17 | env.set("SomeArray", "1,2,3,4"); 18 | 19 | expect(env.getString("SomeString")).toEqual("foo"); 20 | expect(env.getUint("SomeUint")).toEqual(100_000_000_000_000_000_000_000); 21 | expect(env.getBool("SomeBool")).toBeTrue(); 22 | expect(env.getUintArray("SomeArray", ",")[0]).toEqual(1); 23 | expect(env.getUintArray("SomeArray", ",")[1]).toEqual(2); 24 | expect(env.getUintArray("SomeArray", ",")[2]).toEqual(3); 25 | expect(env.getUintArray("SomeArray", ",")[3]).toEqual(4); 26 | } 27 | } 28 | 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - 'docs/**' 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - name: Install mdbook 17 | run: | 18 | mkdir mdbook 19 | curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.14/mdbook-v0.4.14-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook 20 | echo `pwd`/mdbook >> $GITHUB_PATH 21 | - name: Deploy GitHub Pages 22 | run: | 23 | cd docs 24 | mdbook build 25 | git worktree add gh-pages 26 | git config user.name "Deploy from CI" 27 | git config user.email "" 28 | cd gh-pages 29 | # Delete the ref to avoid keeping history. 30 | git update-ref -d refs/heads/gh-pages 31 | rm -rf * 32 | mv ../book/* . 33 | git add . 34 | git commit -m "Deploy $GITHUB_SHA to gh-pages" 35 | git push --force --set-upstream origin gh-pages -------------------------------------------------------------------------------- /docs/src/references/Events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | ## events 4 | 5 | 6 | 7 | ### **topic(uint256 _param) → (bytes32)** 8 | 9 | Obtains the topic representation of an `uint256` parameter. 10 | 11 | ### **topic(string _param) → (bytes32)** 12 | 13 | Obtains the topic representation of a `string` parameter. 14 | 15 | ### **topic(address _param) → (bytes32)** 16 | 17 | Obtains the topic representation of an `address` parameter. 18 | 19 | ### **topic(bytes32 _param) → (bytes32)** 20 | 21 | Obtains the topic representation of a `bytes32` parameter. 22 | 23 | ### **topic(bytes _param) → (bytes32)** 24 | 25 | Obtains the topic representation of a `bytes` parameter. 26 | 27 | ### **topic(bool _param) → (bytes32)** 28 | 29 | Obtains the topic representation of a `bool` parameter. 30 | 31 | ### **topic(int256 _param) → (bytes32)** 32 | 33 | Obtains the topic representation of a `int256` parameter. 34 | 35 | ### **recordLogs()** 36 | 37 | Starts recording all transactions logs. 38 | 39 | ### **getRecordedLogs() → (Log[] logs)** 40 | 41 | Obtains all recorded transactions logs. 42 | 43 | -------------------------------------------------------------------------------- /test/examples/results/ResultsExample02.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, commands, ctx, expect, CommandResult, CommandError} from "vulcan/test.sol"; 5 | 6 | /// @title Working with Errors 7 | /// @dev Different ways of handling errors. 8 | contract ResultExample is Test { 9 | function test() external { 10 | // Run a non existent command 11 | CommandResult result = commands.run(["asdf12897u391723"]); 12 | 13 | ctx.expectRevert(); 14 | 15 | // Use unwrap to revert with the default error message 16 | result.unwrap(); 17 | 18 | ctx.expectRevert("Command not executed"); 19 | 20 | // Use expect to revert with a custom error message 21 | result.expect("Command not executed"); 22 | 23 | bool failed = false; 24 | 25 | // Handle the error manually 26 | if (result.isError()) { 27 | if (result.toError().matches(CommandError.NotExecuted)) { 28 | failed = true; 29 | } 30 | } 31 | 32 | expect(failed).toBeTrue(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/examples/strings/StringsExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, strings} from "vulcan/test.sol"; 5 | 6 | /// @title Transforming and parsing 7 | /// @dev Transform values to strings and parse strings to values 8 | contract StringsExample is Test { 9 | using strings for *; 10 | 11 | function test() external { 12 | uint256 uintValue = 123; 13 | string memory uintString = uintValue.toString(); 14 | expect(uintString).toEqual("123"); 15 | expect(uintString.parseUint()).toEqual(uintValue); 16 | 17 | bool boolValue = true; 18 | string memory boolString = boolValue.toString(); 19 | expect(boolString).toEqual("true"); 20 | expect(boolString.parseBool()).toEqual(true); 21 | 22 | bytes32 bytes32Value = bytes32(uintValue); 23 | string memory bytes32String = bytes32Value.toString(); 24 | expect(bytes32String).toEqual("0x000000000000000000000000000000000000000000000000000000000000007b"); 25 | expect(bytes32String.parseBytes32()).toEqual(bytes32Value); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/examples/format/FormatExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, accounts, expect, fmt, println} from "vulcan/test.sol"; 5 | 6 | /// @title Using templates 7 | /// @dev Using templates with the `format` module to format data 8 | contract FormatExample is Test { 9 | using accounts for address; 10 | 11 | function test() external { 12 | address target = address(1).setBalance(1); 13 | uint256 balance = target.balance; 14 | 15 | // Store it as a string 16 | // NOTE: The {address} and {uint} placeholders can be abbreviated as {a} and {u} 17 | // For available placeholders and abbreviations see: TODO 18 | string memory result = fmt.format("The account {address} has {uint} wei", abi.encode(target, balance)); 19 | 20 | expect(result).toEqual("The account 0x0000000000000000000000000000000000000001 has 1 wei"); 21 | 22 | // Format is also used internally by Vulcan's println, which you can use as an alternative to console.log 23 | println("The account {address} has {uint} wei", abi.encode(target, balance)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/examples/results/ResultsExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, StringResult} from "vulcan/test.sol"; 5 | // This import is just to demonstration, it's not meant to be imported on projects using Vulcan 6 | import {Ok} from "vulcan/_internal/Result.sol"; 7 | 8 | /// @title Working with result values 9 | /// @dev Different methods of getting the underlyng value of a `Result` 10 | contract ResultExample is Test { 11 | function test() external { 12 | StringResult result = Ok(string("foo")); 13 | 14 | // Use unwrap to get the value or revert if the result is an `Error` 15 | string memory value = result.unwrap(); 16 | expect(value).toEqual("foo"); 17 | 18 | // Use expect to get the value or revert with a custom message if 19 | // the result is an `Error` 20 | value = result.expect("Result failed"); 21 | expect(value).toEqual("foo"); 22 | 23 | // Safely handling the result 24 | if (result.isOk()) { 25 | value = result.toValue(); 26 | expect(value).toEqual("foo"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/src/examples/strings/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Transforming and parsing 3 | 4 | Transform values to strings and parse strings to values 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, strings} from "vulcan/test.sol"; 11 | 12 | contract StringsExample is Test { 13 | using strings for *; 14 | 15 | function test() external { 16 | uint256 uintValue = 123; 17 | string memory uintString = uintValue.toString(); 18 | expect(uintString).toEqual("123"); 19 | expect(uintString.parseUint()).toEqual(uintValue); 20 | 21 | bool boolValue = true; 22 | string memory boolString = boolValue.toString(); 23 | expect(boolString).toEqual("true"); 24 | expect(boolString.parseBool()).toEqual(true); 25 | 26 | bytes32 bytes32Value = bytes32(uintValue); 27 | string memory bytes32String = bytes32Value.toString(); 28 | expect(bytes32String).toEqual("0x000000000000000000000000000000000000000000000000000000000000007b"); 29 | expect(bytes32String.parseBytes32()).toEqual(bytes32Value); 30 | } 31 | } 32 | 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /test/examples/requests/RequestsExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, request, Response} from "vulcan/test.sol"; 5 | 6 | /// @title Sending requests 7 | /// @dev How to send requests to an http server 8 | contract RequestExample is Test { 9 | function test() external { 10 | Response memory getResponse = request.create().get("https://httpbin.org/get").send().unwrap(); 11 | Response memory postResponse = request.create().post("https://httpbin.org/post").send().unwrap(); 12 | Response memory patchResponse = request.create().patch("https://httpbin.org/patch").send().unwrap(); 13 | Response memory putResponse = request.create().put("https://httpbin.org/put").send().unwrap(); 14 | Response memory deleteResponse = request.create().del("https://httpbin.org/delete").send().unwrap(); 15 | 16 | expect(getResponse.status).toEqual(200); 17 | expect(postResponse.status).toEqual(200); 18 | expect(patchResponse.status).toEqual(200); 19 | expect(putResponse.status).toEqual(200); 20 | expect(deleteResponse.status).toEqual(200); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/examples/fs/FsExample03.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test} from "vulcan/test.sol"; 5 | import {expect} from "vulcan/test/Expect.sol"; 6 | import {fs, FsMetadata} from "vulcan/test/Fs.sol"; 7 | import {BoolResult} from "vulcan/test/Result.sol"; 8 | 9 | /// @title Other operations 10 | /// @dev Obtain metadata and check if file exists 11 | contract FsExample is Test { 12 | // These files are available only on the context of vulcan 13 | // You will need to provide your own files and edit the read permissions accordingly 14 | string constant READ_EXAMPLE = "./test/fixtures/fs/read/hello_world.txt"; 15 | string constant NOT_FOUND_EXAMPLE = "./test/fixtures/fs/read/lkjjsadflkjasdf.txt"; 16 | 17 | function test() external { 18 | FsMetadata memory metadata = fs.metadata(READ_EXAMPLE).expect("Failed to get metadata"); 19 | expect(metadata.isDir).toBeFalse(); 20 | 21 | bool exists = fs.fileExists(READ_EXAMPLE).unwrap(); 22 | expect(exists).toBeTrue(); 23 | 24 | exists = fs.fileExists(NOT_FOUND_EXAMPLE).unwrap(); 25 | expect(exists).toBeFalse(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/examples/fs/FsExample02.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, fs, BytesResult, StringResult, EmptyResult} from "vulcan/test.sol"; 5 | 6 | /// @title Writing files 7 | /// @dev Write files as strings or bytes 8 | contract FsExample is Test { 9 | // These files are available only on the context of vulcan 10 | // You will need to provide your own files and edit the read permissions accordingly 11 | string constant TEXT_TEST_FILE = "./test/fixtures/fs/write/example.txt"; 12 | 13 | function test() external { 14 | // Write string 15 | fs.writeFile(TEXT_TEST_FILE, "This is a test").expect("Failed to write file"); 16 | 17 | string memory readStringResult = fs.readFile(TEXT_TEST_FILE).unwrap(); 18 | 19 | expect(readStringResult).toEqual("This is a test"); 20 | 21 | // Write binary 22 | fs.writeFileBinary(TEXT_TEST_FILE, "This is a test in binary").expect("Failed to write file"); 23 | 24 | bytes memory readBytesResult = fs.readFileBinary(TEXT_TEST_FILE).unwrap(); 25 | 26 | expect(readBytesResult).toEqual("This is a test in binary"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/src/references/Huff.md: -------------------------------------------------------------------------------- 1 | # Huff 2 | 3 | ## Structs 4 | 5 | ### Huffc 6 | 7 | ```solidity 8 | struct Huffc { 9 | string compilerPath 10 | string filePath 11 | string outputPath 12 | string mainName 13 | string constructorName 14 | bool onlyRuntime 15 | string[] constantOverrides 16 | } 17 | ``` 18 | 19 | 20 | 21 | ## huff 22 | 23 | 24 | 25 | ### **create() → (Huffc)** 26 | 27 | 28 | 29 | ### **compile(Huffc self) → (CommandResult)** 30 | 31 | 32 | 33 | ### **toCommand(Huffc self) → (Command)** 34 | 35 | 36 | 37 | ### **setCompilerPath(Huffc self, string compilerPath) → (Huffc)** 38 | 39 | 40 | 41 | ### **setFilePath(Huffc self, string filePath) → (Huffc)** 42 | 43 | 44 | 45 | ### **setOutputPath(Huffc self, string outputPath) → (Huffc)** 46 | 47 | 48 | 49 | ### **setMainName(Huffc self, string mainName) → (Huffc)** 50 | 51 | 52 | 53 | ### **setConstructorName(Huffc self, string constructorName) → (Huffc)** 54 | 55 | 56 | 57 | ### **setOnlyRuntime(Huffc self, bool onlyRuntime) → (Huffc)** 58 | 59 | 60 | 61 | ### **addConstantOverride(Huffc self, string const, bytes32 value) → (Huffc)** 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/src/references/Fe.md: -------------------------------------------------------------------------------- 1 | # Fe 2 | 3 | ## Structs 4 | 5 | ### Fe 6 | 7 | ```solidity 8 | struct Fe { 9 | string compilerPath 10 | string filePath 11 | string outputDir 12 | bool overwrite 13 | } 14 | ``` 15 | 16 | 17 | 18 | ## fe 19 | 20 | 21 | 22 | ### **create() → (Fe)** 23 | 24 | Creates a new `Fe` struct with default values. 25 | 26 | ### **build(Fe self) → (CommandResult)** 27 | 28 | Builds a binary file from a `.fe` file. 29 | 30 | ### **toCommand(Fe self) → (Command)** 31 | 32 | Transforms a `Fe` struct to a `Command` struct. 33 | 34 | ### **setCompilerPath(Fe self, string compilerPath) → (Fe)** 35 | 36 | Sets the `fe` compiler path. 37 | 38 | ### **setFilePath(Fe self, string filePath) → (Fe)** 39 | 40 | Sets the `fe` file path to build. 41 | 42 | ### **setOutputDir(Fe self, string outputDir) → (Fe)** 43 | 44 | Sets the `fe` build command output directory. 45 | 46 | ### **setOverwrite(Fe self, bool overwrite) → (Fe)** 47 | 48 | Sets the `fe` build command overwrite flag. 49 | 50 | ### **getBytecode(Fe self, string contractName) → (BytesResult)** 51 | 52 | Obtains the bytecode of a compiled contract with `contractName`. 53 | 54 | -------------------------------------------------------------------------------- /src/_internal/Config.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {vulcan, Hevm} from "./Vulcan.sol"; 5 | 6 | /// @dev Struct that represents an RPC endpoint 7 | struct RpcConfig { 8 | string name; 9 | string url; 10 | } 11 | 12 | library config { 13 | /// @dev Obtains a specific RPC from the configuration by name. 14 | /// @param name The name of the RPC to query. 15 | /// @return The url of the RPC. 16 | function rpcUrl(string memory name) internal view returns (string memory) { 17 | return vulcan.hevm.rpcUrl(name); 18 | } 19 | 20 | /// @dev Obtains all the RPCs from the configuration. 21 | /// @return All the RPCs as `[name, url][]`. 22 | function rpcUrls() internal view returns (string[2][] memory) { 23 | return vulcan.hevm.rpcUrls(); 24 | } 25 | 26 | /// @dev Obtains all the RPCs from the configuration. 27 | /// @return rpcs All the RPCs as `Rpc[]`. 28 | function rpcUrlStructs() internal view returns (RpcConfig[] memory rpcs) { 29 | Hevm.Rpc[] memory _rpcs = vulcan.hevm.rpcUrlStructs(); 30 | assembly { 31 | rpcs := _rpcs 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/examples/requests/RequestsExample04.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, expect, request, Headers, Response, Request, RequestClient} from "vulcan/test.sol"; 5 | 6 | /// @title Working with headers 7 | /// @dev Using the request module to work with request headers 8 | contract RequestExample is Test { 9 | function test() external { 10 | // Setting a default header as key value 11 | RequestClient memory client = request.create().defaultHeader("X-Foo", "true"); 12 | 13 | expect(client.headers.get("X-Foo")).toEqual("true"); 14 | 15 | // The default header gets passed to the request 16 | Request memory req = client.post("https://some-http-server.com").request.unwrap(); 17 | 18 | expect(req.headers.get("X-Foo")).toEqual("true"); 19 | 20 | // Setting multiple headers with a Header variable 21 | Headers headers = request.createHeaders().insert("X-Bar", "true").insert("X-Baz", "true"); 22 | client = request.create().defaultHeaders(headers); 23 | 24 | expect(client.headers.get("X-Bar")).toEqual("true"); 25 | expect(client.headers.get("X-Baz")).toEqual("true"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/src/references/Pointer.md: -------------------------------------------------------------------------------- 1 | # Pointer 2 | 3 | ## Custom types 4 | 5 | ### Pointer 6 | 7 | ```solidity 8 | type Pointer is bytes32; 9 | ``` 10 | 11 | 12 | 13 | ## LibPointer 14 | 15 | 16 | 17 | ### **asBytes32(Pointer self) → (bytes32)** 18 | 19 | 20 | 21 | ### **asString(Pointer self) → (string val)** 22 | 23 | 24 | 25 | ### **asBytes(Pointer self) → (bytes val)** 26 | 27 | 28 | 29 | ### **asBool(Pointer self) → (bool val)** 30 | 31 | 32 | 33 | ### **asUint256(Pointer self) → (uint256 val)** 34 | 35 | 36 | 37 | ### **asInt256(Pointer self) → (int256 val)** 38 | 39 | 40 | 41 | ### **asAddress(Pointer self) → (address val)** 42 | 43 | 44 | 45 | ### **toPointer(bytes32 value) → (Pointer ptr)** 46 | 47 | 48 | 49 | ### **toPointer(string value) → (Pointer ptr)** 50 | 51 | 52 | 53 | ### **toPointer(bytes value) → (Pointer ptr)** 54 | 55 | 56 | 57 | ### **toPointer(bool value) → (Pointer ptr)** 58 | 59 | 60 | 61 | ### **toPointer(uint256 value) → (Pointer ptr)** 62 | 63 | 64 | 65 | ### **toPointer(int256 value) → (Pointer ptr)** 66 | 67 | 68 | 69 | ### **toPointer(address value) → (Pointer ptr)** 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /test/examples/expect/ExpectExample01.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, accounts, expect} from "vulcan/test.sol"; 5 | 6 | /// @title Use different matchers 7 | /// @dev Using the `expect` function and its different matchers 8 | contract ExpectExample is Test { 9 | function test() external { 10 | expect(string("foo")).toEqual("foo"); 11 | expect(string("foo")).not.toEqual("bar"); 12 | expect(string("foo bar")).toContain("foo"); 13 | expect(string("foo bar")).toContain("bar"); 14 | 15 | expect(uint256(1)).toEqual(1); 16 | expect(uint256(1)).not.toEqual(0); 17 | expect(uint256(1)).toBeGreaterThan(0); 18 | expect(uint256(1)).toBeGreaterThanOrEqual(1); 19 | expect(uint256(0)).toBeLessThan(1); 20 | expect(uint256(0)).toBeLessThanOrEqual(0); 21 | 22 | address alice = accounts.create("Alice"); 23 | address bob = accounts.create("Bob"); 24 | expect(alice).toEqual(alice); 25 | expect(alice).not.toEqual(bob); 26 | 27 | expect(true).toBeTrue(); 28 | expect(false).toBeFalse(); 29 | expect((10 % 5) == 0).toBeTrue(); 30 | expect((10 % 6) == 4).toBeTrue(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | // Common imports 5 | import "./_imports.sol"; 6 | 7 | // Unsafe or test only imports 8 | import {accountsUnsafe as accounts} from "./_internal/Accounts.sol"; 9 | import {ctxUnsafe as ctx} from "./_internal/Context.sol"; 10 | import {expect, any} from "./_internal/Expect.sol"; 11 | import {forksUnsafe as forks, Fork} from "./_internal/Forks.sol"; 12 | import {InvariantsBase, invariants} from "./_internal/Invariants.sol"; 13 | import {bound, formatError} from "./_internal/Utils.sol"; 14 | 15 | // @dev Main entry point to Vulcan tests 16 | contract Test is InvariantsBase { 17 | bool public IS_TEST = true; 18 | 19 | constructor() { 20 | vulcan.init(); 21 | } 22 | 23 | function failed() public view returns (bool) { 24 | return vulcan.failed(); 25 | } 26 | 27 | modifier shouldFail() { 28 | bool pre = vulcan.failed(); 29 | _; 30 | bool post = vulcan.failed(); 31 | 32 | if (pre) { 33 | return; 34 | } 35 | 36 | if (!post) { 37 | revert(formatError("test", "shouldFail()", "Test expected to fail")); 38 | } 39 | 40 | vulcan.clearFailure(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/src/testing/README.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Vulcan provides a simple testing framework through the base `Test` contract and the `expect` function. 4 | 5 | ```solidity 6 | import { Test, expect } from "vulcan/test.sol"; 7 | 8 | contract ExampleTest is Test { 9 | function test_something() external { 10 | uint256 value = 1; 11 | expect(value).toEqual(1); 12 | expect(value).not.toEqual(2); 13 | expect(value).toBeLessThan(2); 14 | expect("Hello World!").toContain("World"); 15 | } 16 | } 17 | ``` 18 | 19 | # Invariant testing 20 | 21 | In addition to the basic testing framework, Vulcan also provides utilities to facilitate the use of Foundry's invariant testing. These functions are provided through the `invariants` module. 22 | 23 | ```solidity 24 | import { Test, invariants, accounts } from "vulcan/test.sol"; 25 | 26 | contract ExampleTest is Test { 27 | function setUp() external { 28 | // { Bootstrap invariant testing scenario } 29 | 30 | invariants.excludeContract(myContract); 31 | 32 | // Use 10 different senders 33 | invariants.targetSenders(accounts.createMany(10)); 34 | } 35 | 36 | function invariant_checkSomething() external { 37 | //... 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/src/examples/expect/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Use different matchers 3 | 4 | Using the `expect` function and its different matchers 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, accounts, expect} from "vulcan/test.sol"; 11 | 12 | contract ExpectExample is Test { 13 | function test() external { 14 | expect(string("foo")).toEqual("foo"); 15 | expect(string("foo")).not.toEqual("bar"); 16 | expect(string("foo bar")).toContain("foo"); 17 | expect(string("foo bar")).toContain("bar"); 18 | 19 | expect(uint256(1)).toEqual(1); 20 | expect(uint256(1)).not.toEqual(0); 21 | expect(uint256(1)).toBeGreaterThan(0); 22 | expect(uint256(1)).toBeGreaterThanOrEqual(1); 23 | expect(uint256(0)).toBeLessThan(1); 24 | expect(uint256(0)).toBeLessThanOrEqual(0); 25 | 26 | address alice = accounts.create("Alice"); 27 | address bob = accounts.create("Bob"); 28 | expect(alice).toEqual(alice); 29 | expect(alice).not.toEqual(bob); 30 | 31 | expect(true).toBeTrue(); 32 | expect(false).toBeFalse(); 33 | expect((10 % 5) == 0).toBeTrue(); 34 | expect((10 % 6) == 4).toBeTrue(); 35 | } 36 | } 37 | 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /docs/src/examples/utils/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Using println 3 | 4 | Using the println function to log formatted data 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, println} from "vulcan/test.sol"; 11 | 12 | contract UtilsExample is Test { 13 | function test() external view { 14 | println("Log a simple string"); 15 | 16 | string memory someString = "someString"; 17 | println("This is a string: {s}", abi.encode(someString)); 18 | 19 | uint256 aNumber = 123; 20 | println("This is a uint256: {u}", abi.encode(aNumber)); 21 | 22 | println("A string: {s} and a number: {u}", abi.encode(someString, aNumber)); 23 | } 24 | } 25 | 26 | ``` 27 | 28 | ### Using format 29 | 30 | Using the format function to format data 31 | 32 | ```solidity 33 | // SPDX-License-Identifier: MIT 34 | pragma solidity ^0.8.13; 35 | 36 | import {Test, expect, format} from "vulcan/test.sol"; 37 | 38 | contract FormatExample is Test { 39 | function test() external { 40 | uint256 uno = 1; 41 | 42 | string memory formatted = format("is {u} greater than 0? {bool}", abi.encode(uno, uno > 0)); 43 | 44 | expect(formatted).toEqual("is 1 greater than 0? true"); 45 | } 46 | } 47 | 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /test/modules/Config.t.sol: -------------------------------------------------------------------------------- 1 | //// SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test} from "../../src/test.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {config, RpcConfig} from "src/test/Config.sol"; 7 | 8 | contract ConfigTest is Test { 9 | function testItCanObtainRpcUrls() external { 10 | string memory key = "mainnet"; 11 | 12 | expect(config.rpcUrl(key)).toEqual("https://mainnet.rpc.io"); 13 | } 14 | 15 | function testItCanObtainAllRpcUrls() external { 16 | string[2][] memory rpcs = config.rpcUrls(); 17 | 18 | expect(rpcs.length).toEqual(2); 19 | expect(rpcs[0][0]).toEqual("arbitrum"); 20 | expect(rpcs[0][1]).toEqual("https://arbitrum.rpc.io"); 21 | expect(rpcs[1][0]).toEqual("mainnet"); 22 | expect(rpcs[1][1]).toEqual("https://mainnet.rpc.io"); 23 | } 24 | 25 | function testItCanObtainAllRpcUrlsAsStructs() external { 26 | RpcConfig[] memory rpcs = config.rpcUrlStructs(); 27 | 28 | expect(rpcs.length).toEqual(2); 29 | expect(rpcs[0].name).toEqual("arbitrum"); 30 | expect(rpcs[0].url).toEqual("https://arbitrum.rpc.io"); 31 | expect(rpcs[1].name).toEqual("mainnet"); 32 | expect(rpcs[1].url).toEqual("https://mainnet.rpc.io"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/_imports.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {console} from "./_internal/Console.sol"; 5 | import {vulcan, Log} from "./_internal/Vulcan.sol"; 6 | import {commands, Command, CommandResult, CommandOutput, CommandError} from "./_internal/Commands.sol"; 7 | import {env} from "./_internal/Env.sol"; 8 | import {gas} from "./_internal/Gas.sol"; 9 | import {events} from "./_internal/Events.sol"; 10 | import {fs, FsMetadata, FsMetadataResult, FsErrors} from "./_internal/Fs.sol"; 11 | import {json, JsonObject, JsonResult} from "./_internal/Json.sol"; 12 | import {strings} from "./_internal/Strings.sol"; 13 | import {config, RpcConfig} from "./_internal/Config.sol"; 14 | import {fmt} from "./_internal/Fmt.sol"; 15 | import {bound, format, println, removeSelector} from "./_internal/Utils.sol"; 16 | import {huff, Huffc} from "./_internal/Huff.sol"; 17 | import {fe, Fe} from "./_internal/Fe.sol"; 18 | import {semver, Semver} from "./_internal/Semver.sol"; 19 | import {StringResult, BoolResult, BytesResult, EmptyResult} from "./_internal/Result.sol"; 20 | import {Error} from "./_internal/Error.sol"; 21 | import { 22 | request, 23 | Headers, 24 | ResponseResult, 25 | RequestResult, 26 | Response, 27 | Request, 28 | RequestClient, 29 | RequestBuilder 30 | } from "./_internal/Request.sol"; 31 | import {rpc} from "./_internal/Rpc.sol"; 32 | -------------------------------------------------------------------------------- /docs/src/references/Strings.md: -------------------------------------------------------------------------------- 1 | # Strings 2 | 3 | ## strings 4 | 5 | 6 | 7 | ### **format(string template, bytes args) → (string)** 8 | 9 | 10 | 11 | ### **toString(address value) → (string)** 12 | 13 | Transforms an address to a string. 14 | 15 | ### **toString(bytes value) → (string)** 16 | 17 | Transforms a byte array to a string. 18 | 19 | ### **toString(bytes32 value) → (string)** 20 | 21 | Transforms a bytes32 to a string. 22 | 23 | ### **toString(bool value) → (string)** 24 | 25 | Transforms a boolean to a string. 26 | 27 | ### **toString(uint256 value) → (string)** 28 | 29 | Transforms an uint256 to a string. 30 | 31 | ### **toString(int256 value) → (string)** 32 | 33 | Transforms an int256 to a string. 34 | 35 | ### **parseBytes(string value) → (bytes)** 36 | 37 | Parses a byte array string. 38 | 39 | ### **parseAddress(string value) → (address)** 40 | 41 | Parses an address string. 42 | 43 | ### **parseUint(string value) → (uint256)** 44 | 45 | Parses an uint256 string. 46 | 47 | ### **parseInt(string value) → (int256)** 48 | 49 | Parses an int256 string. 50 | 51 | ### **parseBytes32(string value) → (bytes32)** 52 | 53 | Parses a bytes32 string. 54 | 55 | ### **parseBool(string value) → (bool)** 56 | 57 | Parses a boolean string. 58 | 59 | ### **parseJson(string value) → (JsonResult)** 60 | 61 | Parses a JSON string. 62 | 63 | -------------------------------------------------------------------------------- /docs/src/examples/commands/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Run a simple command 3 | 4 | Run a simple command and obtain the output 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, commands, CommandResult, CommandOutput} from "vulcan/test.sol"; 11 | 12 | contract RunCommandExample is Test { 13 | function test() external { 14 | // Run the command 15 | CommandOutput memory result = commands.run(["echo", "Hello, World!"]).unwrap(); 16 | 17 | // Check the output 18 | expect(string(result.stdout)).toEqual("Hello, World!"); 19 | } 20 | } 21 | 22 | ``` 23 | 24 | ### Reuse a command 25 | 26 | Reuse a command with different arguments 27 | 28 | ```solidity 29 | // SPDX-License-Identifier: MIT 30 | pragma solidity ^0.8.13; 31 | 32 | import {Test, expect, commands, Command, CommandResult, CommandOutput} from "vulcan/test.sol"; 33 | 34 | contract ReuseACommandExample is Test { 35 | function test() external { 36 | // Create a command 37 | Command memory echo = commands.create("echo"); 38 | 39 | // Run the commands and unwrap the results 40 | CommandOutput memory fooOutput = echo.arg("foo").run().unwrap(); 41 | CommandOutput memory barOutput = echo.arg("bar").run().unwrap(); 42 | 43 | // Check the outputs 44 | expect(string(fooOutput.stdout)).toEqual("foo"); 45 | expect(string(barOutput.stdout)).toEqual("bar"); 46 | } 47 | } 48 | 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /docs/src/references/Error.md: -------------------------------------------------------------------------------- 1 | # Error 2 | 3 | ## Custom types 4 | 5 | ### Error 6 | 7 | ```solidity 8 | type Error is bytes32; 9 | ``` 10 | 11 | 12 | 13 | ## LibError 14 | 15 | 16 | 17 | ### **toPointer(Error err) → (Pointer)** 18 | 19 | 20 | 21 | ### **decode(Error err) → (bytes32 id, string message, bytes data)** 22 | 23 | 24 | 25 | ### **encodeError(function fn, string message) → (Error err)** 26 | 27 | 28 | 29 | ### **toErrorId(function fn) → (bytes32 id)** 30 | 31 | 32 | 33 | ### **matches(Error err, function fn) → (bool)** 34 | 35 | 36 | 37 | ### **decodeAs(Error, function)** 38 | 39 | 40 | 41 | ### **encodeError(function fn, string message, uint256 p0) → (Error err)** 42 | 43 | 44 | 45 | ### **toErrorId(function fn) → (bytes32 id)** 46 | 47 | 48 | 49 | ### **matches(Error err, function fn) → (bool)** 50 | 51 | 52 | 53 | ### **decodeAs(Error err, function) → (uint256)** 54 | 55 | 56 | 57 | ### **encodeError(function fn, string message, string p0) → (Error err)** 58 | 59 | 60 | 61 | ### **toErrorId(function fn) → (bytes32 id)** 62 | 63 | 64 | 65 | ### **matches(Error err, function fn) → (bool)** 66 | 67 | 68 | 69 | ### **decodeAs(Error err, function) → (string)** 70 | 71 | 72 | 73 | ### **toStringResult(Error self) → (StringResult)** 74 | 75 | 76 | 77 | ### **toBytesResult(Error self) → (BytesResult)** 78 | 79 | 80 | 81 | ### **toBoolResult(Error self) → (BoolResult)** 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/src/testing/expect.md: -------------------------------------------------------------------------------- 1 | # Expect 2 | 3 | ## `expect(value)` 4 | 5 | The `expect` function allows you to assert that a value meets a certain condition through the use of matchers. 6 | 7 | ```solidity 8 | import { Test, expect } from "vulcan/test.sol"; 9 | 10 | function testSomething() external { 11 | uint256 a = 1; 12 | uint256 b = 1; 13 | expect(a).toEqual(b); 14 | } 15 | ``` 16 | 17 | ## `.not` 18 | 19 | > Note: `.not` is not yet implemented for all matchers. 20 | 21 | The `.not` property inverts the result of the assertion. 22 | 23 | ```solidity 24 | function testSomething() external { 25 | expect("hello").not.toEqual("world"); 26 | } 27 | ``` 28 | 29 | # Matchers 30 | 31 | > Note: Matchers are only implemented for basic types for now. Arrays, structs and other types will be supported in the future. 32 | 33 | ### `toEqual(value)` 34 | 35 | ### `toBeTrue()` 36 | 37 | ### `toBeFalse()` 38 | 39 | ### `toBeGreaterThan(value)` 40 | 41 | ### `toBeGreaterThanOrEqual(value)` 42 | 43 | ### `toBeLessThan(value)` 44 | 45 | ### `toBeLessThanOrEqual(value)` 46 | 47 | ### `toBeAContract()` 48 | 49 | ### `toBeTheHashOf(value)` 50 | 51 | ### `toContain(value)` 52 | 53 | ### `toHaveLength(value)` 54 | 55 | ### `toBeCloseTo(value, delta)` 56 | 57 | ### `toHaveReverted()` 58 | 59 | ### `toHaveRevertedWith(selector|message)` 60 | 61 | ### `toHaveSucceeded()` 62 | 63 | ### `toHaveEmitted(topics)` 64 | 65 | ### `toHaveEmitted(topics, data)` 66 | 67 | ### `toHaveEmitted(signature, topics, data)` 68 | 69 | -------------------------------------------------------------------------------- /docs/src/modules/watchers.md: -------------------------------------------------------------------------------- 1 | # Watchers 2 | 3 | Monitor contract calls and emitted events. 4 | 5 | > **Important:** 6 | > Watchers work by replacing an address code with a proxy contract that records all calls and events. 7 | ```solidity 8 | import { Test, events, watchers, expect, any } from "vulcan/test.sol"; 9 | 10 | contract TestMyContract is Test { 11 | using watchers for *; 12 | using events for *; 13 | 14 | function testMyContract() external { 15 | MyContract mc = new MyContract(); 16 | 17 | // Start watching the `mc` contract. 18 | // This will replace the `mc` contract code with a `proxy` 19 | address(mc).watch().captureReverts(); 20 | 21 | mc.doSomething(); 22 | mc.doSomethingElse(); 23 | 24 | // Two calls were made to `mc` 25 | expect(address(mc).calls().length).toEqual(2); 26 | // First call reverted with a message 27 | expect(address(mc).getCall(0)).toHaveRevertedWith("Something went wrong"); 28 | // Second call emitted an event 29 | expect(address(mc).getCall(1)).toHaveEmitted( 30 | "SomeEvent(address,bytes32,uint256)", 31 | [address(1).topic(), any()], // Event topics (indexed arguments) 32 | abi.encode(123) // Event data 33 | ); 34 | 35 | // Stop watching the `mc` contract. 36 | // The `mc` contract code returns to the original state. 37 | address(mc).stopWatcher(); 38 | } 39 | } 40 | ``` 41 | 42 | [**Watchers API reference**](../references/watchers.md) 43 | -------------------------------------------------------------------------------- /src/_internal/Rpc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {vulcan} from "./Vulcan.sol"; 5 | import {forksUnsafe} from "./Forks.sol"; 6 | 7 | library rpc { 8 | /// @dev Calls an JSON-RPC method on a specific RPC endpoint. If there was a previous active fork it will return back to that one once the method is called. 9 | /// @param urlOrName The url or name of the RPC endpoint to use 10 | /// @param method The JSON-RPC method to call 11 | /// @param params The method params as a JSON string 12 | function call(string memory urlOrName, string memory method, string memory params) 13 | internal 14 | returns (bytes memory data) 15 | { 16 | uint256 currentFork; 17 | bool hasActiveFork; 18 | 19 | try vulcan.hevm.activeFork() returns (uint256 forkId) { 20 | currentFork = forkId; 21 | hasActiveFork = true; 22 | } catch (bytes memory) {} 23 | 24 | forksUnsafe.create(urlOrName).select(); 25 | 26 | bytes memory result = call(method, params); 27 | 28 | if (hasActiveFork) { 29 | vulcan.hevm.selectFork(currentFork); 30 | } 31 | 32 | return result; 33 | } 34 | 35 | /// @dev Calls an JSON-RPC method on the current active fork 36 | /// @param method The JSON-RPC method to call 37 | /// @param params The method params as a JSON string 38 | function call(string memory method, string memory params) internal returns (bytes memory data) { 39 | return vulcan.hevm.rpc(method, params); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/src/scripts/README.md: -------------------------------------------------------------------------------- 1 | # Scripts 2 | 3 | Most of Vulcan's modules used for testing can be used in `Scripts` but not all the functions are 4 | recommended to be used in this context. A few examples are: 5 | - [`Accounts`](../modules/accounts.md): Only the functions in the [`accountsSafe`](https://github.com/nomoixyz/vulcan/blob/6b182b6351714592d010ef5bfefc5780710d84e6/src/_modules/Accounts.sol#L10) library should be used. If there is a need to use `unsafe` functions use the `accountsUnsafe` module exported in `vulcan/script.sol`. 6 | - [`Context`](../modules/context.md): Only the functions in the [`ctxSafe`](https://github.com/nomoixyz/vulcan/blob/6b182b6351714592d010ef5bfefc5780710d84e6/src/_modules/Context.sol#L35) library should be used. If there is a need to use `unsafe` functions use the `ctxUnsafe` module exported in `vulcan/script.sol`; 7 | - [`Forks`](../modules/forks.md): None of the functions in this module should be used. If there is a 8 | need to use `unsafe` functions use the `forksUnsafe` module exported in `vulcan/script.sol`. 9 | 10 | ```solidity 11 | // SPDX-License-Identifier: MIT 12 | pragma solidity ^0.8.13; 13 | 14 | import {MyContract} from "src/MyContract.sol"; 15 | import {Script, ctx, println, request} from "vulcan/script.sol"; 16 | 17 | contract DeployScript is Script { 18 | function run() public { 19 | ctx.startBroadcast(); 20 | 21 | new MyContract(); 22 | 23 | ctx.stopBroadcast(); 24 | 25 | println("Notifying API"); 26 | 27 | request 28 | .create() 29 | .post("https://my-api.io/webhook/notify/deployment") 30 | .send() 31 | .expect("Failed to trigger webhook"); 32 | } 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /test/modules/Forks.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test, forks, Fork, println} from "src/test.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {commands, CommandResult, CommandOutput} from "src/test/Commands.sol"; 7 | import {forks, Fork} from "src/test/Forks.sol"; 8 | import {println} from "src/utils.sol"; 9 | import {Sender} from "../mocks/Sender.sol"; 10 | 11 | contract ForksTest is Test { 12 | string private constant ENDPOINT = "http://localhost:8545"; 13 | 14 | modifier skipIfEndpointFails() { 15 | string memory data = '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":67}'; 16 | 17 | CommandResult res = commands.create("curl").args( 18 | ["--silent", "-H", "Content-Type: application/json", "-X", "POST", "--data", data, ENDPOINT] 19 | ).run(); 20 | 21 | CommandOutput memory cmdOutput = res.unwrap(); 22 | 23 | if (cmdOutput.stdout.length == 0) { 24 | println("Skipping test because forking endpoint is not available"); 25 | return; 26 | } 27 | 28 | _; 29 | } 30 | 31 | function testItCanForkAtBlock() external skipIfEndpointFails { 32 | uint256 blockNumber = 1337; 33 | 34 | Fork fork = forks.createAtBlock(ENDPOINT, blockNumber).select(); 35 | 36 | expect(Fork.unwrap(forks.active())).toEqual(Fork.unwrap(fork)); 37 | expect(block.number).toEqual(blockNumber); 38 | } 39 | 40 | function testItCanSetBlockNumber() external skipIfEndpointFails { 41 | uint256 blockNumber = 1337; 42 | 43 | Fork fork = forks.createAtBlock(ENDPOINT, blockNumber).select(); 44 | 45 | uint256 newBlockNumber = 1338; 46 | 47 | fork.setBlockNumber(newBlockNumber); 48 | 49 | expect(block.number).toEqual(newBlockNumber); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | [Introduction](./README.md) 2 | 3 | # User Guide 4 | 5 | - [Installation](./guide/installation.md) 6 | - [Testing](./testing/README.md) 7 | - [Scripts](./scripts/README.md) 8 | 9 | # Modules 10 | 11 | - [Accounts](./modules/accounts.md) 12 | - [Commands](./modules/commands.md) 13 | - [Context](./modules/context.md) 14 | - [Expect](./modules/expect.md) 15 | - [Fe](./modules/fe.md) 16 | - [Format](./modules/fmt.md) 17 | - [Fs](./modules/fs.md) 18 | - [Gas](./modules/gas.md) 19 | - [Huff](./modules/huff.md) 20 | - [Json](./modules/json.md) 21 | - [Results \& Errors](./modules/results.md) 22 | - [Requests](./modules/requests.md) 23 | - [Utils](./modules/utils.md) 24 | - [Others](./OTHER_MODULES.md) 25 | - [Config](./modules/config.md) 26 | - [Console](./modules/console.md) 27 | - [Env](./modules/env.md) 28 | - [Events](./modules/events.md) 29 | - [Forks](./modules/forks.md) 30 | - [RPC](./modules/rpc.md) 31 | - [Strings](./modules/strings.md) 32 | 33 | # References 34 | 35 | - [Accounts](./references/Accounts.md) 36 | - [Commands](./references/Commands.md) 37 | - [Config](./references/Config.md) 38 | - [Context](./references/Context.md) 39 | - [Env](./references/Env.md) 40 | - [Error](./references/Error.md) 41 | - [Events](./references/Events.md) 42 | - [Expect](./references/Expect.md) 43 | - [Fe](./references/Fe.md) 44 | - [Fmt](./references/Fmt.md) 45 | - [Forks](./references/Forks.md) 46 | - [Fs](./references/Fs.md) 47 | - [Gas](./references/Gas.md) 48 | - [Huff](./references/Huff.md) 49 | - [Invariants](./references/Invariants.md) 50 | - [Json](./references/Json.md) 51 | - [Pointer](./references/Pointer.md) 52 | - [Result](./references/Result.md) 53 | - [Semver](./references/Semver.md) 54 | - [Strings](./references/Strings.md) 55 | - [Utils](./references/Utils.md) 56 | - [Vulcan](./references/Vulcan.md) 57 | - [Watchers](./references/Watchers.md) 58 | -------------------------------------------------------------------------------- /docs/src/references/Watchers.md: -------------------------------------------------------------------------------- 1 | # Watchers 2 | 3 | ## Structs 4 | 5 | ### Call 6 | 7 | ```solidity 8 | struct Call { 9 | bytes callData 10 | bool success 11 | bytes returnData 12 | Log[] logs 13 | } 14 | ``` 15 | 16 | 17 | 18 | ## watchersUnsafe 19 | 20 | 21 | 22 | ### **watcherAddress(address target) → (address)** 23 | 24 | Obtains the address of the watcher for `target`. 25 | 26 | ### **targetAddress(address _watcher) → (address)** 27 | 28 | Obtains the address of the target for `_target`. 29 | 30 | ### **watcher(address target) → (Watcher)** 31 | 32 | Obtains the Watcher implementation for the `target` address. 33 | 34 | ### **watch(address target) → (Watcher)** 35 | 36 | Starts watching a `target` address. 37 | 38 | ### **stop(address target)** 39 | 40 | Stops watching the `target` address. 41 | 42 | ### **stopWatcher(address target)** 43 | 44 | Stops watching the `target` address. 45 | 46 | ### **calls(address target) → (Call[])** 47 | 48 | Obtains all the calls made to the `target` address. 49 | 50 | ### **getCall(address target, uint256 index) → (Call)** 51 | 52 | Obtains an specific call made to the `target` address at an specific index. 53 | 54 | ### **firstCall(address target) → (Call)** 55 | 56 | Obtains the first call made to the `target` address. 57 | 58 | ### **lastCall(address target) → (Call)** 59 | 60 | Obtains the last call made to the `target` address. 61 | 62 | ### **captureReverts(address target) → (Watcher)** 63 | 64 | Starts capturing reverts for the `target` address. This will prevent the `target` contract to 65 | revert until `disableCaptureReverts` is called. This is meant to be used in conjunction with the `toHaveReverted` and 66 | `toHaveRevertedWith` functions from the expect library. 67 | 68 | ### **disableCaptureReverts(address target) → (Watcher)** 69 | 70 | Stops capturing reverts for the `target` address. 71 | 72 | -------------------------------------------------------------------------------- /docs/src/examples/config/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Obtain a specific RPC URL 3 | 4 | Read a specific RPC URL from the foundry configuration 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, config} from "vulcan/test.sol"; 11 | 12 | contract ConfigExample is Test { 13 | function test() external { 14 | string memory key = "mainnet"; 15 | 16 | expect(config.rpcUrl(key)).toEqual("https://mainnet.rpc.io"); 17 | } 18 | } 19 | 20 | ``` 21 | 22 | ### Obtain all the RPC URLs 23 | 24 | Read all the RPC URLs from the foundry configuration 25 | 26 | ```solidity 27 | // SPDX-License-Identifier: MIT 28 | pragma solidity ^0.8.13; 29 | 30 | import {Test, expect, config} from "vulcan/test.sol"; 31 | 32 | contract ConfigExample is Test { 33 | function test() external { 34 | string[2][] memory rpcs = config.rpcUrls(); 35 | 36 | expect(rpcs.length).toEqual(2); 37 | expect(rpcs[0][0]).toEqual("arbitrum"); 38 | expect(rpcs[0][1]).toEqual("https://arbitrum.rpc.io"); 39 | expect(rpcs[1][0]).toEqual("mainnet"); 40 | expect(rpcs[1][1]).toEqual("https://mainnet.rpc.io"); 41 | } 42 | } 43 | 44 | ``` 45 | 46 | ### Obtain all the RPC URLs using structs 47 | 48 | Read all the RPC URL from the foundry configuration as structs 49 | 50 | ```solidity 51 | // SPDX-License-Identifier: MIT 52 | pragma solidity ^0.8.13; 53 | 54 | import {Test, expect, config, RpcConfig} from "vulcan/test.sol"; 55 | 56 | contract ConfigExample is Test { 57 | function test() external { 58 | RpcConfig[] memory rpcs = config.rpcUrlStructs(); 59 | 60 | expect(rpcs.length).toEqual(2); 61 | expect(rpcs[0].name).toEqual("arbitrum"); 62 | expect(rpcs[0].url).toEqual("https://arbitrum.rpc.io"); 63 | expect(rpcs[1].name).toEqual("mainnet"); 64 | expect(rpcs[1].url).toEqual("https://mainnet.rpc.io"); 65 | } 66 | } 67 | 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /docs/src/examples/format/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Using templates 3 | 4 | Using templates with the `format` module to format data 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, accounts, expect, fmt, println} from "vulcan/test.sol"; 11 | 12 | contract FormatExample is Test { 13 | using accounts for address; 14 | 15 | function test() external { 16 | address target = address(1).setBalance(1); 17 | uint256 balance = target.balance; 18 | 19 | // Store it as a string 20 | // NOTE: The {address} and {uint} placeholders can be abbreviated as {a} and {u} 21 | // For available placeholders and abbreviations see: TODO 22 | string memory result = fmt.format("The account {address} has {uint} wei", abi.encode(target, balance)); 23 | 24 | expect(result).toEqual("The account 0x0000000000000000000000000000000000000001 has 1 wei"); 25 | 26 | // Format is also used internally by Vulcan's println, which you can use as an alternative to console.log 27 | println("The account {address} has {uint} wei", abi.encode(target, balance)); 28 | } 29 | } 30 | 31 | ``` 32 | 33 | ### Formatting decimals 34 | 35 | Use the `{uint:dx}` placeholder to format numbers with decimals 36 | 37 | ```solidity 38 | // SPDX-License-Identifier: MIT 39 | pragma solidity ^0.8.13; 40 | 41 | import {Test, accounts, expect, fmt} from "vulcan/test.sol"; 42 | 43 | contract FormatExample is Test { 44 | using accounts for address; 45 | 46 | function test() external { 47 | address target = address(1).setBalance(1e17); 48 | uint256 balance = target.balance; 49 | 50 | // Store it as a string 51 | string memory result = fmt.format("The account {address} has {uint:d18} eth", abi.encode(target, balance)); 52 | 53 | expect(result).toEqual("The account 0x0000000000000000000000000000000000000001 has 0.1 eth"); 54 | } 55 | } 56 | 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- /test/modules/Fmt.t.sol: -------------------------------------------------------------------------------- 1 | //// SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test} from "src/test.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {fmt} from "src/test/Fmt.sol"; 7 | import {Type, Placeholder} from "src/_internal/Fmt.sol"; 8 | 9 | contract FmtTest is Test { 10 | function testFormat() external { 11 | string memory template = "{address} hello {string} world {bool}"; 12 | string memory result = fmt.format(template, abi.encode(address(123), "foo", true)); 13 | 14 | expect(result).toEqual("0x000000000000000000000000000000000000007B hello foo world true"); 15 | } 16 | 17 | function testAbbreviatedFormat() external { 18 | string memory template = "{a} hello {s} world {u}"; 19 | string memory result = fmt.format(template, abi.encode(address(123), "foo", uint256(1))); 20 | 21 | expect(result).toEqual("0x000000000000000000000000000000000000007B hello foo world 1"); 22 | } 23 | 24 | function testNoTemplate() external { 25 | string memory template = "hello world"; 26 | string memory result = fmt.format(template, abi.encode()); 27 | expect(result).toEqual("hello world"); 28 | 29 | string memory result2 = fmt.format(template, abi.encode(address(123), "foo", true)); 30 | expect(result2).toEqual("hello world"); 31 | } 32 | 33 | function testFormatDecimals() external { 34 | expect(fmt.format("{uint:d18}", abi.encode(1e17))).toEqual("0.1"); 35 | expect(fmt.format("{uint:d17}", abi.encode(1e17))).toEqual("1.0"); 36 | expect(fmt.format("{uint:d19}", abi.encode(1e17))).toEqual("0.01"); 37 | expect(fmt.format("{uint:d2}", abi.encode(123))).toEqual("1.23"); 38 | expect(fmt.format("{uint:d2}", abi.encode(103))).toEqual("1.03"); 39 | expect(fmt.format("{uint:d2}", abi.encode(1003))).toEqual("10.03"); 40 | expect(fmt.format("{uint:d2}", abi.encode(1000))).toEqual("10.0"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/src/references/Invariants.md: -------------------------------------------------------------------------------- 1 | # Invariants 2 | 3 | ## Structs 4 | 5 | ### FuzzSelector 6 | 7 | ```solidity 8 | struct FuzzSelector { 9 | address addr 10 | bytes4[] selectors 11 | } 12 | ``` 13 | 14 | A struct that represents a Fuzz Selector 15 | 16 | ## invariants 17 | 18 | 19 | 20 | ### **getState() → (State state)** 21 | 22 | Returns the state struct that contains the invariants related data 23 | 24 | ### **excludeContract(address newExcludedContract)** 25 | 26 | Excludes a contract 27 | 28 | ### **excludeContracts(address[] newExcludedContracts)** 29 | 30 | Excludes multiple contracts 31 | 32 | ### **excludeSender(address newExcludedSender)** 33 | 34 | Excludes a sender 35 | 36 | ### **excludeSenders(address[] newExcludedSenders)** 37 | 38 | Excludes multiple senders 39 | 40 | ### **excludeArtifact(string newExcludedArtifact)** 41 | 42 | Excludes an artifact 43 | 44 | ### **excludeArtifacts(string[] newExcludedArtifacts)** 45 | 46 | Excludes multiple artifacts 47 | 48 | ### **targetArtifact(string newTargetedArtifact)** 49 | 50 | Targets an artifact 51 | 52 | ### **targetArtifacts(string[] newTargetedArtifacts)** 53 | 54 | Targets multiple artifacts 55 | 56 | ### **targetArtifactSelector(FuzzSelector newTargetedArtifactSelector)** 57 | 58 | Targets an artifact selector 59 | 60 | ### **targetArtifactSelectors(FuzzSelector[] newTargetedArtifactSelectors)** 61 | 62 | Targets multiple artifact selectors 63 | 64 | ### **targetContract(address newTargetedContract)** 65 | 66 | Targets a contract 67 | 68 | ### **targetContracts(address[] newTargetedContracts)** 69 | 70 | Targets multiple contracts 71 | 72 | ### **targetSelector(FuzzSelector newTargetedSelector)** 73 | 74 | Targets a selector 75 | 76 | ### **targetSelectors(FuzzSelector[] newTargetedSelectors)** 77 | 78 | Targets multiple selectors 79 | 80 | ### **targetSender(address newTargetedSender)** 81 | 82 | Targets a sender 83 | 84 | ### **targetSenders(address[] newTargetedSenders)** 85 | 86 | Targets multiple senders 87 | 88 | -------------------------------------------------------------------------------- /test/modules/Events.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.13 <0.9.0; 2 | 3 | import {Test, expect, events, Log} from "../../src/test.sol"; 4 | import {events} from "src/test/Events.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {Log} from "src/test/Vulcan.sol"; 7 | 8 | contract EventsTest is Test { 9 | using events for *; 10 | 11 | function testItCanConvertUintToTopic(uint256 value) external { 12 | expect(value.topic()).toEqual(bytes32(value)); 13 | } 14 | 15 | function testItCanConvertStringToTopic(string memory value) external { 16 | expect(value.topic()).toEqual(keccak256(bytes(value))); 17 | } 18 | 19 | function testItCanConvertAddressToTopic(address value) external { 20 | expect(value.topic()).toEqual(bytes32(uint256(uint160(value)))); 21 | } 22 | 23 | function testItCanConvertBytes32ToTopic(bytes32 value) external { 24 | expect(value.topic()).toEqual(value); 25 | } 26 | 27 | function testItCanConvertBytesToTopic(bytes memory value) external { 28 | expect(value.topic()).toEqual(keccak256(value)); 29 | } 30 | 31 | function testItCanConvertBoolToTopic() external { 32 | expect(false.topic()).toEqual(bytes32(uint256(0))); 33 | expect(true.topic()).toEqual(bytes32(uint256(1))); 34 | } 35 | 36 | function testItCanConvertIntToTopic(int256 value) external { 37 | expect(value.topic()).toEqual(bytes32(uint256(value))); 38 | } 39 | 40 | event SomeEvent(uint256 indexed a, address b); 41 | 42 | function testItCanGetRecordedLogs(uint256 a, address b) external { 43 | events.recordLogs(); 44 | 45 | emit SomeEvent(a, b); 46 | 47 | Log[] memory logs = events.getRecordedLogs(); 48 | 49 | expect(logs.length).toEqual(1); 50 | expect(logs[0].emitter).toEqual(address(this)); 51 | expect(logs[0].topics[0]).toEqual(SomeEvent.selector); 52 | expect(logs[0].topics[1]).toEqual(a.topic()); 53 | expect(logs[0].data).toEqual(abi.encode(b)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/src/examples/context/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Modify chain parameters 3 | 4 | Use the context module to modify chain parameters 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, ctx} from "vulcan/test.sol"; 11 | 12 | contract ContextExample is Test { 13 | function test() external { 14 | ctx.setBlockTimestamp(1); 15 | expect(block.timestamp).toEqual(1); 16 | 17 | ctx.setBlockNumber(123); 18 | expect(block.number).toEqual(123); 19 | 20 | ctx.setBlockBaseFee(99999); 21 | expect(block.basefee).toEqual(99999); 22 | 23 | ctx.setBlockPrevrandao(bytes32(uint256(123))); 24 | expect(block.prevrandao).toEqual(uint256(bytes32(uint256(123)))); 25 | 26 | ctx.setChainId(666); 27 | expect(block.chainid).toEqual(666); 28 | 29 | ctx.setBlockCoinbase(address(1)); 30 | expect(block.coinbase).toEqual(address(1)); 31 | 32 | ctx.setGasPrice(1e18); 33 | expect(tx.gasprice).toEqual(1e18); 34 | } 35 | } 36 | 37 | ``` 38 | 39 | ### Modify chain parameters using method chaining 40 | 41 | Use the context module to modify chain parameters using method chaining 42 | 43 | ```solidity 44 | // SPDX-License-Identifier: MIT 45 | pragma solidity ^0.8.13; 46 | 47 | import {Test, expect, ctx} from "vulcan/test.sol"; 48 | 49 | contract ContextExample is Test { 50 | function test() external { 51 | ctx.setBlockTimestamp(1).setBlockNumber(123).setBlockBaseFee(99999).setBlockPrevrandao(bytes32(uint256(123))) 52 | .setChainId(666).setBlockCoinbase(address(1)).setGasPrice(1e18); 53 | 54 | expect(block.timestamp).toEqual(1); 55 | expect(block.number).toEqual(123); 56 | expect(block.basefee).toEqual(99999); 57 | expect(block.prevrandao).toEqual(uint256(bytes32(uint256(123)))); 58 | expect(block.chainid).toEqual(666); 59 | expect(block.coinbase).toEqual(address(1)); 60 | expect(tx.gasprice).toEqual(1e18); 61 | } 62 | } 63 | 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /test/modules/Error.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test} from "src/test.sol"; 5 | import {env} from "src/test/Env.sol"; 6 | import {Error} from "src/test/Error.sol"; 7 | import {expect} from "src/test/Expect.sol"; 8 | import {json} from "src/test/Json.sol"; 9 | import {vulcan} from "src/test/Vulcan.sol"; 10 | import {println} from "src/utils.sol"; 11 | 12 | import {LibError, Error} from "../../src/_internal/Error.sol"; 13 | 14 | function FooError(uint256 value) pure returns (Error) { 15 | return LibError.encodeError(FooError, "Foo error message", value); 16 | } 17 | 18 | function BarError(uint256 value) pure returns (Error) { 19 | return LibError.encodeError(BarError, "Bar error message", value); 20 | } 21 | 22 | function BazError(string memory value) pure returns (Error) { 23 | return LibError.encodeError(BazError, "Baz error message", value); 24 | } 25 | 26 | contract ErrorTest is Test { 27 | using LibError for *; 28 | 29 | function testErrorUint() external { 30 | Error err = FooError(123); 31 | 32 | expect(err.decodeAs(FooError)).toEqual(123); 33 | expect(err.matches(FooError)).toEqual(true); 34 | expect(err.matches(BarError)).toEqual(false); 35 | 36 | (bytes32 id, string memory message, bytes memory data) = err.decode(); 37 | 38 | expect(id).toEqual(FooError.toErrorId()); 39 | expect(message).toEqual("Foo error message"); 40 | expect(data).toEqual(abi.encode(123)); 41 | } 42 | 43 | function testErrorString() external { 44 | Error err = BazError("Hello, World!"); 45 | 46 | expect(err.decodeAs(BazError)).toEqual("Hello, World!"); 47 | expect(err.matches(BazError)).toEqual(true); 48 | expect(err.matches(FooError)).toEqual(false); 49 | 50 | (bytes32 id, string memory message, bytes memory data) = err.decode(); 51 | 52 | expect(id).toEqual(BazError.toErrorId()); 53 | expect(message).toEqual("Baz error message"); 54 | expect(data).toEqual(abi.encode("Hello, World!")); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/_internal/Vulcan.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Vm as Hevm} from "forge-std/Vm.sol"; 5 | import {watchersUnsafe as _watchersUnsafe, Call, Watcher} from "./Watchers.sol"; 6 | import {ctxUnsafe as _ctxUnsafe} from "./Context.sol"; 7 | 8 | /// @dev Struct that represent an EVM log 9 | struct Log { 10 | bytes32[] topics; 11 | bytes data; 12 | address emitter; 13 | } 14 | 15 | library vulcan { 16 | using vulcan for *; 17 | 18 | bytes32 constant GLOBAL_FAILED_SLOT = bytes32("failed"); 19 | 20 | /// @dev forge-std VM 21 | Hevm internal constant hevm = Hevm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); 22 | 23 | /// @dev Initializes the context module 24 | function init() internal { 25 | _ctxUnsafe.init(); 26 | } 27 | 28 | /// @dev Checks if `fail` was called at some point. 29 | /// @return true if `fail` was called, false otherwise 30 | function failed() internal view returns (bool) { 31 | bytes32 globalFailed = vulcan.hevm.load(address(hevm), GLOBAL_FAILED_SLOT); 32 | return globalFailed == bytes32(uint256(1)); 33 | } 34 | 35 | /// @dev Signal that an expectation/assertion failed. 36 | function fail() internal { 37 | vulcan.hevm.store(address(hevm), GLOBAL_FAILED_SLOT, bytes32(uint256(1))); 38 | } 39 | 40 | /// @dev Resets the failed state. 41 | function clearFailure() internal { 42 | vulcan.hevm.store(address(hevm), GLOBAL_FAILED_SLOT, bytes32(uint256(0))); 43 | } 44 | 45 | /// @dev Starts monitoring an address. 46 | /// @param _target The address to monitor. 47 | /// @return The Watcher contract that monitors the `_target` address. 48 | function watch(address _target) internal returns (Watcher) { 49 | return _watchersUnsafe.watch(_target); 50 | } 51 | 52 | /// @dev Stops monitoring an address. 53 | /// @param _target The address to stop monitoring. 54 | function stopWatcher(address _target) internal { 55 | _watchersUnsafe.stop(_target); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/_internal/Gas.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "./Vulcan.sol"; 5 | import {accountsUnsafe as accounts} from "./Accounts.sol"; 6 | import {formatError} from "./Utils.sol"; 7 | 8 | library gas { 9 | bytes32 constant GAS_MEASUREMENTS_MAGIC = keccak256("vulcan.gas.measurements.magic"); 10 | 11 | function record(string memory name) internal { 12 | bytes32 startSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "start")); 13 | accounts.setStorage(address(vulcan.hevm), startSlot, bytes32(gasleft())); 14 | } 15 | 16 | function stopRecord(string memory name) internal returns (uint256) { 17 | uint256 endGas = gasleft(); 18 | 19 | bytes32 startSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "start")); 20 | uint256 startGas = uint256(accounts.readStorage(address(vulcan.hevm), startSlot)); 21 | 22 | if (endGas > startGas) { 23 | revert(_formatError("stopRecord", "Gas used can't have a negative value")); 24 | } 25 | 26 | bytes32 endSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "end")); 27 | accounts.setStorage(address(vulcan.hevm), endSlot, bytes32(endGas)); 28 | 29 | return startGas - endGas; 30 | } 31 | 32 | function getRecord(string memory name) internal view returns (uint256, uint256) { 33 | bytes32 startSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "start")); 34 | uint256 startGas = uint256(accounts.readStorage(address(vulcan.hevm), startSlot)); 35 | 36 | bytes32 endSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "end")); 37 | uint256 endGas = uint256(accounts.readStorage(address(vulcan.hevm), endSlot)); 38 | 39 | return (startGas, endGas); 40 | } 41 | 42 | function used(string memory name) internal view returns (uint256) { 43 | (uint256 startGas, uint256 endGas) = getRecord(name); 44 | 45 | return startGas - endGas; 46 | } 47 | 48 | function _formatError(string memory func, string memory message) private pure returns (string memory) { 49 | return formatError("gas", func, message); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/src/README.md: -------------------------------------------------------------------------------- 1 |

2 | Vulcan 3 |

4 | 5 | Development framework for Foundry projects, with a focus on developer experience and readability. 6 | 7 | Built on top of [`forge-std`](https://github.com/foundry-rs/forge-std) 8 | 9 | Initially, Vulcan will provide functionality similar to what is already included in forge's VM and `forge-std`. 10 | 11 | Over time, Vulcan will grow to include more functionality and utilities, eventually becoming a feature-rich development framework. 12 | 13 | ## Why Vulcan? 14 | 15 | Our goal is to provide: 16 | 17 | - Better naming for VM functionality (no more `prank`, `roll`, `warp`, ...) 18 | - A testing framework with better readability and a familiar syntax 19 | - Improved ergonomics 20 | - [Huff language](https://huff.sh/) support out of the box 21 | 22 | Vulcan test example: 23 | 24 | ```solidity 25 | import { Test, expect, commands, Command, CommandResult, println } from "vulcan/test.sol"; 26 | 27 | contract TestSomething is Test { 28 | 29 | function testSomething() external { 30 | // Format strings with rust-like syntax 31 | println("Hello {s}", abi.encode("world!")); // Hello world! 32 | 33 | // Format numbers as decimals 34 | println("Balance: {u:d18}", abi.encode(1e17)); // Balance: 0.1 35 | 36 | // Nice external command API! 37 | Command memory ping = commands.create("ping").args(["-c", "1"]); 38 | CommandResult pingResult = ping.arg("etherscan.io").run(); 39 | 40 | // Rust-like results 41 | bytes memory pingOutput = pingResult.expect("Ping command failed").stdout; 42 | 43 | println("Ping result: {s}", abi.encode(pingOutput)); 44 | 45 | // Expect style assertions! 46 | expect(pingResult.isError()).toBeFalse(); 47 | expect(pingResult.isOk()).toBeTrue(); 48 | 49 | // And much more! 50 | } 51 | } 52 | ``` 53 | 54 | ## Planned Features 55 | 56 | - Mocking framework 57 | - Deployment management framework 58 | 59 | ## Contributing 60 | 61 | At this stage we are looking for all kinds of feedback, as the general direction of the project is not fully defined yet. If you have any ideas to improve naming, ergonomics, or anything else, please open an issue or a PR. 62 | -------------------------------------------------------------------------------- /docs/src/examples/results/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Working with result values 3 | 4 | Different methods of getting the underlyng value of a `Result` 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, StringResult} from "vulcan/test.sol"; 11 | // This import is just to demonstration, it's not meant to be imported on projects using Vulcan 12 | import {Ok} from "vulcan/_internal/Result.sol"; 13 | 14 | contract ResultExample is Test { 15 | function test() external { 16 | StringResult result = Ok(string("foo")); 17 | 18 | // Use unwrap to get the value or revert if the result is an `Error` 19 | string memory value = result.unwrap(); 20 | expect(value).toEqual("foo"); 21 | 22 | // Use expect to get the value or revert with a custom message if 23 | // the result is an `Error` 24 | value = result.expect("Result failed"); 25 | expect(value).toEqual("foo"); 26 | 27 | // Safely handling the result 28 | if (result.isOk()) { 29 | value = result.toValue(); 30 | expect(value).toEqual("foo"); 31 | } 32 | } 33 | } 34 | 35 | ``` 36 | 37 | ### Working with Errors 38 | 39 | Different ways of handling errors. 40 | 41 | ```solidity 42 | // SPDX-License-Identifier: MIT 43 | pragma solidity ^0.8.13; 44 | 45 | import {Test, commands, ctx, expect, CommandResult, CommandError} from "vulcan/test.sol"; 46 | 47 | contract ResultExample is Test { 48 | function test() external { 49 | // Run a non existent command 50 | CommandResult result = commands.run(["asdf12897u391723"]); 51 | 52 | ctx.expectRevert(); 53 | 54 | // Use unwrap to revert with the default error message 55 | result.unwrap(); 56 | 57 | ctx.expectRevert("Command not executed"); 58 | 59 | // Use expect to revert with a custom error message 60 | result.expect("Command not executed"); 61 | 62 | bool failed = false; 63 | 64 | // Handle the error manually 65 | if (result.isError()) { 66 | if (result.toError().matches(CommandError.NotExecuted)) { 67 | failed = true; 68 | } 69 | } 70 | 71 | expect(failed).toBeTrue(); 72 | } 73 | } 74 | 75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /src/_internal/Pointer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | type Pointer is bytes32; 5 | 6 | library LibPointer { 7 | function asBytes32(Pointer self) internal pure returns (bytes32) { 8 | return Pointer.unwrap(self); 9 | } 10 | 11 | function asString(Pointer self) internal pure returns (string memory val) { 12 | assembly { 13 | val := self 14 | } 15 | } 16 | 17 | function asBytes(Pointer self) internal pure returns (bytes memory val) { 18 | assembly { 19 | val := self 20 | } 21 | } 22 | 23 | function asBool(Pointer self) internal pure returns (bool val) { 24 | assembly { 25 | val := self 26 | } 27 | } 28 | 29 | function asUint256(Pointer self) internal pure returns (uint256 val) { 30 | assembly { 31 | val := self 32 | } 33 | } 34 | 35 | function asInt256(Pointer self) internal pure returns (int256 val) { 36 | assembly { 37 | val := self 38 | } 39 | } 40 | 41 | function asAddress(Pointer self) internal pure returns (address val) { 42 | assembly { 43 | val := self 44 | } 45 | } 46 | 47 | function toPointer(bytes32 value) internal pure returns (Pointer ptr) { 48 | assembly { 49 | ptr := value 50 | } 51 | } 52 | 53 | function toPointer(string memory value) internal pure returns (Pointer ptr) { 54 | assembly { 55 | ptr := value 56 | } 57 | } 58 | 59 | function toPointer(bytes memory value) internal pure returns (Pointer ptr) { 60 | assembly { 61 | ptr := value 62 | } 63 | } 64 | 65 | function toPointer(bool value) internal pure returns (Pointer ptr) { 66 | assembly { 67 | ptr := value 68 | } 69 | } 70 | 71 | function toPointer(uint256 value) internal pure returns (Pointer ptr) { 72 | assembly { 73 | ptr := value 74 | } 75 | } 76 | 77 | function toPointer(int256 value) internal pure returns (Pointer ptr) { 78 | assembly { 79 | ptr := value 80 | } 81 | } 82 | 83 | function toPointer(address value) internal pure returns (Pointer ptr) { 84 | assembly { 85 | ptr := value 86 | } 87 | } 88 | } 89 | 90 | using LibPointer for Pointer global; 91 | -------------------------------------------------------------------------------- /docs/src/references/Forks.md: -------------------------------------------------------------------------------- 1 | # Forks 2 | 3 | ## Custom types 4 | 5 | ### Fork 6 | 7 | ```solidity 8 | type Fork is uint256; 9 | ``` 10 | 11 | Holds a fork's id. 12 | 13 | ## forksUnsafe 14 | 15 | 16 | 17 | ### **create(string nameOrEndpoint) → (Fork)** 18 | 19 | Create a new fork using the provided endpoint. 20 | 21 | ### **createAtBlock(string nameOrEndpoint, uint256 blockNumber) → (Fork)** 22 | 23 | Create a new fork using the provided endpoint at a given block number. 24 | 25 | ### **createBeforeTx(string nameOrEndpoint, bytes32 txHash) → (Fork)** 26 | 27 | Create a new fork using the provided endpoint at a state right before the provided transaction hash. 28 | 29 | ### **select(Fork self) → (Fork)** 30 | 31 | Set the provided fork as the current active fork. 32 | 33 | ### **active() → (Fork)** 34 | 35 | Get the current active fork. 36 | 37 | ### **setBlockNumber(Fork self, uint256 blockNumber) → (Fork)** 38 | 39 | Set the block number of the provided fork. 40 | 41 | ### **beforeTx(Fork self, bytes32 txHash) → (Fork)** 42 | 43 | Set the provided fork to the state right before the provided transaction hash. 44 | 45 | ### **persistBetweenForks(address self) → (address)** 46 | 47 | Make the state of the provided address persist between forks. 48 | 49 | ### **persistBetweenForks(address who1, address who2)** 50 | 51 | Make the state of the provided addresses persist between forks. 52 | 53 | ### **persistBetweenForks(address who1, address who2, address who3)** 54 | 55 | Make the state of the provided addresses persist between forks. 56 | 57 | ### **persistBetweenForks(address[] whos)** 58 | 59 | Make the state of the provided addresses persist between forks. 60 | 61 | ### **stopPersist(address who) → (address)** 62 | 63 | Revoke the persistent state of the provided address. 64 | 65 | ### **stopPersist(address[] whos)** 66 | 67 | Revoke the persistent state of the provided addresses. 68 | 69 | ### **isPersistent(address who) → (bool)** 70 | 71 | Check if the provided address is being persisted between forks. 72 | 73 | ### **allowCheatcodes(address who) → (address)** 74 | 75 | Allow cheatcodes to be used by the provided address in forking mode. 76 | 77 | ### **executeTx(bytes32 txHash)** 78 | 79 | Executes an existing transaction in the current active fork. 80 | 81 | ### **executeTx(Fork self, bytes32 txHash) → (Fork)** 82 | 83 | Executes an existing transaction in the provided fork. 84 | 85 | -------------------------------------------------------------------------------- /test/modules/Fe.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test} from "src/test.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {commands, Command} from "src/test/Commands.sol"; 7 | import {fe, Fe} from "src/test/Fe.sol"; 8 | import {fs} from "src/test/Fs.sol"; 9 | import {strings} from "src/test/Strings.sol"; 10 | import {StringResult} from "src/test/Result.sol"; 11 | import {println} from "src/utils.sol"; 12 | 13 | contract FeTest is Test { 14 | function testToCommandAllSet() external { 15 | string memory filePath = "./filePath.fe"; 16 | Command memory command = 17 | fe.create().setFilePath(filePath).setCompilerPath("difffe").setOutputDir("./feoutput").toCommand(); 18 | 19 | expect(command.inputs.length).toEqual(7); 20 | expect(command.inputs[0]).toEqual("difffe"); 21 | expect(command.inputs[1]).toEqual("build"); 22 | expect(command.inputs[2]).toEqual("-e"); 23 | expect(command.inputs[3]).toEqual("bytecode"); 24 | expect(command.inputs[4]).toEqual("-o"); 25 | expect(command.inputs[5]).toEqual("./feoutput"); 26 | expect(command.inputs[6]).toEqual(filePath); 27 | } 28 | 29 | function testToCommandMinimumSet() external { 30 | string memory filePath = "./filePath.fe"; 31 | 32 | Command memory command = fe.create().setFilePath(filePath).toCommand(); 33 | 34 | expect(command.inputs.length).toEqual(7); 35 | expect(command.inputs[0]).toEqual("fe"); 36 | expect(command.inputs[1]).toEqual("build"); 37 | expect(command.inputs[2]).toEqual("-e"); 38 | expect(command.inputs[3]).toEqual("bytecode"); 39 | expect(command.inputs[4]).toEqual("-o"); 40 | expect(command.inputs[5]).toEqual("./out/__TMP/fe_builds"); 41 | expect(command.inputs[6]).toEqual("./filePath.fe"); 42 | } 43 | 44 | function testCompile() external { 45 | fe.create().setFilePath("./test/mocks/guest_book.fe").setOutputDir("./test/fixtures/fe/output").setOverwrite( 46 | true 47 | ).build().unwrap(); 48 | 49 | StringResult result = fs.readFile("./test/fixtures/fe/output/MyFeContract/MyFeContract.bin"); 50 | 51 | expect(bytes(result.unwrap()).length).toBeGreaterThan(0); 52 | } 53 | 54 | function testCompileAndRead() external { 55 | Fe memory feCmd = fe.create().setFilePath("./test/mocks/guest_book.fe").setOverwrite(true); 56 | 57 | feCmd.build().unwrap(); 58 | 59 | string memory expectedBytecode = "600180600c6000396000f3fe00"; 60 | 61 | expect(string(feCmd.getBytecode("MyFeContract").toValue())).toEqual(expectedBytecode); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docs/src/examples/accounts/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Create an address 3 | 4 | How to create a simple address 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, accounts} from "vulcan/test.sol"; 11 | 12 | contract AccountsExample is Test { 13 | function test() external { 14 | address alice = accounts.create(); 15 | 16 | expect(alice).not.toEqual(address(0)); 17 | } 18 | } 19 | 20 | ``` 21 | 22 | ### Create a labeled address 23 | 24 | Creating an address labeled as "Alice" 25 | 26 | ```solidity 27 | // SPDX-License-Identifier: MIT 28 | pragma solidity ^0.8.13; 29 | 30 | import {Test, expect, accounts} from "vulcan/test.sol"; 31 | 32 | contract AccountsExample is Test { 33 | function test() external { 34 | address alice = accounts.create("Alice"); 35 | 36 | expect(alice).not.toEqual(address(0)); 37 | } 38 | } 39 | 40 | ``` 41 | 42 | ### Create multiple addresses 43 | 44 | Creating multiple addresses 45 | 46 | ```solidity 47 | // SPDX-License-Identifier: MIT 48 | pragma solidity ^0.8.13; 49 | 50 | import {Test, expect, accounts} from "vulcan/test.sol"; 51 | 52 | contract AccountsExample is Test { 53 | function test() external { 54 | address[] memory addresses = accounts.createMany(10); 55 | 56 | expect(addresses.length).toEqual(10); 57 | } 58 | } 59 | 60 | ``` 61 | 62 | ### Create multiple labeled addresses with a prefix 63 | 64 | Creating multiple addresses labeled with the prefix `Account` 65 | 66 | ```solidity 67 | // SPDX-License-Identifier: MIT 68 | pragma solidity ^0.8.13; 69 | 70 | import {Test, expect, accounts} from "vulcan/test.sol"; 71 | 72 | contract AccountsExample is Test { 73 | function test() external { 74 | address[] memory addresses = accounts.createMany(10, "Account"); 75 | 76 | expect(addresses.length).toEqual(10); 77 | } 78 | } 79 | 80 | ``` 81 | 82 | ### Use method chaining on addresses 83 | 84 | Use method chaining on addresses to call multiple methods 85 | 86 | ```solidity 87 | // SPDX-License-Identifier: MIT 88 | pragma solidity ^0.8.13; 89 | 90 | import {Test, expect, accounts} from "vulcan/test.sol"; 91 | 92 | contract AccountsExample05 is Test { 93 | using accounts for address; 94 | 95 | function test() external { 96 | address alice = accounts.create("Alice").setNonce(666).setBalance(100e18); 97 | 98 | address bob = accounts.create("Bob").setBalance(10e18).impersonateOnce(); 99 | 100 | payable(alice).transfer(bob.balance); 101 | 102 | expect(alice.balance).toEqual(110e18); 103 | expect(alice.getNonce()).toEqual(666); 104 | expect(bob.balance).toEqual(0); 105 | } 106 | } 107 | 108 | ``` 109 | 110 | -------------------------------------------------------------------------------- /src/_internal/Events.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Vm as Hevm} from "forge-std/Vm.sol"; 5 | import "./Vulcan.sol"; 6 | 7 | library events { 8 | /// @dev Obtains the topic representation of an `uint256` parameter. 9 | /// @param _param The `uint256` value. 10 | /// @return The representation of `_param` as an event topic. 11 | function topic(uint256 _param) internal pure returns (bytes32) { 12 | return bytes32(_param); 13 | } 14 | 15 | /// @dev Obtains the topic representation of a `string` parameter. 16 | /// @param _param The `string` value. 17 | /// @return The representation of `_param` as an event topic. 18 | function topic(string memory _param) internal pure returns (bytes32) { 19 | return keccak256(bytes(_param)); 20 | } 21 | 22 | /// @dev Obtains the topic representation of an `address` parameter. 23 | /// @param _param The `address` value. 24 | /// @return The representation of `_param` as an event topic. 25 | function topic(address _param) internal pure returns (bytes32) { 26 | return bytes32(uint256(uint160(_param))); 27 | } 28 | 29 | /// @dev Obtains the topic representation of a `bytes32` parameter. 30 | /// @param _param The `bytes32` value. 31 | /// @return The representation of `_param` as an event topic. 32 | function topic(bytes32 _param) internal pure returns (bytes32) { 33 | return _param; 34 | } 35 | 36 | /// @dev Obtains the topic representation of a `bytes` parameter. 37 | /// @param _param The `bytes` value. 38 | /// @return The representation of `_param` as an event topic. 39 | function topic(bytes memory _param) internal pure returns (bytes32) { 40 | return keccak256(_param); 41 | } 42 | 43 | /// @dev Obtains the topic representation of a `bool` parameter. 44 | /// @param _param The `bool` value. 45 | /// @return The representation of `_param` as an event topic. 46 | function topic(bool _param) internal pure returns (bytes32) { 47 | return bytes32(uint256(_param ? 1 : 0)); 48 | } 49 | 50 | /// @dev Obtains the topic representation of a `int256` parameter. 51 | /// @param _param The `int256` value. 52 | /// @return The representation of `_param` as an event topic. 53 | function topic(int256 _param) internal pure returns (bytes32) { 54 | return bytes32(uint256(_param)); 55 | } 56 | 57 | /// @dev Starts recording all transactions logs. 58 | function recordLogs() internal { 59 | vulcan.hevm.recordLogs(); 60 | } 61 | 62 | /// @dev Obtains all recorded transactions logs. 63 | function getRecordedLogs() internal returns (Log[] memory logs) { 64 | Hevm.Log[] memory recorded = vulcan.hevm.getRecordedLogs(); 65 | assembly { 66 | logs := recorded 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/modules/Commands.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test} from "src/test.sol"; 5 | import {commands, Command, CommandResult, CommandOutput, CommandError} from "src/test/Commands.sol"; 6 | import {ctx} from "src/test/Context.sol"; 7 | import {Error} from "src/test/Error.sol"; 8 | import {expect} from "src/test/Expect.sol"; 9 | 10 | contract CommandsTest is Test { 11 | using commands for *; 12 | 13 | function testCanCreateEmptyCommand() external { 14 | Command memory cmd = commands.create().arg("echo"); 15 | 16 | expect(cmd.toString()).toEqual("echo"); 17 | } 18 | 19 | function testItCanRunCommands() external { 20 | string[2] memory inputs = ["echo", "'Hello, World!'"]; 21 | 22 | Command memory cmd = commands.create(inputs[0]).arg(inputs[1]); 23 | 24 | expect(string(cmd.run().unwrap().stdout)).toEqual(inputs[1]); 25 | } 26 | 27 | function testItCanRunCommandsDirectly() external { 28 | string[2] memory inputs = ["echo", "'Hello, World!'"]; 29 | 30 | CommandResult result = commands.run(inputs); 31 | 32 | expect(string(result.unwrap().stdout)).toEqual(inputs[1]); 33 | } 34 | 35 | function testCommandToString() external { 36 | Command memory ping = commands.create("ping").args(["-c", "1", "nomoi.xyz"]); 37 | 38 | expect(ping.toString()).toEqual("ping -c 1 nomoi.xyz"); 39 | } 40 | 41 | function testIsOk() external { 42 | CommandResult result = commands.run(["echo", "'Hello World'"]); 43 | 44 | expect(result.isOk()).toBeTrue(); 45 | } 46 | 47 | function testIsNotOk() external { 48 | CommandResult result = commands.run(["nonexistentcommand", "--hlkfshjfhjas"]); 49 | 50 | expect(result.isOk()).toBeFalse(); 51 | } 52 | 53 | function testIsError() external { 54 | CommandResult result = commands.run(["nonexistentcommand", "--hlkfshjfhjas"]); 55 | 56 | expect(result.isError()).toBeTrue(); 57 | } 58 | 59 | function testIsNotError() external { 60 | CommandResult result = commands.run(["echo", "'Hello World'"]); 61 | 62 | expect(result.isError()).toBeFalse(); 63 | } 64 | 65 | function testUnwrap() external { 66 | CommandResult result = commands.run(["echo", "'Hello World'"]); 67 | 68 | bytes memory output = result.unwrap().stdout; 69 | 70 | expect(string(output)).toEqual("'Hello World'"); 71 | } 72 | 73 | function testUnwrapReverts() external { 74 | CommandResult result = commands.run(["nonexistentcommand", "--hlkfshjfhjas"]); 75 | 76 | ctx.expectRevert(); 77 | 78 | result.unwrap(); 79 | } 80 | 81 | function testCommandError() external { 82 | Error error = CommandError.NotExecuted("Test"); 83 | 84 | expect(error.matches(CommandError.NotExecuted)).toBeTrue(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/modules/Strings.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test} from "../../src/test.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {strings} from "src/test/Strings.sol"; 7 | 8 | contract StringsTest is Test { 9 | using strings for *; 10 | 11 | function testItCanTransformAddressToString() external { 12 | address addr = address(1); 13 | 14 | expect(addr.toString()).toEqual(string("0x0000000000000000000000000000000000000001")); 15 | } 16 | 17 | function testItCanTransformBytesToString() external { 18 | bytes memory value = abi.encode(uint256(1)); 19 | 20 | expect(value.toString()).toEqual(string("0x0000000000000000000000000000000000000000000000000000000000000001")); 21 | } 22 | 23 | function testItCanTransformBytes32ToString() external { 24 | bytes32 value = bytes32(uint256(1)); 25 | 26 | expect(value.toString()).toEqual(string("0x0000000000000000000000000000000000000000000000000000000000000001")); 27 | } 28 | 29 | function testItCanTransformBoolToString() external { 30 | expect(true.toString()).toEqual(string("true")); 31 | expect(false.toString()).toEqual(string("false")); 32 | } 33 | 34 | function testItCanTransformUintToString() external { 35 | expect(uint256(1337).toString()).toEqual("1337"); 36 | } 37 | 38 | function testItCanTransformIntToString() external { 39 | expect(int256(-1).toString()).toEqual("-1"); 40 | expect(int256(1).toString()).toEqual("1"); 41 | } 42 | 43 | function testItCanParseBytes() external { 44 | string memory value = "0x0000000000000000000000000000000000000000000000000000000000000002"; 45 | 46 | expect(value.parseBytes()).toEqual(abi.encode(uint256(2))); 47 | } 48 | 49 | function testItCanParseAddress() external { 50 | string memory value = "0x0000000000000000000000000000000000000001"; 51 | 52 | expect(value.parseAddress()).toEqual(address(1)); 53 | } 54 | 55 | function testItCanParseUint() external { 56 | string memory value = "1337"; 57 | 58 | expect(value.parseUint()).toEqual(uint256(1337)); 59 | } 60 | 61 | function testItCanParseInt() external { 62 | string memory value = "-1"; 63 | 64 | expect(value.parseInt()).toEqual(int256(-1)); 65 | } 66 | 67 | function testItCanParseBytes32() external { 68 | string memory value = "0x0000000000000000000000000000000000000000000000000000000000000003"; 69 | 70 | expect(value.parseBytes32()).toEqual(bytes32(uint256(3))); 71 | } 72 | 73 | function testItCanParseBool() external { 74 | string memory value = "true"; 75 | 76 | expect(value.parseBool()).toBeTrue(); 77 | 78 | value = "false"; 79 | 80 | expect(value.parseBool()).toBeFalse(); 81 | } 82 | 83 | function testItCanParseJson() external { 84 | string memory value = '{"foo": "bar"}'; 85 | 86 | expect(value.parseJson().expect("Invalid JSON").getString(".foo")).toEqual("bar"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/modules/Huff.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test} from "src/test.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {Command, CommandResult} from "src/test/Commands.sol"; 7 | import {huff, Huffc} from "src/test/Huff.sol"; 8 | 9 | bytes32 constant SLOT = 0x0000000000000000000000000000000000000000000000000000000000000001; 10 | bytes32 constant OTHER_SLOT = 0x0000000000000000000000000000000000000000000000000000000000000002; 11 | string constant SLOT_AS_STRING = "SLOT=0x0000000000000000000000000000000000000000000000000000000000000001"; 12 | string constant SLOTS_AS_STRING = 13 | "SLOT=0x0000000000000000000000000000000000000000000000000000000000000001 OTHER_SLOT=0x0000000000000000000000000000000000000000000000000000000000000002"; 14 | 15 | contract HuffTest is Test { 16 | function testToCommandAllSet() external { 17 | Command memory command = huff.create().setCompilerPath("diffhuff").setFilePath("./filePath.huff").setOutputPath( 18 | "./outputPath.json" 19 | ).setMainName("ALT_MAIN").setConstructorName("ALT_CONSTRUCTOR").setOnlyRuntime(true).addConstantOverride( 20 | "SLOT", SLOT 21 | ).addConstantOverride("OTHER_SLOT", OTHER_SLOT).toCommand(); 22 | 23 | expect(command.inputs.length).toEqual(12); 24 | expect(command.inputs[0]).toEqual("diffhuff"); 25 | expect(command.inputs[1]).toEqual("-ao"); 26 | expect(command.inputs[2]).toEqual("./outputPath.json"); 27 | expect(command.inputs[3]).toEqual("-m"); 28 | expect(command.inputs[4]).toEqual("ALT_MAIN"); 29 | expect(command.inputs[5]).toEqual("-l"); 30 | expect(command.inputs[6]).toEqual("ALT_CONSTRUCTOR"); 31 | expect(command.inputs[7]).toEqual("-r"); 32 | expect(command.inputs[8]).toEqual("-c"); 33 | expect(command.inputs[9]).toEqual(SLOTS_AS_STRING); 34 | expect(command.inputs[10]).toEqual("-b"); 35 | expect(command.inputs[11]).toEqual("./filePath.huff"); 36 | } 37 | 38 | function testToCommandConstantOverrideSingle() external { 39 | Command memory command = 40 | huff.create().setFilePath("./filePath.huff").addConstantOverride("SLOT", SLOT).toCommand(); 41 | 42 | expect(command.inputs.length).toEqual(5); 43 | expect(command.inputs[0]).toEqual("huffc"); 44 | expect(command.inputs[1]).toEqual("-c"); 45 | expect(command.inputs[2]).toEqual(SLOT_AS_STRING); 46 | expect(command.inputs[3]).toEqual("-b"); 47 | expect(command.inputs[4]).toEqual("./filePath.huff"); 48 | } 49 | 50 | function testToCommandMinimumSet() external { 51 | Command memory command = huff.create().setFilePath("./filePath.huff").toCommand(); 52 | 53 | expect(command.inputs.length).toEqual(3); 54 | expect(command.inputs[0]).toEqual("huffc"); 55 | expect(command.inputs[1]).toEqual("-b"); 56 | expect(command.inputs[2]).toEqual("./filePath.huff"); 57 | } 58 | 59 | function testCompile() external { 60 | CommandResult initcode = huff.create().setFilePath("./test/mocks/Getter.huff").compile(); 61 | expect(initcode.unwrap().stdout.length).toBeGreaterThan(0); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docs/src/examples/requests/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Sending requests 3 | 4 | How to send requests to an http server 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, request, Response} from "vulcan/test.sol"; 11 | 12 | contract RequestExample is Test { 13 | function test() external { 14 | Response memory getResponse = request.create().get("https://httpbin.org/get").send().unwrap(); 15 | Response memory postResponse = request.create().post("https://httpbin.org/post").send().unwrap(); 16 | Response memory patchResponse = request.create().patch("https://httpbin.org/patch").send().unwrap(); 17 | Response memory putResponse = request.create().put("https://httpbin.org/put").send().unwrap(); 18 | Response memory deleteResponse = request.create().del("https://httpbin.org/delete").send().unwrap(); 19 | 20 | expect(getResponse.status).toEqual(200); 21 | expect(postResponse.status).toEqual(200); 22 | expect(patchResponse.status).toEqual(200); 23 | expect(putResponse.status).toEqual(200); 24 | expect(deleteResponse.status).toEqual(200); 25 | } 26 | } 27 | 28 | ``` 29 | 30 | ### Request authentication 31 | 32 | How to use different methods of authentication 33 | 34 | ```solidity 35 | // SPDX-License-Identifier: MIT 36 | pragma solidity ^0.8.13; 37 | 38 | import {Test, expect, request, Response, RequestClient} from "vulcan/test.sol"; 39 | 40 | contract RequestExample is Test { 41 | function test() external { 42 | RequestClient memory client = request.create(); 43 | 44 | Response memory basicAuthRes = 45 | client.get("https://httpbin.org/basic-auth/user/pass").basicAuth("user", "pass").send().unwrap(); 46 | 47 | expect(basicAuthRes.status).toEqual(200); 48 | 49 | Response memory bearerAuthRes = client.get("https://httpbin.org/bearer").bearerAuth("token").send().unwrap(); 50 | 51 | expect(bearerAuthRes.status).toEqual(200); 52 | } 53 | } 54 | 55 | ``` 56 | 57 | ### Working with headers 58 | 59 | Using the request module to work with request headers 60 | 61 | ```solidity 62 | // SPDX-License-Identifier: MIT 63 | pragma solidity ^0.8.13; 64 | 65 | import {Test, expect, request, Headers, Response, Request, RequestClient} from "vulcan/test.sol"; 66 | 67 | contract RequestExample is Test { 68 | function test() external { 69 | // Setting a default header as key value 70 | RequestClient memory client = request.create().defaultHeader("X-Foo", "true"); 71 | 72 | expect(client.headers.get("X-Foo")).toEqual("true"); 73 | 74 | // The default header gets passed to the request 75 | Request memory req = client.post("https://some-http-server.com").request.unwrap(); 76 | 77 | expect(req.headers.get("X-Foo")).toEqual("true"); 78 | 79 | // Setting multiple headers with a Header variable 80 | Headers headers = request.createHeaders().insert("X-Bar", "true").insert("X-Baz", "true"); 81 | client = request.create().defaultHeaders(headers); 82 | 83 | expect(client.headers.get("X-Bar")).toEqual("true"); 84 | expect(client.headers.get("X-Baz")).toEqual("true"); 85 | } 86 | } 87 | 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /docs/src/examples/fs/example.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | ### Reading files 3 | 4 | Read files as string or bytes 5 | 6 | ```solidity 7 | // SPDX-License-Identifier: MIT 8 | pragma solidity ^0.8.13; 9 | 10 | import {Test, expect, fs, BytesResult, StringResult} from "vulcan/test.sol"; 11 | 12 | contract FsExample is Test { 13 | // These files are available only on the context of vulcan 14 | // You will need to provide your own files and edit the read permissions accordingly 15 | string constant HELLO_WORLD = "./test/fixtures/fs/read/hello_world.txt"; 16 | string constant BINARY_TEST_FILE = "./test/fixtures/fs/write/test_binary.txt"; 17 | 18 | function test() external { 19 | string memory stringResult = fs.readFile(HELLO_WORLD).unwrap(); 20 | expect(stringResult).toEqual("Hello, World!\n"); 21 | 22 | bytes memory bytesResult = fs.readFileBinary(HELLO_WORLD).unwrap(); 23 | expect(bytesResult).toEqual("Hello, World!\n"); 24 | } 25 | } 26 | 27 | ``` 28 | 29 | ### Writing files 30 | 31 | Write files as strings or bytes 32 | 33 | ```solidity 34 | // SPDX-License-Identifier: MIT 35 | pragma solidity ^0.8.13; 36 | 37 | import {Test, expect, fs, BytesResult, StringResult, EmptyResult} from "vulcan/test.sol"; 38 | 39 | contract FsExample is Test { 40 | // These files are available only on the context of vulcan 41 | // You will need to provide your own files and edit the read permissions accordingly 42 | string constant TEXT_TEST_FILE = "./test/fixtures/fs/write/example.txt"; 43 | 44 | function test() external { 45 | // Write string 46 | fs.writeFile(TEXT_TEST_FILE, "This is a test").expect("Failed to write file"); 47 | 48 | string memory readStringResult = fs.readFile(TEXT_TEST_FILE).unwrap(); 49 | 50 | expect(readStringResult).toEqual("This is a test"); 51 | 52 | // Write binary 53 | fs.writeFileBinary(TEXT_TEST_FILE, "This is a test in binary").expect("Failed to write file"); 54 | 55 | bytes memory readBytesResult = fs.readFileBinary(TEXT_TEST_FILE).unwrap(); 56 | 57 | expect(readBytesResult).toEqual("This is a test in binary"); 58 | } 59 | } 60 | 61 | ``` 62 | 63 | ### Other operations 64 | 65 | Obtain metadata and check if file exists 66 | 67 | ```solidity 68 | // SPDX-License-Identifier: MIT 69 | pragma solidity ^0.8.13; 70 | 71 | import {Test} from "vulcan/test.sol"; 72 | import {expect} from "vulcan/test/Expect.sol"; 73 | import {fs, FsMetadata} from "vulcan/test/Fs.sol"; 74 | import {BoolResult} from "vulcan/test/Result.sol"; 75 | 76 | contract FsExample is Test { 77 | // These files are available only on the context of vulcan 78 | // You will need to provide your own files and edit the read permissions accordingly 79 | string constant READ_EXAMPLE = "./test/fixtures/fs/read/hello_world.txt"; 80 | string constant NOT_FOUND_EXAMPLE = "./test/fixtures/fs/read/lkjjsadflkjasdf.txt"; 81 | 82 | function test() external { 83 | FsMetadata memory metadata = fs.metadata(READ_EXAMPLE).expect("Failed to get metadata"); 84 | expect(metadata.isDir).toBeFalse(); 85 | 86 | bool exists = fs.fileExists(READ_EXAMPLE).unwrap(); 87 | expect(exists).toBeTrue(); 88 | 89 | exists = fs.fileExists(NOT_FOUND_EXAMPLE).unwrap(); 90 | expect(exists).toBeFalse(); 91 | } 92 | } 93 | 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > :warning: **Vulcan is not actively maintained. Thank you for your support.** 2 | 3 |

4 | Vulcan 5 |

6 | 7 | ![test workflow](https://github.com/nomoixyz/vulcan/actions/workflows/forge.yml/badge.svg) 8 | [![GitHub release](https://img.shields.io/github/v/release/nomoixyz/vulcan?link=https%3A%2F%2Fgithub.com%2Fnomoixyz%2Fvulcan%2Freleases)](https://github.com/nomoixyz/vulcan/releases) 9 | [![Telegram](https://img.shields.io/badge/chat-gray?logo=telegram)](https://t.me/+XYA0By2mKls3OWIx) 10 | 11 | 12 | 13 | Development framework for Foundry projects, with a focus on developer experience and readability. 14 | 15 | Built on top of [`forge-std`](https://github.com/foundry-rs/forge-std) :heart: 16 | 17 | Initially, Vulcan will provide functionality similar to what is already included in forge's VM and `forge-std`. 18 | 19 | Over time, Vulcan will grow to include more functionality and utilities, eventually becoming a feature-rich development framework. 20 | 21 | ## Installation 22 | 23 | Install the latest release by running: 24 | 25 | ``` 26 | $ forge install nomoixyz/vulcan 27 | ``` 28 | 29 | Or install a specific release with: 30 | 31 | ``` 32 | $ forge install nomoixyz/vulcan@x.y.z 33 | ``` 34 | 35 | Release tags can be found [here](https://github.com/nomoixyz/vulcan/releases) 36 | 37 | ## Usage 38 | 39 | See the [Vulcan Book](https://nomoixyz.github.io/vulcan/) for detailed usage information. 40 | 41 | ## Why Vulcan? 42 | 43 | Our goal is to provide: 44 | 45 | - Better naming for VM functionality (no more `prank`, `roll`, `warp`, ...) 46 | - A testing framework with better readability and a familiar syntax 47 | - Improved ergonomics 48 | - [Huff language](https://huff.sh/) support out of the box 49 | 50 | Vulcan test example: 51 | 52 | ```solidity 53 | import { Test, expect, commands, Command, CommandResult, println } from "vulcan/test.sol"; 54 | 55 | contract TestSomething is Test { 56 | 57 | function testSomething() external { 58 | // Format strings with rust-like syntax 59 | println("Hello {s}", abi.encode("world!")); // Hello world! 60 | 61 | // Format numbers as decimals 62 | println("Balance: {u:d18}", abi.encode(1e17)); // Balance: 0.1 63 | 64 | // Nice external command API! 65 | Command memory ping = commands.create("ping").args(["-c", "1"]); 66 | CommandResult pingResult = ping.arg("etherscan.io").run(); 67 | 68 | // Rust-like results 69 | bytes memory pingOutput = pingResult.expect("Ping command failed").stdout; 70 | 71 | println("Ping result: {s}", abi.encode(pingOutput)); 72 | 73 | // Expect style assertions! 74 | expect(pingResult.isError()).toBeFalse(); 75 | expect(pingResult.isOk()).toBeTrue(); 76 | 77 | // And much more! 78 | } 79 | } 80 | ``` 81 | 82 | ## Planned Features 83 | 84 | - Mocking framework 85 | - Deployment management framework 86 | 87 | ## Contributing 88 | 89 | At this stage we are looking for all kinds of feedback, as the general direction of the project is not fully defined yet. If you have any ideas to improve naming, ergonomics, or anything else, please open an issue or a PR. 90 | 91 | ## Acknowledgments 92 | 93 | Some of the ideas and code in Vulcan are directly inspired by or adapted from the following projects: 94 | 95 | - [forge-std](https://github.com/foundry-rs/forge-std/) 96 | - [memester-xyz/surl](https://github.com/memester-xyz/surl) 97 | -------------------------------------------------------------------------------- /src/_internal/Fe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import "./Commands.sol"; 5 | import "./Strings.sol"; 6 | import "./Fs.sol"; 7 | import {formatError} from "./Utils.sol"; 8 | import {BoolResult, BytesResult} from "./Result.sol"; 9 | 10 | struct Fe { 11 | string compilerPath; 12 | string filePath; 13 | string outputDir; 14 | bool overwrite; 15 | } 16 | 17 | library fe { 18 | using strings for bytes32; 19 | 20 | /// @dev Creates a new `Fe` struct with default values. 21 | function create() internal pure returns (Fe memory) { 22 | return Fe({compilerPath: "fe", filePath: "", outputDir: "", overwrite: false}); 23 | } 24 | 25 | /// @dev Builds a binary file from a `.fe` file. 26 | /// @param self The `Fe` struct to build. 27 | function build(Fe memory self) internal returns (CommandResult) { 28 | return self.toCommand().run(); 29 | } 30 | 31 | /// @dev Transforms a `Fe` struct to a `Command` struct. 32 | /// @param self The `Fe` struct to transform. 33 | function toCommand(Fe memory self) internal pure returns (Command memory) { 34 | Command memory command = commands.create(self.compilerPath); 35 | 36 | command = command.arg("build").args(["-e", "bytecode"]); 37 | 38 | if (bytes(self.outputDir).length == 0) { 39 | self.outputDir = "./out/__TMP/fe_builds"; 40 | } 41 | 42 | command = command.args(["-o", self.outputDir]); 43 | if (self.overwrite) command = command.arg("--overwrite"); 44 | 45 | command = command.arg(self.filePath); 46 | 47 | return command; 48 | } 49 | 50 | /// @dev Sets the `fe` compiler path. 51 | /// @param self The `Fe` struct to modify. 52 | /// @param compilerPath The new compiler path. 53 | function setCompilerPath(Fe memory self, string memory compilerPath) internal pure returns (Fe memory) { 54 | self.compilerPath = compilerPath; 55 | return self; 56 | } 57 | 58 | /// @dev Sets the `fe` file path to build. 59 | /// @param self The `Fe` struct to modify. 60 | /// @param filePath The path to the `.fe` file to build. 61 | function setFilePath(Fe memory self, string memory filePath) internal pure returns (Fe memory) { 62 | self.filePath = filePath; 63 | return self; 64 | } 65 | 66 | /// @dev Sets the `fe` build command output directory. 67 | /// @param self The `Fe` struct to modify. 68 | /// @param outputDir The directory where the binary file will be saved. 69 | function setOutputDir(Fe memory self, string memory outputDir) internal pure returns (Fe memory) { 70 | self.outputDir = outputDir; 71 | return self; 72 | } 73 | 74 | /// @dev Sets the `fe` build command overwrite flag. 75 | /// @param self The `Fe` struct to modify. 76 | /// @param overwrite If true it will overwrite the `outputDir with the new build binaries. 77 | function setOverwrite(Fe memory self, bool overwrite) internal pure returns (Fe memory) { 78 | self.overwrite = overwrite; 79 | return self; 80 | } 81 | 82 | /// @dev Obtains the bytecode of a compiled contract with `contractName`. 83 | /// @param contractName The name of the contract from which to retrive the bytecode. 84 | function getBytecode(Fe memory self, string memory contractName) internal view returns (BytesResult) { 85 | string memory path = string.concat(self.outputDir, "/", contractName, "/", contractName, ".bin"); 86 | 87 | return fs.readFileBinary(path); 88 | } 89 | 90 | function _formatError(string memory func, string memory message) private pure returns (string memory) { 91 | return formatError("fe", func, message); 92 | } 93 | } 94 | 95 | using fe for Fe global; 96 | -------------------------------------------------------------------------------- /src/_internal/Strings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {vulcan} from "./Vulcan.sol"; 5 | import {json, JsonResult} from "./Json.sol"; 6 | import {fmt} from "./Fmt.sol"; 7 | 8 | library strings { 9 | function format(string memory template, bytes memory args) public pure returns (string memory) { 10 | return fmt.format(template, args); 11 | } 12 | 13 | /// @dev Transforms an address to a string. 14 | /// @param value The address to parse. 15 | /// @return The string representation of `value`. 16 | function toString(address value) internal pure returns (string memory) { 17 | return vulcan.hevm.toString(value); 18 | } 19 | 20 | /// @dev Transforms a byte array to a string. 21 | /// @param value The byte array to parse. 22 | /// @return The string representation of `value`. 23 | function toString(bytes memory value) internal pure returns (string memory) { 24 | return vulcan.hevm.toString(value); 25 | } 26 | 27 | /// @dev Transforms a bytes32 to a string. 28 | /// @param value The bytes32 to parse. 29 | /// @return The string representation of `value`. 30 | function toString(bytes32 value) internal pure returns (string memory) { 31 | return vulcan.hevm.toString(value); 32 | } 33 | 34 | /// @dev Transforms a boolean to a string. 35 | /// @param value The boolean to parse. 36 | /// @return The string representation of `value`. 37 | function toString(bool value) internal pure returns (string memory) { 38 | return vulcan.hevm.toString(value); 39 | } 40 | 41 | /// @dev Transforms an uint256 to a string. 42 | /// @param value The uint256 to parse. 43 | /// @return The string representation of `value`. 44 | function toString(uint256 value) internal pure returns (string memory) { 45 | return vulcan.hevm.toString(value); 46 | } 47 | 48 | /// @dev Transforms an int256 to a string. 49 | /// @param value The int256 to parse. 50 | /// @return The string representation of `value`. 51 | function toString(int256 value) internal pure returns (string memory) { 52 | return vulcan.hevm.toString(value); 53 | } 54 | 55 | /// @dev Parses a byte array string. 56 | /// @param value The string to parse. 57 | /// @return The parsed byte array. 58 | function parseBytes(string memory value) internal pure returns (bytes memory) { 59 | return vulcan.hevm.parseBytes(value); 60 | } 61 | 62 | /// @dev Parses an address string. 63 | /// @param value The string to parse. 64 | /// @return The parsed address. 65 | function parseAddress(string memory value) internal pure returns (address) { 66 | return vulcan.hevm.parseAddress(value); 67 | } 68 | 69 | /// @dev Parses an uint256 string. 70 | /// @param value The string to parse. 71 | /// @return The parsed uint256. 72 | function parseUint(string memory value) internal pure returns (uint256) { 73 | return vulcan.hevm.parseUint(value); 74 | } 75 | 76 | /// @dev Parses an int256 string. 77 | /// @param value The string to parse. 78 | /// @return The parsed int256. 79 | function parseInt(string memory value) internal pure returns (int256) { 80 | return vulcan.hevm.parseInt(value); 81 | } 82 | 83 | /// @dev Parses a bytes32 string. 84 | /// @param value The string to parse. 85 | /// @return The parsed bytes32. 86 | function parseBytes32(string memory value) internal pure returns (bytes32) { 87 | return vulcan.hevm.parseBytes32(value); 88 | } 89 | 90 | /// @dev Parses a boolean string. 91 | /// @param value The string to parse. 92 | /// @return The parsed boolean. 93 | function parseBool(string memory value) internal pure returns (bool) { 94 | return vulcan.hevm.parseBool(value); 95 | } 96 | 97 | /// @dev Parses a JSON string. 98 | /// @param value The string to parse 99 | function parseJson(string memory value) internal returns (JsonResult) { 100 | return json.create(value); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/ExampleTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.13 <0.9.0; 2 | 3 | import {Test, expect, accounts, ctx, vulcan, accounts, println} from "../src/test.sol"; 4 | import {Sender} from "./mocks/Sender.sol"; 5 | 6 | contract ExampleTest is Test { 7 | using vulcan for *; 8 | using accounts for *; 9 | 10 | function testSetBlockNumber() external { 11 | uint256 blockNumber = block.number + 1000; 12 | ctx.setBlockNumber(blockNumber); 13 | 14 | expect(block.number).toEqual(blockNumber); 15 | } 16 | 17 | function testBoolExpectation() external { 18 | expect(true).toBeTrue(); 19 | expect(false).toBeFalse(); 20 | expect(true).toEqual(true); 21 | expect(false).toEqual(false); 22 | } 23 | 24 | function testConsoleLog() external view { 25 | println("hello world"); 26 | } 27 | 28 | function testGetNonce() external { 29 | expect(accounts.getNonce(address(1))).toEqual(0); 30 | } 31 | 32 | function testSetNonce() external { 33 | uint64 nonce = 1337; 34 | address target = address(1); 35 | 36 | expect(accounts.setNonce(target, nonce).getNonce()).toEqual(nonce); 37 | } 38 | 39 | function testChainedAddress() external { 40 | uint256 balance = 1e18; 41 | uint64 nonce = 1337; 42 | 43 | address alice = accounts.create("ALICE").setBalance(balance).setNonce(nonce); 44 | 45 | expect(alice.balance).toEqual(balance); 46 | expect(accounts.getNonce(alice)).toEqual(nonce); 47 | 48 | Sender sender = new Sender(); 49 | address bob = accounts.create("BOB").impersonateOnce().setNonce(nonce).setBalance(balance); 50 | expect(sender.get()).toEqual(bob); 51 | expect(sender.get()).toEqual(address(this)); 52 | } 53 | 54 | function testSetBlockBaseFee() external { 55 | uint256 baseFee = 1337; 56 | ctx.setBlockBaseFee(baseFee); 57 | 58 | expect(block.basefee).toEqual(baseFee); 59 | } 60 | 61 | function testSetChainId() external { 62 | uint64 chainId = 1337; 63 | ctx.setChainId(chainId); 64 | 65 | expect(block.chainid).toEqual(chainId); 66 | } 67 | 68 | function testImpersonateOnce() external { 69 | address expectedSender = address(1337); 70 | address expectedOrigin = address(7331); 71 | Sender sender = new Sender(); 72 | 73 | accounts.impersonateOnce(expectedSender); 74 | expect(sender.get()).toEqual(expectedSender); 75 | expect(sender.get()).toEqual(address(this)); 76 | 77 | accounts.impersonateOnce(expectedSender, expectedOrigin); 78 | (address resultSender, address resultOrigin) = sender.getWithOrigin(); 79 | expect(resultSender).toEqual(expectedSender); 80 | expect(resultOrigin).toEqual(expectedOrigin); 81 | (resultSender, resultOrigin) = sender.getWithOrigin(); 82 | expect(resultSender).toEqual(address(this)); 83 | expect(resultOrigin).toEqual(tx.origin); 84 | 85 | accounts.impersonate(expectedSender); 86 | expect(sender.get()).toEqual(expectedSender); 87 | expect(sender.get()).toEqual(expectedSender); 88 | expect(sender.get()).toEqual(expectedSender); 89 | accounts.stopImpersonate(); 90 | 91 | expect(sender.get()).toEqual(address(this)); 92 | 93 | accounts.impersonate(expectedSender, expectedOrigin); 94 | (resultSender, resultOrigin) = sender.getWithOrigin(); 95 | expect(resultSender).toEqual(expectedSender); 96 | expect(resultOrigin).toEqual(expectedOrigin); 97 | (resultSender, resultOrigin) = sender.getWithOrigin(); 98 | expect(resultSender).toEqual(expectedSender); 99 | expect(resultOrigin).toEqual(expectedOrigin); 100 | (resultSender, resultOrigin) = sender.getWithOrigin(); 101 | expect(resultSender).toEqual(expectedSender); 102 | expect(resultOrigin).toEqual(expectedOrigin); 103 | accounts.stopImpersonate(); 104 | 105 | (resultSender, resultOrigin) = sender.getWithOrigin(); 106 | expect(resultSender).toEqual(address(this)); 107 | expect(resultOrigin).toEqual(tx.origin); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/_internal/Huff.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import "./Commands.sol"; 5 | import "./Strings.sol"; 6 | import {formatError} from "./Utils.sol"; 7 | 8 | struct Huffc { 9 | string compilerPath; 10 | string filePath; 11 | string outputPath; 12 | string mainName; 13 | string constructorName; 14 | bool onlyRuntime; 15 | string[] constantOverrides; 16 | } 17 | 18 | using huff for Huffc global; 19 | 20 | library huff { 21 | using strings for bytes32; 22 | 23 | function create() internal pure returns (Huffc memory) { 24 | return Huffc({ 25 | compilerPath: "huffc", 26 | filePath: "", 27 | outputPath: "", 28 | mainName: "", 29 | constructorName: "", 30 | onlyRuntime: false, 31 | constantOverrides: new string[](0) 32 | }); 33 | } 34 | 35 | function compile(Huffc memory self) internal returns (CommandResult) { 36 | return self.toCommand().run(); 37 | } 38 | 39 | function toCommand(Huffc memory self) internal pure returns (Command memory) { 40 | Command memory command = commands.create(self.compilerPath); 41 | require(bytes(self.filePath).length > 0, _formatError("toCommand(Huffc)", "self.filePath not set")); 42 | 43 | if (bytes(self.outputPath).length > 0) command = command.args(["-ao", self.outputPath]); 44 | if (bytes(self.mainName).length > 0) command = command.args(["-m", self.mainName]); 45 | if (bytes(self.constructorName).length > 0) command = command.args(["-l", self.constructorName]); 46 | if (self.onlyRuntime) command = command.args(["-r"]); 47 | if (self.constantOverrides.length > 0) { 48 | string memory overrides = ""; 49 | for (uint256 i; i < self.constantOverrides.length; i++) { 50 | overrides = string.concat(overrides, (i == 0 ? "" : " "), self.constantOverrides[i]); 51 | } 52 | command = command.args(["-c", overrides]); 53 | } 54 | 55 | // MUST be last 56 | command = command.args(["-b", self.filePath]); 57 | return command; 58 | } 59 | 60 | function setCompilerPath(Huffc memory self, string memory compilerPath) internal pure returns (Huffc memory) { 61 | self.compilerPath = compilerPath; 62 | return self; 63 | } 64 | 65 | function setFilePath(Huffc memory self, string memory filePath) internal pure returns (Huffc memory) { 66 | self.filePath = filePath; 67 | return self; 68 | } 69 | 70 | function setOutputPath(Huffc memory self, string memory outputPath) internal pure returns (Huffc memory) { 71 | self.outputPath = outputPath; 72 | return self; 73 | } 74 | 75 | function setMainName(Huffc memory self, string memory mainName) internal pure returns (Huffc memory) { 76 | self.mainName = mainName; 77 | return self; 78 | } 79 | 80 | function setConstructorName(Huffc memory self, string memory constructorName) 81 | internal 82 | pure 83 | returns (Huffc memory) 84 | { 85 | self.constructorName = constructorName; 86 | return self; 87 | } 88 | 89 | function setOnlyRuntime(Huffc memory self, bool onlyRuntime) internal pure returns (Huffc memory) { 90 | self.onlyRuntime = onlyRuntime; 91 | return self; 92 | } 93 | 94 | function addConstantOverride(Huffc memory self, string memory const, bytes32 value) 95 | internal 96 | pure 97 | returns (Huffc memory) 98 | { 99 | string[] memory overrides = new string[](self.constantOverrides.length + 1); 100 | for (uint256 i; i < self.constantOverrides.length; i++) { 101 | overrides[i] = self.constantOverrides[i]; 102 | } 103 | overrides[overrides.length - 1] = string.concat(const, "=", value.toString()); 104 | self.constantOverrides = overrides; 105 | return self; 106 | } 107 | 108 | function _formatError(string memory func, string memory message) private pure returns (string memory) { 109 | return formatError("huff", func, message); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/modules/Context.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {Test} from "../../src/test.sol"; 5 | import {expect} from "src/test/Expect.sol"; 6 | import {commands} from "src/test/Commands.sol"; 7 | import {ctx} from "src/test/Context.sol"; 8 | 9 | contract ContextTest is Test { 10 | function testItCanSetTheBlockTimestamp(uint256 blockTimestamp) external { 11 | ctx.setBlockTimestamp(blockTimestamp); 12 | 13 | expect(block.timestamp).toEqual(blockTimestamp); 14 | } 15 | 16 | function testItCanSetTheBlockNumber(uint256 blockNumber) external { 17 | ctx.setBlockNumber(blockNumber); 18 | 19 | expect(block.number).toEqual(blockNumber); 20 | } 21 | 22 | function testItCanSetTheBlockBaseFee(uint256 baseFee) external { 23 | ctx.setBlockBaseFee(baseFee); 24 | 25 | expect(block.basefee).toEqual(baseFee); 26 | } 27 | 28 | function testItCanSetTheChainId(uint64 chainId) external { 29 | ctx.setChainId(chainId); 30 | 31 | expect(block.chainid).toEqual(chainId); 32 | } 33 | 34 | function testItCanSetTheBlockCoinbase(address coinbase) external { 35 | ctx.setBlockCoinbase(coinbase); 36 | 37 | expect(block.coinbase).toEqual(coinbase); 38 | } 39 | 40 | function testItCanSnapshot() external { 41 | uint256 originalBlockTimestamp = 1337; 42 | 43 | ctx.setBlockTimestamp(originalBlockTimestamp); 44 | 45 | uint256 snapshotId = ctx.snapshot(); 46 | 47 | ctx.setBlockTimestamp(originalBlockTimestamp + 69); 48 | 49 | expect(block.timestamp).toEqual(originalBlockTimestamp + 69); 50 | 51 | ctx.revertToSnapshot(snapshotId); 52 | 53 | expect(block.timestamp).toEqual(originalBlockTimestamp); 54 | } 55 | 56 | function testItCanExpectRevert() external { 57 | ctx.expectRevert(); 58 | Revert.fail(); 59 | } 60 | 61 | function testItCanExpectRevertWithMessage() external { 62 | ctx.expectRevert(bytes("Reverted")); 63 | Revert.failWithMessage(); 64 | } 65 | 66 | function testItCanExpectRevertWithCustomErrror() external { 67 | ctx.expectRevert(); 68 | Revert.failWithCustomError(); 69 | } 70 | 71 | function testItCanExpectRevertWithCustomErrorSelector() external { 72 | ctx.expectRevert(Revert.Custom.selector); 73 | Revert.failWithCustomError(); 74 | } 75 | 76 | function testItCanMockCalls() external { 77 | MockTarget target = new MockTarget(); 78 | 79 | ctx.mockCall(address(target), abi.encodeWithSelector(MockTarget.truth.selector), abi.encode(false)); 80 | 81 | expect(target.truth()).toBeFalse(); 82 | 83 | ctx.mockCall( 84 | address(target), abi.encodeWithSelector(MockTarget.value.selector, uint256(1337)), abi.encode(false) 85 | ); 86 | 87 | expect(target.value{value: uint256(1337)}()).toEqual(uint256(1337)); 88 | } 89 | 90 | function testItCanExpectCalls() external { 91 | MockTarget target = new MockTarget(); 92 | 93 | ctx.expectCall(address(target), abi.encodeWithSelector(MockTarget.truth.selector)); 94 | 95 | target.truth(); 96 | 97 | ctx.expectCall(address(target), uint256(1337), abi.encodeWithSelector(MockTarget.value.selector)); 98 | 99 | target.value{value: uint256(1337)}(); 100 | } 101 | 102 | function testItCanReportGas() external { 103 | ctx.startGasReport("test"); 104 | for (uint256 i = 0; i < 5; i++) { 105 | new MockTarget(); 106 | } 107 | ctx.endGasReport(); 108 | } 109 | } 110 | 111 | contract MockTarget { 112 | function truth() external pure returns (bool) { 113 | return true; 114 | } 115 | 116 | function value() external payable returns (uint256) { 117 | return msg.value; 118 | } 119 | } 120 | 121 | library Revert { 122 | function fail() external pure { 123 | revert(); 124 | } 125 | 126 | function failWithMessage() external pure { 127 | revert("Reverted"); 128 | } 129 | 130 | function failWithCustomError() external pure { 131 | revert Custom(); 132 | } 133 | 134 | error Custom(); 135 | } 136 | -------------------------------------------------------------------------------- /src/_internal/Error.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Pointer} from "./Pointer.sol"; 5 | import {LibResultPointer, ResultType, StringResult, BytesResult, BoolResult} from "./Result.sol"; 6 | 7 | type Error is bytes32; 8 | 9 | library LibError { 10 | using LibError for *; 11 | 12 | function toPointer(Error err) internal pure returns (Pointer) { 13 | return ResultType.Error.encode(Pointer.wrap(Error.unwrap(err))); 14 | } 15 | 16 | // Used internally by error functions 17 | 18 | function decode(Error err) internal pure returns (bytes32 id, string memory message, bytes memory data) { 19 | bytes memory errorData; 20 | assembly { 21 | errorData := err 22 | } 23 | return abi.decode(errorData, (bytes32, string, bytes)); 24 | } 25 | 26 | /* Function definitions for multiple permutations of function types */ 27 | 28 | // () 29 | function encodeError(function() returns(Error) fn, string memory message) internal pure returns (Error err) { 30 | bytes memory data = abi.encode(fn.toErrorId(), message, ""); 31 | assembly { 32 | err := data 33 | } 34 | } 35 | 36 | function toErrorId(function() returns(Error) fn) internal pure returns (bytes32 id) { 37 | assembly { 38 | id := fn 39 | } 40 | } 41 | 42 | function matches(Error err, function() returns(Error) fn) internal pure returns (bool) { 43 | (bytes32 id,,) = decode(err); 44 | return id == toErrorId(fn); 45 | } 46 | 47 | function decodeAs(Error, function() returns(Error)) internal pure { 48 | revert("No data to decode!"); 49 | } 50 | 51 | // (uint256) 52 | 53 | function encodeError(function(uint256) returns(Error) fn, string memory message, uint256 p0) 54 | internal 55 | pure 56 | returns (Error err) 57 | { 58 | bytes memory data = abi.encode(fn.toErrorId(), message, abi.encode(p0)); 59 | assembly { 60 | err := data 61 | } 62 | } 63 | 64 | function toErrorId(function(uint256) returns(Error) fn) internal pure returns (bytes32 id) { 65 | assembly { 66 | id := fn 67 | } 68 | } 69 | 70 | function matches(Error err, function(uint256) returns(Error) fn) internal pure returns (bool) { 71 | (bytes32 id,,) = decode(err); 72 | return id == toErrorId(fn); 73 | } 74 | 75 | function decodeAs(Error err, function(uint256) returns(Error)) internal pure returns (uint256) { 76 | (,, bytes memory data) = decode(err); 77 | return abi.decode(data, (uint256)); 78 | } 79 | 80 | // (string) 81 | 82 | function encodeError(function(string memory) returns(Error) fn, string memory message, string memory p0) 83 | internal 84 | pure 85 | returns (Error err) 86 | { 87 | bytes memory data = abi.encode(fn.toErrorId(), message, abi.encode(p0)); 88 | assembly { 89 | err := data 90 | } 91 | } 92 | 93 | function toErrorId(function(string memory) returns(Error) fn) internal pure returns (bytes32 id) { 94 | assembly { 95 | id := fn 96 | } 97 | } 98 | 99 | function matches(Error err, function(string memory) returns(Error) fn) internal pure returns (bool) { 100 | (bytes32 id,,) = decode(err); 101 | return id == toErrorId(fn); 102 | } 103 | 104 | function decodeAs(Error err, function(string memory) returns(Error)) internal pure returns (string memory) { 105 | (,, bytes memory data) = decode(err); 106 | return abi.decode(data, (string)); 107 | } 108 | 109 | function toStringResult(Error self) internal pure returns (StringResult) { 110 | return StringResult.wrap(Pointer.unwrap(self.toPointer())); 111 | } 112 | 113 | function toBytesResult(Error self) internal pure returns (BytesResult) { 114 | return BytesResult.wrap(Pointer.unwrap(self.toPointer())); 115 | } 116 | 117 | function toBoolResult(Error self) internal pure returns (BoolResult) { 118 | return BoolResult.wrap(Pointer.unwrap(self.toPointer())); 119 | } 120 | } 121 | 122 | using LibError for Error global; 123 | -------------------------------------------------------------------------------- /docs/src/references/Fs.md: -------------------------------------------------------------------------------- 1 | # Fs 2 | 3 | ## Custom types 4 | 5 | ### FsMetadataResult 6 | 7 | ```solidity 8 | type FsMetadataResult is bytes32; 9 | ``` 10 | 11 | 12 | 13 | ## Structs 14 | 15 | ### FsMetadata 16 | 17 | ```solidity 18 | struct FsMetadata { 19 | bool isDir 20 | bool isSymlink 21 | uint256 length 22 | bool readOnly 23 | uint256 modified 24 | uint256 accessed 25 | uint256 created 26 | } 27 | ``` 28 | 29 | 30 | 31 | ## Functions 32 | 33 | ### **Ok(FsMetadata value) → (FsMetadataResult)** 34 | 35 | 36 | 37 | ## fs 38 | 39 | 40 | 41 | ### **readFile(string path) → (StringResult)** 42 | 43 | Reads the file on `path` and returns its content as a `StringResult`. 44 | 45 | ### **readFileBinary(string path) → (BytesResult)** 46 | 47 | Reads the file on `path` and returns its content as a `BytesResult`. 48 | 49 | ### **projectRoot() → (StringResult)** 50 | 51 | Obtains the current project's root. 52 | 53 | ### **metadata(string fileOrDir) → (FsMetadataResult)** 54 | 55 | Obtains the metadata of the specified file or directory. 56 | 57 | ### **readLine(string path) → (StringResult)** 58 | 59 | Reads the next line of the file on `path`. 60 | 61 | ### **writeFile(string path, string data) → (EmptyResult)** 62 | 63 | Modifies the content of the file on `path` with `data`. 64 | 65 | ### **writeFileBinary(string path, bytes data) → (EmptyResult)** 66 | 67 | Modifies the content of the file on `path` with `data`. 68 | 69 | ### **writeLine(string path, string data) → (EmptyResult)** 70 | 71 | Adds a new line to the file on `path`. 72 | 73 | ### **closeFile(string path) → (EmptyResult)** 74 | 75 | Resets the state of the file on `path`. 76 | 77 | ### **removeFile(string path) → (EmptyResult)** 78 | 79 | Deletes the file on `path`. 80 | 81 | ### **copyFile(string origin, string target) → (EmptyResult)** 82 | 83 | Copies a file from `origin` to `target`. 84 | 85 | ### **moveFile(string origin, string target) → (EmptyResult)** 86 | 87 | Moves a file from `origin` to `target`. 88 | 89 | ### **fileExists(string path) → (BoolResult)** 90 | 91 | Checks if a file or directory exists. 92 | 93 | ### **getCode(string path) → (BytesResult)** 94 | 95 | Obtains the creation code from an artifact file located at `path` 96 | 97 | ### **getDeployedCode(string path) → (BytesResult)** 98 | 99 | Obtains the deployed code from an artifact file located at `path` 100 | 101 | ## FsErrors 102 | 103 | 104 | 105 | ### **FailedToRead(string reason) → (Error)** 106 | 107 | 108 | 109 | ### **FailedToReadLine(string reason) → (Error)** 110 | 111 | 112 | 113 | ### **FailedToReadMetadata(string reason) → (Error)** 114 | 115 | 116 | 117 | ### **FailedToGetProjectRoot(string reason) → (Error)** 118 | 119 | 120 | 121 | ### **Forbidden(string reason) → (Error)** 122 | 123 | 124 | 125 | ### **FailedToWrite(string reason) → (Error)** 126 | 127 | 128 | 129 | ### **FailedToWriteLine(string reason) → (Error)** 130 | 131 | 132 | 133 | ### **FailedToCloseFile(string reason) → (Error)** 134 | 135 | 136 | 137 | ### **FailedToRemoveFile(string reason) → (Error)** 138 | 139 | 140 | 141 | ### **FailedToGetCode(string reason) → (Error)** 142 | 143 | 144 | 145 | ### **toFsMetadataResult(Error self) → (FsMetadataResult)** 146 | 147 | 148 | 149 | ### **toEmptyResult(Error self) → (EmptyResult)** 150 | 151 | 152 | 153 | ## LibFsMetadataPointer 154 | 155 | 156 | 157 | ### **toFsMetadata(Pointer self) → (FsMetadata metadata)** 158 | 159 | 160 | 161 | ### **toFsMetadataResult(Pointer self) → (FsMetadataResult ptr)** 162 | 163 | 164 | 165 | ### **toPointer(FsMetadata self) → (Pointer ptr)** 166 | 167 | 168 | 169 | ## LibFsMetadataResult 170 | 171 | 172 | 173 | ### **isOk(FsMetadataResult self) → (bool)** 174 | 175 | 176 | 177 | ### **isError(FsMetadataResult self) → (bool)** 178 | 179 | 180 | 181 | ### **unwrap(FsMetadataResult self) → (FsMetadata val)** 182 | 183 | 184 | 185 | ### **expect(FsMetadataResult self, string err) → (FsMetadata)** 186 | 187 | 188 | 189 | ### **toError(FsMetadataResult self) → (Error)** 190 | 191 | 192 | 193 | ### **toValue(FsMetadataResult self) → (FsMetadata val)** 194 | 195 | 196 | 197 | ### **toPointer(FsMetadataResult self) → (Pointer)** 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /src/_internal/Semver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {strings} from "./Strings.sol"; 5 | import {formatError} from "./Utils.sol"; 6 | 7 | struct Semver { 8 | uint256 major; 9 | uint256 minor; 10 | uint256 patch; 11 | } 12 | 13 | library semver { 14 | using strings for uint256; 15 | using strings for string; 16 | 17 | function create(uint256 major, uint256 minor, uint256 patch) internal pure returns (Semver memory) { 18 | return Semver(major, minor, patch); 19 | } 20 | 21 | function create(uint256 major, uint256 minor) internal pure returns (Semver memory) { 22 | return create(major, minor, 0); 23 | } 24 | 25 | function create(uint256 major) internal pure returns (Semver memory) { 26 | return create(major, 0, 0); 27 | } 28 | 29 | function parse(string memory input) internal pure returns (Semver memory) { 30 | bytes memory inputBytes = bytes(input); 31 | 32 | if (inputBytes.length > 0 && inputBytes[0] == bytes1("v")) { 33 | input = _substring(input, 1, inputBytes.length); 34 | } 35 | 36 | string[] memory parts = _split(input, "."); 37 | 38 | require(parts.length == 3, _formatError("create(string)", "Invalid Semver format")); 39 | 40 | return Semver(parts[0].parseUint(), parts[1].parseUint(), parts[2].parseUint()); 41 | } 42 | 43 | function toString(Semver memory self) internal pure returns (string memory) { 44 | return string.concat(self.major.toString(), ".", self.minor.toString(), ".", self.patch.toString()); 45 | } 46 | 47 | function equals(Semver memory self, Semver memory other) internal pure returns (bool) { 48 | return self.major == other.major && self.minor == other.minor && self.patch == other.patch; 49 | } 50 | 51 | function greaterThan(Semver memory self, Semver memory other) internal pure returns (bool) { 52 | return (self.major > other.major || self.minor > other.minor || self.patch > other.patch); 53 | } 54 | 55 | function greaterThanOrEqual(Semver memory self, Semver memory other) internal pure returns (bool) { 56 | return equals(self, other) || greaterThan(self, other); 57 | } 58 | 59 | function lessThan(Semver memory self, Semver memory other) internal pure returns (bool) { 60 | return !greaterThanOrEqual(self, other); 61 | } 62 | 63 | function lessThanOrEqual(Semver memory self, Semver memory other) internal pure returns (bool) { 64 | return !greaterThan(self, other); 65 | } 66 | 67 | function _split(string memory input, string memory delimiter) private pure returns (string[] memory) { 68 | bytes memory inputBytes = bytes(input); 69 | bytes memory delimiterBytes = bytes(delimiter); 70 | 71 | uint256 partsCount = 1; 72 | 73 | for (uint256 i = 0; i < inputBytes.length; i++) { 74 | if (inputBytes[i] == delimiterBytes[0]) { 75 | partsCount++; 76 | } 77 | } 78 | 79 | string[] memory parts = new string[](partsCount); 80 | 81 | uint256 partIndex = 0; 82 | uint256 startIndex = 0; 83 | 84 | for (uint256 i = 0; i < inputBytes.length; i++) { 85 | if (inputBytes[i] == delimiterBytes[0]) { 86 | parts[partIndex] = _substring(input, startIndex, i); 87 | startIndex = i + 1; 88 | partIndex++; 89 | } 90 | } 91 | 92 | parts[partIndex] = _substring(input, startIndex, inputBytes.length); 93 | 94 | return parts; 95 | } 96 | 97 | function _substring(string memory input, uint256 startIndex, uint256 endIndex) 98 | private 99 | pure 100 | returns (string memory) 101 | { 102 | bytes memory inputBytes = bytes(input); 103 | require( 104 | startIndex < inputBytes.length, _formatError("_substring(uint256,uint256,uint256)", "Invalid start index") 105 | ); 106 | require(endIndex <= inputBytes.length, _formatError("_substring(uint256,uint256,uint256)", "Invalid end index")); 107 | 108 | bytes memory result = new bytes(endIndex - startIndex); 109 | for (uint256 i = startIndex; i < endIndex; i++) { 110 | result[i - startIndex] = inputBytes[i]; 111 | } 112 | 113 | return string(result); 114 | } 115 | 116 | function _formatError(string memory func, string memory message) private pure returns (string memory) { 117 | return formatError("semver", func, message); 118 | } 119 | } 120 | 121 | using semver for Semver global; 122 | -------------------------------------------------------------------------------- /docs/src/references/Result.md: -------------------------------------------------------------------------------- 1 | # Result 2 | 3 | ## Custom types 4 | 5 | ### Bytes32Result 6 | 7 | ```solidity 8 | type Bytes32Result is bytes32; 9 | ``` 10 | 11 | 12 | 13 | ### BytesResult 14 | 15 | ```solidity 16 | type BytesResult is bytes32; 17 | ``` 18 | 19 | 20 | 21 | ### StringResult 22 | 23 | ```solidity 24 | type StringResult is bytes32; 25 | ``` 26 | 27 | 28 | 29 | ### BoolResult 30 | 31 | ```solidity 32 | type BoolResult is bytes32; 33 | ``` 34 | 35 | 36 | 37 | ### EmptyResult 38 | 39 | ```solidity 40 | type EmptyResult is bytes32; 41 | ``` 42 | 43 | 44 | 45 | ## Functions 46 | 47 | ### **Ok() → (EmptyResult)** 48 | 49 | 50 | 51 | ### **Ok(bytes32 value) → (Bytes32Result)** 52 | 53 | 54 | 55 | ### **Ok(bytes value) → (BytesResult)** 56 | 57 | 58 | 59 | ### **Ok(string value) → (StringResult)** 60 | 61 | 62 | 63 | ### **Ok(bool value) → (BoolResult)** 64 | 65 | 66 | 67 | ## LibResultPointer 68 | 69 | 70 | 71 | ### **decode(Pointer self) → (ResultType, Pointer)** 72 | 73 | 74 | 75 | ### **isError(Pointer self) → (bool)** 76 | 77 | 78 | 79 | ### **isOk(Pointer self) → (bool)** 80 | 81 | 82 | 83 | ### **toError(Pointer self) → (Error)** 84 | 85 | 86 | 87 | ### **unwrap(Pointer self) → (Pointer ptr)** 88 | 89 | 90 | 91 | ### **expect(Pointer self, string err) → (Pointer ptr)** 92 | 93 | 94 | 95 | ## LibBytes32ResultPointer 96 | 97 | 98 | 99 | ### **toBytes32Result(Pointer self) → (Bytes32Result res)** 100 | 101 | 102 | 103 | ## LibBytes32Result 104 | 105 | 106 | 107 | ### **isError(Bytes32Result self) → (bool)** 108 | 109 | 110 | 111 | ### **isOk(Bytes32Result self) → (bool)** 112 | 113 | 114 | 115 | ### **toError(Bytes32Result self) → (Error)** 116 | 117 | 118 | 119 | ### **toValue(Bytes32Result self) → (bytes32)** 120 | 121 | 122 | 123 | ### **unwrap(Bytes32Result self) → (bytes32)** 124 | 125 | 126 | 127 | ### **expect(Bytes32Result self, string err) → (bytes32)** 128 | 129 | 130 | 131 | ### **toPointer(Bytes32Result self) → (Pointer)** 132 | 133 | 134 | 135 | ## LibBytesResultPointer 136 | 137 | 138 | 139 | ### **toBytesResult(Pointer self) → (BytesResult res)** 140 | 141 | 142 | 143 | ## LibBytesResult 144 | 145 | 146 | 147 | ### **isOk(BytesResult self) → (bool)** 148 | 149 | 150 | 151 | ### **isError(BytesResult self) → (bool)** 152 | 153 | 154 | 155 | ### **unwrap(BytesResult self) → (bytes)** 156 | 157 | 158 | 159 | ### **expect(BytesResult self, string err) → (bytes)** 160 | 161 | 162 | 163 | ### **toError(BytesResult self) → (Error)** 164 | 165 | 166 | 167 | ### **toValue(BytesResult self) → (bytes)** 168 | 169 | 170 | 171 | ### **toPointer(BytesResult self) → (Pointer)** 172 | 173 | 174 | 175 | ## LibStringResultPointer 176 | 177 | 178 | 179 | ### **toStringResult(Pointer self) → (StringResult res)** 180 | 181 | 182 | 183 | ## LibStringResult 184 | 185 | 186 | 187 | ### **isOk(StringResult self) → (bool)** 188 | 189 | 190 | 191 | ### **isError(StringResult self) → (bool)** 192 | 193 | 194 | 195 | ### **unwrap(StringResult self) → (string val)** 196 | 197 | 198 | 199 | ### **expect(StringResult self, string err) → (string)** 200 | 201 | 202 | 203 | ### **toError(StringResult self) → (Error)** 204 | 205 | 206 | 207 | ### **toValue(StringResult self) → (string val)** 208 | 209 | 210 | 211 | ### **toPointer(StringResult self) → (Pointer)** 212 | 213 | 214 | 215 | ## LibBoolResultPointer 216 | 217 | 218 | 219 | ### **toBoolResult(Pointer self) → (BoolResult res)** 220 | 221 | 222 | 223 | ## LibBoolResult 224 | 225 | 226 | 227 | ### **isOk(BoolResult self) → (bool)** 228 | 229 | 230 | 231 | ### **isError(BoolResult self) → (bool)** 232 | 233 | 234 | 235 | ### **unwrap(BoolResult self) → (bool val)** 236 | 237 | 238 | 239 | ### **expect(BoolResult self, string err) → (bool)** 240 | 241 | 242 | 243 | ### **toError(BoolResult self) → (Error)** 244 | 245 | 246 | 247 | ### **toValue(BoolResult self) → (bool val)** 248 | 249 | 250 | 251 | ### **toPointer(BoolResult self) → (Pointer)** 252 | 253 | 254 | 255 | ## LibEmptyResultPointer 256 | 257 | 258 | 259 | ### **toEmptyResult(Pointer self) → (EmptyResult res)** 260 | 261 | 262 | 263 | ## LibEmptyResult 264 | 265 | 266 | 267 | ### **isOk(EmptyResult self) → (bool)** 268 | 269 | 270 | 271 | ### **isError(EmptyResult self) → (bool)** 272 | 273 | 274 | 275 | ### **unwrap(EmptyResult self)** 276 | 277 | 278 | 279 | ### **expect(EmptyResult self, string err)** 280 | 281 | 282 | 283 | ### **toError(EmptyResult self) → (Error)** 284 | 285 | 286 | 287 | ### **toPointer(EmptyResult self) → (Pointer)** 288 | 289 | 290 | 291 | ## LibResultType 292 | 293 | 294 | 295 | ### **encode(ResultType _type, Pointer _dataPtr) → (Pointer result)** 296 | 297 | 298 | 299 | -------------------------------------------------------------------------------- /docs/src/references/Env.md: -------------------------------------------------------------------------------- 1 | # Env 2 | 3 | ## env 4 | 5 | 6 | 7 | ### **set(string name, string value)** 8 | 9 | sets the value of the environment variable with name `name` to `value`. 10 | 11 | ### **getBool(string name) → (bool)** 12 | 13 | Reads the environment variable with name `name` and returns the value as `bool`. 14 | 15 | ### **getUint(string name) → (uint256)** 16 | 17 | Reads the environment variable with name `name` and returns the value as `uint256`. 18 | 19 | ### **getInt(string name) → (int256)** 20 | 21 | Reads the environment variable with name `name` and returns the value as `int256`. 22 | 23 | ### **getAddress(string name) → (address)** 24 | 25 | Reads the environment variable with name `name` and returns the value as `address`. 26 | 27 | ### **getBytes32(string name) → (bytes32)** 28 | 29 | Reads the environment variable with name `name` and returns the value as `bytes32`. 30 | 31 | ### **getString(string name) → (string)** 32 | 33 | Reads the environment variable with name `name` and returns the value as `string`. 34 | 35 | ### **getBytes(string name) → (bytes)** 36 | 37 | Reads the environment variable with name `name` and returns the value as `bytes`. 38 | 39 | ### **getBoolArray(string name, string delim) → (bool[])** 40 | 41 | Reads the environment variable with name `name` and returns the value as `bool[]`. 42 | 43 | ### **getUintArray(string name, string delim) → (uint256[])** 44 | 45 | Reads the environment variable with name `name` and returns the value as `uint256[]`. 46 | 47 | ### **getIntArray(string name, string delim) → (int256[])** 48 | 49 | Reads the environment variable with name `name` and returns the value as `int256[]`. 50 | 51 | ### **getAddressArray(string name, string delim) → (address[])** 52 | 53 | Reads the environment variable with name `name` and returns the value as `address[]`. 54 | 55 | ### **getBytes32Array(string name, string delim) → (bytes32[])** 56 | 57 | Reads the environment variable with name `name` and returns the value as `bytes32[]`. 58 | 59 | ### **getStringArray(string name, string delim) → (string[])** 60 | 61 | Reads the environment variable with name `name` and returns the value as `string[]`. 62 | 63 | ### **getBytesArray(string name, string delim) → (bytes[])** 64 | 65 | Reads the environment variable with name `name` and returns the value as `bytes[]`. 66 | 67 | ### **getBool(string name, bool defaultValue) → (bool value)** 68 | 69 | Reads the environment variable with name `name` and returns the value as `bool`. 70 | 71 | ### **getUint(string name, uint256 defaultValue) → (uint256 value)** 72 | 73 | Reads the environment variable with name `name` and returns the value as `uint256`. 74 | 75 | ### **getInt(string name, int256 defaultValue) → (int256 value)** 76 | 77 | Reads the environment variable with name `name` and returns the value as `int256`. 78 | 79 | ### **getAddress(string name, address defaultValue) → (address value)** 80 | 81 | Reads the environment variable with name `name` and returns the value as `address`. 82 | 83 | ### **getBytes32(string name, bytes32 defaultValue) → (bytes32 value)** 84 | 85 | Reads the environment variable with name `name` and returns the value as `bytes32`. 86 | 87 | ### **getString(string name, string defaultValue) → (string value)** 88 | 89 | Reads the environment variable with name `name` and returns the value as `string`. 90 | 91 | ### **getBytes(string name, bytes defaultValue) → (bytes value)** 92 | 93 | Reads the environment variable with name `name` and returns the value as `bytes`. 94 | 95 | ### **getBoolArray(string name, string delim, bool[] defaultValue) → (bool[] value)** 96 | 97 | Reads the environment variable with name `name` and returns the value as `bool[]`. 98 | 99 | ### **getUintArray(string name, string delim, uint256[] defaultValue) → (uint256[] value)** 100 | 101 | Reads the environment variable with name `name` and returns the value as `uint256[]`. 102 | 103 | ### **getIntArray(string name, string delim, int256[] defaultValue) → (int256[] value)** 104 | 105 | Reads the environment variable with name `name` and returns the value as `int256[]`. 106 | 107 | ### **getAddressArray(string name, string delim, address[] defaultValue) → (address[] value)** 108 | 109 | Reads the environment variable with name `name` and returns the value as `address[]`. 110 | 111 | ### **getBytes32Array(string name, string delim, bytes32[] defaultValue) → (bytes32[] value)** 112 | 113 | Reads the environment variable with name `name` and returns the value as `bytes32[]`. 114 | 115 | ### **getStringArray(string name, string delim, string[] defaultValue) → (string[] value)** 116 | 117 | Reads the environment variable with name `name` and returns the value as `string[]`. 118 | 119 | ### **getBytesArray(string name, string delim, bytes[] defaultValue) → (bytes[] value)** 120 | 121 | Reads the environment variable with name `name` and returns the value as `bytes[]`. 122 | 123 | -------------------------------------------------------------------------------- /src/_internal/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.13 <0.9.0; 3 | 4 | import {fmt} from "./Fmt.sol"; 5 | 6 | uint256 constant UINT256_MAX = type(uint256).max; 7 | uint256 constant INT256_MIN_ABS = uint256(type(int256).max) + 1; 8 | 9 | // Extracted from forge-std stdUtils: https://github.com/foundry-rs/forge-std/blob/7b4876e8de2a232a54159035f173e35421000c19/src/StdUtils.sol 10 | // The main difference is that we use file-level functions instead of an abstract contract. 11 | function bound(uint256 x, uint256 min, uint256 max) pure returns (uint256 result) { 12 | require(min <= max, formatError("_utils", "bound(uint256,uint256,uint256)", "Max is less than min.")); 13 | // If x is between min and max, return x directly. This is to ensure that dictionary values 14 | // do not get shifted if the min is nonzero. More info: https://github.com/foundry-rs/forge-std/issues/188 15 | if (x >= min && x <= max) return x; 16 | 17 | uint256 size = max - min + 1; 18 | 19 | // If the value is 0, 1, 2, 3, wrap that to min, min+1, min+2, min+3. Similarly for the UINT256_MAX side. 20 | // This helps ensure coverage of the min/max values. 21 | if (x <= 3 && size > x) return min + x; 22 | if (x >= UINT256_MAX - 3 && size > UINT256_MAX - x) return max - (UINT256_MAX - x); 23 | 24 | // Otherwise, wrap x into the range [min, max], i.e. the range is inclusive. 25 | if (x > max) { 26 | uint256 diff = x - max; 27 | uint256 rem = diff % size; 28 | if (rem == 0) return max; 29 | result = min + rem - 1; 30 | } else if (x < min) { 31 | uint256 diff = min - x; 32 | uint256 rem = diff % size; 33 | if (rem == 0) return min; 34 | result = max - rem + 1; 35 | } 36 | } 37 | 38 | function bound(int256 x, int256 min, int256 max) pure returns (int256 result) { 39 | require(min <= max, formatError("_utils", "bound(int256,int256,int256)", "Max is less than min.")); 40 | 41 | // Shifting all int256 values to uint256 to use _bound function. The range of two types are: 42 | // int256 : -(2**255) ~ (2**255 - 1) 43 | // uint256: 0 ~ (2**256 - 1) 44 | // So, add 2**255, INT256_MIN_ABS to the integer values. 45 | // 46 | // If the given integer value is -2**255, we cannot use `-uint256(-x)` because of the overflow. 47 | // So, use `~uint256(x) + 1` instead. 48 | uint256 _x = x < 0 ? (INT256_MIN_ABS - ~uint256(x) - 1) : (uint256(x) + INT256_MIN_ABS); 49 | uint256 _min = min < 0 ? (INT256_MIN_ABS - ~uint256(min) - 1) : (uint256(min) + INT256_MIN_ABS); 50 | uint256 _max = max < 0 ? (INT256_MIN_ABS - ~uint256(max) - 1) : (uint256(max) + INT256_MIN_ABS); 51 | 52 | uint256 y = bound(_x, _min, _max); 53 | 54 | // To move it back to int256 value, subtract INT256_MIN_ABS at here. 55 | result = y < INT256_MIN_ABS ? int256(~(INT256_MIN_ABS - y) + 1) : int256(y - INT256_MIN_ABS); 56 | } 57 | 58 | // Adapted from forge-std stdMath https://github.com/foundry-rs/forge-std/blob/c2236853aadb8e2d9909bbecdc490099519b70a4/src/StdMath.sol#L7 59 | function abs(int256 a) pure returns (uint256) { 60 | if (a == type(int256).min) { 61 | return uint256(type(int256).max) + 1; 62 | } 63 | 64 | return uint256(a > 0 ? a : -a); 65 | } 66 | 67 | function delta(uint256 a, uint256 b) pure returns (uint256) { 68 | return a > b ? a - b : b - a; 69 | } 70 | 71 | // Adapted from forge-std stdMath https://github.com/foundry-rs/forge-std/blob/c2236853aadb8e2d9909bbecdc490099519b70a4/src/StdMath.sol#L20 72 | function delta(int256 a, int256 b) pure returns (uint256) { 73 | uint256 absA = abs(a); 74 | uint256 absB = abs(b); 75 | 76 | // Same sign 77 | if ((a ^ b) > -1) { 78 | return delta(absA, absB); 79 | } else { 80 | return absA + absB; 81 | } 82 | } 83 | 84 | function format(string memory template, bytes memory args) pure returns (string memory) { 85 | return fmt.format(template, args); 86 | } 87 | 88 | function formatError(string memory module, string memory func, string memory message) pure returns (string memory) { 89 | return string.concat("vulcan.", module, ".", func, ": ", message); 90 | } 91 | 92 | function println(string memory template, bytes memory args) view { 93 | rawConsoleLog(fmt.format(template, args)); 94 | } 95 | 96 | function println(string memory arg) view { 97 | rawConsoleLog(arg); 98 | } 99 | 100 | function rawConsoleLog(string memory arg) view { 101 | address console2Addr = 0x000000000000000000636F6e736F6c652e6c6f67; 102 | (bool status,) = console2Addr.staticcall(abi.encodeWithSignature("log(string)", arg)); 103 | status; 104 | } 105 | 106 | function removeSelector(bytes memory data) pure returns (bytes memory) { 107 | require(data.length >= 4, "Input data is too short"); 108 | 109 | // Create a new bytes variable to store the result 110 | bytes memory result = new bytes(data.length - 4); 111 | 112 | // Copy the remaining data (excluding the first 4 bytes) into the result 113 | for (uint256 i = 4; i < data.length; i++) { 114 | result[i - 4] = data[i]; 115 | } 116 | 117 | return result; 118 | } 119 | --------------------------------------------------------------------------------