├── 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 | 
8 | [](https://github.com/nomoixyz/vulcan/releases)
9 | [](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 |
--------------------------------------------------------------------------------