diff --git a/README.md b/README.md index 2299a060..64d48101 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Over time, Vulcan will grow to include more functionality and utilities, eventua ## Installation ``` -$ forge install nomoixyz/vulcan@v0.4.1 +$ forge install nomoixyz/vulcan@v0.4.2 ``` ## Usage diff --git a/docs/src/OTHER_MODULES.md b/docs/src/OTHER_MODULES.md new file mode 100644 index 00000000..012f0ddb --- /dev/null +++ b/docs/src/OTHER_MODULES.md @@ -0,0 +1,8 @@ +# Other modules + +- [Config](./modules/config.md) +- [Console](./modules/console.md) +- [Env](./modules/env.md) +- [Events](./modules/events.md) +- [Forks](./modules/forks.md) +- [Strings](./modules/strings.md) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 4bf1cffa..cf8c2703 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -4,44 +4,28 @@ - [Installation](./guide/installation.md) - [Testing](./testing/README.md) - - [Expect](./testing/expect.md) - [Scripts](./scripts/README.md) -- [Using modules](./modules/README.md) - - [Accounts](./modules/accounts.md) - - [Commands](./modules/commands.md) + +# Modules + +- [Accounts](./modules/accounts.md) +- [Commands](./modules/commands.md) +- [Context](./modules/context.md) +- [Expect](./modules/expect.md) +- [Fe](./modules/fe.md) +- [Format](./modules/fmt.md) +- [Fs](./modules/fs.md) +- [Gas](./modules/gas.md) +- [Huff](./modules/huff.md) +- [Json](./modules/json.md) +- [Results \& Errors](./modules/results.md) +- [Requests](./modules/requests.md) +- [Utils](./modules/utils.md) +- [Others](./OTHER_MODULES.md) - [Config](./modules/config.md) - [Console](./modules/console.md) - - [Context](./modules/context.md) - [Env](./modules/env.md) - [Events](./modules/events.md) - - [Fe](./modules/fe.md) - - [Format](./modules/fmt.md) - [Forks](./modules/forks.md) - - [Fs](./modules/fs.md) - - [Huff](./modules/huff.md) - - [Json](./modules/json.md) - [Strings](./modules/strings.md) - - [Utils](./modules/utils.md) - - [Watchers](./modules/watchers.md) - - [Results](./modules/results.md) - - [Requests](./modules/requests.md) - -# Reference - -- [Modules](./reference/modules/README.md) - - [Accounts](./reference/modules/accounts.md) - - [Commands](./reference/modules/commands.md) - - [Config](./reference/modules/config.md) - - [Context](./reference/modules/context.md) - - [Env](./reference/modules/env.md) - - [Events](./reference/modules/events.md) - - [Fe](./reference/modules/fe.md) - - [Format](./reference/modules/fmt.md) - - [Forks](./reference/modules/forks.md) - - [Fs](./reference/modules/fs.md) - - [Huff](./reference/modules/huff.md) - - [Invariants](./reference/modules/invariants.md) - - [Json](./reference/modules/json.md) - - [Strings](./reference/modules/strings.md) - - [Watchers](./reference/modules/watchers.md) diff --git a/docs/src/examples/accounts/example.md b/docs/src/examples/accounts/example.md new file mode 100644 index 00000000..341309c9 --- /dev/null +++ b/docs/src/examples/accounts/example.md @@ -0,0 +1,109 @@ +## Examples +### Create an address + +How to create a simple address + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +contract AccountsExample is Test { + function test() external { + address alice = accounts.create(); + + expect(alice).not.toEqual(address(0)); + } +} + +``` + +### Create a labeled address + +Creating an address labeled as "Alice" + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +contract AccountsExample is Test { + function test() external { + address alice = accounts.create("Alice"); + + expect(alice).not.toEqual(address(0)); + } +} + +``` + +### Create multiple addresses + +Creating multiple addresses + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +contract AccountsExample is Test { + function test() external { + address[] memory addresses = accounts.createMany(10); + + expect(addresses.length).toEqual(10); + } +} + +``` + +### Create multiple labeled addresses with a prefix + +Creating multiple addresses labeled with the prefix `Account` + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +contract AccountsExample is Test { + function test() external { + address[] memory addresses = accounts.createMany(10, "Account"); + + expect(addresses.length).toEqual(10); + } +} + +``` + +### Use method chaining on addresses + +Use method chaining on addresses to call multiple methods + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +contract AccountsExample05 is Test { + using accounts for address; + + function test() external { + address alice = accounts.create("Alice").setNonce(666).setBalance(100e18); + + address bob = accounts.create("Bob").setBalance(10e18).impersonateOnce(); + + payable(alice).transfer(bob.balance); + + expect(alice.balance).toEqual(110e18); + expect(alice.getNonce()).toEqual(666); + expect(bob.balance).toEqual(0); + } +} + +``` + diff --git a/docs/src/examples/commands/example.md b/docs/src/examples/commands/example.md new file mode 100644 index 00000000..52ccca3e --- /dev/null +++ b/docs/src/examples/commands/example.md @@ -0,0 +1,57 @@ +## Examples +### Run a simple command + +Run a simple command and obtain the output + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, commands, CommandResult, CommandOutput} from "vulcan/test.sol"; + +contract RunCommandExample is Test { + function test() external { + // Run a command to get a result + CommandResult cmdResult = commands.run(["echo", "Hello, World!"]); + + // Obtain the output from the result + CommandOutput memory output = cmdResult.expect("Failed to run command"); + + // Check the output + expect(string(output.stdout)).toEqual("Hello, World!"); + } +} + +``` + +### Reuse a command + +Reuse a command with different arguments + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, commands, Command, CommandResult, CommandOutput} from "vulcan/test.sol"; + +contract ReuseACommandExample is Test { + function test() external { + // Create a command + Command memory echo = commands.create("echo"); + + // Run the commands and get the results + CommandResult fooResult = echo.arg("foo").run(); + CommandResult barResult = echo.arg("bar").run(); + + // Obtain the outputs from the results + CommandOutput memory fooOutput = fooResult.expect("Failed to run echo 'foo'"); + CommandOutput memory barOutput = barResult.expect("Failed to run echo 'bar'"); + + // Check the outputs + expect(string(fooOutput.stdout)).toEqual("foo"); + expect(string(barOutput.stdout)).toEqual("bar"); + } +} + +``` + diff --git a/docs/src/examples/config/example.md b/docs/src/examples/config/example.md new file mode 100644 index 00000000..c792e42f --- /dev/null +++ b/docs/src/examples/config/example.md @@ -0,0 +1,69 @@ +## Examples +### Obtain a specific RPC URL + +Read a specific RPC URL from the foundry configuration + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, config} from "vulcan/test.sol"; + +contract ConfigExample is Test { + function test() external { + string memory key = "mainnet"; + + expect(config.rpcUrl(key)).toEqual("https://mainnet.rpc.io"); + } +} + +``` + +### Obtain all the RPC URLs + +Read all the RPC URLs from the foundry configuration + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, config} from "vulcan/test.sol"; + +contract ConfigExample is Test { + function test() external { + string[2][] memory rpcs = config.rpcUrls(); + + expect(rpcs.length).toEqual(2); + expect(rpcs[0][0]).toEqual("arbitrum"); + expect(rpcs[0][1]).toEqual("https://arbitrum.rpc.io"); + expect(rpcs[1][0]).toEqual("mainnet"); + expect(rpcs[1][1]).toEqual("https://mainnet.rpc.io"); + } +} + +``` + +### Obtain all the RPC URLs using structs + +Read all the RPC URL from the foundry configuration as structs + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, config, Rpc} from "vulcan/test.sol"; + +contract ConfigExample is Test { + function test() external { + Rpc[] memory rpcs = config.rpcUrlStructs(); + + expect(rpcs.length).toEqual(2); + expect(rpcs[0].name).toEqual("arbitrum"); + expect(rpcs[0].url).toEqual("https://arbitrum.rpc.io"); + expect(rpcs[1].name).toEqual("mainnet"); + expect(rpcs[1].url).toEqual("https://mainnet.rpc.io"); + } +} + +``` + diff --git a/docs/src/examples/console/example.md b/docs/src/examples/console/example.md new file mode 100644 index 00000000..da9f97ed --- /dev/null +++ b/docs/src/examples/console/example.md @@ -0,0 +1,33 @@ +## Examples +### Log values + +Use the `console` function to log values. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, console} from "vulcan/test.sol"; + +contract ConsoleExample is Test { + function test() external pure { + string memory foo = "foo"; + string memory bar = "bar"; + + uint256 oneTwoThree = 123; + uint256 threeTwoOne = 321; + + bool isTrue = true; + + console.log(foo); + console.log(foo, bar); + console.log(foo, bar, threeTwoOne); + console.log(foo, bar, threeTwoOne, isTrue); + console.log(threeTwoOne, oneTwoThree); + console.log(threeTwoOne + oneTwoThree); + console.log(1 > 0); + } +} + +``` + diff --git a/docs/src/examples/context/example.md b/docs/src/examples/context/example.md new file mode 100644 index 00000000..d9202e11 --- /dev/null +++ b/docs/src/examples/context/example.md @@ -0,0 +1,65 @@ +## Examples +### Modify chain parameters + +Use the context module to modify chain parameters + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, ctx} from "vulcan/test.sol"; + +contract ContextExample is Test { + function test() external { + ctx.setBlockTimestamp(1); + expect(block.timestamp).toEqual(1); + + ctx.setBlockNumber(123); + expect(block.number).toEqual(123); + + ctx.setBlockBaseFee(99999); + expect(block.basefee).toEqual(99999); + + ctx.setBlockPrevrandao(bytes32(uint256(123))); + expect(block.prevrandao).toEqual(uint256(bytes32(uint256(123)))); + + ctx.setChainId(666); + expect(block.chainid).toEqual(666); + + ctx.setBlockCoinbase(address(1)); + expect(block.coinbase).toEqual(address(1)); + + ctx.setGasPrice(1e18); + expect(tx.gasprice).toEqual(1e18); + } +} + +``` + +### Modify chain parameters using method chaining + +Use the context module to modify chain parameters using method chaining + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, ctx} from "vulcan/test.sol"; + +contract ContextExample is Test { + function test() external { + ctx.setBlockTimestamp(1).setBlockNumber(123).setBlockBaseFee(99999).setBlockPrevrandao(bytes32(uint256(123))) + .setChainId(666).setBlockCoinbase(address(1)).setGasPrice(1e18); + + expect(block.timestamp).toEqual(1); + expect(block.number).toEqual(123); + expect(block.basefee).toEqual(99999); + expect(block.prevrandao).toEqual(uint256(bytes32(uint256(123)))); + expect(block.chainid).toEqual(666); + expect(block.coinbase).toEqual(address(1)); + expect(tx.gasprice).toEqual(1e18); + } +} + +``` + diff --git a/docs/src/examples/env/example.md b/docs/src/examples/env/example.md new file mode 100644 index 00000000..9abbdb43 --- /dev/null +++ b/docs/src/examples/env/example.md @@ -0,0 +1,30 @@ +## Examples +### Set and get environment variables + +Use the `env` module to set and read environment variables + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, env} from "vulcan/test.sol"; + +contract EnvExample is Test { + function test() external { + env.set("SomeString", "foo"); + env.set("SomeUint", "100000000000000000000000"); + env.set("SomeBool", "true"); + env.set("SomeArray", "1,2,3,4"); + + expect(env.getString("SomeString")).toEqual("foo"); + expect(env.getUint("SomeUint")).toEqual(100_000_000_000_000_000_000_000); + expect(env.getBool("SomeBool")).toBeTrue(); + expect(env.getUintArray("SomeArray", ",")[0]).toEqual(1); + expect(env.getUintArray("SomeArray", ",")[1]).toEqual(2); + expect(env.getUintArray("SomeArray", ",")[2]).toEqual(3); + expect(env.getUintArray("SomeArray", ",")[3]).toEqual(4); + } +} + +``` + diff --git a/docs/src/examples/events/example.md b/docs/src/examples/events/example.md new file mode 100644 index 00000000..8ae4b5fb --- /dev/null +++ b/docs/src/examples/events/example.md @@ -0,0 +1,36 @@ +## Examples +### Logging events + +Logging events and reading events topics and data + +```solidity +//// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, events, Log} from "vulcan/test.sol"; + +contract EventsExample is Test { + using events for *; + + event SomeEvent(uint256 indexed a, address b); + + function run() external { + uint256 a = 666; + address b = address(333); + + events.recordLogs(); + + emit SomeEvent(a, b); + + Log[] memory logs = events.getRecordedLogs(); + + expect(logs.length).toEqual(1); + expect(logs[0].emitter).toEqual(address(this)); + expect(logs[0].topics[0]).toEqual(SomeEvent.selector); + expect(logs[0].topics[1]).toEqual(a.topic()); + expect(logs[0].data).toEqual(abi.encode(b)); + } +} + +``` + diff --git a/docs/src/examples/expect/example.md b/docs/src/examples/expect/example.md new file mode 100644 index 00000000..21c8f946 --- /dev/null +++ b/docs/src/examples/expect/example.md @@ -0,0 +1,37 @@ +## Examples +### Use different matchers + +Using the `expect` function and its different matchers + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect} from "vulcan/test.sol"; + +contract ExpectExample is Test { + function test() external { + expect(string("foo")).toEqual(string("foo")); + expect(string("foo")).not.toEqual(string("bar")); + expect(string("foo bar")).toContain(string("foo")); + expect(string("foo bar")).toContain(string("bar")); + + expect(uint256(1)).toEqual(uint256(1)); + expect(uint256(1)).not.toEqual(uint256(0)); + expect(uint256(1)).toBeGreaterThan(uint256(0)); + expect(uint256(1)).toBeGreaterThanOrEqual(uint256(1)); + expect(uint256(0)).toBeLessThan(uint256(1)); + expect(uint256(0)).toBeLessThanOrEqual(uint256(0)); + + expect(address(1)).toEqual(address(1)); + expect(address(1)).not.toEqual(address(0)); + + expect(true).toBeTrue(); + expect(false).toBeFalse(); + expect((10 % 5) == 0).toBeTrue(); + expect((10 % 6) == 4).toBeTrue(); + } +} + +``` + diff --git a/docs/src/examples/fe/example.md b/docs/src/examples/fe/example.md new file mode 100644 index 00000000..d5111ecb --- /dev/null +++ b/docs/src/examples/fe/example.md @@ -0,0 +1,25 @@ +## Examples +### How to compile `fe` code + +How to compile `fe` using the `fe` module (Requires to have `fe` installed) + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, fe, Fe} from "vulcan/test.sol"; + +contract FeExample is Test { + function test() external { + Fe memory feCmd = fe.create().setFilePath("./test/mocks/guest_book.fe").setOverwrite(true); + + feCmd.build().unwrap(); + + string memory expectedBytecode = "600180600c6000396000f3fe00"; + + expect(string(feCmd.getBytecode("A").toValue())).toEqual(expectedBytecode); + } +} + +``` + diff --git a/docs/src/examples/format/example.md b/docs/src/examples/format/example.md new file mode 100644 index 00000000..259c080f --- /dev/null +++ b/docs/src/examples/format/example.md @@ -0,0 +1,51 @@ +## Examples +### Using templates + +Using templates with the `format` module to format data + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, accounts, expect, fmt} from "vulcan/test.sol"; + +contract FormatExample is Test { + using accounts for address; + + function test() external { + address target = address(1).setBalance(1); + uint256 balance = target.balance; + + expect(fmt.format("The account {address} has {uint} wei", abi.encode(target, balance))).toEqual( + "The account 0x0000000000000000000000000000000000000001 has 1 wei" + ); + } +} + +``` + +### Formatting decimals + +Use the `{uint:dx}` placeholder to format numbers with decimals + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, accounts, expect, fmt} from "vulcan/test.sol"; + +contract FormatExample is Test { + using accounts for address; + + function test() external { + address target = address(1).setBalance(1e17); + uint256 balance = target.balance; + + expect(fmt.format("The account {address} has {uint:d18} eth", abi.encode(target, balance))).toEqual( + "The account 0x0000000000000000000000000000000000000001 has 0.1 eth" + ); + } +} + +``` + diff --git a/docs/src/examples/fs/example.md b/docs/src/examples/fs/example.md new file mode 100644 index 00000000..c4cbfe33 --- /dev/null +++ b/docs/src/examples/fs/example.md @@ -0,0 +1,100 @@ +## Examples +### Reading files + +Read files as string or bytes + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, fs, BytesResult, StringResult} from "vulcan/test.sol"; + +contract FsExample is Test { + // These files are available only on the context of vulcan + // You will need to provide your own files and edit the read permissions accordingly + string constant HELLO_WORLD = "./test/fixtures/fs/read/hello_world.txt"; + string constant BINARY_TEST_FILE = "./test/fixtures/fs/write/test_binary.txt"; + + function test() external { + StringResult stringResult = fs.readFile(HELLO_WORLD); + BytesResult bytesResult = fs.readFileBinary(HELLO_WORLD); + + expect(stringResult.isOk()).toBeTrue(); + expect(bytesResult.isOk()).toBeTrue(); + + expect(stringResult.unwrap()).toEqual("Hello, World!\n"); + expect(bytesResult.toValue()).toEqual(bytes("Hello, World!\n")); + } +} + +``` + +### Writing files + +Write files as strings or bytes + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, fs, BytesResult, StringResult, EmptyResult} from "vulcan/test.sol"; + +contract FsExample is Test { + // These files are available only on the context of vulcan + // You will need to provide your own files and edit the read permissions accordingly + string constant TEXT_TEST_FILE = "./test/fixtures/fs/write/example.txt"; + + function test() external { + EmptyResult writeStringResult = fs.writeFile(TEXT_TEST_FILE, "This is a test"); + + expect(writeStringResult.isOk()).toBeTrue(); + + StringResult readStringResult = fs.readFile(TEXT_TEST_FILE); + + expect(readStringResult.unwrap()).toEqual("This is a test"); + + EmptyResult writeBytesResult = fs.writeFileBinary(TEXT_TEST_FILE, bytes("This is a test in binary")); + + expect(writeBytesResult.isOk()).toBeTrue(); + + BytesResult readBytesResult = fs.readFileBinary(TEXT_TEST_FILE); + + expect(readBytesResult.unwrap()).toEqual(bytes("This is a test in binary")); + } +} + +``` + +### Other operations + +Obtain metadata and check if file exists + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, fs, BoolResult, FsMetadataResult} from "vulcan/test.sol"; + +contract FsExample is Test { + // These files are available only on the context of vulcan + // You will need to provide your own files and edit the read permissions accordingly + string constant READ_EXAMPLE = "./test/fixtures/fs/read/hello_world.txt"; + string constant NOT_FOUND_EXAMPLE = "./test/fixtures/fs/read/lkjjsadflkjasdf.txt"; + + function test() external { + FsMetadataResult metadataResult = fs.metadata(READ_EXAMPLE); + expect(metadataResult.isOk()).toBeTrue(); + expect(metadataResult.unwrap().isDir).toBeFalse(); + + BoolResult existsResult = fs.fileExists(READ_EXAMPLE); + expect(existsResult.isOk()).toBeTrue(); + expect(existsResult.unwrap()).toBeTrue(); + + BoolResult notFoundResult = fs.fileExists(NOT_FOUND_EXAMPLE); + expect(notFoundResult.isOk()).toBeTrue(); + expect(notFoundResult.unwrap()).toBeFalse(); + } +} + +``` + diff --git a/docs/src/examples/gas/example.md b/docs/src/examples/gas/example.md new file mode 100644 index 00000000..0e6b5ae8 --- /dev/null +++ b/docs/src/examples/gas/example.md @@ -0,0 +1,27 @@ +## Examples +### Measuring gas + +Obtain the gas cost of a operation + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, gas} from "vulcan/test.sol"; + +contract GasExample is Test { + function test() external { + // Start recording gas usage + gas.record("example"); + + payable(0).transfer(1e18); + + // Stop recording and obtain the amount of gas used + uint256 used = gas.stopRecord("example"); + + expect(used).toBeGreaterThan(0); + } +} + +``` + diff --git a/docs/src/examples/huff/example.md b/docs/src/examples/huff/example.md new file mode 100644 index 00000000..f2271bdc --- /dev/null +++ b/docs/src/examples/huff/example.md @@ -0,0 +1,20 @@ +## Examples +### How to compile `huff` code + +How to compile `huff` code using the `huff` module (Requires to have `huff` installed) + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, huff, CommandResult} from "vulcan/test.sol"; + +contract HuffExample is Test { + function test() external { + CommandResult initcode = huff.create().setFilePath("./test/mocks/Getter.huff").compile(); + expect(initcode.unwrap().stdout.length).toBeGreaterThan(0); + } +} + +``` + diff --git a/docs/src/examples/json/example.md b/docs/src/examples/json/example.md new file mode 100644 index 00000000..b33164ab --- /dev/null +++ b/docs/src/examples/json/example.md @@ -0,0 +1,32 @@ +## Examples +### Work with JSON objects + +Create a JSON object, populate it and read it + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, json, JsonObject} from "vulcan/test.sol"; + +contract JSONExample is Test { + function test() external { + // Create an empty JsonObject + JsonObject memory obj = json.create(); + + string memory key = "foo"; + string memory value = "bar"; + + obj.set(key, value); + + expect(obj.getString(".foo")).toEqual(value); + + // Create a populated JsonObject + obj = json.create("{ \"foo\": { \"bar\": \"baz\" } }").unwrap(); + + expect(obj.getString(".foo.bar")).toEqual("baz"); + } +} + +``` + diff --git a/docs/src/examples/requests/example.md b/docs/src/examples/requests/example.md new file mode 100644 index 00000000..56d973cd --- /dev/null +++ b/docs/src/examples/requests/example.md @@ -0,0 +1,115 @@ +## Examples +### Sending requests + +How to send requests to an http server + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, request, Response} from "vulcan/test.sol"; + +contract RequestExample is Test { + function test() external { + Response memory getResponse = request.create().get("https://httpbin.org/get").send().unwrap(); + Response memory postResponse = request.create().post("https://httpbin.org/post").send().unwrap(); + Response memory patchResponse = request.create().patch("https://httpbin.org/patch").send().unwrap(); + Response memory putResponse = request.create().put("https://httpbin.org/put").send().unwrap(); + Response memory deleteResponse = request.create().del("https://httpbin.org/delete").send().unwrap(); + + expect(getResponse.status).toEqual(200); + expect(postResponse.status).toEqual(200); + expect(patchResponse.status).toEqual(200); + expect(putResponse.status).toEqual(200); + expect(deleteResponse.status).toEqual(200); + } +} + +``` + +### Sending a JSON payload + +How to send a request with a JSON body + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, request, Response, RequestClient, JsonObject} from "vulcan/test.sol"; + +contract RequestExample is Test { + function test() external { + RequestClient memory client = request.create(); + + Response memory jsonRes = client.post("https://httpbin.org/post").json("{ \"foo\": \"bar\" }").send().unwrap(); + + expect(jsonRes.status).toEqual(200); + + JsonObject memory responseBody = jsonRes.json().unwrap(); + + expect(responseBody.getString(".json.foo")).toEqual("bar"); + } +} + +``` + +### Request authentication + +How to use different methods of authentication + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, request, Response, RequestClient} from "vulcan/test.sol"; + +contract RequestExample is Test { + function test() external { + RequestClient memory client = request.create(); + + Response memory basicAuthRes = + client.get("https://httpbin.org/basic-auth/user/pass").basicAuth("user", "pass").send().unwrap(); + + expect(basicAuthRes.status).toEqual(200); + + Response memory bearerAuthRes = client.get("https://httpbin.org/bearer").bearerAuth("token").send().unwrap(); + + expect(bearerAuthRes.status).toEqual(200); + } +} + +``` + +### Working with headers + +Using the request module to work with request headers + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, request, Headers, Response, Request, RequestClient} from "vulcan/test.sol"; + +contract RequestExample is Test { + function test() external { + // Setting a default header as key value + RequestClient memory client = request.create().defaultHeader("X-Foo", "true"); + + expect(client.headers.get("X-Foo")).toEqual("true"); + + // The default header gets passed to the request + Request memory req = client.post("https://some-http-server.com").request.unwrap(); + + expect(req.headers.get("X-Foo")).toEqual("true"); + + // Setting multiple headers with a Header variable + Headers headers = request.createHeaders().insert("X-Bar", "true").insert("X-Baz", "true"); + client = request.create().defaultHeaders(headers); + + expect(client.headers.get("X-Bar")).toEqual("true"); + expect(client.headers.get("X-Baz")).toEqual("true"); + } +} + +``` + diff --git a/docs/src/examples/results/example.md b/docs/src/examples/results/example.md new file mode 100644 index 00000000..2dda4f8b --- /dev/null +++ b/docs/src/examples/results/example.md @@ -0,0 +1,74 @@ +## Examples +### Working with result values + +Different methods of getting the underlyng value of a `Result` + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, StringResult, Ok} from "vulcan/test.sol"; + +contract ResultExample is Test { + function test() external { + StringResult result = Ok(string("foo")); + + // Use unwrap to get the value or revert if the result is an `Error` + string memory unwrapValue = result.unwrap(); + expect(unwrapValue).toEqual("foo"); + + // Use expect to get the value or revert with a custom message if + // the result is an `Error` + string memory expectValue = result.expect("Result failed"); + expect(expectValue).toEqual("foo"); + + // Safely getting the value + if (result.isOk()) { + string memory value = result.toValue(); + expect(value).toEqual("foo"); + } + } +} + +``` + +### Working with Errors + +Different ways of handling errors. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, commands, ctx, expect, CommandResult, CommandError} from "vulcan/test.sol"; + +contract ResultExample is Test { + function test() external { + // Run a non existent command + CommandResult result = commands.run(["asdf12897u391723"]); + + // Use unwrap to revert with the default error message + ctx.expectRevert( + "The command was not executed: \"Failed to execute command: No such file or directory (os error 2)\"" + ); + result.unwrap(); + + // Use expect to revert with a custom error message + ctx.expectRevert("Command not executed"); + result.expect("Command not executed"); + + bool failed = false; + + // Handle the error manually + if (result.isError()) { + if (result.toError().matches(CommandError.NotExecuted)) { + failed = true; + } + } + + expect(failed).toBeTrue(); + } +} + +``` + diff --git a/docs/src/examples/strings/example.md b/docs/src/examples/strings/example.md new file mode 100644 index 00000000..3815a39c --- /dev/null +++ b/docs/src/examples/strings/example.md @@ -0,0 +1,34 @@ +## Examples +### Transforming and parsing + +Transform values to strings and parse strings to values + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, strings} from "vulcan/test.sol"; + +contract StringsExample is Test { + using strings for *; + + function test() external { + uint256 uintValue = 123; + string memory uintString = uintValue.toString(); + expect(uintString).toEqual("123"); + expect(uintString.parseUint()).toEqual(uintValue); + + bool boolValue = true; + string memory boolString = boolValue.toString(); + expect(boolString).toEqual("true"); + expect(boolString.parseBool()).toEqual(true); + + bytes32 bytes32Value = bytes32(uintValue); + string memory bytes32String = bytes32Value.toString(); + expect(bytes32String).toEqual("0x000000000000000000000000000000000000000000000000000000000000007b"); + expect(bytes32String.parseBytes32()).toEqual(bytes32Value); + } +} + +``` + diff --git a/docs/src/examples/utils/example.md b/docs/src/examples/utils/example.md new file mode 100644 index 00000000..96ef606c --- /dev/null +++ b/docs/src/examples/utils/example.md @@ -0,0 +1,49 @@ +## Examples +### Using println + +Using the println function to log formatted data + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, println} from "vulcan/test.sol"; + +contract UtilsExample is Test { + function test() external view { + println("Log a simple string"); + + string memory someString = "someString"; + println("This is a string: {s}", abi.encode(someString)); + + uint256 aNumber = 123; + println("This is a uint256: {u}", abi.encode(aNumber)); + + println("A string: {s} and a number: {u}", abi.encode(someString, aNumber)); + } +} + +``` + +### Using format + +Using the format function to format data + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, format} from "vulcan/test.sol"; + +contract FormatExample is Test { + function test() external { + uint256 uno = 1; + + string memory formatted = format("is {u} greater than 0? {bool}", abi.encode(uno, uno > 0)); + + expect(formatted).toEqual("is 1 greater than 0? true"); + } +} + +``` + diff --git a/docs/src/guide/installation.md b/docs/src/guide/installation.md index 572a08e2..4543c21f 100644 --- a/docs/src/guide/installation.md +++ b/docs/src/guide/installation.md @@ -2,5 +2,5 @@ In an existing Foundry project, use `forge install`: ``` -$ forge install nomoixyz/vulcan@0.4.1 +$ forge install nomoixyz/vulcan@0.4.2 ``` diff --git a/docs/src/modules/accounts.md b/docs/src/modules/accounts.md index 50fd2c3f..9db543f5 100644 --- a/docs/src/modules/accounts.md +++ b/docs/src/modules/accounts.md @@ -2,37 +2,6 @@ Utilities to operate over accounts (balances, impersonation, etc.) -```solidity -import { Test, accounts } from "vulcan/test.sol"; +{{#include ../examples/accounts/example.md}} -contract TestMyContract is Test { - using accounts for *; - - function testMyContract() external { - // Create an address from a string, sets the ETH balance and impersonate calls - address alice = accounts.create("Alice").setBalance(123).impersonate(); - - // Stop impersonating `alice` - accounts.stopImpersonate(); - - DAI dai = DAI(0x6B175474E89094C44Da98b954EedeAC495271d0F); - - // Create an address from a string, mint tokens to the address and impersonate - // the next call - address bob = accounts.create("Bob").mintToken(address(dai), 1337).impersonateOnce(); - - // There is no need to `create` an address - address charlie = address(0x01).setNonce(10).setBalance(1e18); - - DAI daiClone = DAI(address(0x02)); - // Inject code into an address - address(daiClone).setCode(address(dai).code); - - // The storage of an address can be manipulated - address(daiClone).setStorage(bytes32(1), bytes32(10e18)); - // The storage of an address can be read - bytes32 daiSlot1 = address(daiClone).readStorage(bytes32(1)); - } -} -``` [**Accounts API reference**](../reference/modules/accounts.md) diff --git a/docs/src/modules/commands.md b/docs/src/modules/commands.md index 301964f5..7afb6f72 100644 --- a/docs/src/modules/commands.md +++ b/docs/src/modules/commands.md @@ -3,42 +3,6 @@ Execute external commands. The `ffi` setting must be enabled on `foundry.toml` for this module to work. The `commands` module uses [`Results`](./results.md) when returning values. -```solidity -import { Test, Command, commands, CommandResult } from "vulcan/test.sol"; +{{#include ../examples/commands/example.md}} -contract TestMyContract is Test { - using commands for *; - - function testMyContract() external { - // run `echo Hello World`. - // There is no need to create a dynamic array for the arguments - CommandResult memory res = commands.run(["echo", "Hello World"]); - - if (res.isOk()) { - // do something - } - - if (res.isError()) { - // do something else - } - - // This will return the output from `stdout` or revert if the command failed. - bytes memory output = res.unwrap(); - - // or provide a custom error message in case the command execution fails. - output = res.expect("Somehow echo failed"); - - // A comand can be created to facilitate multiple executions - Command memory cmd = commands.create("echo").arg("Hello World"); - res = cmd.run(); - res = cmd.run(); - res = cmd.run(); - - // A base command can be created and then be executed with different arguments - Command memory ping = commands.create("ping").args(["-c", "1"]); - res = ping.arg("etherscan.io").run(); - res = ping.arg("arbiscan.io").run(); - } -} -``` [**Commands API reference**](../reference/modules/commands.md) diff --git a/docs/src/modules/config.md b/docs/src/modules/config.md index ccaee2f8..cd88055c 100644 --- a/docs/src/modules/config.md +++ b/docs/src/modules/config.md @@ -1,22 +1,7 @@ # Config -Foundry project configuration stuff. +Access the Foundry project configuration. For now it can only read RPC URLs. -```solidity -import { Test, config, Rpc } from "vulcan/test.sol"; +{{#include ../examples/config/example.md}} -contract TestMyContract is Test { - function testMyContract() external { - // Obtain the RPC URL from one of the keys configured on `foundry.toml` - string memory rpcUrl = config.rpcUrl("mainnet"); - - // Obtain all the RPCs as [name, url][] - string[2][] memory rpcs = config.rpcUrls(); - - // Obtain all the RPCs as an array of `Rpc`s - // Rpc { name, url } - Rpc[] memory rpcsAsStruct = config.rpcUrlStructs(); - } -} -``` [**Config API reference**](../reference/modules/config.md) diff --git a/docs/src/modules/console.md b/docs/src/modules/console.md index a044411f..dcce6d56 100644 --- a/docs/src/modules/console.md +++ b/docs/src/modules/console.md @@ -2,14 +2,5 @@ Print to the console. Uses `forge-std/console2.sol`. -```solidity -import { Test, console } from "vulcan/test.sol"; +{{#include ../examples/console/example.md}} -contract TestMyContract is Test { - function testMyContract() external { - // Same API as forge-std's console2 - console.log("Hello World"); - console.log("Some value: ", uint256(1337)); - } -} -``` diff --git a/docs/src/modules/context.md b/docs/src/modules/context.md index 6f32c416..dd889077 100644 --- a/docs/src/modules/context.md +++ b/docs/src/modules/context.md @@ -7,25 +7,5 @@ Functionality to interact with the current runtime context: [`watchers`](./watchers.md)) - Vm state snapshots -```solidity -import { Test, ctx } from "vulcan/test.sol"; +{{#include ../examples/context/example.md}} -contract TestMyContract is Test { - function testMyContract() external { - // Update block state - ctx.setBlockTimestamp(123).setBlockNumber(456); - - // Use snapshots - uint256 snapshotId = ctx.snapshot(); - ctx.revertToSnapshot(snapshotId); - - // Enable/disable gas metering - ctx.pauseGasMetering(); - ctx.resumeGasMetering(); - - // Use Forge's `expectRevert` - ctx.expectRevert(); - myContract.mayRevert(); - } -} -``` diff --git a/docs/src/modules/env.md b/docs/src/modules/env.md index 81379716..882d6c6e 100644 --- a/docs/src/modules/env.md +++ b/docs/src/modules/env.md @@ -2,28 +2,5 @@ Set and read environmental variables. -```solidity -import { Test, env } from "vulcan/test.sol"; +{{#include ../examples/env/example.md}} -contract TestMyContract is Test { - function testMyContract() external { - // Sets the value of the environment variable `MY_VAR` to `Hello World` - env.set("MY_VAR", string("Hello World")); - - // Reads the content of the `MY_VAR` environment variable - string memory MY_VAR = env.getString("MY_VAR"); - - // Reads an environment variable and sets a default value - bytes32 foo = env.getBytes32("FOO", bytes32(123)); - - // Reads an environment variable string as an array of booleans where - // the string uses `,` to separate each value - bool[] bar = env.getBoolArray("BAR", ","); - - uint256[] memory defaultValue = new uint256[](100); - // Reads an environment variable string as an array of uint256 where - // the string uses `;` to separate each value and provides a default. - uint256[] baz = env.getUintArray("BAZ", ";", defaultValue); - } -} -``` diff --git a/docs/src/modules/events.md b/docs/src/modules/events.md index a1ee4f1c..0fbae26c 100644 --- a/docs/src/modules/events.md +++ b/docs/src/modules/events.md @@ -2,26 +2,6 @@ Provides utilities to get logs and transform values into their topic representation. -```solidity -import { Test, events, Log } from "vulcan/test.sol"; +{{#include ../examples/events/example.md}} -contract TestMyContract is Test { - using events for *; - - function testMyContract() external { - // Gets the topic representation of an `uint256` value - bytes32 topic1 = uint256(1).topic(); - // Gets the topic representation of a `bool` value - bytes32 topic2 = false.topic(); - // Gets the topic representation of an `address` value - bytes32 topic3 = address(1).topic(); - - // Start recording logs. Same as `forge-std/Vm.sol:recordLogs` - events.recordLogs(); - - // Gets the recorded logs. Same as `forge-std/Vm.sol:getRecordedLogs` - Log[] memory logs = events.getRecordedLogs(); - } -} -``` [**Events API reference**](../reference/modules/events.md) diff --git a/docs/src/modules/expect.md b/docs/src/modules/expect.md new file mode 100644 index 00000000..86204502 --- /dev/null +++ b/docs/src/modules/expect.md @@ -0,0 +1,9 @@ +# Expect + +The `Expect` module introduces the `expect` function, designed to validate if specified conditions are satisfied. +Depending on the type of the input parameter, the `expect` function offers a range of matchers tailored to the +context. For instance, with a string like `"Hello World"`, you can use `.toContain("Hello")`, or for numbers, +`expect(1).toBeGreaterThan(0)` can be applied. This adaptability ensures precise and concise condition +checking across various data types. + +{{#include ../examples/expect/example.md}} diff --git a/docs/src/modules/fe.md b/docs/src/modules/fe.md index 21a4a3f4..cfbf8062 100644 --- a/docs/src/modules/fe.md +++ b/docs/src/modules/fe.md @@ -1,25 +1,9 @@ # Fe Provides [Fe](https://fe-lang.org/) compiler support. The `ffi` setting must be enabled on `foundry.toml` for this module -to work. +to work. This module requires the `fe` binary installed in order to work. -```solidity -import { Test, fe } from "vulcan/test.sol"; +{{ #include ../examples/fe/example.md }} -contract TestMyContract is Test { - function testCompile() external { - fe - .create() - .setFilePath("./test/mocks/guest_book.fe") - .setOutputDir("./test/fixtures/fe/output") - .setOverwrite(true) - .build() - .unwrap(); - - bytes memory bytecode = fe.getBytecode("GuestBook").unwrap(); - } - -} -``` [**Fe API reference**](../reference/modules/fe.md) diff --git a/docs/src/modules/fmt.md b/docs/src/modules/fmt.md index ebfb5ebf..b4945136 100644 --- a/docs/src/modules/fmt.md +++ b/docs/src/modules/fmt.md @@ -1,24 +1,17 @@ # Format -The format function defined under the `fmt` module enables you to format strings dynamically by using a template string and the ABI encoded arguments: +The format function defined under the `fmt` module enables you to format strings dynamically by using a template string and the ABI encoded arguments. -```solidity -import {Test, expect, console, fmt} from "vulcan/test.sol"; +The accepted placeholders are: +- `{address} or {a}` for the `address` type. +- `{bytes32} or {b32}` for the `bytes32` type. +- `{string} or {s}` for the `string` type. +- `{bytes} or {b}` for the `bytes` type. +- `{uint} or {u}` for the `uint256` type. +- `{int} or {i}` for the `int256` type. +- `{bool}` for the `bool` type. + +For the `uint256` type there is a special placeholder to deal with decimals `{u:dx}` where `x` is +the number of decimals, for example `{u:d18}` to format numbers with `18` decimals. -contract TestMyContract is Test { - function testFormat() external { - string memory template = "{address} hello {string} world {bool}"; - - // You can also use abbreviations: "{a} hello {s} world {b}"; - template = "{a} hello {s} world {b}"; - - string memory result = fmt.format(template, abi.encode(address(123), "foo", true)); - expect(result).toEqual("0x000000000000000000000000000000000000007B hello foo world true"); - - // For numerical values, you can even specify the number of decimals to format with - expect(fmt.format("{uint:d18}", abi.encode(1e17))).toEqual("0.1"); - } -} -``` - -This example demonstrates the use of a string template with placeholders and a custom formatting function to generate a formatted string. It is a simple and efficient way to manage dynamic values on strings. \ No newline at end of file +{{#include ../examples/format/example.md}} diff --git a/docs/src/modules/fs.md b/docs/src/modules/fs.md index dce14da8..25b327fe 100644 --- a/docs/src/modules/fs.md +++ b/docs/src/modules/fs.md @@ -3,38 +3,6 @@ Provides utilities to interact with the filesystem. In order to use this module the `fs_permissions` setting must be set correctly in `foundry.toml`. -```solidity -import { Test, fs, BytesResult } from "vulcan/test.sol"; +{{#include ../examples/fs/example.md}} -contract TestMyContract is Test { - function testMyContract() external { - // Write a string to a file - fs.write("test.txt", "Hello World").unwrap(); - - // Write bytes to a file - fs.writeBinary("test.bin", abi.encodeWithSignature("test(uint256)", 1e18)).unwrap(); - - // Read a file as a string - string memory content = fs.read("test.txt").unwrap(); - - // Read a file as bytes - BytesResult binContentResult = fs.readBinary("test.bin"); - bytes memory binContent = binContentResult.unwrap(); - - // Delete files - fs.remove("delete.me").unwrap(); - - // Copy files - fs.copy("file.original", "file.backup").unwrap(); - - // Move files - fs.move("hold.txt", "hodl.txt").unwrap(); - - // Check if a file or directory exists - if (fs.exists("some_file.txt").unwrap()) { - fs.remove("some_file.txt").unwrap(); - } - } -} -``` [**Fs API reference**](../reference/modules/fs.md) diff --git a/docs/src/modules/gas.md b/docs/src/modules/gas.md new file mode 100644 index 00000000..c33b0158 --- /dev/null +++ b/docs/src/modules/gas.md @@ -0,0 +1,5 @@ +Provides method to measure gas usage in a more granular way. + +{{#include ../examples/gas/example.md}} + +[**Fs API reference**](../reference/modules/fs.md) diff --git a/docs/src/modules/huff.md b/docs/src/modules/huff.md index f003d0fd..8561b5d7 100644 --- a/docs/src/modules/huff.md +++ b/docs/src/modules/huff.md @@ -1,39 +1,8 @@ # Huff -Provides Huff compiler support. The `ffi` setting must be enabled on `foundry.toml` for this module -to work. +Provides [Huff](https://huff.sh/) compiler support. The `ffi` setting must be enabled on `foundry.toml` for this module +to work. This module requires the `huffc` binary installed in order to work. -```solidity -import { Test, huff } from "vulcan/test.sol"; +{{ #include ../examples/huff/example.md }} -contract TestMyContract is Test { - function testMyContractSimple() external { - // create the `Huffc` structure, mutate the configuration, and compile. - // - // runs: - // `huffc -b ./filePath.huff` - bytes memory initcode = huffc.create() - .setFilePath("./filePath.huff") - .compile(); - } - - function testMyContractComplex() external { - bytes32 SLOT = 0x0000000000000000000000000000000000000000000000000000000000000000; - - // create the `Huffc` structure, mutate the configuration, and compile. - // - // runs: - // `dhuff -ao ./outputPath.json -m ALT_MAIN -l ALT_CON -r -c SLOT=0x00 -b ./filePath.huff` - bytes memory runtimecode = huffc.create() - .setCompilerPath("dhuff") - .setFilePath("./filePath.huff") - .setOutputPath("./outputPath.json") - .setMainName("ALT_MAIN") - .setConstructorName("ALT_CON") - .setOnlyRuntime(true) - .addConstantOverride("SLOT", SLOT) - .compile(); - } -} -``` [**Huff API reference**](../reference/modules/huff.md) diff --git a/docs/src/modules/json.md b/docs/src/modules/json.md index 9794d0f4..27c3c79f 100644 --- a/docs/src/modules/json.md +++ b/docs/src/modules/json.md @@ -2,28 +2,6 @@ Manipulate JSON data. -```solidity -import { Test, JsonObject, json } from "vulcan/test.sol"; - -contract TestMyContract is Test { - function testMyContract() external { - // Create a JsonObject struct - JsonObject memory obj = json.create(); - - // Set the property `foo` with a value of `true` - obj.set("foo", true); - - // Obtain the set Json string - expect(obj.serialized).toEqual('{"foo":true}'); - - // Nested Objects - JsonObject memory nested = json.create(); - - nested.set("bar", obj); - - expect(nested.serialized).toEqual('{"bar":{"foo":true}}'); - } -} -``` +{{#include ../examples/json/example.md}} [**Json API reference**](../reference/modules/json.md) diff --git a/docs/src/modules/requests.md b/docs/src/modules/requests.md index 97d8d93a..6852c326 100644 --- a/docs/src/modules/requests.md +++ b/docs/src/modules/requests.md @@ -4,37 +4,5 @@ The request module is inspired by Rust's `reqwest` crate. It provides a flexible with external web services allowing to work with request headers, request authorization, response headers, parsing a response body as JSON and others. -```solidity -import { Test, request, RequestClient, ResponseResult, Response, JsonObject } from "vulcan/test.sol"; +{{#include ../examples/requests/example.md}} -contract HttpRequests is Test { - function getFoo() external returns (string memory) { - // Create a request client - RequestClient memory client = request.create().defaultHeader("Content-Type", "application/json"); - - // Send a GET request - ResponseResult responseResult = client.get("https://httpbin.org/get?foo=bar").send(); - - // Check if it's an error - if (responseResult.isError()) { - revert("Request to httpbin.org failed"); - } - - // Get the response from the response result - Response memory response = responseResult.toValue(); - - // Check status code - if (response.status == 401) { - revert("Request to httpbin.org is Unauthorized"); - } - - // Get the response body as a JsonObject - JsonObject memory jsonBody = response.json().unwrap(); - - // Get the value of `foo` from the json body response from httpbin - string memory foo = jsonBody.getString(".args.foo"); - - return foo; - } -} -``` diff --git a/docs/src/modules/results.md b/docs/src/modules/results.md index 4a7f6d52..07097f9d 100644 --- a/docs/src/modules/results.md +++ b/docs/src/modules/results.md @@ -1,4 +1,4 @@ -# Results +# Results \& Errors 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. @@ -10,31 +10,4 @@ Similar to Rust's Results API, Vulcan implements the following functions for all - `toError()`: transforms the Result into an `Error` - `toValue()`: gets the Result's underlying value (if the Result is Ok) -```solidity -import { Test, StringResult, Ok, console, CommandResult, Error } from "vulcan/test.sol"; - -contract TestMyContract is Test { - function testMyContract() external { - CommandResult result = commands.run(["echo", "Hello, world!"]); - - // We can handle different types of errors - if (result.isError()) { - Error err = result.toError(); - - if (err.matches(CommandError.NotExecuted)) { - revert("Something weird happened!"); - } else { - revert("Unknown error"); - } - - // Or we could immediately get the actual output, reverting if the command failed to run - bytes memory out = result.expect("wtf echo failed!").stdout; - - // Another way is to check if the result is ok - if (result.isOk()) { - // We know the result is ok so we can access the underlying value - out = result.toValue().stdout; - } - } -} -``` +{{#include ../examples/results/example.md}} diff --git a/docs/src/modules/strings.md b/docs/src/modules/strings.md index 09d71a20..dd05ab8a 100644 --- a/docs/src/modules/strings.md +++ b/docs/src/modules/strings.md @@ -2,36 +2,6 @@ Convert basic types from / to strings. -```solidity -import { Test, strings } from "vulcan/test.sol"; +{{#include ../examples/strings/example.md}} -contract TestMyContract is Test { - using strings for *; - - function testMyContract() external { - // Obtain the string representation of uints - expect(uint256(1).toString()).toEqual("1"); - - // Obtain the string representation of booleans - expect(true.toString()).toEqual("true"); - expect(false.toString()).toEqual("false"); - - // Obtain the string representation of an address - expect(address(1).toString()).toEqual("0x0000000000000000000000000000000000000001") - - // Parse a number string to a `uint256` - expect("1".parseUint()).toEqual(1); - - // Parse a boolean string to a `bool` - expect("true".parseBool()).toBeTrue(); - - // Parse an address string to an `address` - expect("0x13DFD56424777BAC80070a98Cf83DD82246c2bC0".parseAddress()).toEqual(0x13DFD56424777BAC80070a98Cf83DD82246c2bC0); - - // Format - see the Format module for more details - uint256 amount = 1e17; - expect("Token amount: {u:d18}".format(abi.encode(amount))).toEqual("Token amount: 0.1"); - } -} -``` [**Strings API reference**](../reference/modules/strings.md) diff --git a/docs/src/modules/utils.md b/docs/src/modules/utils.md index e987813e..144c14f2 100644 --- a/docs/src/modules/utils.md +++ b/docs/src/modules/utils.md @@ -2,19 +2,10 @@ This module provides a set of utility functions that make use of other modules in Vulcan. -```solidity -import {Test, expect, println, format} from "vulcan/test.sol"; +Some of the utility functions: +- `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. +- `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 + the `format` function underneath -contract TestMyContract is Test { - function testUtils() external { - // Print a string - println("Hello world!"); +{{#include ../examples/utils/example.md}} - // Print a formatted string - see Format module for more details - println("Hello {string}!", abi.encode("world")); - - // Format a string - expect(format("Hello {string}!", abi.encode("world"))).toEqual("Hello world!"); - } -} -``` \ No newline at end of file diff --git a/docs/src/scripts/README.md b/docs/src/scripts/README.md index 59d9cb4b..7cc16e55 100644 --- a/docs/src/scripts/README.md +++ b/docs/src/scripts/README.md @@ -1 +1,34 @@ # Scripts + +Most of Vulcan's modules used for testing can be used in `Scripts` but not all the functions are +recommended to be used in this context. A few examples are: +- [`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`. +- [`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`; +- [`Forks`](../modules/forks.md): None of the functions in this module should be used. If there is a + need to use `unsafe` functions use the `forksUnsafe` module exported in `vulcan/script.sol`. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {MyContract} from "src/MyContract.sol"; +import {Script, ctx, println, request} from "vulcan/script.sol"; + +contract DeployScript is Script { + function run() public { + ctx.startBroadcast(); + + new MyContract(); + + ctx.stopBroadcast(); + + println("Notifying API"); + + request + .create() + .post("https://my-api.io/webhook/notify/deployment") + .send() + .expect("Failed to trigger webhook"); + } +} +``` diff --git a/docs/src/testing/README.md b/docs/src/testing/README.md index 93bbaa75..eefbf8ca 100644 --- a/docs/src/testing/README.md +++ b/docs/src/testing/README.md @@ -11,11 +11,13 @@ contract ExampleTest is Test { expect(value).toEqual(1); expect(value).not.toEqual(2); expect(value).toBeLessThan(2); - expect("Hello World!).toContain("World"); + expect("Hello World!").toContain("World"); } } ``` +# Invariant testing + 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. ```solidity diff --git a/foundry.toml b/foundry.toml index c82919a1..64810763 100644 --- a/foundry.toml +++ b/foundry.toml @@ -12,7 +12,7 @@ fs_permissions = [ ] [rpc_endpoints] -fake = "https://my-fake-rpc-url" -solana-rulz = "https://solana-rulz.gg" +mainnet = "https://mainnet.rpc.io" +arbitrum = "https://arbitrum.rpc.io" # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/remappings.txt b/remappings.txt index 845bd0af..e29db5e7 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,2 +1,3 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ +vulcan/=src/ diff --git a/src/_modules/Accounts.sol b/src/_modules/Accounts.sol index 6a38154f..017b2997 100644 --- a/src/_modules/Accounts.sol +++ b/src/_modules/Accounts.sol @@ -430,4 +430,12 @@ library accounts { function createMany(uint256 length) internal returns (address[] memory) { return accountsSafe.createMany(length); } + + /// @dev Generates an array of addresses with a specific length and a prefix as label. + /// The label for each address will be `{prefix}_{i}`. + /// @param length The amount of addresses to generate. + /// @param prefix The prefix of the label for each address. + function createMany(uint256 length, string memory prefix) internal returns (address[] memory) { + return accountsSafe.createMany(length, prefix); + } } diff --git a/test/_modules/Config.t.sol b/test/_modules/Config.t.sol index 51dac9db..3e706e82 100644 --- a/test/_modules/Config.t.sol +++ b/test/_modules/Config.t.sol @@ -5,28 +5,28 @@ import {Test, expect, config, Rpc, console} from "../../src/test.sol"; contract ConfigTest is Test { function testItCanObtainRpcUrls() external { - string memory key = "fake"; + string memory key = "mainnet"; - expect(config.rpcUrl(key)).toEqual("https://my-fake-rpc-url"); + expect(config.rpcUrl(key)).toEqual("https://mainnet.rpc.io"); } function testItCanObtainAllRpcUrls() external { string[2][] memory rpcs = config.rpcUrls(); expect(rpcs.length).toEqual(2); - expect(rpcs[0][0]).toEqual("fake"); - expect(rpcs[0][1]).toEqual("https://my-fake-rpc-url"); - expect(rpcs[1][0]).toEqual("solana-rulz"); - expect(rpcs[1][1]).toEqual("https://solana-rulz.gg"); + expect(rpcs[0][0]).toEqual("arbitrum"); + expect(rpcs[0][1]).toEqual("https://arbitrum.rpc.io"); + expect(rpcs[1][0]).toEqual("mainnet"); + expect(rpcs[1][1]).toEqual("https://mainnet.rpc.io"); } function testItCanObtainAllRpcUrlsAsStructs() external { Rpc[] memory rpcs = config.rpcUrlStructs(); expect(rpcs.length).toEqual(2); - expect(rpcs[0].name).toEqual("fake"); - expect(rpcs[0].url).toEqual("https://my-fake-rpc-url"); - expect(rpcs[1].name).toEqual("solana-rulz"); - expect(rpcs[1].url).toEqual("https://solana-rulz.gg"); + expect(rpcs[0].name).toEqual("arbitrum"); + expect(rpcs[0].url).toEqual("https://arbitrum.rpc.io"); + expect(rpcs[1].name).toEqual("mainnet"); + expect(rpcs[1].url).toEqual("https://mainnet.rpc.io"); } } diff --git a/test/examples/accounts/AccountsExample01.t.sol b/test/examples/accounts/AccountsExample01.t.sol new file mode 100644 index 00000000..97e1db28 --- /dev/null +++ b/test/examples/accounts/AccountsExample01.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +/// @title Create an address +/// @dev How to create a simple address +contract AccountsExample is Test { + function test() external { + address alice = accounts.create(); + + expect(alice).not.toEqual(address(0)); + } +} diff --git a/test/examples/accounts/AccountsExample02.t.sol b/test/examples/accounts/AccountsExample02.t.sol new file mode 100644 index 00000000..feb9dfdc --- /dev/null +++ b/test/examples/accounts/AccountsExample02.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +/// @title Create a labeled address +/// @dev Creating an address labeled as "Alice" +contract AccountsExample is Test { + function test() external { + address alice = accounts.create("Alice"); + + expect(alice).not.toEqual(address(0)); + } +} diff --git a/test/examples/accounts/AccountsExample03.t.sol b/test/examples/accounts/AccountsExample03.t.sol new file mode 100644 index 00000000..2355f5ef --- /dev/null +++ b/test/examples/accounts/AccountsExample03.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +/// @title Create multiple addresses +/// @dev Creating multiple addresses +contract AccountsExample is Test { + function test() external { + address[] memory addresses = accounts.createMany(10); + + expect(addresses.length).toEqual(10); + } +} diff --git a/test/examples/accounts/AccountsExample04.t.sol b/test/examples/accounts/AccountsExample04.t.sol new file mode 100644 index 00000000..a596fa81 --- /dev/null +++ b/test/examples/accounts/AccountsExample04.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +/// @title Create multiple labeled addresses with a prefix +/// @dev Creating multiple addresses labeled with the prefix `Account` +contract AccountsExample is Test { + function test() external { + address[] memory addresses = accounts.createMany(10, "Account"); + + expect(addresses.length).toEqual(10); + } +} diff --git a/test/examples/accounts/AccountsExample05.t.sol b/test/examples/accounts/AccountsExample05.t.sol new file mode 100644 index 00000000..4e7071ad --- /dev/null +++ b/test/examples/accounts/AccountsExample05.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, accounts} from "vulcan/test.sol"; + +/// @title Use method chaining on addresses +/// @dev Use method chaining on addresses to call multiple methods +contract AccountsExample05 is Test { + using accounts for address; + + function test() external { + address alice = accounts.create("Alice").setNonce(666).setBalance(100e18); + + address bob = accounts.create("Bob").setBalance(10e18).impersonateOnce(); + + payable(alice).transfer(bob.balance); + + expect(alice.balance).toEqual(110e18); + expect(alice.getNonce()).toEqual(666); + expect(bob.balance).toEqual(0); + } +} diff --git a/test/examples/commands/CommandExample01.t.sol b/test/examples/commands/CommandExample01.t.sol new file mode 100644 index 00000000..bb7416d1 --- /dev/null +++ b/test/examples/commands/CommandExample01.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, commands, CommandResult, CommandOutput} from "vulcan/test.sol"; + +/// @title Run a simple command +/// @dev Run a simple command and obtain the output +contract RunCommandExample is Test { + function test() external { + // Run a command to get a result + CommandResult cmdResult = commands.run(["echo", "Hello, World!"]); + + // Obtain the output from the result + CommandOutput memory output = cmdResult.expect("Failed to run command"); + + // Check the output + expect(string(output.stdout)).toEqual("Hello, World!"); + } +} diff --git a/test/examples/commands/CommandExample02.t.sol b/test/examples/commands/CommandExample02.t.sol new file mode 100644 index 00000000..d8251ae3 --- /dev/null +++ b/test/examples/commands/CommandExample02.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, commands, Command, CommandResult, CommandOutput} from "vulcan/test.sol"; + +/// @title Reuse a command +/// @dev Reuse a command with different arguments +contract ReuseACommandExample is Test { + function test() external { + // Create a command + Command memory echo = commands.create("echo"); + + // Run the commands and get the results + CommandResult fooResult = echo.arg("foo").run(); + CommandResult barResult = echo.arg("bar").run(); + + // Obtain the outputs from the results + CommandOutput memory fooOutput = fooResult.expect("Failed to run echo 'foo'"); + CommandOutput memory barOutput = barResult.expect("Failed to run echo 'bar'"); + + // Check the outputs + expect(string(fooOutput.stdout)).toEqual("foo"); + expect(string(barOutput.stdout)).toEqual("bar"); + } +} diff --git a/test/examples/config/ConfigExample01.t.sol b/test/examples/config/ConfigExample01.t.sol new file mode 100644 index 00000000..a267ec6c --- /dev/null +++ b/test/examples/config/ConfigExample01.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, config} from "vulcan/test.sol"; + +/// @title Obtain a specific RPC URL +/// @dev Read a specific RPC URL from the foundry configuration +contract ConfigExample is Test { + function test() external { + string memory key = "mainnet"; + + expect(config.rpcUrl(key)).toEqual("https://mainnet.rpc.io"); + } +} diff --git a/test/examples/config/ConfigExample02.t.sol b/test/examples/config/ConfigExample02.t.sol new file mode 100644 index 00000000..5f70b153 --- /dev/null +++ b/test/examples/config/ConfigExample02.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, config} from "vulcan/test.sol"; + +/// @title Obtain all the RPC URLs +/// @dev Read all the RPC URLs from the foundry configuration +contract ConfigExample is Test { + function test() external { + string[2][] memory rpcs = config.rpcUrls(); + + expect(rpcs.length).toEqual(2); + expect(rpcs[0][0]).toEqual("arbitrum"); + expect(rpcs[0][1]).toEqual("https://arbitrum.rpc.io"); + expect(rpcs[1][0]).toEqual("mainnet"); + expect(rpcs[1][1]).toEqual("https://mainnet.rpc.io"); + } +} diff --git a/test/examples/config/ConfigExample03.t.sol b/test/examples/config/ConfigExample03.t.sol new file mode 100644 index 00000000..15bff5ce --- /dev/null +++ b/test/examples/config/ConfigExample03.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, config, Rpc} from "vulcan/test.sol"; + +/// @title Obtain all the RPC URLs using structs +/// @dev Read all the RPC URL from the foundry configuration as structs +contract ConfigExample is Test { + function test() external { + Rpc[] memory rpcs = config.rpcUrlStructs(); + + expect(rpcs.length).toEqual(2); + expect(rpcs[0].name).toEqual("arbitrum"); + expect(rpcs[0].url).toEqual("https://arbitrum.rpc.io"); + expect(rpcs[1].name).toEqual("mainnet"); + expect(rpcs[1].url).toEqual("https://mainnet.rpc.io"); + } +} diff --git a/test/examples/console/ConsoleExample01.t.sol b/test/examples/console/ConsoleExample01.t.sol new file mode 100644 index 00000000..a17a0e88 --- /dev/null +++ b/test/examples/console/ConsoleExample01.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, console} from "vulcan/test.sol"; + +/// @title Log values +/// @dev Use the `console` function to log values. +contract ConsoleExample is Test { + function test() external pure { + string memory foo = "foo"; + string memory bar = "bar"; + + uint256 oneTwoThree = 123; + uint256 threeTwoOne = 321; + + bool isTrue = true; + + console.log(foo); + console.log(foo, bar); + console.log(foo, bar, threeTwoOne); + console.log(foo, bar, threeTwoOne, isTrue); + console.log(threeTwoOne, oneTwoThree); + console.log(threeTwoOne + oneTwoThree); + console.log(1 > 0); + } +} diff --git a/test/examples/context/ContextExample01.t.sol b/test/examples/context/ContextExample01.t.sol new file mode 100644 index 00000000..656d640b --- /dev/null +++ b/test/examples/context/ContextExample01.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, ctx} from "vulcan/test.sol"; + +/// @title Modify chain parameters +/// @dev Use the context module to modify chain parameters +contract ContextExample is Test { + function test() external { + ctx.setBlockTimestamp(1); + expect(block.timestamp).toEqual(1); + + ctx.setBlockNumber(123); + expect(block.number).toEqual(123); + + ctx.setBlockBaseFee(99999); + expect(block.basefee).toEqual(99999); + + ctx.setBlockPrevrandao(bytes32(uint256(123))); + expect(block.prevrandao).toEqual(uint256(bytes32(uint256(123)))); + + ctx.setChainId(666); + expect(block.chainid).toEqual(666); + + ctx.setBlockCoinbase(address(1)); + expect(block.coinbase).toEqual(address(1)); + + ctx.setGasPrice(1e18); + expect(tx.gasprice).toEqual(1e18); + } +} diff --git a/test/examples/context/ContextExample02.t.sol b/test/examples/context/ContextExample02.t.sol new file mode 100644 index 00000000..8e25291a --- /dev/null +++ b/test/examples/context/ContextExample02.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, ctx} from "vulcan/test.sol"; + +/// @title Modify chain parameters using method chaining +/// @dev Use the context module to modify chain parameters using method chaining +contract ContextExample is Test { + function test() external { + ctx.setBlockTimestamp(1).setBlockNumber(123).setBlockBaseFee(99999).setBlockPrevrandao(bytes32(uint256(123))) + .setChainId(666).setBlockCoinbase(address(1)).setGasPrice(1e18); + + expect(block.timestamp).toEqual(1); + expect(block.number).toEqual(123); + expect(block.basefee).toEqual(99999); + expect(block.prevrandao).toEqual(uint256(bytes32(uint256(123)))); + expect(block.chainid).toEqual(666); + expect(block.coinbase).toEqual(address(1)); + expect(tx.gasprice).toEqual(1e18); + } +} diff --git a/test/examples/env/EnvExample01.t.sol b/test/examples/env/EnvExample01.t.sol new file mode 100644 index 00000000..539ee180 --- /dev/null +++ b/test/examples/env/EnvExample01.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, env} from "vulcan/test.sol"; + +/// @title Set and get environment variables +/// @dev Use the `env` module to set and read environment variables +contract EnvExample is Test { + function test() external { + env.set("SomeString", "foo"); + env.set("SomeUint", "100000000000000000000000"); + env.set("SomeBool", "true"); + env.set("SomeArray", "1,2,3,4"); + + expect(env.getString("SomeString")).toEqual("foo"); + expect(env.getUint("SomeUint")).toEqual(100_000_000_000_000_000_000_000); + expect(env.getBool("SomeBool")).toBeTrue(); + expect(env.getUintArray("SomeArray", ",")[0]).toEqual(1); + expect(env.getUintArray("SomeArray", ",")[1]).toEqual(2); + expect(env.getUintArray("SomeArray", ",")[2]).toEqual(3); + expect(env.getUintArray("SomeArray", ",")[3]).toEqual(4); + } +} diff --git a/test/examples/events/EventsExample01.t.sol b/test/examples/events/EventsExample01.t.sol new file mode 100644 index 00000000..198bec71 --- /dev/null +++ b/test/examples/events/EventsExample01.t.sol @@ -0,0 +1,29 @@ +//// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, events, Log} from "vulcan/test.sol"; + +/// @title Logging events +/// @dev Logging events and reading events topics and data +contract EventsExample is Test { + using events for *; + + event SomeEvent(uint256 indexed a, address b); + + function run() external { + uint256 a = 666; + address b = address(333); + + events.recordLogs(); + + emit SomeEvent(a, b); + + Log[] memory logs = events.getRecordedLogs(); + + expect(logs.length).toEqual(1); + expect(logs[0].emitter).toEqual(address(this)); + expect(logs[0].topics[0]).toEqual(SomeEvent.selector); + expect(logs[0].topics[1]).toEqual(a.topic()); + expect(logs[0].data).toEqual(abi.encode(b)); + } +} diff --git a/test/examples/expect/ExpectExample01.t.sol b/test/examples/expect/ExpectExample01.t.sol new file mode 100644 index 00000000..db780409 --- /dev/null +++ b/test/examples/expect/ExpectExample01.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect} from "vulcan/test.sol"; + +/// @title Use different matchers +/// @dev Using the `expect` function and its different matchers +contract ExpectExample is Test { + function test() external { + expect(string("foo")).toEqual(string("foo")); + expect(string("foo")).not.toEqual(string("bar")); + expect(string("foo bar")).toContain(string("foo")); + expect(string("foo bar")).toContain(string("bar")); + + expect(uint256(1)).toEqual(uint256(1)); + expect(uint256(1)).not.toEqual(uint256(0)); + expect(uint256(1)).toBeGreaterThan(uint256(0)); + expect(uint256(1)).toBeGreaterThanOrEqual(uint256(1)); + expect(uint256(0)).toBeLessThan(uint256(1)); + expect(uint256(0)).toBeLessThanOrEqual(uint256(0)); + + expect(address(1)).toEqual(address(1)); + expect(address(1)).not.toEqual(address(0)); + + expect(true).toBeTrue(); + expect(false).toBeFalse(); + expect((10 % 5) == 0).toBeTrue(); + expect((10 % 6) == 4).toBeTrue(); + } +} diff --git a/test/examples/fe/FeExample01.t.sol b/test/examples/fe/FeExample01.t.sol new file mode 100644 index 00000000..bddb384d --- /dev/null +++ b/test/examples/fe/FeExample01.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, fe, Fe} from "vulcan/test.sol"; + +/// @title How to compile `fe` code +/// @dev How to compile `fe` using the `fe` module (Requires to have `fe` installed) +contract FeExample is Test { + function test() external { + Fe memory feCmd = fe.create().setFilePath("./test/mocks/guest_book.fe").setOverwrite(true); + + feCmd.build().unwrap(); + + string memory expectedBytecode = "600180600c6000396000f3fe00"; + + expect(string(feCmd.getBytecode("A").toValue())).toEqual(expectedBytecode); + } +} diff --git a/test/examples/format/FormatExample01.t.sol b/test/examples/format/FormatExample01.t.sol new file mode 100644 index 00000000..d2814a00 --- /dev/null +++ b/test/examples/format/FormatExample01.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, accounts, expect, fmt} from "vulcan/test.sol"; + +/// @title Using templates +/// @dev Using templates with the `format` module to format data +contract FormatExample is Test { + using accounts for address; + + function test() external { + address target = address(1).setBalance(1); + uint256 balance = target.balance; + + expect(fmt.format("The account {address} has {uint} wei", abi.encode(target, balance))).toEqual( + "The account 0x0000000000000000000000000000000000000001 has 1 wei" + ); + } +} diff --git a/test/examples/format/FormatExample02.t.sol b/test/examples/format/FormatExample02.t.sol new file mode 100644 index 00000000..9a9ce9fd --- /dev/null +++ b/test/examples/format/FormatExample02.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, accounts, expect, fmt} from "vulcan/test.sol"; + +/// @title Formatting decimals +/// @dev Use the `{uint:dx}` placeholder to format numbers with decimals +contract FormatExample is Test { + using accounts for address; + + function test() external { + address target = address(1).setBalance(1e17); + uint256 balance = target.balance; + + expect(fmt.format("The account {address} has {uint:d18} eth", abi.encode(target, balance))).toEqual( + "The account 0x0000000000000000000000000000000000000001 has 0.1 eth" + ); + } +} diff --git a/test/examples/fs/FsExample01.t.sol b/test/examples/fs/FsExample01.t.sol new file mode 100644 index 00000000..359fd4ef --- /dev/null +++ b/test/examples/fs/FsExample01.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, fs, BytesResult, StringResult} from "vulcan/test.sol"; + +/// @title Reading files +/// @dev Read files as string or bytes +contract FsExample is Test { + // These files are available only on the context of vulcan + // You will need to provide your own files and edit the read permissions accordingly + string constant HELLO_WORLD = "./test/fixtures/fs/read/hello_world.txt"; + string constant BINARY_TEST_FILE = "./test/fixtures/fs/write/test_binary.txt"; + + function test() external { + StringResult stringResult = fs.readFile(HELLO_WORLD); + BytesResult bytesResult = fs.readFileBinary(HELLO_WORLD); + + expect(stringResult.isOk()).toBeTrue(); + expect(bytesResult.isOk()).toBeTrue(); + + expect(stringResult.unwrap()).toEqual("Hello, World!\n"); + expect(bytesResult.toValue()).toEqual(bytes("Hello, World!\n")); + } +} diff --git a/test/examples/fs/FsExample02.t.sol b/test/examples/fs/FsExample02.t.sol new file mode 100644 index 00000000..591c56c7 --- /dev/null +++ b/test/examples/fs/FsExample02.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, fs, BytesResult, StringResult, EmptyResult} from "vulcan/test.sol"; + +/// @title Writing files +/// @dev Write files as strings or bytes +contract FsExample is Test { + // These files are available only on the context of vulcan + // You will need to provide your own files and edit the read permissions accordingly + string constant TEXT_TEST_FILE = "./test/fixtures/fs/write/example.txt"; + + function test() external { + EmptyResult writeStringResult = fs.writeFile(TEXT_TEST_FILE, "This is a test"); + + expect(writeStringResult.isOk()).toBeTrue(); + + StringResult readStringResult = fs.readFile(TEXT_TEST_FILE); + + expect(readStringResult.unwrap()).toEqual("This is a test"); + + EmptyResult writeBytesResult = fs.writeFileBinary(TEXT_TEST_FILE, bytes("This is a test in binary")); + + expect(writeBytesResult.isOk()).toBeTrue(); + + BytesResult readBytesResult = fs.readFileBinary(TEXT_TEST_FILE); + + expect(readBytesResult.unwrap()).toEqual(bytes("This is a test in binary")); + } +} diff --git a/test/examples/fs/FsExample03.t.sol b/test/examples/fs/FsExample03.t.sol new file mode 100644 index 00000000..b02e528c --- /dev/null +++ b/test/examples/fs/FsExample03.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, fs, BoolResult, FsMetadataResult} from "vulcan/test.sol"; + +/// @title Other operations +/// @dev Obtain metadata and check if file exists +contract FsExample is Test { + // These files are available only on the context of vulcan + // You will need to provide your own files and edit the read permissions accordingly + string constant READ_EXAMPLE = "./test/fixtures/fs/read/hello_world.txt"; + string constant NOT_FOUND_EXAMPLE = "./test/fixtures/fs/read/lkjjsadflkjasdf.txt"; + + function test() external { + FsMetadataResult metadataResult = fs.metadata(READ_EXAMPLE); + expect(metadataResult.isOk()).toBeTrue(); + expect(metadataResult.unwrap().isDir).toBeFalse(); + + BoolResult existsResult = fs.fileExists(READ_EXAMPLE); + expect(existsResult.isOk()).toBeTrue(); + expect(existsResult.unwrap()).toBeTrue(); + + BoolResult notFoundResult = fs.fileExists(NOT_FOUND_EXAMPLE); + expect(notFoundResult.isOk()).toBeTrue(); + expect(notFoundResult.unwrap()).toBeFalse(); + } +} diff --git a/test/examples/gas/GasExample01.t.sol b/test/examples/gas/GasExample01.t.sol new file mode 100644 index 00000000..bf5bcfad --- /dev/null +++ b/test/examples/gas/GasExample01.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, gas} from "vulcan/test.sol"; + +/// @title Measuring gas +/// @dev Obtain the gas cost of a operation +contract GasExample is Test { + function test() external { + // Start recording gas usage + gas.record("example"); + + payable(0).transfer(1e18); + + // Stop recording and obtain the amount of gas used + uint256 used = gas.stopRecord("example"); + + expect(used).toBeGreaterThan(0); + } +} diff --git a/test/examples/huff/HuffExample01.t.sol b/test/examples/huff/HuffExample01.t.sol new file mode 100644 index 00000000..7e55e90a --- /dev/null +++ b/test/examples/huff/HuffExample01.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, huff, CommandResult} from "vulcan/test.sol"; + +/// @title How to compile `huff` code +/// @dev How to compile `huff` code using the `huff` module (Requires to have `huff` installed) +contract HuffExample is Test { + function test() external { + CommandResult initcode = huff.create().setFilePath("./test/mocks/Getter.huff").compile(); + expect(initcode.unwrap().stdout.length).toBeGreaterThan(0); + } +} diff --git a/test/examples/json/JSONExample01.t.sol b/test/examples/json/JSONExample01.t.sol new file mode 100644 index 00000000..e67060cb --- /dev/null +++ b/test/examples/json/JSONExample01.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, json, JsonObject} from "vulcan/test.sol"; + +/// @title Work with JSON objects +/// @dev Create a JSON object, populate it and read it +contract JSONExample is Test { + function test() external { + // Create an empty JsonObject + JsonObject memory obj = json.create(); + + string memory key = "foo"; + string memory value = "bar"; + + obj.set(key, value); + + expect(obj.getString(".foo")).toEqual(value); + + // Create a populated JsonObject + obj = json.create("{ \"foo\": { \"bar\": \"baz\" } }").unwrap(); + + expect(obj.getString(".foo.bar")).toEqual("baz"); + } +} diff --git a/test/examples/requests/RequestsExample01.t.sol b/test/examples/requests/RequestsExample01.t.sol new file mode 100644 index 00000000..9d5da9ed --- /dev/null +++ b/test/examples/requests/RequestsExample01.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, request, Response} from "vulcan/test.sol"; + +/// @title Sending requests +/// @dev How to send requests to an http server +contract RequestExample is Test { + function test() external { + Response memory getResponse = request.create().get("https://httpbin.org/get").send().unwrap(); + Response memory postResponse = request.create().post("https://httpbin.org/post").send().unwrap(); + Response memory patchResponse = request.create().patch("https://httpbin.org/patch").send().unwrap(); + Response memory putResponse = request.create().put("https://httpbin.org/put").send().unwrap(); + Response memory deleteResponse = request.create().del("https://httpbin.org/delete").send().unwrap(); + + expect(getResponse.status).toEqual(200); + expect(postResponse.status).toEqual(200); + expect(patchResponse.status).toEqual(200); + expect(putResponse.status).toEqual(200); + expect(deleteResponse.status).toEqual(200); + } +} diff --git a/test/examples/requests/RequestsExample02.t.sol b/test/examples/requests/RequestsExample02.t.sol new file mode 100644 index 00000000..127b5716 --- /dev/null +++ b/test/examples/requests/RequestsExample02.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, request, Response, RequestClient, JsonObject} from "vulcan/test.sol"; + +/// @title Sending a JSON payload +/// @dev How to send a request with a JSON body +contract RequestExample is Test { + function test() external { + RequestClient memory client = request.create(); + + Response memory jsonRes = client.post("https://httpbin.org/post").json("{ \"foo\": \"bar\" }").send().unwrap(); + + expect(jsonRes.status).toEqual(200); + + JsonObject memory responseBody = jsonRes.json().unwrap(); + + expect(responseBody.getString(".json.foo")).toEqual("bar"); + } +} diff --git a/test/examples/requests/RequestsExample03.t.sol b/test/examples/requests/RequestsExample03.t.sol new file mode 100644 index 00000000..a5cfbf04 --- /dev/null +++ b/test/examples/requests/RequestsExample03.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, request, Response, RequestClient} from "vulcan/test.sol"; + +/// @title Request authentication +/// @dev How to use different methods of authentication +contract RequestExample is Test { + function test() external { + RequestClient memory client = request.create(); + + Response memory basicAuthRes = + client.get("https://httpbin.org/basic-auth/user/pass").basicAuth("user", "pass").send().unwrap(); + + expect(basicAuthRes.status).toEqual(200); + + Response memory bearerAuthRes = client.get("https://httpbin.org/bearer").bearerAuth("token").send().unwrap(); + + expect(bearerAuthRes.status).toEqual(200); + } +} diff --git a/test/examples/requests/RequestsExample04.t.sol b/test/examples/requests/RequestsExample04.t.sol new file mode 100644 index 00000000..49fb0ee7 --- /dev/null +++ b/test/examples/requests/RequestsExample04.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, request, Headers, Response, Request, RequestClient} from "vulcan/test.sol"; + +/// @title Working with headers +/// @dev Using the request module to work with request headers +contract RequestExample is Test { + function test() external { + // Setting a default header as key value + RequestClient memory client = request.create().defaultHeader("X-Foo", "true"); + + expect(client.headers.get("X-Foo")).toEqual("true"); + + // The default header gets passed to the request + Request memory req = client.post("https://some-http-server.com").request.unwrap(); + + expect(req.headers.get("X-Foo")).toEqual("true"); + + // Setting multiple headers with a Header variable + Headers headers = request.createHeaders().insert("X-Bar", "true").insert("X-Baz", "true"); + client = request.create().defaultHeaders(headers); + + expect(client.headers.get("X-Bar")).toEqual("true"); + expect(client.headers.get("X-Baz")).toEqual("true"); + } +} diff --git a/test/examples/results/ResultsExample01.t.sol b/test/examples/results/ResultsExample01.t.sol new file mode 100644 index 00000000..ce39d130 --- /dev/null +++ b/test/examples/results/ResultsExample01.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, StringResult, Ok} from "vulcan/test.sol"; + +/// @title Working with result values +/// @dev Different methods of getting the underlyng value of a `Result` +contract ResultExample is Test { + function test() external { + StringResult result = Ok(string("foo")); + + // Use unwrap to get the value or revert if the result is an `Error` + string memory unwrapValue = result.unwrap(); + expect(unwrapValue).toEqual("foo"); + + // Use expect to get the value or revert with a custom message if + // the result is an `Error` + string memory expectValue = result.expect("Result failed"); + expect(expectValue).toEqual("foo"); + + // Safely getting the value + if (result.isOk()) { + string memory value = result.toValue(); + expect(value).toEqual("foo"); + } + } +} diff --git a/test/examples/results/ResultsExample02.t.sol b/test/examples/results/ResultsExample02.t.sol new file mode 100644 index 00000000..f6d9c376 --- /dev/null +++ b/test/examples/results/ResultsExample02.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, commands, ctx, expect, CommandResult, CommandError} from "vulcan/test.sol"; + +/// @title Working with Errors +/// @dev Different ways of handling errors. +contract ResultExample is Test { + function test() external { + // Run a non existent command + CommandResult result = commands.run(["asdf12897u391723"]); + + // Use unwrap to revert with the default error message + ctx.expectRevert( + "The command was not executed: \"Failed to execute command: No such file or directory (os error 2)\"" + ); + result.unwrap(); + + // Use expect to revert with a custom error message + ctx.expectRevert("Command not executed"); + result.expect("Command not executed"); + + bool failed = false; + + // Handle the error manually + if (result.isError()) { + if (result.toError().matches(CommandError.NotExecuted)) { + failed = true; + } + } + + expect(failed).toBeTrue(); + } +} diff --git a/test/examples/strings/StringsExample01.t.sol b/test/examples/strings/StringsExample01.t.sol new file mode 100644 index 00000000..ea1360db --- /dev/null +++ b/test/examples/strings/StringsExample01.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, strings} from "vulcan/test.sol"; + +/// @title Transforming and parsing +/// @dev Transform values to strings and parse strings to values +contract StringsExample is Test { + using strings for *; + + function test() external { + uint256 uintValue = 123; + string memory uintString = uintValue.toString(); + expect(uintString).toEqual("123"); + expect(uintString.parseUint()).toEqual(uintValue); + + bool boolValue = true; + string memory boolString = boolValue.toString(); + expect(boolString).toEqual("true"); + expect(boolString.parseBool()).toEqual(true); + + bytes32 bytes32Value = bytes32(uintValue); + string memory bytes32String = bytes32Value.toString(); + expect(bytes32String).toEqual("0x000000000000000000000000000000000000000000000000000000000000007b"); + expect(bytes32String.parseBytes32()).toEqual(bytes32Value); + } +} diff --git a/test/examples/utils/UtilsExample01.t.sol b/test/examples/utils/UtilsExample01.t.sol new file mode 100644 index 00000000..0c118c62 --- /dev/null +++ b/test/examples/utils/UtilsExample01.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, println} from "vulcan/test.sol"; + +/// @title Using println +/// @dev Using the println function to log formatted data +contract UtilsExample is Test { + function test() external view { + println("Log a simple string"); + + string memory someString = "someString"; + println("This is a string: {s}", abi.encode(someString)); + + uint256 aNumber = 123; + println("This is a uint256: {u}", abi.encode(aNumber)); + + println("A string: {s} and a number: {u}", abi.encode(someString, aNumber)); + } +} diff --git a/test/examples/utils/UtilsExample02.t.sol b/test/examples/utils/UtilsExample02.t.sol new file mode 100644 index 00000000..c4bd9869 --- /dev/null +++ b/test/examples/utils/UtilsExample02.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, expect, format} from "vulcan/test.sol"; + +/// @title Using format +/// @dev Using the format function to format data +contract FormatExample is Test { + function test() external { + uint256 uno = 1; + + string memory formatted = format("is {u} greater than 0? {bool}", abi.encode(uno, uno > 0)); + + expect(formatted).toEqual("is 1 greater than 0? true"); + } +}