├── test
├── fixture-projects
│ ├── basic_ingot
│ │ ├── src
│ │ │ ├── ding
│ │ │ │ ├── dang.fe
│ │ │ │ └── dong.fe
│ │ │ ├── bar.fe
│ │ │ ├── bar
│ │ │ │ ├── baz.fe
│ │ │ │ └── mee.fe
│ │ │ ├── bing.fe
│ │ │ └── main.fe
│ │ └── hardhat.config.js
│ ├── .gitignore
│ ├── none-fe-config
│ │ └── hardhat.config.js
│ ├── empty-fe-config
│ │ └── hardhat.config.js
│ ├── fe-config-with-version
│ │ └── hardhat.config.js
│ ├── bountiful
│ │ ├── hardhat.config.js
│ │ └── contracts
│ │ │ ├── BountyRegistry.fe
│ │ │ ├── Game.fe
│ │ │ └── Game_i8.fe
│ └── sunstone_compiler
│ │ ├── hardhat.config.js
│ │ └── contracts
│ │ ├── foo.fe
│ │ ├── greeter.fe
│ │ └── main.fe
├── helpers.ts
└── project.test.ts
├── .mocharc.json
├── .editorconfig
├── .travis.yml
├── src
├── type-extensions.ts
├── task-names.ts
├── constants.ts
├── util.ts
├── types.ts
├── index.ts
├── downloader.ts
└── compile.ts
├── tsconfig.json
├── tslint.json
├── LICENSE
├── .gitignore
├── package.json
└── README.md
/test/fixture-projects/basic_ingot/src/ding/dang.fe:
--------------------------------------------------------------------------------
1 | type Dang = Array
2 |
--------------------------------------------------------------------------------
/test/fixture-projects/basic_ingot/src/bar.fe:
--------------------------------------------------------------------------------
1 |
2 | pub fn file_items_work() -> bool:
3 | return true
4 |
--------------------------------------------------------------------------------
/test/fixture-projects/basic_ingot/src/bar/baz.fe:
--------------------------------------------------------------------------------
1 | pub struct Baz:
2 | pub my_bool: bool
3 | pub my_u256: u256
4 |
--------------------------------------------------------------------------------
/test/fixture-projects/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 |
4 | #Hardhat files
5 | cache
6 | artifacts
7 |
8 | fe_output
9 |
--------------------------------------------------------------------------------
/test/fixture-projects/none-fe-config/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require("../../../src/index");
2 |
3 | module.exports = {
4 | };
5 |
--------------------------------------------------------------------------------
/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": "ts-node/register/files",
3 | "ignore": ["test/fixture-projects/**/*"],
4 | "timeout": 6000
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixture-projects/empty-fe-config/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require("../../../src/index");
2 |
3 | module.exports = {
4 | fe: {
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/test/fixture-projects/fe-config-with-version/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require("../../../src/index");
2 |
3 | module.exports = {
4 | fe: {
5 | version: "1.0.0",
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/test/fixture-projects/basic_ingot/src/bar/mee.fe:
--------------------------------------------------------------------------------
1 | pub struct Mee:
2 | pub fn kawum() -> u256:
3 | return 1
4 |
5 | pub fn rums(self) -> u256:
6 | return 1
7 |
--------------------------------------------------------------------------------
/test/fixture-projects/bountiful/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require("../../../src/index");
2 | const constants = require("../../../src/constants");
3 |
4 | module.exports = {
5 | fe: {
6 | version: constants.QUARTZ_VERSION
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/test/fixture-projects/sunstone_compiler/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require("../../../src/index");
2 | const constants = require("../../../src/constants");
3 |
4 | module.exports = {
5 | fe: {
6 | version: constants.DEFAULT_FE_VERSION,
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/test/fixture-projects/basic_ingot/src/ding/dong.fe:
--------------------------------------------------------------------------------
1 | use ingot::bing::Bing
2 |
3 | pub struct Dyng:
4 | pub my_address: address
5 | pub my_u256: u256
6 | pub my_i8: i8
7 |
8 | fn get_bing() -> Bing:
9 | return Bing(my_address: address(0))
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_style = space
8 | indent_size = 2
9 | insert_final_newline = true
10 | trim_trailing_whitespace = false
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "8"
5 | - "10"
6 | - "11"
7 |
8 | install:
9 | - npm ci
10 |
11 | script:
12 | - npm run test
13 | - npm run lint
14 |
15 | cache: npm
16 |
17 | branches:
18 | only:
19 | - master
20 |
--------------------------------------------------------------------------------
/test/fixture-projects/sunstone_compiler/contracts/foo.fe:
--------------------------------------------------------------------------------
1 | pub struct Foo {
2 | my_address: address
3 | }
4 |
5 | pub struct Bar {
6 | pub my_u256: u256
7 | }
8 |
9 | pub contract FooBar {
10 | pub fn add(x: u256, y: u256) -> u256 {
11 | return x + y
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/type-extensions.ts:
--------------------------------------------------------------------------------
1 | import "hardhat/types/config";
2 |
3 | import { FeBuild } from "./types";
4 |
5 | declare module "hardhat/types/config" {
6 | interface HardhatUserConfig {
7 | fe?: Partial;
8 | }
9 |
10 | interface HardhatConfig {
11 | fe: FeBuild;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/fixture-projects/basic_ingot/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require("../../../src/index");
2 |
3 | const constants = require("../../../src/constants");
4 |
5 | module.exports = {
6 | paths: {
7 | sources: "./src",
8 | },
9 | fe: {
10 | version: constants.QUARTZ_VERSION,
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/test/fixture-projects/basic_ingot/src/bing.fe:
--------------------------------------------------------------------------------
1 | use std::error::Error
2 |
3 | pub struct Bing:
4 | pub my_address: address
5 |
6 | pub fn get_42_backend() -> u256:
7 | return std::evm::add(21, 21)
8 |
9 | pub contract BingContract:
10 | pub fn add(_ x: u256, _ y: u256) -> u256:
11 | return x + y
12 |
--------------------------------------------------------------------------------
/src/task-names.ts:
--------------------------------------------------------------------------------
1 | export const TASK_COMPILE_FE = "compile:fe";
2 | export const TASK_COMPILE_FE_RUN_BINARY = "compile:fe:binary:run";
3 | export const TASK_COMPILE_FE_GET_BUILD = "compile:fe:binary:get-build";
4 | export const TASK_COMPILE_FE_LOG_DOWNLOAD_COMPILER_START =
5 | "compile:fe:log:download-compiler-start";
6 | export const TASK_COMPILE_FE_LOG_DOWNLOAD_COMPILER_END =
7 | "compile:fe:log:download-compiler-end";
--------------------------------------------------------------------------------
/test/fixture-projects/sunstone_compiler/contracts/greeter.fe:
--------------------------------------------------------------------------------
1 | use std::context::Context
2 |
3 | contract GuestBook {
4 | messages: Map>
5 |
6 | pub fn sign(self, ctx: Context, book_msg: String<100>) {
7 | self.messages[ctx.msg_sender()] = book_msg
8 | }
9 |
10 | pub fn get_msg(self, addr: address) -> String<100> {
11 | return self.messages[addr].to_mem()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "module": "commonjs",
5 | "declaration": true,
6 | "declarationMap": true,
7 | "sourceMap": true,
8 | "outDir": "./dist",
9 | "strict": true,
10 | "rootDirs": ["./src", "./test"],
11 | "esModuleInterop": true
12 | },
13 | "exclude": ["dist", "node_modules"],
14 | "include": [
15 | "./test",
16 | "./src"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/test/fixture-projects/sunstone_compiler/contracts/main.fe:
--------------------------------------------------------------------------------
1 | use foo::{Foo, Bar, FooBar}
2 | use std::context::Context
3 |
4 | contract FooBarBing {
5 | pub fn get_bar() -> Bar {
6 | return Bar(
7 | my_u256: 24
8 | )
9 | }
10 |
11 | pub fn create_foobar_contract(ctx: Context) -> u256 {
12 | let foo_bar: FooBar = FooBar.create(ctx, 0)
13 | return foo_bar.add(x: 40, y: 50)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/ethereum/fe/releases/tag/v0.17.0-alpha
2 | export const QUARTZ_VERSION = "0.17.0-alpha"
3 | // https://github.com/ethereum/fe/releases/tag/v0.19.1-alpha
4 | export const SUNSTONE_VERSION = "0.19.1-alpha"
5 | export const DEFAULT_FE_VERSION = SUNSTONE_VERSION;
6 | export const ARTIFACT_FORMAT_VERSION = "hh-fe-artifact-1";
7 | export const DEBUG_NAMESPACE = "hardhat:plugin:fe";
8 |
9 | export const GITHUB_RELEASES_URL =
10 | "https://api.github.com/repos/ethereum/fe/releases?per_page=100";
11 |
--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
1 | import type { Debugger } from "debug";
2 | import { NomicLabsHardhatPluginError } from "hardhat/plugins";
3 | import { DEBUG_NAMESPACE } from "./constants";
4 |
5 | export class FePluginError extends NomicLabsHardhatPluginError {
6 | constructor(message: string, parent?: Error, shouldBeReported?: boolean) {
7 | super("hardhat-fe", message, parent, shouldBeReported);
8 | }
9 | }
10 |
11 | export function getLogger(suffix: string): Debugger {
12 | const debug = require("debug");
13 | return debug(`${DEBUG_NAMESPACE}:${suffix}`);
14 | }
15 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export interface FeBuild {
2 | version: string;
3 | compilerPath: string;
4 | }
5 |
6 | export enum CompilerPlatform {
7 | LINUX = "amd64",
8 | MACOS = "mac",
9 | }
10 |
11 | export interface CompilerReleaseAsset {
12 | name: string;
13 | // eslint-disable-next-line @typescript-eslint/naming-convention
14 | browser_download_url: string;
15 | }
16 |
17 | export interface CompilerRelease {
18 | assets: CompilerReleaseAsset[];
19 | // eslint-disable-next-line @typescript-eslint/naming-convention
20 | tag_name: string;
21 | }
22 |
23 | export type CompilersList = CompilerRelease[];
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint:latest",
4 | "tslint-plugin-prettier",
5 | "tslint-config-prettier"
6 | ],
7 | "rules": {
8 | "prettier": true,
9 | "object-literal-sort-keys": false,
10 | "no-submodule-imports": false,
11 | "interface-name": false,
12 | "max-classes-per-file": false,
13 | "no-empty": false,
14 | "no-console": false,
15 | "only-arrow-functions": false,
16 | "variable-name": [
17 | true,
18 | "check-format",
19 | "allow-leading-underscore",
20 | "allow-pascal-case"
21 | ],
22 | "ordered-imports": [
23 | true,
24 | {
25 | "grouped-imports": true,
26 | "import-sources-order": "case-insensitive"
27 | }
28 | ],
29 | "no-floating-promises": true,
30 | "prefer-conditional-expression": false,
31 | "no-implicit-dependencies": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/fixture-projects/basic_ingot/src/main.fe:
--------------------------------------------------------------------------------
1 | use bar::baz::Baz
2 | use bar::file_items_work
3 | use bing::Bing as Bong
4 | use bing::get_42_backend
5 | use ding::{dang::Dang as Dung, dong}
6 | use bing::BingContract
7 | use std::context::Context
8 |
9 | contract Foo:
10 | pub fn get_my_baz() -> Baz:
11 | assert file_items_work()
12 | return Baz(my_bool: true, my_u256: 26)
13 |
14 | pub fn get_my_bing() -> Bong:
15 | return Bong(my_address: address(42))
16 |
17 | pub fn get_42() -> u256:
18 | return get_42_backend()
19 |
20 | pub fn get_26() -> u256:
21 | return std::evm::add(13, 13)
22 |
23 | pub fn call_on_path():
24 | assert bar::mee::Mee::kawum() == 1
25 | assert bar::mee::Mee().rums() == 1
26 |
27 | pub fn get_my_dyng() -> dong::Dyng:
28 | return dong::Dyng(
29 | my_address: address(8),
30 | my_u256: 42,
31 | my_i8: -1
32 | )
33 |
34 | pub fn create_bing_contract(ctx: Context) -> u256:
35 | let bing_contract: BingContract = BingContract.create(ctx, 0)
36 | return bing_contract.add(40, 50)
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Nomic Labs LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # next.js build output
60 | .next
61 |
62 | # environment variables
63 | .env
64 |
--------------------------------------------------------------------------------
/test/helpers.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import { assert } from "chai";
3 | import * as fsExtra from "fs-extra";
4 | import { resetHardhatContext } from "hardhat/plugins-testing";
5 | import { HardhatRuntimeEnvironment } from "hardhat/types";
6 |
7 | declare module "mocha" {
8 | interface Context {
9 | hre: HardhatRuntimeEnvironment;
10 | }
11 | }
12 |
13 | export function assertFileExists(pathToFile: string) {
14 | assert.isTrue(
15 | fsExtra.existsSync(pathToFile),
16 | `Expected ${pathToFile} to exist`
17 | );
18 | }
19 |
20 | export function useFixtureProject(projectName: string) {
21 | let projectPath: string;
22 | let prevWorkingDir: string;
23 |
24 | before(() => {
25 | projectPath = getFixtureProjectPath(projectName);
26 | });
27 |
28 | before(() => {
29 | prevWorkingDir = process.cwd();
30 | process.chdir(projectPath);
31 | });
32 |
33 | after(() => {
34 | process.chdir(prevWorkingDir);
35 | });
36 | }
37 |
38 | function getFixtureProjectPath(projectName: string): string {
39 | const projectPath = path.join(__dirname, "fixture-projects", projectName);
40 |
41 | if (!fsExtra.pathExistsSync(projectPath)) {
42 | throw new Error(`Fixture project ${projectName} doesn't exist`);
43 | }
44 |
45 | return fsExtra.realpathSync(projectPath);
46 | }
47 |
48 | export function useEnvironment(configPath?: string) {
49 | beforeEach("Loading hardhat environment", function () {
50 | if (configPath !== undefined) {
51 | process.env.HARDHAT_CONFIG = configPath;
52 | }
53 |
54 | this.hre = require("hardhat");
55 | });
56 |
57 | afterEach("Resetting hardhat context", function () {
58 | delete process.env.HARDHAT_CONFIG;
59 | resetHardhatContext();
60 | });
61 | }
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@developerdao/hardhat-fe",
3 | "version": "0.5.0",
4 | "description": "Hardhat plugin to develop smart contracts with fe",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/Developer-DAO/hardhat-fe.git"
8 | },
9 | "author": "Developer DAO",
10 | "license": "MIT",
11 | "main": "dist/src/index.js",
12 | "types": "dist/src/index.d.ts",
13 | "keywords": [
14 | "ethereum",
15 | "smart-contracts",
16 | "hardhat",
17 | "hardhat-plugin",
18 | "fe"
19 | ],
20 | "scripts": {
21 | "lint:fix": "prettier --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && tslint --fix --config tslint.json --project tsconfig.json",
22 | "lint": "tslint --config tslint.json --project tsconfig.json",
23 | "test": "mocha --exit --recursive 'test/**/*.test.ts'",
24 | "build": "tsc",
25 | "watch": "tsc -w",
26 | "prepublishOnly": "npm run build"
27 | },
28 | "files": [
29 | "dist/src/",
30 | "src/",
31 | "LICENSE",
32 | "README.md"
33 | ],
34 | "devDependencies": {
35 | "@types/chai": "^4.1.7",
36 | "@types/debug": "^4.1.7",
37 | "@types/fs-extra": "^5.0.4",
38 | "@types/glob": "^7.2.0",
39 | "@types/mocha": "^5.2.6",
40 | "@types/node": "^8.10.38",
41 | "@types/semver": "^7.3.9",
42 | "chai": "^4.2.0",
43 | "hardhat": "^2.9.3",
44 | "mocha": "^7.1.2",
45 | "prettier": "2.0.5",
46 | "ts-node": "^8.1.0",
47 | "tslint": "^5.16.0",
48 | "tslint-config-prettier": "^1.18.0",
49 | "tslint-plugin-prettier": "^2.0.1",
50 | "typescript": "^4.0.3"
51 | },
52 | "peerDependencies": {
53 | "hardhat": "^2.0.0"
54 | },
55 | "dependencies": {
56 | "glob": "^8.0.1",
57 | "semver": "^7.3.7"
58 | },
59 | "bugs": {
60 | "url": "https://github.com/Developer-DAO/hardhat-fe/issues"
61 | },
62 | "homepage": "https://github.com/Developer-DAO/hardhat-fe#readme",
63 | "directories": {
64 | "test": "test"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
hardhat-fe
3 |
4 | Hardhat plugin to develop smart contracts with Fe
5 |
6 |
7 |
8 | [](https://badge.fury.io/js/@developerdao%2Fhardhat-fe)
9 | [](https://hardhat.org)
10 |
11 | Related [DevDAO RFC](https://forum.developerdao.com/t/rfc-hardhat-fe-hardhat-plugin-to-develop-smart-contracts-with-fe/2000)
12 |
13 | ## What
14 |
15 | This plugin adds support for Fe to Hardhat. Once installed, Fe contracts can be compiled by running the `compile` task.
16 |
17 | This plugin plans to generate the same artifact format as the built-in Solidity compiler, so that it can be used in conjunction with all other plugins.
18 |
19 | The Fe compiler is run using the [official binary releases](https://github.com/ethereum/fe/releases).
20 |
21 | ## Installation
22 |
23 | First, you need to install the plugin by running
24 |
25 | ```bash
26 | npm install --save-dev @developerdao/hardhat-fe
27 | ```
28 |
29 | And add the following statement to your `hardhat.config.js`:
30 |
31 | ```js
32 | require("@developerdao/hardhat-fe");
33 | ```
34 |
35 | Or, if you are using TypeScript, add this to your `hardhat.config.ts`:
36 |
37 | ```js
38 | import "@developerdao/hardhat-fe";
39 | ```
40 |
41 | ## Required plugins
42 |
43 | No plugins dependencies.
44 |
45 | ## Tasks
46 |
47 | This plugin creates no additional tasks.
48 |
49 | ## Environment extensions
50 |
51 | This plugin does not extend the Hardhat Runtime Environment.
52 |
53 | ## Configuration
54 |
55 | This plugin adds an optional `fe` entry to Hardhat's config, which lets you specify the Fe version to use.
56 |
57 | This is an example of how to set it:
58 |
59 | ```js
60 | module.exports = {
61 | fe: {
62 | version: "0.19.1-alpha",
63 | },
64 | };
65 | ```
66 |
67 | Or you can use a customized compiler by specifying the path to the binary instead of downloading from official releases:
68 |
69 | ```js
70 | module.exports = {
71 | fe: {
72 | compilerPath: "absolute_path_to_fe_compiler_binary",
73 | },
74 | };
75 | ```
76 |
77 | Otherwise, if you leave the `fe` config entry blank, the plugin will use the default version of Fe compiler which currently is `0.19.1-alpha`
78 |
79 | ## Usage
80 |
81 | There are no additional steps you need to take for this plugin to work.
82 |
83 | ## Known issues
84 |
85 | since Fe is still under heavy development
86 |
87 | * the abi file generated by Fe compiler does not contain `deployedBytecode` attribute
88 | * the abi file generated by Fe compiler does not contain `stateMutability` attribute, [related issue](https://github.com/ethereum/fe/issues/557)
89 |
90 | ## Development
91 |
92 | ```
93 | npm install // install dependencies
94 | npm run build // build before publish
95 | npm run test // run tests
96 | ```
97 |
--------------------------------------------------------------------------------
/test/fixture-projects/bountiful/contracts/BountyRegistry.fe:
--------------------------------------------------------------------------------
1 | use std::context::Context
2 |
3 | # Poor man's interface
4 | contract ISolvable:
5 |
6 | pub fn is_solved(self) -> bool:
7 | revert
8 |
9 | # Revert errors
10 | struct AlreadyLocked:
11 | pass
12 |
13 | struct InvalidClaim:
14 | pass
15 |
16 | struct OnlyAdmin:
17 | pass
18 |
19 | struct InvalidDeposit:
20 | pass
21 |
22 | struct MissingLock:
23 | pass
24 |
25 | # Structure to represent a lock with a lease time
26 | struct ClaimLock:
27 | pub claimer: address
28 | pub valid_until_block: u256
29 |
30 | const LOCK_PERIOD_IN_BLOCKS: u256 = 1000
31 | const ONE_ETH_IN_WEI: u256 = 1000000000000000000
32 |
33 | contract BountyRegistry:
34 |
35 | lock: ClaimLock
36 | open_challenges: Map
37 | admin: address
38 |
39 | pub fn __init__(self, admin: address):
40 | self.admin = admin
41 |
42 | pub fn lock(self, ctx: Context):
43 | if self.is_locked(ctx):
44 | revert AlreadyLocked()
45 | elif ctx.msg_value() < ONE_ETH_IN_WEI:
46 | revert InvalidDeposit()
47 | else:
48 | self.lock = ClaimLock(claimer:ctx.msg_sender(), valid_until_block: ctx.block_number() + LOCK_PERIOD_IN_BLOCKS)
49 |
50 | pub fn validate_owns_lock(self, ctx: Context, owner: address):
51 | if not self.is_locked(ctx) or self.lock.claimer != owner:
52 | revert MissingLock()
53 |
54 | pub fn is_locked(self, ctx: Context) -> bool:
55 | return self.lock.valid_until_block >= ctx.block_number()
56 |
57 | pub fn register_challenge(self, ctx: Context, challenge: address):
58 | self.validate_is_admin(ctx)
59 | self.open_challenges[challenge] = true
60 |
61 | pub fn remove_challenge(self, ctx: Context, challenge: address):
62 | self.validate_is_admin(ctx)
63 |
64 | if self.is_locked(ctx):
65 | revert AlreadyLocked()
66 | else:
67 | self.open_challenges[challenge] = false
68 |
69 | pub fn is_open_challenge(self, challenge: address) -> bool:
70 | return self.open_challenges[challenge]
71 |
72 | pub fn claim(self, ctx: Context, challenge: address):
73 | self.validate_owns_lock(ctx, owner: ctx.msg_sender())
74 |
75 | if not self.open_challenges[challenge]:
76 | revert InvalidClaim()
77 | else:
78 | let puzzle: ISolvable = ISolvable(ctx, challenge)
79 | if puzzle.is_solved():
80 | ctx.send_value(to: ctx.msg_sender(), wei: ctx.self_balance())
81 | else:
82 | revert InvalidClaim()
83 |
84 | pub fn withdraw(self, ctx: Context):
85 | # Admin should be allowed to withdraw funds if there's no lock in progress
86 | # This would be used to migrate to a new bounty registry
87 | self.validate_is_admin(ctx)
88 | if self.is_locked(ctx):
89 | revert AlreadyLocked()
90 | else:
91 | ctx.send_value(to: ctx.msg_sender(), wei: ctx.self_balance())
92 |
93 | fn validate_is_admin(self, ctx: Context):
94 | if ctx.msg_sender() != self.admin:
95 | revert OnlyAdmin()
96 |
--------------------------------------------------------------------------------
/test/project.test.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import * as assert from "assert";
3 | import * as fsExtra from "fs-extra";
4 | import { DEFAULT_FE_VERSION, QUARTZ_VERSION } from "../src/constants";
5 | import { useFixtureProject, useEnvironment, assertFileExists } from "./helpers";
6 | import { TASK_COMPILE } from "hardhat/builtin-tasks/task-names";
7 |
8 | describe("Default project tests", () => {
9 | beforeEach(function () {
10 | fsExtra.removeSync("artifacts");
11 | fsExtra.removeSync("cache");
12 | });
13 |
14 | describe("user config without fe config", function () {
15 | useFixtureProject("none-fe-config");
16 | useEnvironment();
17 |
18 | it("use default fe version", function () {
19 | assert.equal(this.hre.config.fe.version, DEFAULT_FE_VERSION);
20 | });
21 | });
22 | describe("fe config without version", function () {
23 | useFixtureProject("empty-fe-config");
24 | useEnvironment();
25 |
26 | it("use default fe version", function () {
27 | assert.equal(this.hre.config.fe.version, DEFAULT_FE_VERSION);
28 | });
29 | });
30 | describe("fe config with version", function () {
31 | useFixtureProject("fe-config-with-version");
32 | useEnvironment();
33 |
34 | it("use specified fe version", function () {
35 | assert.equal(this.hre.config.fe.version, "1.0.0");
36 | });
37 | });
38 | describe("compile ingot module", function () {
39 | // https://github.com/ethereum/fe/tree/master/crates/test-files/fixtures/ingots/basic_ingot
40 | useFixtureProject("basic_ingot");
41 | useEnvironment();
42 |
43 | it("The specified version must be used", function () {
44 | assert.equal(this.hre.config.fe.version, QUARTZ_VERSION);
45 | });
46 | it("should compile and emit artifacts", async function () {
47 | await this.hre.run(TASK_COMPILE);
48 |
49 | assertFileExists(path.join("artifacts", "src", "main.fe", "Foo.json"));
50 | });
51 | });
52 | describe("compile separated modules", function () {
53 | // bunch of files need to be compiled
54 | this.timeout(15000);
55 | // https://github.com/cburgdorf/bountiful
56 | useFixtureProject("bountiful");
57 | useEnvironment();
58 |
59 | it("The specified version must be used", function () {
60 | assert.equal(this.hre.config.fe.version, QUARTZ_VERSION);
61 | });
62 | it("should compile and emit artifacts", async function () {
63 | await this.hre.run(TASK_COMPILE);
64 |
65 | assertFileExists(path.join("artifacts", "contracts", "BountyRegistry.fe", "BountyRegistry.json"));
66 | assertFileExists(path.join("artifacts", "contracts", "BountyRegistry.fe", "ISolvable.json"));
67 | assertFileExists(path.join("artifacts", "contracts", "Game_i8.fe", "Game.json"));
68 | assertFileExists(path.join("artifacts", "contracts", "Game_i8.fe", "ILockValidator.json"));
69 | assertFileExists(path.join("artifacts", "contracts", "Game.fe", "Game.json"));
70 | assertFileExists(path.join("artifacts", "contracts", "Game.fe", "ILockValidator.json"));
71 | });
72 | });
73 | describe("compile with 0.19.1-alpha 'Sunstone' compiler", function () {
74 | this.timeout(15000);
75 | useFixtureProject("sunstone_compiler");
76 | useEnvironment();
77 |
78 | it("The specified version must be used", function () {
79 | assert.equal(this.hre.config.fe.version, DEFAULT_FE_VERSION);
80 | });
81 | it("should compile and emit artifacts", async function () {
82 | await this.hre.run(TASK_COMPILE);
83 |
84 | assertFileExists(path.join("artifacts", "contracts", "main.fe", "FooBar.json"));
85 | assertFileExists(path.join("artifacts", "contracts", "main.fe", "FooBarBing.json"));
86 | assertFileExists(path.join("artifacts", "contracts", "main.fe", "GuestBook.json"));
87 | });
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/test/fixture-projects/bountiful/contracts/Game.fe:
--------------------------------------------------------------------------------
1 | use std::context::Context
2 |
3 | # Poor man's interface
4 | contract ILockValidator:
5 |
6 | pub fn validate_owns_lock(self, owner: address):
7 | revert
8 |
9 | struct InvalidIndex:
10 | pass
11 |
12 | struct NotMovable:
13 | pass
14 |
15 | const EMPTY_FIELD_MARKER: u256 = 0
16 | # We don't have dynamically sized arrays. Since the number of moves
17 | # varies depending on the position of the empty field, we need a marker
18 | # for invalid moves.
19 |
20 | # We could turn this into a slightly cleaner value of -1 if we used signed integers
21 | # That's blocked on https://github.com/ethereum/fe/issues/564
22 | const INVALID_FIELD_MARKER: u256 = 666
23 |
24 | contract Game:
25 |
26 | lock_validator: ILockValidator
27 | board: Array
28 | moves: Map>
29 |
30 | pub fn __init__(self, ctx: Context, lock_validator: address, board: Array):
31 | self.lock_validator = ILockValidator(ctx, lock_validator)
32 | self.board = board
33 | # Map each empty slot to a list of possible moves
34 | # It would be nice to have fixed-size maps that work with `const` (baked into code)
35 | self.moves[0] = [1, 4, INVALID_FIELD_MARKER, INVALID_FIELD_MARKER]
36 | self.moves[1] = [0, 2, 5, INVALID_FIELD_MARKER]
37 | self.moves[2] = [1, 3, 6, INVALID_FIELD_MARKER]
38 | self.moves[3] = [2, 7, INVALID_FIELD_MARKER, INVALID_FIELD_MARKER]
39 | self.moves[4] = [0, 5, 8, INVALID_FIELD_MARKER]
40 | self.moves[5] = [1, 4, 6, 9]
41 | self.moves[6] = [2, 5, 7, 10]
42 | self.moves[7] = [3, 6, 11, INVALID_FIELD_MARKER]
43 | self.moves[8] = [4, 9, 12, INVALID_FIELD_MARKER]
44 | self.moves[9] = [5, 8, 10, 13]
45 | self.moves[10] = [6, 9, 11, 14]
46 | self.moves[11] = [7, 10, 15, INVALID_FIELD_MARKER]
47 | self.moves[12] = [8, 13, INVALID_FIELD_MARKER, INVALID_FIELD_MARKER]
48 | self.moves[13] = [9, 12, 14, INVALID_FIELD_MARKER]
49 | self.moves[14] = [10, 13, 15, INVALID_FIELD_MARKER]
50 | self.moves[15] = [11, 14, INVALID_FIELD_MARKER, INVALID_FIELD_MARKER]
51 |
52 | pub fn get_board(self) -> Array:
53 | return self.board.to_mem()
54 |
55 | pub fn is_solved(self) -> bool:
56 | let index: u256 = 0
57 | let current_board: Array = self.board.to_mem()
58 | # Workaround for: https://github.com/ethereum/fe/issues/528
59 | for _field in current_board:
60 | if current_board[index] != get_winning_state()[index]:
61 | return false
62 | index += 1
63 |
64 | return true
65 |
66 | pub fn move_field(self, ctx: Context, index: u256):
67 | self.lock_validator.validate_owns_lock(owner: ctx.msg_sender())
68 |
69 | if not self.is_valid_index(index):
70 | revert InvalidIndex()
71 | else:
72 | let movable_fields: Array = self.moves[self.get_index_of_empty_field()].to_mem()
73 | if not is_in(num: index, values: movable_fields):
74 | revert NotMovable()
75 | else:
76 | let empty_field_index: u256 = self.get_index_of_empty_field()
77 | let field_value: u256 = self.board[index]
78 | self.board[index] = EMPTY_FIELD_MARKER
79 | self.board[empty_field_index] = field_value
80 |
81 | fn get_index_of_empty_field(self) -> u256:
82 | let index: u256 = 0
83 | let current_board: Array = self.board.to_mem()
84 | for field in current_board:
85 | if field == 0:
86 | break
87 | else:
88 | index += 1
89 |
90 | return index
91 |
92 | fn get_winning_state() -> Array:
93 | # TODO: Make this a constant when complex constants are supported
94 | return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0]
95 |
96 | fn is_valid_index(self, index: u256) -> bool:
97 | return index >= 0 and index <= 15
98 |
99 | fn is_in(num: u256, values: Array) -> bool:
100 | for val in values:
101 | if val == num:
102 | return true
103 | return false
104 |
--------------------------------------------------------------------------------
/test/fixture-projects/bountiful/contracts/Game_i8.fe:
--------------------------------------------------------------------------------
1 | use std::context::Context
2 |
3 | # Poor man's interface
4 | contract ILockValidator:
5 |
6 | pub fn validate_owns_lock(self, owner: address):
7 | revert
8 |
9 | struct InvalidIndex:
10 | pass
11 |
12 | struct NotMovable:
13 | pass
14 |
15 | const EMPTY_FIELD_MARKER: u8 = 0
16 | # We don't have dynamically sized arrays. Since the number of moves
17 | # varies depending on the position of the empty field, we need a marker
18 | # for invalid moves.
19 | # TODO: We don't yet support type constructors in consts, hence the function
20 | fn invalid_field_marker() -> i8:
21 | return i8(-1)
22 |
23 | contract Game:
24 |
25 | lock_validator: ILockValidator
26 | board: Array
27 | moves: Map>
28 |
29 | pub fn __init__(self, ctx: Context, lock_validator: address, board: Array):
30 | self.lock_validator = ILockValidator(ctx, lock_validator)
31 | self.board = board
32 | # Map each empty slot to a list of possible moves
33 | # It would be nice to have fixed-size maps that work with `const` (baked into code)
34 | self.moves[u8(0)] = [i8(1), i8(4), invalid_field_marker(), invalid_field_marker()]
35 | self.moves[u8(1)] = [i8(0), i8(2), i8(5), invalid_field_marker()]
36 | self.moves[u8(2)] = [i8(1), i8(3), i8(6), invalid_field_marker()]
37 | self.moves[u8(3)] = [i8(2), i8(7), invalid_field_marker(), invalid_field_marker()]
38 | self.moves[u8(4)] = [i8(0), i8(5), i8(8), invalid_field_marker()]
39 | self.moves[u8(5)] = [i8(1), i8(4), i8(6), i8(9)]
40 | self.moves[u8(6)] = [i8(2), i8(5), i8(7), i8(10)]
41 | self.moves[u8(7)] = [i8(3), i8(6), i8(11), invalid_field_marker()]
42 | self.moves[u8(8)] = [i8(4), i8(9), i8(12), invalid_field_marker()]
43 | self.moves[u8(9)] = [i8(5), i8(8), i8(10), i8(13)]
44 | self.moves[u8(10)] = [i8(6), i8(9), i8(11), i8(14)]
45 | self.moves[u8(11)] = [i8(7), i8(10), i8(15), invalid_field_marker()]
46 | self.moves[u8(12)] = [i8(8), i8(13), invalid_field_marker(), invalid_field_marker()]
47 | self.moves[u8(13)] = [i8(9), i8(12), i8(14), invalid_field_marker()]
48 | self.moves[u8(14)] = [i8(10), i8(13), i8(15), invalid_field_marker()]
49 | self.moves[u8(15)] = [i8(11), i8(14), invalid_field_marker(), invalid_field_marker()]
50 |
51 | pub fn get_board(self) -> Array:
52 | return self.board.to_mem()
53 |
54 | pub fn is_solved(self) -> bool:
55 | let index: u256 = 0
56 | let current_board: Array = self.board.to_mem()
57 | # Workaround for: https://github.com/ethereum/fe/issues/528
58 | for _field in current_board:
59 | if current_board[index] != get_winning_state()[index]:
60 | return false
61 | index += 1
62 |
63 | return true
64 |
65 | pub fn move_field(self, ctx: Context, index: u8):
66 | self.lock_validator.validate_owns_lock(owner: ctx.msg_sender())
67 |
68 | if not self.is_valid_index(index):
69 | revert InvalidIndex()
70 | else:
71 | let movable_fields: Array = self.moves[self.get_index_of_empty_field()].to_mem()
72 | if not is_in(num:index, values: movable_fields):
73 | revert NotMovable()
74 | else:
75 | let empty_field_index: u8 = self.get_index_of_empty_field()
76 | let field_value: u8 = self.board[u256(index)]
77 | self.board[u256(index)] = EMPTY_FIELD_MARKER
78 | self.board[u256(empty_field_index)] = field_value
79 |
80 | fn get_index_of_empty_field(self) -> u8:
81 | let index: u8 = 0
82 | let current_board: Array = self.board.to_mem()
83 | for field in current_board:
84 | if field == 0:
85 | break
86 | else:
87 | index += 1
88 |
89 | return index
90 |
91 | fn get_winning_state() -> Array:
92 | # TODO: Make this a constant when complex constants are supported
93 | return [u8(1), u8(2), u8(3), u8(4), u8(5), u8(6), u8(7), u8(8), u8(9), u8(10), u8(11), u8(12), u8(13), u8(14), u8(15), 0]
94 |
95 | fn is_valid_index(self, index: u8) -> bool:
96 | return index >= 0 and index <= 15
97 |
98 | fn is_in(num: u8, values: Array) -> bool:
99 | for val in values:
100 | if val == i8(num):
101 | return true
102 | return false
103 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { extendConfig, subtask, types } from "hardhat/config";
2 | import { getCompilersDir } from "hardhat/internal/util/global-dir";
3 | import { TASK_COMPILE_GET_COMPILATION_TASKS } from "hardhat/builtin-tasks/task-names";
4 | import {
5 | TASK_COMPILE_FE,
6 | TASK_COMPILE_FE_GET_BUILD,
7 | TASK_COMPILE_FE_RUN_BINARY,
8 | TASK_COMPILE_FE_LOG_DOWNLOAD_COMPILER_START,
9 | TASK_COMPILE_FE_LOG_DOWNLOAD_COMPILER_END,
10 | } from "./task-names";
11 | import { compile } from "./compile";
12 | import { CompilerDownloader } from "./downloader";
13 | import { DEFAULT_FE_VERSION } from "./constants";
14 |
15 | import "./type-extensions";
16 | import { FeBuild } from "./types";
17 | import { FePluginError } from "./util";
18 | import { Artifacts, HardhatConfig } from "hardhat/types";
19 |
20 | extendConfig((config, userConfig) => {
21 | const defaultConfig = { version: DEFAULT_FE_VERSION };
22 | config.fe = { ...defaultConfig, ...config.fe };
23 | });
24 |
25 | subtask(
26 | TASK_COMPILE_GET_COMPILATION_TASKS,
27 | async (_, __, runSuper): Promise => {
28 | const otherTasks = await runSuper();
29 | return [...otherTasks, TASK_COMPILE_FE];
30 | }
31 | );
32 |
33 | subtask(TASK_COMPILE_FE)
34 | .addParam("quiet", undefined, undefined, types.boolean)
35 | .setAction(
36 | async ({ quiet }: { quiet: boolean }, { artifacts, config, run }) => {
37 | const feVersion = config.fe.version;
38 | const compilerPath = config.fe.compilerPath;
39 |
40 | var fePath;
41 | if (compilerPath) {
42 | fePath = compilerPath;
43 | console.log("using local fe executable:", fePath);
44 | } else {
45 | const feBuild: FeBuild = await run(TASK_COMPILE_FE_GET_BUILD, {
46 | quiet,
47 | feVersion,
48 | });
49 | fePath = feBuild.compilerPath;
50 | console.log("using downloaded fe executable:", fePath);
51 | }
52 |
53 | await run(
54 | TASK_COMPILE_FE_RUN_BINARY,
55 | {
56 | fePath: fePath,
57 | feVersion: feVersion,
58 | artifacts: artifacts,
59 | config: config
60 | }
61 | );
62 | }
63 | );
64 |
65 | subtask(TASK_COMPILE_FE_RUN_BINARY)
66 | .addParam("fePath", undefined, undefined, types.string)
67 | .addParam("feVersion", undefined, undefined, types.string)
68 | .setAction(
69 | async ({
70 | fePath,
71 | feVersion,
72 | artifacts,
73 | config
74 | }: {
75 | fePath: string;
76 | feVersion: string;
77 | artifacts: Artifacts;
78 | config: HardhatConfig;
79 | }) => {
80 | await compile(fePath, feVersion, config.paths, artifacts);
81 | }
82 | );
83 |
84 | subtask(TASK_COMPILE_FE_GET_BUILD)
85 | .addParam("quiet", undefined, undefined, types.boolean)
86 | .addParam("feVersion", undefined, undefined, types.string)
87 | .setAction(async (
88 | { quiet, feVersion }: { quiet: boolean; feVersion: string },
89 | { run }
90 | ): Promise => {
91 | const compilersCache = await getCompilersDir();
92 | const downloader = new CompilerDownloader(compilersCache);
93 |
94 | await downloader.initCompilersList();
95 |
96 | const isDownloaded = downloader.isCompilerDownloaded(feVersion);
97 |
98 | await run(TASK_COMPILE_FE_LOG_DOWNLOAD_COMPILER_START, {
99 | feVersion,
100 | isDownloaded,
101 | quiet,
102 | });
103 |
104 | const compilerPath = await downloader.getOrDownloadCompiler(feVersion);
105 |
106 | if (compilerPath === undefined) {
107 | throw new FePluginError("Can't download fe compiler");
108 | }
109 |
110 | await run(TASK_COMPILE_FE_LOG_DOWNLOAD_COMPILER_END, {
111 | feVersion,
112 | isDownloaded,
113 | quiet,
114 | });
115 |
116 | return { compilerPath, version: feVersion };
117 | }
118 | );
119 |
120 | subtask(TASK_COMPILE_FE_LOG_DOWNLOAD_COMPILER_START)
121 | .addParam("quiet", undefined, undefined, types.boolean)
122 | .addParam("isDownloaded", undefined, undefined, types.boolean)
123 | .addParam("feVersion", undefined, undefined, types.string)
124 | .setAction(
125 | async ({
126 | quiet,
127 | isDownloaded,
128 | feVersion,
129 | }: {
130 | quiet: boolean;
131 | isDownloaded: boolean;
132 | feVersion: string;
133 | }) => {
134 | if (isDownloaded || quiet) return;
135 |
136 | console.log(`Downloading compiler ${feVersion}`);
137 | }
138 | );
139 |
140 | subtask(TASK_COMPILE_FE_LOG_DOWNLOAD_COMPILER_END)
141 | .addParam("quiet", undefined, undefined, types.boolean)
142 | .addParam("isDownloaded", undefined, undefined, types.boolean)
143 | .addParam("feVersion", undefined, undefined, types.string)
144 | .setAction(
145 | async ({}: {
146 | quiet: boolean;
147 | isDownloaded: boolean;
148 | feVersion: string;
149 | }) => {}
150 | );
151 |
--------------------------------------------------------------------------------
/src/downloader.ts:
--------------------------------------------------------------------------------
1 | import os from "os";
2 | import path from "path";
3 | import fsExtra from "fs-extra";
4 | import semver from "semver";
5 |
6 | import {
7 | CompilerReleaseAsset,
8 | CompilersList,
9 | CompilerPlatform,
10 | CompilerRelease,
11 | } from "./types";
12 | import { GITHUB_RELEASES_URL } from "./constants";
13 | import { FePluginError, getLogger } from "./util";
14 |
15 | const log = getLogger("downloader");
16 |
17 | async function downloadFile(
18 | url: string,
19 | destinationFile: string
20 | ): Promise {
21 | const { download } = await import("hardhat/internal/util/download");
22 | log(`Downloading from ${url} to ${destinationFile}`);
23 | await download(url, destinationFile);
24 | }
25 |
26 | type DownloadFunction = (url: string, destinationFile: string) => Promise;
27 |
28 | interface CompilerDownloaderOptions {
29 | download?: DownloadFunction;
30 | }
31 |
32 | export class CompilerDownloader {
33 | private readonly _download: DownloadFunction;
34 | private readonly _platform: CompilerPlatform;
35 | public compilersList: CompilersList = [];
36 |
37 | constructor(
38 | private readonly _compilersDir: string,
39 | options: CompilerDownloaderOptions = {}
40 | ) {
41 | this._download = options.download ?? downloadFile;
42 | this._platform = this._getCurrentPlatform();
43 | }
44 |
45 | public get compilersListExists(): boolean {
46 | return this._fileExists(this.compilersListPath);
47 | }
48 |
49 | public get downloadsDir(): string {
50 | return path.join(this._compilersDir, "fe", this._platform);
51 | }
52 |
53 | public get compilersListPath(): string {
54 | return path.join(this.downloadsDir, "list.json");
55 | }
56 |
57 | public isCompilerDownloaded(version: string): boolean {
58 | return this._fileExists(this._getDownloadedFilePath(version));
59 | }
60 |
61 | public async initCompilersList(
62 | { forceDownload } = { forceDownload: true }
63 | ): Promise {
64 | if (forceDownload || !this.compilersListExists) {
65 | await this._downloadCompilersList();
66 | }
67 |
68 | this.compilersList = this._getCompilersListFromDisk();
69 | }
70 |
71 | public async getCompilerAsset(
72 | version: string
73 | ): Promise {
74 | let versionRelease = this._findVersionRelease(version);
75 |
76 | if (versionRelease === undefined) {
77 | await this.initCompilersList({ forceDownload: true });
78 | versionRelease = this._findVersionRelease(version);
79 |
80 | if (versionRelease === undefined) {
81 | throw new FePluginError(`Unsupported fe version: ${version}`);
82 | }
83 | }
84 |
85 | const compilerAsset = versionRelease.assets.find((asset) =>
86 | asset.name.includes(this._platform)
87 | );
88 |
89 | if (compilerAsset === undefined) {
90 | throw new FePluginError(
91 | `Fe version ${version} is not supported on platform ${this._platform}`
92 | );
93 | }
94 |
95 | return compilerAsset;
96 | }
97 |
98 | public async getOrDownloadCompiler(
99 | version: string
100 | ): Promise {
101 | try {
102 | const downloadedFilePath = this._getDownloadedFilePath(version);
103 | log("downloaded file path:", downloadedFilePath);
104 |
105 | if (!this._fileExists(downloadedFilePath)) {
106 | const compilerAsset = await this.getCompilerAsset(version);
107 | log("compiler asset:", compilerAsset);
108 |
109 | log(`Downloading compiler version ${version} platform ${this._platform}`);
110 | await this._downloadCompiler(compilerAsset, downloadedFilePath);
111 | }
112 |
113 | if (this._isUnix) {
114 | fsExtra.chmodSync(downloadedFilePath, 0o755);
115 | }
116 |
117 | return downloadedFilePath;
118 | } catch (e: unknown) {
119 | if (FePluginError.isNomicLabsHardhatPluginError(e)) {
120 | throw e;
121 | } else {
122 | throw new FePluginError(
123 | "An unexpected error occurred",
124 | e as Error,
125 | true
126 | );
127 | }
128 | }
129 | }
130 |
131 | private _findVersionRelease(version: string): CompilerRelease | undefined {
132 | return this.compilersList.find((release) =>
133 | semver.eq(release.tag_name, version)
134 | );
135 | }
136 |
137 | private async _downloadCompilersList(): Promise {
138 | try {
139 | await this._download(GITHUB_RELEASES_URL, this.compilersListPath);
140 | } catch {
141 | throw new FePluginError(
142 | "Failed to download compiler list",
143 | undefined,
144 | true
145 | );
146 | }
147 | }
148 |
149 | private _getCompilersListFromDisk(): CompilersList {
150 | return fsExtra.readJSONSync(this.compilersListPath);
151 | }
152 |
153 | private get _isUnix(): boolean {
154 | return (
155 | this._platform === CompilerPlatform.MACOS ||
156 | this._platform === CompilerPlatform.LINUX
157 | );
158 | }
159 |
160 | private async _downloadCompiler(
161 | compilerAsset: CompilerReleaseAsset,
162 | downloadedFilePath: string
163 | ): Promise {
164 | try {
165 | await this._download(
166 | compilerAsset.browser_download_url,
167 | downloadedFilePath
168 | );
169 | } catch (e: unknown) {
170 | throw new FePluginError("Compiler download failed", e as Error);
171 | }
172 | }
173 |
174 | private _getDownloadedFilePath(version: string): string {
175 | return path.join(this.downloadsDir, version);
176 | }
177 |
178 | private _fileExists(filepath: string): boolean {
179 | return fsExtra.pathExistsSync(filepath);
180 | }
181 |
182 | private _getCurrentPlatform(): CompilerPlatform {
183 | switch (os.platform()) {
184 | case "linux":
185 | return CompilerPlatform.LINUX;
186 | case "darwin":
187 | return CompilerPlatform.MACOS;
188 | default:
189 | throw new FePluginError(
190 | `Fe not supported on platform ${os.platform()}`
191 | );
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/compile.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import * as fs from "fs";
3 | import { getLogger } from "./util";
4 | import { ARTIFACT_FORMAT_VERSION } from "./constants";
5 | import { localPathToSourceName } from "hardhat/utils/source-names";
6 | import { Artifact, Artifacts, ProjectPathsConfig } from "hardhat/types";
7 | import { Artifacts as ArtifactsImpl } from "hardhat/internal/artifacts";
8 |
9 | const log = getLogger("compile");
10 |
11 | function getFeTempOutputFolder() {
12 | return process.cwd() + "/fe_output";
13 | }
14 |
15 | function isLegacyVersion(feVersion: string) {
16 | let [major, minor] = feVersion.split("-")[0].split(".");
17 | // versions earlier before v0.18.0-alpha
18 | return major == "0" && (parseInt(minor) <= 18);
19 | }
20 |
21 | function compileFileWithFeBinary(fePath: string, feVersion: string, fileName: string) {
22 | const fe_options = "--overwrite --emit=abi,bytecode";
23 | const outputFolder = getFeTempOutputFolder();
24 |
25 | if (fileName.endsWith(".git")) fileName = fileName.slice(0, -4);
26 | // if (!fileName.endsWith(".fe")) return;
27 |
28 | var compileCommand;
29 | if (isLegacyVersion(feVersion)) {
30 | compileCommand = `${fePath} ${fileName} ${fe_options} --output-dir ${outputFolder}`;
31 | } else {
32 | compileCommand = `${fePath} build ${fileName} ${fe_options} --output-dir ${outputFolder}`;
33 | }
34 |
35 | try {
36 | log(compileCommand);
37 | require("child_process").execSync(compileCommand);
38 | } catch (e) {
39 | console.log("[Compiler Exception] " + e);
40 | }
41 | }
42 |
43 | function getCompileResultFromBinaryBuild() {
44 | var compilerResult: { [k: string]: any } = {};
45 | compilerResult.contracts = {};
46 | const FE_OUTPUT = fs.readdirSync("fe_output");
47 | for (const fileName of FE_OUTPUT) {
48 | compilerResult.contracts[fileName] = {};
49 | compilerResult.contracts[fileName].bytecode = fs.readFileSync(
50 | `fe_output/${fileName}/${fileName}.bin`,
51 | "utf8"
52 | );
53 | compilerResult.contracts[fileName].abi = fs.readFileSync(
54 | `fe_output/${fileName}/${fileName}_abi.json`,
55 | "utf8"
56 | );
57 | compilerResult.contracts[fileName].abi = JSON.parse(compilerResult.contracts[fileName].abi);
58 | }
59 | return compilerResult;
60 | }
61 |
62 | function cleanup() {
63 | const outputFolder = getFeTempOutputFolder();
64 | const cleanUpCommand = `rm -rf ${outputFolder}`;
65 | require("child_process").execSync(cleanUpCommand);
66 | }
67 |
68 | function isIngotProject(files: string[]): [boolean, string] {
69 | for (const file of files) {
70 | if (file.endsWith("main.fe")) {
71 | return [true, file.replace("/main.fe", "")];
72 | }
73 | }
74 | return [false, ""];
75 | }
76 |
77 | export async function compile(
78 | fePath: string,
79 | feVersion: string,
80 | paths: ProjectPathsConfig,
81 | artifacts: Artifacts
82 | ) {
83 | const files = await getFeSources(paths);
84 | log(files);
85 |
86 | const [isIngot, ingotModule] = isIngotProject(files);
87 |
88 | if (isIngot) {
89 | const sourceName = await localPathToSourceName(paths.root, `${ingotModule}/main.fe`);
90 | console.log(`Compiling module ${ingotModule} with Fe binary ${fePath}`);
91 | compileFileWithFeBinary(fePath, feVersion, ingotModule);
92 |
93 | const compilerResult = getCompileResultFromBinaryBuild();
94 | log("compilerResult:", compilerResult);
95 |
96 | let contractNames = [];
97 | for (const key of Object.keys(compilerResult.contracts)) {
98 | const artifact = getArtifactFromFeOutput(
99 | sourceName,
100 | key,
101 | compilerResult.contracts[key]
102 | );
103 | log("artifact:", artifact);
104 | // https://github.com/NomicFoundation/hardhat/blob/master/packages/hardhat-ethers/src/internal/helpers.ts#L20
105 | await artifacts.saveArtifactAndDebugFile(artifact);
106 | contractNames.push(artifact.contractName);
107 | }
108 | const artifactsImpl = artifacts as ArtifactsImpl;
109 | artifactsImpl.addValidArtifacts([{ sourceName: sourceName, artifacts: contractNames }]);
110 |
111 | cleanup();
112 | } else {
113 | for (const file of files) {
114 | const sourceName = await localPathToSourceName(paths.root, file);
115 | console.log(`Compiling ${file} with Fe binary ${fePath}`);
116 | compileFileWithFeBinary(fePath, feVersion, file);
117 |
118 | const compilerResult = getCompileResultFromBinaryBuild();
119 | log("compilerResult:", compilerResult);
120 |
121 | let contractNames = [];
122 | for (const key of Object.keys(compilerResult.contracts)) {
123 | const artifact = getArtifactFromFeOutput(
124 | sourceName,
125 | key,
126 | compilerResult.contracts[key]
127 | );
128 | log("artifact:", artifact);
129 | // https://github.com/NomicFoundation/hardhat/blob/master/packages/hardhat-ethers/src/internal/helpers.ts#L20
130 | await artifacts.saveArtifactAndDebugFile(artifact);
131 | contractNames.push(artifact.contractName);
132 | }
133 | const artifactsImpl = artifacts as ArtifactsImpl;
134 | artifactsImpl.addValidArtifacts([{ sourceName: sourceName, artifacts: contractNames }]);
135 |
136 | cleanup();
137 | }
138 | }
139 | }
140 |
141 | async function getFeSources(paths: ProjectPathsConfig) {
142 | const glob = await import("glob");
143 | const feFiles = glob.sync(path.join(paths.sources, "**", "*.fe"));
144 |
145 | return feFiles;
146 | }
147 |
148 | function getArtifactFromFeOutput(
149 | sourceName: string,
150 | contractName: string,
151 | output: any
152 | ): Artifact {
153 |
154 | return {
155 | _format: ARTIFACT_FORMAT_VERSION,
156 | contractName,
157 | sourceName,
158 | abi: output.abi,
159 | bytecode: add0xPrefixIfNecessary(output.bytecode),
160 | deployedBytecode: "", //add0xPrefixIfNecessary(output.bytecode_runtime),
161 | linkReferences: {},
162 | deployedLinkReferences: {},
163 | };
164 | }
165 |
166 | function add0xPrefixIfNecessary(hex: string): string {
167 | log("...hex...");
168 | log(hex);
169 | hex = hex.toLowerCase();
170 |
171 | if (hex.slice(0, 2) === "0x") {
172 | return hex;
173 | }
174 |
175 | return `0x${hex}`;
176 | }
177 |
--------------------------------------------------------------------------------