From aae06b8086d3caf62d2e3fe7df96231b970bdb98 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Jun 2024 12:31:27 +0300 Subject: [PATCH] add hello world example --- .github/workflows/sync.yml | 24 ++ README.md | 101 +++++ contracts/README.md | 9 - contracts/contracts/.gitkeep | 0 contracts/contracts/ExampleHelloWorld.sol | 19 + contracts/contracts/interfaces/.gitkeep | 0 .../contracts/interfaces/IHelloWorld.sol | 13 + contracts/contracts/test/.gitkeep | 0 .../contracts/test/ExampleHelloWorldTest.sol | 42 ++ contracts/scripts/.gitkeep | 0 contracts/scripts/deployExampleHelloWorld.ts | 15 + contracts/tasks.ts | 63 ++- contracts/test/.gitkeep | 0 contracts/test/hello_world.ts | 68 ++++ go.mod | 15 +- go.sum | 61 +++ helloworld/README.md | 23 ++ helloworld/config.go | 77 ++++ helloworld/config_test.go | 88 ++++ helloworld/contract.abi | 1 + helloworld/contract.go | 253 ++++++++++++ helloworld/contract_test.go | 379 ++++++++++++++++++ helloworld/event.go | 90 +++++ helloworld/module.go | 75 ++++ plugin/main.go | 2 + tests/precompile/genesis/.gitkeep | 0 tests/precompile/genesis/hello_world.json | 36 ++ tests/precompile/solidity/suites.go | 20 +- 28 files changed, 1456 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/sync.yml delete mode 100644 contracts/contracts/.gitkeep create mode 100644 contracts/contracts/ExampleHelloWorld.sol delete mode 100644 contracts/contracts/interfaces/.gitkeep create mode 100644 contracts/contracts/interfaces/IHelloWorld.sol delete mode 100644 contracts/contracts/test/.gitkeep create mode 100644 contracts/contracts/test/ExampleHelloWorldTest.sol delete mode 100644 contracts/scripts/.gitkeep create mode 100644 contracts/scripts/deployExampleHelloWorld.ts delete mode 100644 contracts/test/.gitkeep create mode 100644 contracts/test/hello_world.ts create mode 100644 helloworld/README.md create mode 100644 helloworld/config.go create mode 100644 helloworld/config_test.go create mode 100644 helloworld/contract.abi create mode 100644 helloworld/contract.go create mode 100644 helloworld/contract_test.go create mode 100644 helloworld/event.go create mode 100644 helloworld/module.go delete mode 100644 tests/precompile/genesis/.gitkeep create mode 100644 tests/precompile/genesis/hello_world.json diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 0000000..ee68f97 --- /dev/null +++ b/.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" diff --git a/README.md b/README.md index 207d372..50f4e38 100644 --- a/README.md +++ b/README.md @@ -35,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`. diff --git a/contracts/README.md b/contracts/README.md index 98322d3..b62dffe 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -111,15 +111,6 @@ Subnet-EVM must activate any precompiles used in the test in the genesis: { "config": { "chainId": 43214, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, "feeConfig": { "gasLimit": 8000000, "minBaseFee": 25000000000, diff --git a/contracts/contracts/.gitkeep b/contracts/contracts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/contracts/contracts/ExampleHelloWorld.sol b/contracts/contracts/ExampleHelloWorld.sol new file mode 100644 index 0000000..b64fd0a --- /dev/null +++ b/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); + } +} diff --git a/contracts/contracts/interfaces/.gitkeep b/contracts/contracts/interfaces/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/contracts/contracts/interfaces/IHelloWorld.sol b/contracts/contracts/interfaces/IHelloWorld.sol new file mode 100644 index 0000000..d7c220f --- /dev/null +++ b/contracts/contracts/interfaces/IHelloWorld.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; +import "@avalabs/subnet-evm-contracts/contracts/interfaces/IAllowList.sol"; + +interface IHelloWorld is IAllowList { + event GreetingChanged(address indexed sender, string oldGreeting, string newGreeting); + // 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; +} diff --git a/contracts/contracts/test/.gitkeep b/contracts/contracts/test/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/contracts/contracts/test/ExampleHelloWorldTest.sol b/contracts/contracts/test/ExampleHelloWorldTest.sol new file mode 100644 index 0000000..16d10a6 --- /dev/null +++ b/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); + } +} diff --git a/contracts/scripts/.gitkeep b/contracts/scripts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/contracts/scripts/deployExampleHelloWorld.ts b/contracts/scripts/deployExampleHelloWorld.ts new file mode 100644 index 0000000..9189a1f --- /dev/null +++ b/contracts/scripts/deployExampleHelloWorld.ts @@ -0,0 +1,15 @@ +import { ethers } from "hardhat" +import { ExampleHelloWorld } from "typechain-types" + +const main = async (): Promise => { + const contract: ExampleHelloWorld = await ethers.deployContract("ExampleHelloWorld") + await contract.waitForDeployment() + console.log(`Contract deployed to: ${contract.target}`) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/contracts/tasks.ts b/contracts/tasks.ts index 03ceff1..f2a7694 100644 --- a/contracts/tasks.ts +++ b/contracts/tasks.ts @@ -1,6 +1,20 @@ import { task } from "hardhat/config" +const HELLO_WORLD_ADDRESS = "0x0300000000000000000000000000000000000000" + +const ROLES = { + 0: "None", + 1: "Enabled", + 2: "Admin", +} + +const getRole = async (allowList, address) => { + const role = await allowList.readAllowList(address) + console.log(`${address} has role: ${ROLES[role.toNumber()]}`) +} + +// npx hardhat accounts --network local task("accounts", "Prints the list of accounts", async (args, hre): Promise => { const accounts = await hre.ethers.getSigners() accounts.forEach((account): void => { @@ -8,6 +22,7 @@ task("accounts", "Prints the list of accounts", async (args, hre): Promise }) }) +// npx hardhat balances --network local task("balances", "Prints the list of account balances", async (args, hre): Promise => { const accounts = await hre.ethers.getSigners() for (const account of accounts) { @@ -18,7 +33,7 @@ task("balances", "Prints the list of account balances", async (args, hre): Promi } }) - +// npx hardhat balance --network local --address [address] task("balance", "get the balance") .addParam("address", "the address you want to know balance of") .setAction(async (args, hre) => { @@ -27,3 +42,49 @@ task("balance", "get the balance") console.log(`balance: ${balanceInCoin} Coin`) }) +// npx hardhat helloWorld:readRole --network local --address [address] +task("helloWorld:readRole", "Gets the network enabled allow list") + .addParam("address", "the address you want to know the allowlist role for") + .setAction(async (args, hre) => { + const allowList = await hre.ethers.getContractAt("IHelloWorld", HELLO_WORLD_ADDRESS) + await getRole(allowList, args.address) + }) + +// npx hardhat helloWorld:addEnabled --network local --address [address] +task("helloWorld:addEnabled", "Adds the enabled on the allow list") + .addParam("address", "the address you want to add as a enabled") + .setAction(async (args, hre) => { + const allowList = await hre.ethers.getContractAt("IHelloWorld", HELLO_WORLD_ADDRESS) + // ADD CODE BELOW + await allowList.setEnabled(args.address) + await getRole(allowList, args.address) + }) + +// npx hardhat helloWorld:addAdmin --network local --address [address] +task("helloWorld:addAdmin", "Adds an admin on the allowlist") + .addParam("address", "the address you want to add as a admin") + .setAction(async (args, hre) => { + const allowList = await hre.ethers.getContractAt("IHelloWorld", HELLO_WORLD_ADDRESS) + await allowList.setAdmin(args.address) + await getRole(allowList, args.address) + }) + +// npx hardhat helloWorld:sayHello --network local +task("helloWorld:sayHello", "Says hello") + .setAction(async (args, hre) => { + const helloWorld = await hre.ethers.getContractAt("IHelloWorld", HELLO_WORLD_ADDRESS) + const result = await helloWorld.sayHello() + console.log(result) + }) + +// npx hardhat helloWorld:setGreeting --network local --greeting [greeting] +task("helloWorld:setGreeting", "Says hello") + .addParam("greeting", "the greeting string you want to set") + .setAction(async (args, hre) => { + const helloWorld = await hre.ethers.getContractAt("IHelloWorld", HELLO_WORLD_ADDRESS) + const result = await helloWorld.setGreeting(args.greeting) + console.log(result) + }) + + + diff --git a/contracts/test/.gitkeep b/contracts/test/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/contracts/test/hello_world.ts b/contracts/test/hello_world.ts new file mode 100644 index 0000000..0dafbdc --- /dev/null +++ b/contracts/test/hello_world.ts @@ -0,0 +1,68 @@ +// (c) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + + +import { expect } from "chai" +import { Signer } from "ethers" +import { ethers } from "hardhat" +import { test } from "@avalabs/subnet-evm-contracts" +import { IHelloWorld } from "typechain-types" + +// make sure this is always an admin for hello world precompile +const ADMIN_ADDRESS = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" +const HELLO_WORLD_ADDRESS = "0x0300000000000000000000000000000000000000" + +describe("ExampleHelloWorldTest", function () { + this.timeout("30s") + + beforeEach('Setup DS-Test contract', async function () { + const signer = await ethers.getSigner(ADMIN_ADDRESS) + const helloWorldPromise = ethers.getContractAt("IHelloWorld", HELLO_WORLD_ADDRESS, signer) + + return ethers.getContractFactory("ExampleHelloWorldTest", { signer }) + .then(factory => factory.deploy()) + .then(contract => { + this.testContract = contract + return contract.waitForDeployment().then(() => contract) + }) + .then(() => Promise.all([helloWorldPromise])) + .then(([helloWorld]) => helloWorld.setAdmin(this.testContract.target)) + .then(tx => tx.wait()) + }) + + test("should gets default hello world", ["step_getDefaultHelloWorld"]) + + test("should not set greeting before enabled", "step_doesNotSetGreetingBeforeEnabled") + + test("should set and get greeting with enabled account", "step_setAndGetGreeting") +}); + +describe("IHelloWorld events", function () { + let owner: Signer + let ownerAddress: string + let contract: IHelloWorld + let defaultGreeting = "Hello, World!" + before(async function () { + owner = await ethers.getSigner(ADMIN_ADDRESS); + ownerAddress = await owner.getAddress() + contract = await ethers.getContractAt("IHelloWorld", HELLO_WORLD_ADDRESS, owner) + + // reset greeting + let tx = await contract.setGreeting(defaultGreeting) + await tx.wait() + }); + + it("should emit GreetingChanged event", async function () { + let newGreeting = "helloprecompile" + let tx = await contract.setGreeting(newGreeting) + let receipt = await tx.wait() + await expect(receipt) + .to.emit(contract, "GreetingChanged").withArgs( + ownerAddress, + // old greeting + defaultGreeting, + // new greeting + newGreeting + ) + }) +}) diff --git a/go.mod b/go.mod index 615d97d..6095a5b 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,22 @@ go 1.21.11 require ( github.com/ava-labs/avalanchego v1.11.7 github.com/ava-labs/subnet-evm v0.6.6 + github.com/ethereum/go-ethereum v1.13.8 github.com/onsi/ginkgo/v2 v2.13.1 github.com/onsi/gomega v1.29.0 github.com/stretchr/testify v1.8.4 + go.uber.org/mock v0.4.0 ) require ( github.com/DataDog/zstd v1.5.2 // indirect + github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect + github.com/ava-labs/coreth v0.13.5-rc.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect @@ -34,12 +39,12 @@ require ( github.com/dlclark/regexp2 v1.7.0 // indirect github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect - github.com/ethereum/go-ethereum v1.13.8 // indirect github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect + github.com/go-cmd/cmd v1.4.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect @@ -48,10 +53,12 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/rpc v1.2.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect @@ -62,6 +69,9 @@ require ( github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.4 // indirect + github.com/huin/goupnp v1.3.0 // indirect + github.com/jackpal/gateway v1.0.6 // indirect + github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -78,6 +88,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pires/go-proxyproto v0.6.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.16.0 // indirect @@ -86,6 +97,7 @@ require ( github.com/prometheus/procfs v0.10.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/rs/cors v1.7.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/spf13/afero v1.8.2 // indirect @@ -111,7 +123,6 @@ require ( go.opentelemetry.io/otel/sdk v1.22.0 // indirect go.opentelemetry.io/otel/trace v1.22.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect - go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.21.0 // indirect diff --git a/go.sum b/go.sum index 17f6cd6..48b62a2 100644 --- a/go.sum +++ b/go.sum @@ -46,15 +46,20 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanchego v1.11.7 h1:BemCJEa6+f79fzRLdR0+iJAA3v9shOyhVniAKuibFyk= github.com/ava-labs/avalanchego v1.11.7/go.mod h1:aPYTETkM0KjtC7vFwPO6S8z2L0QTKaXjVUi98pTdNO4= +github.com/ava-labs/coreth v0.13.5-rc.0 h1:PJQbR9o2RrW3j9ba4r1glXnmM2PNAP3xR569+gMcBd0= +github.com/ava-labs/coreth v0.13.5-rc.0/go.mod h1:cm5c12xo5NiTgtbmeduv8i2nYdzgkczz9Wm3yiwwTRU= github.com/ava-labs/subnet-evm v0.6.6 h1:F2s40f2SE2Yt/e97ZA6jMVaqDW+/1PLewcqOiUWDFd0= github.com/ava-labs/subnet-evm v0.6.6/go.mod h1:xq048mtkxCkNPl7Yb0wSkWzXX70aYkPnYQUvfCaBW3U= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= @@ -62,10 +67,30 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= +github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -113,6 +138,7 @@ github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJ github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -120,8 +146,10 @@ github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6 github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= @@ -168,6 +196,8 @@ github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnR github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-cmd/cmd v1.4.1 h1:JUcEIE84v8DSy02XTZpUDeGKExk2oW3DA10hTjbQwmc= +github.com/go-cmd/cmd v1.4.1/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= @@ -187,6 +217,8 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= +github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= @@ -234,6 +266,8 @@ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -275,6 +309,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -300,6 +336,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -311,6 +349,13 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jackpal/gateway v1.0.6 h1:/MJORKvJEwNVldtGVJC2p2cwCnsSoLn3hl3zxmZT7tk= +github.com/jackpal/gateway v1.0.6/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -324,6 +369,7 @@ github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= @@ -398,14 +444,18 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU= github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= @@ -419,6 +469,8 @@ github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwb github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= +github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -443,6 +495,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -491,6 +545,7 @@ github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/thepudds/fzgen v0.4.2 h1:HlEHl5hk2/cqEomf2uK5SA/FeJc12s/vIHmOG+FbACw= @@ -561,6 +616,7 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -613,6 +669,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -643,6 +700,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -713,8 +771,10 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -952,6 +1012,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXL gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/helloworld/README.md b/helloworld/README.md new file mode 100644 index 0000000..d81e622 --- /dev/null +++ b/helloworld/README.md @@ -0,0 +1,23 @@ +There are some must-be-done changes waiting in the generated file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. +Additionally there are other files you need to edit to activate your precompile. +These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". +For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. +See the tutorial in for more information about precompile development. + +General guidelines for precompile development: +1- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" +2- Read the comment and set a suitable contract address in generated module.go. E.g: +ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. +Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +4- Set gas costs in generated contract.go +5- Force import your precompile package in precompile/registry/registry.go +6- Add your config unit tests under generated package config_test.go +7- Add your contract unit tests under generated package contract_test.go +8- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. +9- Add your solidity interface and test contract to contracts/contracts +10- Write solidity contract tests for your precompile in contracts/contracts/test +11- Write TypeScript DS-Test counterparts for your solidity tests in contracts/test +12- Create your genesis with your precompile enabled in tests/precompile/genesis/ +13- Create e2e test for your solidity test in tests/precompile/solidity/suites.go +14- Run your e2e precompile Solidity tests with './scripts/run_ginkgo.sh` diff --git a/helloworld/config.go b/helloworld/config.go new file mode 100644 index 0000000..60fb1c5 --- /dev/null +++ b/helloworld/config.go @@ -0,0 +1,77 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package helloworld + +import ( + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" + + "github.com/ethereum/go-ethereum/common" +) + +var _ precompileconfig.Config = &Config{} + +// Config implements the precompileconfig.Config interface and +// adds specific configuration for HelloWorld. +type Config struct { + allowlist.AllowListConfig + precompileconfig.Upgrade + // CUSTOM CODE STARTS HERE + // Add your own custom fields for Config here +} + +// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables +// HelloWorld with the given [admins], [enableds] and [managers] members of the allowlist . +func NewConfig(blockTimestamp *uint64, admins []common.Address, enableds []common.Address, managers []common.Address) *Config { + return &Config{ + AllowListConfig: allowlist.AllowListConfig{ + AdminAddresses: admins, + EnabledAddresses: enableds, + ManagerAddresses: managers, + }, + Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, + } +} + +// NewDisableConfig returns config for a network upgrade at [blockTimestamp] +// that disables HelloWorld. +func NewDisableConfig(blockTimestamp *uint64) *Config { + return &Config{ + Upgrade: precompileconfig.Upgrade{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +// Key returns the key for the HelloWorld precompileconfig. +// This should be the same key as used in the precompile module. +func (*Config) Key() string { return ConfigKey } + +// Verify tries to verify Config and returns an error accordingly. +func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { + // Verify AllowList first + if err := c.AllowListConfig.Verify(chainConfig, c.Upgrade); err != nil { + return err + } + // CUSTOM CODE STARTS HERE + // Add your own custom verify code for Config here + // and return an error accordingly + return nil +} + +// Equal returns true if [s] is a [*Config] and it has been configured identical to [c]. +func (c *Config) Equal(s precompileconfig.Config) bool { + // typecast before comparison + other, ok := (s).(*Config) + if !ok { + return false + } + // CUSTOM CODE STARTS HERE + // modify this boolean accordingly with your custom Config, to check if [other] and the current [c] are equal + // if Config contains only Upgrade and AllowListConfig you can skip modifying it. + equals := c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) + return equals +} diff --git a/helloworld/config_test.go b/helloworld/config_test.go new file mode 100644 index 0000000..f07cb75 --- /dev/null +++ b/helloworld/config_test.go @@ -0,0 +1,88 @@ +// Code generated +// This file is a generated precompile config test with the skeleton of test functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package helloworld + +import ( + "testing" + + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" + "github.com/ava-labs/subnet-evm/precompile/testutils" + "github.com/ava-labs/subnet-evm/utils" + + "github.com/ethereum/go-ethereum/common" + "go.uber.org/mock/gomock" +) + +// TestVerify tests the verification of Config. +func TestVerify(t *testing.T) { + admins := []common.Address{allowlist.TestAdminAddr} + enableds := []common.Address{allowlist.TestEnabledAddr} + managers := []common.Address{allowlist.TestManagerAddr} + tests := map[string]testutils.ConfigVerifyTest{ + "valid config": { + Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), + ChainConfig: func() precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() + return config + }(), + ExpectedError: "", + }, + // CUSTOM CODE STARTS HERE + // Add your own Verify tests here, e.g.: + // "your custom test name": { + // Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), + // ExpectedError: ErrYourCustomError.Error(), + // }, + "invalid allow list config in hello world allowlist": { + Config: NewConfig(utils.NewUint64(3), admins, admins, nil), + ExpectedError: "cannot set address", + }, + } + // Verify the precompile with the allowlist. + // This adds allowlist verify tests to your custom tests + // and runs them all together. + // Even if you don't add any custom tests, keep this. This will still + // run the default allowlist verify tests. + allowlist.VerifyPrecompileWithAllowListTests(t, Module, tests) +} + +// TestEqual tests the equality of Config with other precompile configs. +func TestEqual(t *testing.T) { + admins := []common.Address{allowlist.TestAdminAddr} + enableds := []common.Address{allowlist.TestEnabledAddr} + managers := []common.Address{allowlist.TestManagerAddr} + tests := map[string]testutils.ConfigEqualTest{ + "non-nil config and nil other": { + Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), + Other: nil, + Expected: false, + }, + "different type": { + Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), + Other: precompileconfig.NewMockConfig(gomock.NewController(t)), + Expected: false, + }, + "different timestamp": { + Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), + Other: NewConfig(utils.NewUint64(4), admins, enableds, managers), + Expected: false, + }, + "same config": { + Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), + Other: NewConfig(utils.NewUint64(3), admins, enableds, managers), + Expected: true, + }, + // CUSTOM CODE STARTS HERE + // Add your own Equal tests here + } + // Run allow list equal tests. + // This adds allowlist equal tests to your custom tests + // and runs them all together. + // Even if you don't add any custom tests, keep this. This will still + // run the default allowlist equal tests. + allowlist.EqualPrecompileWithAllowListTests(t, Module, tests) +} diff --git a/helloworld/contract.abi b/helloworld/contract.abi new file mode 100644 index 0000000..94b0074 --- /dev/null +++ b/helloworld/contract.abi @@ -0,0 +1 @@ +[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"string","name":"oldGreeting","type":"string"},{"indexed":false,"internalType":"string","name":"newGreeting","type":"string"}],"name":"GreetingChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"role","type":"uint256"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldRole","type":"uint256"}],"name":"RoleSet","type":"event"},{"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":"setManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/helloworld/contract.go b/helloworld/contract.go new file mode 100644 index 0000000..758695c --- /dev/null +++ b/helloworld/contract.go @@ -0,0 +1,253 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package helloworld + +import ( + "errors" + "fmt" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + + _ "embed" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + // Gas costs for each function. These are set to 1 by default. + // You should set a gas cost for each function in your contract. + // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. + // There are some predefined gas costs in contract/utils.go that you can use. + // This contract also uses AllowList precompile. + // You should also increase gas costs of functions that read from AllowList storage. + SayHelloGasCost uint64 = contract.ReadGasCostPerSlot + SetGreetingGasCost uint64 = contract.WriteGasCostPerSlot + allowlist.ReadAllowListGasCost +) + +// Singleton StatefulPrecompiledContract and signatures. +var ( + ErrCannotSetGreeting = errors.New("non-enabled cannot call setGreeting") + ErrInputExceedsLimit = errors.New("input string is longer than 32 bytes") + + // HelloWorldRawABI contains the raw ABI of HelloWorld contract. + //go:embed contract.abi + HelloWorldRawABI string + + HelloWorldABI = contract.ParseABI(HelloWorldRawABI) + + HelloWorldPrecompile = createHelloWorldPrecompile() + + storageKeyHash = common.BytesToHash([]byte("storageKey")) +) + +// GetHelloWorldAllowListStatus returns the role of [address] for the HelloWorld list. +func GetHelloWorldAllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { + return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) +} + +// SetHelloWorldAllowListStatus sets the permissions of [address] to [role] for the +// HelloWorld list. Assumes [role] has already been verified as valid. +// This stores the [role] in the contract storage with address [ContractAddress] +// and [address] hash. It means that any reusage of the [address] key for different value +// conflicts with the same slot [role] is stored. +// Precompile implementations must use a different key than [address] for their storage. +func SetHelloWorldAllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { + allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) +} + +// PackSayHello packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackSayHello() ([]byte, error) { + return HelloWorldABI.Pack("sayHello") +} + +// PackSayHelloOutput attempts to pack given result of type string +// to conform the ABI outputs. +func PackSayHelloOutput(result string) ([]byte, error) { + return HelloWorldABI.PackOutput("sayHello", result) +} + +// UnpackSayHelloOutput attempts to unpack given [output] into the string type output +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackSayHelloOutput(output []byte) (string, error) { + res, err := HelloWorldABI.Unpack("sayHello", output) + if err != nil { + return "", err + } + unpacked := *abi.ConvertType(res[0], new(string)).(*string) + return unpacked, nil +} + +// GetGreeting returns the value of the storage key "storageKey" in the contract storage, +// with leading zeroes trimmed. +// This function is mostly used for tests. +func GetGreeting(stateDB contract.StateDB) string { + // Get the value set at recipient + value := stateDB.GetState(ContractAddress, storageKeyHash) + return string(common.TrimLeftZeroes(value.Bytes())) +} + +// sayHello is the reader fucntion that returns the value of greeting stored in the contract storage. +func sayHello(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SayHelloGasCost); err != nil { + return nil, 0, err + } + // no input provided for this function + + // CUSTOM CODE STARTS HERE + + // Get the current state + currentState := accessibleState.GetStateDB() + // Get the value set at recipient + value := GetGreeting(currentState) + packedOutput, err := PackSayHelloOutput(value) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// UnpackSetGreetingInput attempts to unpack [input] into the string type argument +// assumes that [input] does not include selector (omits first 4 func signature bytes) +// if [useStrictMode] is true, it will return an error if the length of [input] is not [common.HashLength] +func UnpackSetGreetingInput(input []byte, useStrictMode bool) (string, error) { + // Initially we had this check to ensure that the input was the correct length. + // However solidity does not always pack the input to the correct length, and allows + // for extra padding bytes to be added to the end of the input. Therefore, we have removed + // this check with the Durango. We still need to keep this check for backwards compatibility. + if useStrictMode && len(input) > common.HashLength { + return "", ErrInputExceedsLimit + } + res, err := HelloWorldABI.UnpackInput("setGreeting", input, useStrictMode) + if err != nil { + return "", err + } + unpacked := *abi.ConvertType(res[0], new(string)).(*string) + return unpacked, nil +} + +// PackSetGreeting packs [response] of type string into the appropriate arguments for setGreeting. +// the packed bytes include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackSetGreeting(response string) ([]byte, error) { + return HelloWorldABI.Pack("setGreeting", response) +} + +// StoreGreeting sets the value of the storage key "storageKey" in the contract storage. +func StoreGreeting(stateDB contract.StateDB, input string) { + inputPadded := common.LeftPadBytes([]byte(input), common.HashLength) + inputHash := common.BytesToHash(inputPadded) + + stateDB.SetState(ContractAddress, storageKeyHash, inputHash) +} + +// setGreeting is a state-changer function that sets the value of the greeting in the contract storage. +func setGreeting(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SetGreetingGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // do not use strict mode after Durango + useStrictMode := !contract.IsDurangoActivated(accessibleState) + // attempts to unpack [input] into the arguments to the SetGreetingInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackSetGreetingInput(input, useStrictMode) + if err != nil { + return nil, remainingGas, err + } + + // Allow list is enabled and SetGreeting is a state-changer function. + // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. + // You can modify/delete this code if you don't want this function to be restricted by the allow list. + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotSetGreeting, caller) + } + // allow list code ends here. + + // CUSTOM CODE STARTS HERE + // With Durango, you can emit an event in your state-changing precompile functions. + // Note: If you have been using the precompile before Durango, you should activate it only after Durango. + // Activating this code before Durango will result in a consensus failure. + // If this is a new precompile and never deployed before Durango, you can activate it immediately by removing + // the if condition. + // This example assumes that the HelloWorld precompile contract has been deployed before Durango. + if contract.IsDurangoActivated(accessibleState) { + // We will first read the old greeting. So we should charge the gas for reading the storage. + if remainingGas, err = contract.DeductGas(remainingGas, contract.ReadGasCostPerSlot); err != nil { + return nil, 0, err + } + oldGreeting := GetGreeting(stateDB) + + eventData := GreetingChangedEventData{ + OldGreeting: oldGreeting, + NewGreeting: inputStruct, + } + topics, data, err := PackGreetingChangedEvent(caller, eventData) + if err != nil { + return nil, remainingGas, err + } + // Charge the gas for emitting the event. + eventGasCost := GetGreetingChangedEventGasCost(eventData) + if remainingGas, err = contract.DeductGas(remainingGas, eventGasCost); err != nil { + return nil, 0, err + } + + // Emit the event + stateDB.AddLog( + ContractAddress, + topics, + data, + accessibleState.GetBlockContext().Number().Uint64(), + ) + } + + // setGreeting is the execution function + // "SetGreeting(name string)" and sets the storageKey + // in the string returned by hello world + StoreGreeting(stateDB, inputStruct) + + // This function does not return an output, leave this one as is + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// createHelloWorldPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. +// Access to the getters/setters is controlled by an allow list for ContractAddress. +func createHelloWorldPrecompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction + functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) + + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ + "sayHello": sayHello, + "setGreeting": setGreeting, + } + + for name, function := range abiFunctionMap { + method, ok := HelloWorldABI.Methods[name] + if !ok { + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) + } + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) + } + // Construct the contract with no fallback function. + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) + if err != nil { + panic(err) + } + return statefulContract +} diff --git a/helloworld/contract_test.go b/helloworld/contract_test.go new file mode 100644 index 0000000..0e7d377 --- /dev/null +++ b/helloworld/contract_test.go @@ -0,0 +1,379 @@ +// Code generated +// This file is a generated precompile contract test with the skeleton of test functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package helloworld + +import ( + "testing" + + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" + "github.com/ava-labs/subnet-evm/precompile/testutils" + "github.com/ava-labs/subnet-evm/utils" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +// These tests are run against the precompile contract directly with +// the given input and expected output. They're just a guide to +// help you write your own tests. These tests are for general cases like +// allowlist, readOnly behaviour, and gas cost. You should write your own +// tests for specific cases. +const testGreeting = "test" +const longString = "a very long string that is longer than 32 bytes and will cause an error" + +var ( + tests = map[string]testutils.PrecompileTest{ + "calling sayHello from NoRole should succeed": { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackSayHello() + require.NoError(t, err) + return input + }, + // This test is for a successful call. You can set the expected output here. + // CUSTOM CODE STARTS HERE + ExpectedRes: func() []byte { + // by default we don't Configure initial state for + // the module since Config is empty. + // This means we don't apply default greeting to the state. + res, err := PackSayHelloOutput("") + if err != nil { + panic(err) + } + return res + }(), + SuppliedGas: SayHelloGasCost, + ReadOnly: false, + ExpectedErr: "", + }, + "calling sayHello from Enabled should succeed": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackSayHello() + require.NoError(t, err) + return input + }, + // This test is for a successful call. You can set the expected output here. + // CUSTOM CODE STARTS HERE + ExpectedRes: func() []byte { + // by default we don't Configure initial state for + // the module since Config is empty. + // This means we don't apply default greeting to the state. + res, err := PackSayHelloOutput("") + if err != nil { + panic(err) + } + return res + }(), + SuppliedGas: SayHelloGasCost, + ReadOnly: false, + ExpectedErr: "", + }, + "calling sayHello from Manager should succeed": { + Caller: allowlist.TestManagerAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackSayHello() + require.NoError(t, err) + return input + }, + // This test is for a successful call. You can set the expected output here. + // CUSTOM CODE STARTS HERE + ExpectedRes: func() []byte { + // by default we don't Configure initial state for + // the module since Config is empty. + // This means we don't apply default greeting to the state. + res, err := PackSayHelloOutput("") + if err != nil { + panic(err) + } + return res + }(), + SuppliedGas: SayHelloGasCost, + ReadOnly: false, + ExpectedErr: "", + }, + "calling sayHello from Admin should succeed": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackSayHello() + require.NoError(t, err) + return input + }, + // This test is for a successful call. You can set the expected output here. + // CUSTOM CODE STARTS HERE + ExpectedRes: func() []byte { + // by default we don't Configure initial state for + // the module since Config is empty. + // This means we don't apply default greeting to the state. + res, err := PackSayHelloOutput("") + if err != nil { + panic(err) + } + return res + }(), + SuppliedGas: SayHelloGasCost, + ReadOnly: false, + ExpectedErr: "", + }, + "calling sayHello from NoRole with a config should return default greeting": { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + Config: NewConfig(utils.NewUint64(0), nil, nil, nil), + InputFn: func(t testing.TB) []byte { + input, err := PackSayHello() + require.NoError(t, err) + return input + }, + // This test is for a successful call. You can set the expected output here. + // CUSTOM CODE STARTS HERE + ExpectedRes: func() []byte { + res, err := PackSayHelloOutput(defaultGreeting) + if err != nil { + panic(err) + } + return res + }(), + SuppliedGas: SayHelloGasCost, + ReadOnly: false, + ExpectedErr: "", + }, + "insufficient gas for sayHello should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + input, err := PackSayHello() + require.NoError(t, err) + return input + }, + SuppliedGas: SayHelloGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "calling setGreeting from NoRole should fail": { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + input, err := PackSetGreeting(testGreeting) + require.NoError(t, err) + return input + }, + SuppliedGas: SetGreetingGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotSetGreeting.Error(), + }, + "calling setGreeting from Enabled should succeed": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + input, err := PackSetGreeting(testGreeting) + require.NoError(t, err) + return input + }, + // This test is for a successful call. You can set the expected output here. + // CUSTOM CODE STARTS HERE + ExpectedRes: []byte{}, + SuppliedGas: SetGreetingGasCost + contract.ReadGasCostPerSlot + GetGreetingChangedEventGasCost(GreetingChangedEventData{ + OldGreeting: "", + NewGreeting: testGreeting, + }), + ReadOnly: false, + ExpectedErr: "", + AfterHook: func(t testing.TB, state contract.StateDB) { + greeting := GetGreeting(state) + require.Equal(t, greeting, testGreeting) + }, + }, + "calling setGreeting from Manager should succeed": { + Caller: allowlist.TestManagerAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + input, err := PackSetGreeting(testGreeting) + require.NoError(t, err) + return input + }, + // This test is for a successful call. You can set the expected output here. + // CUSTOM CODE STARTS HERE + ExpectedRes: []byte{}, + SuppliedGas: SetGreetingGasCost + contract.ReadGasCostPerSlot + GetGreetingChangedEventGasCost(GreetingChangedEventData{ + OldGreeting: "", + NewGreeting: testGreeting, + }), + ReadOnly: false, + ExpectedErr: "", + AfterHook: func(t testing.TB, state contract.StateDB) { + greeting := GetGreeting(state) + require.Equal(t, greeting, testGreeting) + }, + }, + "calling setGreeting from Admin should succeed": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + input, err := PackSetGreeting(testGreeting) + require.NoError(t, err) + return input + }, + // This test is for a successful call. You can set the expected output here. + // CUSTOM CODE STARTS HERE + ExpectedRes: []byte{}, + SuppliedGas: SetGreetingGasCost + contract.ReadGasCostPerSlot + GetGreetingChangedEventGasCost(GreetingChangedEventData{ + OldGreeting: "", + NewGreeting: testGreeting, + }), + ReadOnly: false, + ExpectedErr: "", + AfterHook: func(t testing.TB, state contract.StateDB) { + greeting := GetGreeting(state) + require.Equal(t, greeting, testGreeting) + }, + }, + "readOnly setGreeting should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + var testInput string + input, err := PackSetGreeting(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: SetGreetingGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "insufficient gas for setGreeting should fail": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + input, err := PackSetGreeting(testGreeting) + require.NoError(t, err) + return input + }, + SuppliedGas: SetGreetingGasCost + contract.ReadGasCostPerSlot + GetGreetingChangedEventGasCost(GreetingChangedEventData{ + OldGreeting: "", + NewGreeting: testGreeting, + }) - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + // more custom tests + "store greeting then say hello from non-enabled address": { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: func(t testing.TB, state contract.StateDB) { + allowlist.SetDefaultRoles(Module.Address)(t, state) + StoreGreeting(state, testGreeting) + }, + InputFn: func(t testing.TB) []byte { + input, err := PackSayHello() + require.NoError(t, err) + return input + }, + SuppliedGas: SayHelloGasCost, + ReadOnly: true, + ExpectedRes: func() []byte { + res, err := PackSayHelloOutput(testGreeting) + if err != nil { + panic(err) + } + return res + }(), + }, + "set a very long greeting from enabled address before Durango": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + // By default Durango is enabled in the tests. + ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(ctrl) + config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() + return config + }, + InputFn: func(t testing.TB) []byte { + longString := "a very long string that is longer than 32 bytes and will cause an error" + input, err := PackSetGreeting(longString) + require.NoError(t, err) + + return input + }, + SuppliedGas: SetGreetingGasCost, + ReadOnly: false, + ExpectedErr: ErrInputExceedsLimit.Error(), + }, + "set a very long greeting from enabled address after Durango": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackSetGreeting(longString) + require.NoError(t, err) + + return input + }, + SuppliedGas: SetGreetingGasCost + contract.ReadGasCostPerSlot + GetGreetingChangedEventGasCost(GreetingChangedEventData{ + OldGreeting: "", + NewGreeting: longString, + }), + ReadOnly: false, + ExpectedErr: "", + ExpectedRes: []byte{}, + }, + } +) + +// TestHelloWorldRun tests the Run function of the precompile contract. +func TestHelloWorldRun(t *testing.T) { + // Run tests with allowlist tests. + // This adds allowlist run tests to your custom tests + // and runs them all together. + // Even if you don't add any custom tests, keep this. This will still + // run the default allowlist tests. + allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) +} + +// TestPackUnpackGreetingChangedEventData tests the Pack/UnpackGreetingChangedEventData. +func TestPackUnpackGreetingChangedEventData(t *testing.T) { + // CUSTOM CODE STARTS HERE + // set test inputs with proper values here + var senderInput common.Address = common.Address{} + + dataInput := GreetingChangedEventData{ + OldGreeting: "", + NewGreeting: "", + } + + _, data, err := PackGreetingChangedEvent( + senderInput, + dataInput, + ) + require.NoError(t, err) + + unpacked, err := UnpackGreetingChangedEventData(data) + require.NoError(t, err) + require.Equal(t, dataInput, unpacked) +} + +func BenchmarkHelloWorld(b *testing.B) { + // Benchmark tests with allowlist tests. + // This adds allowlist run tests to your custom tests + // and benchmarks them all together. + // Even if you don't add any custom tests, keep this. This will still + // run the default allowlist tests. + allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) +} diff --git a/helloworld/event.go b/helloworld/event.go new file mode 100644 index 0000000..b0e6cdb --- /dev/null +++ b/helloworld/event.go @@ -0,0 +1,90 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package helloworld + +import ( + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" +) + +/* NOTE: Events can only be emitted in state-changing functions. So you cannot use events in read-only (view) functions. +Events are generally emitted at the end of a state-changing function with AddLog method of the StateDB. The AddLog method takes 4 arguments: + 1. Address of the contract that emitted the event. + 2. Topic hashes of the event. + 3. Encoded non-indexed data of the event. + 4. Block number at which the event was emitted. +The first argument is the address of the contract that emitted the event. +Topics can be at most 4 elements, the first topic is the hash of the event signature and the rest are the indexed event arguments. There can be at most 3 indexed arguments. +Topics cannot be fully unpacked into their original values since they're 32-bytes hashes. +The non-indexed arguments are encoded using the ABI encoding scheme. The non-indexed arguments can be unpacked into their original values. +Before packing the event, you need to calculate the gas cost of the event. The gas cost of an event is the base gas cost + the gas cost of the topics + the gas cost of the non-indexed data. +See Get{EvetName}EventGasCost functions for more details. +You can use the following code to emit an event in your state-changing precompile functions (generated packer might be different)): +topics, data, err := PackMyEvent( + topic1, + topic2, + data1, + data2, +) +if err != nil { + return nil, remainingGas, err +} +accessibleState.GetStateDB().AddLog( + ContractAddress, + topics, + data, + accessibleState.GetBlockContext().Number().Uint64(), +) +*/ + +// HelloWorldGreetingChanged represents a GreetingChanged non-indexed event data raised by the HelloWorld contract. +type GreetingChangedEventData struct { + OldGreeting string + NewGreeting string +} + +// GetGreetingChangedEventGasCost returns the gas cost of the event. +// The gas cost of an event is the base gas cost + the gas cost of the topics + the gas cost of the non-indexed data. +// The base gas cost and the gas cost of per topics are fixed and can be found in the contract package. +// The gas cost of the non-indexed data depends on the data type and the data size. +func GetGreetingChangedEventGasCost(data GreetingChangedEventData) uint64 { + gas := contract.LogGas // base gas cost + + // Add topics gas cost (2 topics) + // Topics always include the signature hash of the event. The rest are the indexed event arguments. + gas += contract.LogTopicGas * 2 + + // CUSTOM CODE STARTS HERE + // TODO: calculate gas cost for packing the data.oldGreeting according to the type. + // Keep in mind that the data here will be encoded using the ABI encoding scheme. + // So the computation cost might change according to the data type + data size and should be charged accordingly. + // i.e gas += LogDataGas * uint64(len(data.oldGreeting)) + gas += contract.LogDataGas * uint64(len(data.OldGreeting)) // * ... + // CUSTOM CODE ENDS HERE + // CUSTOM CODE STARTS HERE + // TODO: calculate gas cost for packing the data.newGreeting according to the type. + // Keep in mind that the data here will be encoded using the ABI encoding scheme. + // So the computation cost might change according to the data type + data size and should be charged accordingly. + // i.e gas += LogDataGas * uint64(len(data.newGreeting)) + gas += contract.LogDataGas * uint64(len(data.NewGreeting)) // * ... + // CUSTOM CODE ENDS HERE + + // CUSTOM CODE STARTS HERE + // TODO: do any additional gas cost calculation here (only if needed) + return gas +} + +// PackGreetingChangedEvent packs the event into the appropriate arguments for GreetingChanged. +// It returns topic hashes and the encoded non-indexed data. +func PackGreetingChangedEvent(sender common.Address, data GreetingChangedEventData) ([]common.Hash, []byte, error) { + return HelloWorldABI.PackEvent("GreetingChanged", sender, data.OldGreeting, data.NewGreeting) +} + +// UnpackGreetingChangedEventData attempts to unpack non-indexed [dataBytes]. +func UnpackGreetingChangedEventData(dataBytes []byte) (GreetingChangedEventData, error) { + eventData := GreetingChangedEventData{} + err := HelloWorldABI.UnpackIntoInterface(&eventData, "GreetingChanged", dataBytes) + return eventData, err +} diff --git a/helloworld/module.go b/helloworld/module.go new file mode 100644 index 0000000..0ab997f --- /dev/null +++ b/helloworld/module.go @@ -0,0 +1,75 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package helloworld + +import ( + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" + + "github.com/ethereum/go-ethereum/common" +) + +var _ contract.Configurator = &configurator{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// must be unique across all precompiles. +const ConfigKey = "helloWorldConfig" + +// ContractAddress is the defined address of the precompile contract. +// This should be unique across all precompile contracts. +// See precompile/registry/registry.go for registered precompile contracts and more information. +var ContractAddress = common.HexToAddress("0x0300000000000000000000000000000000000000") // SET A SUITABLE HEX ADDRESS HERE + +// Module is the precompile module. It is used to register the precompile contract. +var Module = modules.Module{ + ConfigKey: ConfigKey, + Address: ContractAddress, + Contract: HelloWorldPrecompile, + Configurator: &configurator{}, +} + +const defaultGreeting = "Hello World!" + +type configurator struct{} + +func init() { + // Register the precompile module. + // Each precompile contract registers itself through [RegisterModule] function. + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} + +// MakeConfig returns a new precompile config instance. +// This is required to Marshal/Unmarshal the precompile config. +func (*configurator) MakeConfig() precompileconfig.Config { + return new(Config) +} + +// Configure configures [state] with the given [cfg] precompileconfig. +// This function is called by the EVM once per precompile contract activation. +// You can use this function to set up your precompile contract's initial state, +// by using the [cfg] config and [state] stateDB. +func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + // CUSTOM CODE STARTS HERE + + // This will be called in the first block where HelloWorld stateful precompile is enabled. + // 1) If BlockTimestamp is nil, this will not be called + // 2) If BlockTimestamp is 0, this will be called while setting up the genesis block + // 3) If BlockTimestamp is 1000, this will be called while processing the first block + // whose timestamp is >= 1000 + // + // Set the initial value under [common.BytesToHash([]byte("storageKey")] to "Hello World!" + StoreGreeting(state, defaultGreeting) + // AllowList is activated for this precompile. Configuring allowlist addresses here. + return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) +} diff --git a/plugin/main.go b/plugin/main.go index 0843e57..9882b3f 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -9,9 +9,11 @@ import ( "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/subnet-evm/plugin/evm" "github.com/ava-labs/subnet-evm/plugin/runner" + // Each precompile generated by the precompilegen tool has a self-registering init function // that registers the precompile with the subnet-evm. Importing the precompile package here // will cause the precompile to be registered with the subnet-evm. + _ "github.com/ava-labs/precompile-evm/helloworld" // ADD YOUR PRECOMPILE HERE //_ "github.com/ava-labs/precompile-evm/{yourprecompilepkg}" ) diff --git a/tests/precompile/genesis/.gitkeep b/tests/precompile/genesis/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/precompile/genesis/hello_world.json b/tests/precompile/genesis/hello_world.json new file mode 100644 index 0000000..f1973d7 --- /dev/null +++ b/tests/precompile/genesis/hello_world.json @@ -0,0 +1,36 @@ +{ + "config": { + "chainId": 99999, + "feeConfig": { + "gasLimit": 20000000, + "minBaseFee": 1000000000, + "targetGas": 100000000, + "baseFeeChangeDenominator": 48, + "minBlockGasCost": 0, + "maxBlockGasCost": 10000000, + "targetBlockRate": 2, + "blockGasCostStep": 500000 + }, + "helloWorldConfig": { + "blockTimestamp": 0, + "adminAddresses": [ + "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" + ] + } + }, + "alloc": { + "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { + "balance": "0x52B7D2DCC80CD2E4000000" + } + }, + "nonce": "0x0", + "timestamp": "0x66350B4E", + "extraData": "0x00", + "gasLimit": "0x1312D00", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/tests/precompile/solidity/suites.go b/tests/precompile/solidity/suites.go index a0e37fc..139e0ac 100644 --- a/tests/precompile/solidity/suites.go +++ b/tests/precompile/solidity/suites.go @@ -5,6 +5,11 @@ package solidity import ( + "context" + "fmt" + "time" + + "github.com/ava-labs/subnet-evm/tests/utils" "github.com/onsi/ginkgo/v2" ) @@ -12,7 +17,6 @@ import ( // Before running the tests, this function creates all subnets given in the genesis files // and then runs the hardhat tests for each one asynchronously if called with `ginkgo run -procs=`. func RegisterAsyncTests() { - /* Uncomment these if you want to use default hardhat tests // Tests here assumes that the genesis files are in ./tests/precompile/genesis/ // with the name {precompile_name}.json genesisFiles, err := utils.GetFilesAndAliases("./tests/precompile/genesis/*.json") @@ -23,11 +27,17 @@ func RegisterAsyncTests() { ginkgo.AbortSuite("No genesis files found") } subnetsSuite := utils.CreateSubnetsSuite(genesisFiles) - */ + _ = ginkgo.Describe("[Asynchronized Precompile Tests]", func() { - // Uncomment below and register the ping test first - // utils.RegisterPingTest() + // Register the ping test first + utils.RegisterPingTest() + ginkgo.It("hello world", ginkgo.Label("Precompile"), ginkgo.Label("HelloWorld"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + blockchainID := subnetsSuite.GetBlockchainID("hello_world") + runDefaultHardhatTests(ctx, blockchainID, "hello_world") + }) // ADD YOUR PRECOMPILE HERE /* ginkgo.It("your precompile", ginkgo.Label("Precompile"), ginkgo.Label("YourPrecompile"), func() { @@ -43,7 +53,6 @@ func RegisterAsyncTests() { }) } -/* Uncomment this if you want to use default hardhat tests // Default parameters are: // // 1. Hardhat contract environment is located at ./contracts @@ -55,4 +64,3 @@ func runDefaultHardhatTests(ctx context.Context, blockchainID, testName string) testPath := fmt.Sprintf("./test/%s.ts", testName) utils.RunHardhatTests(ctx, blockchainID, cmdPath, testPath) } -*/