Skip to content

Commit

Permalink
add example precompile
Browse files Browse the repository at this point in the history
  • Loading branch information
ceyonur committed Oct 2, 2023
1 parent 197588c commit 8ffe814
Show file tree
Hide file tree
Showing 36 changed files with 1,223 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Expand Up @@ -11,7 +11,7 @@ ENV AVALANCHEGO_PLUGIN_PATH=$GOPATH/src/github.com/ava-labs/avalanchego/build/pl
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs

# Clone the avalanchego repository
RUN git clone -b v1.10.5 https://github.com/ava-labs/avalanchego.git $GOPATH/src/github.com/ava-labs/avalanchego
RUN git clone -b v1.10.11 https://github.com/ava-labs/avalanchego.git $GOPATH/src/github.com/ava-labs/avalanchego

# Set the working directory to the cloned repository
WORKDIR $GOPATH/src/github.com/ava-labs/avalanchego
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/lint-tests-release.yml
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '~1.19.12'
go-version: '~1.20.8'
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
Expand All @@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '~1.19.12'
go-version: '~1.20.8'
check-latest: true
- run: go mod download
shell: bash
Expand All @@ -54,7 +54,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '~1.19.12'
go-version: '~1.20.8'
check-latest: true
- name: Use Node.js
uses: actions/setup-node@v3
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/sync.yml
@@ -0,0 +1,24 @@
name: Sync
on:
push:
branches:
- main

jobs:
sync-branches:
runs-on: ubuntu-latest
name: Syncing branches
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: 12
- name: Opening pull request
id: pull
uses: tretuna/sync-branches@1.4.0
with:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
FROM_BRANCH: "main"
TO_BRANCH: "develop"
107 changes: 105 additions & 2 deletions README.md
Expand Up @@ -5,6 +5,7 @@ Precompile-EVM is a repository for registering precompiles to Subnet-EVM without
## Environment Setup

To effectively build, run, and test Precompile-EVM, the following is a (non-exhaustive) list of dependencies that you will need:

- Golang
- Node.js
- [AvalancheGo](https://github.com/ava-labs/avalanchego)
Expand All @@ -16,7 +17,7 @@ To get started easily, we provide a Dev Container specification, that can be use

Codespaces is a development environment service offered by GitHub that allows developers to write, run, test, and debug their code directly on a cloud machine provided by GitHub. The developer can edit the code through a VS Code running in the browser or locally.

To run a Codespace click on the **Code** and switch to the **Codespaces** tab. There, click **Create Codespace on branch [...]**.
To run a Codespace click on the **Code** and switch to the **Codespaces** tab. There, click **Create Codespace on branch [...]**.

### Local Dev Container

Expand All @@ -34,10 +35,111 @@ To get a comprehensive introduction to Precompile-EVM, take the Avalanche Academ

There is an example branch [hello-world-example](https://github.com/ava-labs/precompile-evm/tree/hello-world-example) in this repository. You can check the example branch to see how to register precompiles and test them.

### Clone the Repo

```zsh
git clone https://github.com/ava-labs/precompile-evm.git
cd precompile-evm/ # change directory to the precompile-evm/ directory
```

### Checkout the `hello-world-example` Branch

```zsh
git checkout hello-world-example

branch 'hello-world-example' set up to track 'origin/hello-world-example'.
Switched to a new branch 'hello-world-example'
```

### Install NodeJS Dependencies

First you have to `cd contracts/` and run `npm install` to get the dependencies.

```zsh
cd contracts/ # change directory to the contracts/ directory
npm install
```

### Create a New Contract

`hello-world-example` branch has already a precompile contract called `HelloWorld.sol`. All necessary files were already created for you. You can check existing files and see how a fully implemented precompile should look like. If you'd like to redo steps to create a new precompile contract, you can follow the steps below.

Copy the existing `IHelloWorld.sol` interface to a new file called `IHolaMundo.sol`.

```zsh
cd .. # change directory back to the root of the repo
cp contracts/contracts/interfaces/IHelloWorld.sol contracts/contracts/interfaces/IHolaMundo.sol
```

### Install `solc` and Confirm Dependency Version

Install the `solc` dependency.

```zsh
brew install solidity
```

Confirm `solc` is >=0.8.8.

```zsh
solc --version

solc, the solidity compiler commandline interface
Version: 0.8.17+commit.8df45f5f.Darwin.appleclang
```

### Generate an `.abi`

Now generate a `.abi` from a `.sol` using `solc`.

Passing in the following flags

- `--abi`
- ABI specification of the contracts.
- `--base-path path`
- Use the given path as the root of the source tree instead of the root of the filesystem.
- `--include-path path`
- Make an additional source directory available to the default import callback. Use this option if you want to import contracts whose location is not fixed in relation to your main source tree, e.g. third-party libraries installed using a package manager. Can be used multiple times. Can only be used if base path has a non-empty value.
- `--output-dir path`
- If given, creates one file per output component and contract/file at the specified directory.
- `--overwrite`
- Overwrite existing files (used together with `--output-dir`).

```zsh
cd contracts/ # change directory to the contracts/ directory
solc --abi contracts/interfaces/IHolaMundo.sol --output-dir abis --base-path . --include-path ./node_modules --overwrite

Compiler run successful. Artifact(s) can be found in directory "abis".
```

### Generate Precompile Files

First, you need to create your precompile contract interface in the `contracts` directory and build the ABI. Then you can generate your precompile files with `./scripts/generate_precompile.sh --abi {abiPath} --out {outPath}`. This script installs the `precompilegen` tool from Subnet-EVM and runs it to generate your precompile.

```zsh
cd .. # change directory back to the root directory of the repo
./scripts/generate_precompile.sh --abi contracts/abis/IHolaMundo.abi --out holamundo/

Using branch: hello-world-example
installing precompilegen from Subnet-EVM v0.5.2
generating precompile with Subnet-EVM v0.5.2
Precompile files generated successfully at: holamundo/
```

Confirm that the new `holamundo/` directory has the appropriate files.

```zsh
ls -lh helloworld

-rw-r--r-- 1 user group 2.3K Jul 5 13:26 README.md
-rw-r--r-- 1 user group 2.3K Jul 5 13:26 config.go
-rw-r--r-- 1 user group 2.8K Jul 5 13:26 config_test.go
-rw-r--r-- 1 user group 963B Jul 5 13:26 contract.abi
-rw-r--r-- 1 user group 8.1K Jul 5 13:26 contract.go
-rw-r--r-- 1 user group 8.3K Jul 5 13:26 contract_test.go
-rw-r--r-- 1 user group 2.7K Jul 5 13:26 module.go
```

### Register Precompile

In `plugin/main.go` Subnet-EVM is already imported and ready to be Run from the main package. All you need to do is explicitly register your precompiles to Subnet-EVM in `plugin/main.go` and build it together with Subnet-EVM. Precompiles generated by `precompilegen` tool have a self-registering mechanism in their `module.go/init()` function. All you need to do is to force-import your precompile packprecompile package in `plugin/main.go`.
Expand All @@ -59,5 +161,6 @@ In order to upgrade the Subnet-EVM version, you need to change the version in `g
```text
[v0.1.0-v0.1.1] AvalancheGo@v1.10.1-v1.10.4 (Protocol Version: 26)
[v0.1.2] AvalancheGo@v1.10.5-v1.10.8 (Protocol Version: 27)
[v0.1.3] AvalancheGo@v1.10.9 (Protocol Version: 28)
[v0.1.3] AvalancheGo@v1.10.9-v1.10.11 (Protocol Version: 28)
[v0.1.4] AvalancheGo@v1.10.9-v1.10.11 (Protocol Version: 28)
```
1 change: 1 addition & 0 deletions compatibility.json
@@ -1,5 +1,6 @@
{
"rpcChainVMProtocolVersion": {
"v0.1.4": 28,
"v0.1.3": 28,
"v0.1.2": 27,
"v0.1.1": 26,
Expand Down
1 change: 1 addition & 0 deletions contracts/abis/IAllowList.abi
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}]
1 change: 1 addition & 0 deletions contracts/abis/IHelloWorld.abi
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sayHello","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"response","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Empty file removed contracts/contracts/.gitkeep
Empty file.
19 changes: 19 additions & 0 deletions contracts/contracts/ExampleHelloWorld.sol
@@ -0,0 +1,19 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./interfaces/IHelloWorld.sol";

address constant HELLO_WORLD_ADDRESS = 0x0300000000000000000000000000000000000000;

// ExampleHelloWorld shows how the HelloWorld precompile can be used in a smart contract.
contract ExampleHelloWorld {
IHelloWorld helloWorld = IHelloWorld(HELLO_WORLD_ADDRESS);

function sayHello() public view returns (string memory) {
return helloWorld.sayHello();
}

function setGreeting(string calldata greeting) public {
helloWorld.setGreeting(greeting);
}
}
Empty file.
12 changes: 12 additions & 0 deletions contracts/contracts/interfaces/IHelloWorld.sol
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;
import "@avalabs/subnet-evm-contracts/contracts/interfaces/IAllowList.sol";

interface IHelloWorld is IAllowList {
// sayHello returns the stored greeting string
function sayHello() external view returns (string calldata result);

// setGreeting stores the greeting string
function setGreeting(string calldata response) external;
}
Empty file removed contracts/contracts/test/.gitkeep
Empty file.
42 changes: 42 additions & 0 deletions contracts/contracts/test/ExampleHelloWorldTest.sol
@@ -0,0 +1,42 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../ExampleHelloWorld.sol";
import "../interfaces/IHelloWorld.sol";
import "@avalabs/subnet-evm-contracts/contracts/test/AllowListTest.sol";

contract ExampleHelloWorldTest is AllowListTest {
IHelloWorld helloWorld = IHelloWorld(HELLO_WORLD_ADDRESS);

function step_getDefaultHelloWorld() public {
ExampleHelloWorld example = new ExampleHelloWorld();
address exampleAddress = address(example);

assertRole(helloWorld.readAllowList(exampleAddress), AllowList.Role.None);
assertEq(example.sayHello(), "Hello World!");
}

function step_doesNotSetGreetingBeforeEnabled() public {
ExampleHelloWorld example = new ExampleHelloWorld();
address exampleAddress = address(example);

assertRole(helloWorld.readAllowList(exampleAddress), AllowList.Role.None);

try example.setGreeting("testing") {
assertTrue(false, "setGreeting should fail");
} catch {} // TODO should match on an error to make sure that this is failing in the way that's expected
}

function step_setAndGetGreeting() public {
ExampleHelloWorld example = new ExampleHelloWorld();
address exampleAddress = address(example);

assertRole(helloWorld.readAllowList(exampleAddress), AllowList.Role.None);
helloWorld.setEnabled(exampleAddress);
assertRole(helloWorld.readAllowList(exampleAddress), AllowList.Role.Enabled);

string memory greeting = "testgreeting";
example.setGreeting(greeting);
assertEq(example.sayHello(), greeting);
}
}
2 changes: 1 addition & 1 deletion contracts/hardhat.config.ts
@@ -1,6 +1,6 @@
import "@nomiclabs/hardhat-waffle"
import "@nomiclabs/hardhat-ethers"
import "./tasks.ts"
import "./tasks"

// HardHat users must populate these environment variables in order to connect to their subnet-evm instance
// Since the blockchainID is not known in advance, there's no good default to use and we use the C-Chain here.
Expand Down
38 changes: 28 additions & 10 deletions contracts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion contracts/package.json
Expand Up @@ -13,7 +13,7 @@
"types": "dist/index.d.ts",
"module": "dist/index.js",
"dependencies": {
"@avalabs/subnet-evm-contracts": "^1.0.0",
"@avalabs/subnet-evm-contracts": "^1.1.0",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-waffle": "^2.0.6",
"@types/node": "^16.7.1",
Expand All @@ -32,6 +32,7 @@
},
"license": "BSD-3-Clause",
"scripts": {
"build": "rm -rf dist/ && tsc -b",
"compile": "npx hardhat compile",
"console": "npx hardhat console",
"test": "npx hardhat test",
Expand Down
Empty file removed contracts/scripts/.gitkeep
Empty file.
20 changes: 20 additions & 0 deletions contracts/scripts/deployExampleHelloWorld.ts
@@ -0,0 +1,20 @@
import {
Contract,
ContractFactory
} from "ethers"
import { ethers } from "hardhat"

const main = async (): Promise<any> => {
const contractFactory: ContractFactory = await ethers.getContractFactory("ExampleHelloWorld")
const contract: Contract = await contractFactory.deploy()

await contract.deployed()
console.log(`Contract deployed to: ${contract.address}`)
}

main()
.then(() => process.exit(0))
.catch(error => {
console.error(error)
process.exit(1)
})

0 comments on commit 8ffe814

Please sign in to comment.