diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9891804..0f0ada6a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,11 @@ jobs: with: version: nightly + - name: Install Fe + run: | + curl -L https://github.com/ethereum/fe/releases/download/v0.24.0/fe_amd64 -o /usr/local/bin/fe -s + chmod +x /usr/local/bin/fe + - name: Run Forge build run: | forge --version @@ -52,4 +57,4 @@ jobs: run: forge --version - name: Check formatting - run: forge fmt --check \ No newline at end of file + run: forge fmt --check diff --git a/.gitignore b/.gitignore index e7931471..42714ffc 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ out/ .env .DS_Store + +# Test stuff +test/fixtures/fe diff --git a/docs/src/modules/fe.md b/docs/src/modules/fe.md new file mode 100644 index 00000000..9064beb5 --- /dev/null +++ b/docs/src/modules/fe.md @@ -0,0 +1,23 @@ +# Fe + +Provides [Fe](https://fe-lang.org/) compiler support. The `ffi` setting must be enabled on `foundry.toml` for this module +to work. + +```solidity +import { Test, fe } from "vulcan/test.sol"; + +contract TestMyContract is Test { + function testCompile() external { + fe.create().setFilePath("./test/mocks/guest_book.fe").setOutputDir("./test/fixtures/fe/output").setOverwrite( + true + ).build(); + + string memory result = fs.readFile("./test/fixtures/fe/output/GuestBook/GuestBook.bin"); + + expect(bytes(result).length).toBeGreaterThan(0); + } + +} +``` +[**Fe API reference**](../reference/modules/fe.md) + diff --git a/docs/src/reference/fe.md b/docs/src/reference/fe.md new file mode 100644 index 00000000..0753d774 --- /dev/null +++ b/docs/src/reference/fe.md @@ -0,0 +1,47 @@ +# Fe + +#### **`create() → (Fe)`** + +Creates a new `Fe` struct with the following defaults. + +```solidity +Fe({ + compilerPath: "fe", + filePath: "", + emitOptions: "", + outputDir: "", + overwrite: false +}); +``` + +> Notice: The `filePath` is the only required field, if it is empty, `.build` and `.toCommand` +> will revert. + +#### **`build(Fe self) → (bytes)`** + +Builds a binary file from a `.fe` file. + +#### **`toCommand(Fe self) → (Command)`** + +Converts the `Fe` struct into a `Command` struct. + +#### **`setCompilerPath(Fe self, string compilerPath) → (Fe)`** + +Overwrites the compiler path. + +#### **`setFilePath(Fe self, string filePath) → (Fe)`** + +Overwrites the file path. + +#### **`setOutputDir(Fe self, string outputDir) → (Fe)`** + +Overwrites the default artifacts directory. + +#### **`setEmitOptions(Fe memory self, string memory emitOptions) → (Fe)`** + +Overwrites the default emit options. `abi,bytecode` is the default value. Use `fe build --help` to +get all the other possible values. + +#### **`setOverwrite(Fe memory self, bool overwrite) → (Fe)`** + +Sets the build command overwrite flag. If `true` the contents of `outputDir` will be overwritten. diff --git a/foundry.toml b/foundry.toml index 7496fe20..6afc1c3a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,7 +6,8 @@ ffi = true fs_permissions = [ { access = "read", path = "./out"}, { access = "read", path = "./test/fixtures/fs/read" }, - { access = "read-write", path = "./test/fixtures/fs/write" } + { access = "read-write", path = "./test/fixtures/fs/write" }, + { access = "read-write", path = "./test/fixtures/fe/output" } ] [rpc_endpoints] diff --git a/src/_modules/Fe.sol b/src/_modules/Fe.sol new file mode 100644 index 00000000..991a51f6 --- /dev/null +++ b/src/_modules/Fe.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.13 <0.9.0; + +import "./Commands.sol"; +import "./Strings.sol"; + +struct Fe { + string compilerPath; + string filePath; + string emitOptions; + string outputDir; + bool overwrite; +} + +library fe { + using strings for bytes32; + + /// @dev Creates a new `Fe` struct with default values. + function create() internal pure returns (Fe memory) { + return Fe({compilerPath: "fe", filePath: "", emitOptions: "", outputDir: "", overwrite: false}); + } + + /// @dev Builds a binary file from a `.fe` file. + /// @param self The `Fe` struct to build. + function build(Fe memory self) internal returns (bytes memory) { + bytes memory output = self.toCommand().run(); + // TODO: add error handling + return output; + } + + /// @dev Transforms a `Fe` struct to a `Command` struct. + /// @param self The `Fe` struct to transform. + function toCommand(Fe memory self) internal pure returns (Command memory) { + Command memory command = commands.create(self.compilerPath); + require(bytes(self.filePath).length > 0, "fe.toCommand: self.filePath not set"); + + // MUST be last + command = command.arg("build"); + + if (bytes(self.outputDir).length > 0) command = command.args(["-o", self.outputDir]); + if (bytes(self.emitOptions).length > 0) command = command.args(["-e", self.emitOptions]); + if (self.overwrite) command = command.arg("--overwrite"); + + command = command.arg(self.filePath); + + return command; + } + + /// @dev Sets the `fe` compiler path. + /// @param self The `Fe` struct to modify. + /// @param compilerPath The new compiler path. + function setCompilerPath(Fe memory self, string memory compilerPath) internal pure returns (Fe memory) { + self.compilerPath = compilerPath; + return self; + } + + /// @dev Sets the `fe` file path to build. + /// @param self The `Fe` struct to modify. + /// @param filePath The path to the `.fe` file to build. + function setFilePath(Fe memory self, string memory filePath) internal pure returns (Fe memory) { + self.filePath = filePath; + return self; + } + + /// @dev Sets the `fe` build command emit options. + /// @param self The `Fe` struct to modify. + /// @param emitOptions The build command emit options. + function setEmitOptions(Fe memory self, string memory emitOptions) internal pure returns (Fe memory) { + self.emitOptions = emitOptions; + return self; + } + + /// @dev Sets the `fe` build command output directory. + /// @param self The `Fe` struct to modify. + /// @param outputDir The directory where the binary file will be saved. + function setOutputDir(Fe memory self, string memory outputDir) internal pure returns (Fe memory) { + self.outputDir = outputDir; + return self; + } + + /// @dev Sets the `fe` build command overwrite flag. + /// @param self The `Fe` struct to modify. + /// @param overwrite If true it will overwrite the `outputDir with the new build binaries. + function setOverwrite(Fe memory self, bool overwrite) internal pure returns (Fe memory) { + self.overwrite = overwrite; + return self; + } +} + +using fe for Fe global; diff --git a/src/script.sol b/src/script.sol index a15081ec..2026c914 100644 --- a/src/script.sol +++ b/src/script.sol @@ -17,6 +17,7 @@ import {config, Rpc} from "./_modules/Config.sol"; import {fmt} from "./_modules/Fmt.sol"; import {format} from "./_utils/format.sol"; import {println} from "./_utils/println.sol"; +import {fe, Fe} from "./_modules/Fe.sol"; contract Script { bool public IS_SCRIPT = true; diff --git a/src/test.sol b/src/test.sol index 2ba35e69..8e88f44a 100644 --- a/src/test.sol +++ b/src/test.sol @@ -19,6 +19,7 @@ import {strings} from "./_modules/Strings.sol"; import {watchers, Watcher} from "./_modules/Watchers.sol"; import {config, Rpc} from "./_modules/Config.sol"; import {fmt} from "./_modules/Fmt.sol"; +import {fe, Fe} from "./_modules/Fe.sol"; import {format} from "./_utils/format.sol"; import {println} from "./_utils/println.sol"; diff --git a/test/_modules/Fe.t.sol b/test/_modules/Fe.t.sol new file mode 100644 index 00000000..c7b86ef9 --- /dev/null +++ b/test/_modules/Fe.t.sol @@ -0,0 +1,39 @@ +pragma solidity >=0.8.13 <0.9.0; + +import {Test, expect, commands, Command, fe, Fe, fs} from "../../src/test.sol"; + +contract FeTest is Test { + function testToCommandAllSet() external { + Command memory command = fe.create().setCompilerPath("difffe").setFilePath("./filePath.fe").setEmitOptions( + "abi" + ).setOutputDir("./feoutput").toCommand(); + + expect(command.inputs.length).toEqual(7); + expect(command.inputs[0]).toEqual("difffe"); + expect(command.inputs[1]).toEqual("build"); + expect(command.inputs[2]).toEqual("-o"); + expect(command.inputs[3]).toEqual("./feoutput"); + expect(command.inputs[4]).toEqual("-e"); + expect(command.inputs[5]).toEqual("abi"); + expect(command.inputs[6]).toEqual("./filePath.fe"); + } + + function testToCommandMinimumSet() external { + Command memory command = fe.create().setFilePath("./filePath.fe").toCommand(); + + expect(command.inputs.length).toEqual(3); + expect(command.inputs[0]).toEqual("fe"); + expect(command.inputs[1]).toEqual("build"); + expect(command.inputs[2]).toEqual("./filePath.fe"); + } + + function testCompile() external { + fe.create().setFilePath("./test/mocks/guest_book.fe").setOutputDir("./test/fixtures/fe/output").setOverwrite( + true + ).build(); + + string memory result = fs.readFile("./test/fixtures/fe/output/GuestBook/GuestBook.bin"); + + expect(bytes(result).length).toBeGreaterThan(0); + } +} diff --git a/test/mocks/guest_book.fe b/test/mocks/guest_book.fe new file mode 100644 index 00000000..7a4d808d --- /dev/null +++ b/test/mocks/guest_book.fe @@ -0,0 +1,11 @@ +contract GuestBook { + messages: Map> + + pub fn sign(mut self, ctx: Context, book_msg: String<100>) { + self.messages[ctx.msg_sender()] = book_msg + } + + pub fn get_msg(self, addr: address) -> String<100> { + return self.messages[addr].to_mem() + } +}