Skip to content

Commit

Permalink
add hello world example
Browse files Browse the repository at this point in the history
  • Loading branch information
ceyonur committed Jul 6, 2023
1 parent 49cfd8b commit be6911a
Show file tree
Hide file tree
Showing 22 changed files with 999 additions and 5 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/sync.yml
Original file line number Diff line number Diff line change
@@ -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/[email protected]
with:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
FROM_BRANCH: "main"
TO_BRANCH: "develop"
1 change: 1 addition & 0 deletions contracts/abis/IAllowList.abi
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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"}]
19 changes: 19 additions & 0 deletions contracts/contracts/ExampleHelloWorld.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
12 changes: 12 additions & 0 deletions contracts/contracts/interfaces/IHelloWorld.sol
Original file line number Diff line number Diff line change
@@ -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;
}
42 changes: 42 additions & 0 deletions contracts/contracts/test/ExampleHelloWorldTest.sol
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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
20 changes: 20 additions & 0 deletions contracts/scripts/deployExampleHelloWorld.ts
Original file line number Diff line number Diff line change
@@ -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)
})
61 changes: 60 additions & 1 deletion contracts/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@ import { task } from "hardhat/config"
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"
import { BigNumber } from "ethers"

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<void> => {
const accounts: SignerWithAddress[] = await hre.ethers.getSigners()
accounts.forEach((account: SignerWithAddress): void => {
console.log(account.address)
})
})

// npx hardhat balances --network local
task("balances", "Prints the list of account balances", async (args, hre): Promise<void> => {
const accounts: SignerWithAddress[] = await hre.ethers.getSigners()
for (const account of accounts) {
Expand All @@ -19,7 +34,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) => {
Expand All @@ -28,5 +43,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)
})



34 changes: 34 additions & 0 deletions contracts/test/hello_world.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// (c) 2019-2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

import { ethers } from "hardhat"
import { test } from "@avalabs/subnet-evm-contracts"

// 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.deployed().then(() => contract)
})
.then(() => Promise.all([helloWorldPromise]))
.then(([helloWorld]) => helloWorld.setAdmin(this.testContract.address))
.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")
});
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ go 1.19
require (
github.com/ava-labs/avalanchego v1.10.2
github.com/ava-labs/subnet-evm v0.5.2
github.com/ethereum/go-ethereum v1.10.26
github.com/onsi/ginkgo/v2 v2.8.1
github.com/onsi/gomega v1.26.0
github.com/stretchr/testify v1.8.3
)

require (
Expand All @@ -21,7 +23,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/ethereum/go-ethereum v1.10.26 // 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
Expand Down Expand Up @@ -75,7 +76,6 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.12.0 // indirect
github.com/status-im/keycard-go v0.2.0 // indirect
github.com/stretchr/testify v1.8.3 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/supranational/blst v0.3.11-0.20220920110316-f72618070295 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
Expand Down
28 changes: 28 additions & 0 deletions helloworld/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Hello World Precompile

This is a example precompile to demonstrate how to develop a precompile for Precompile-EVM. This is mainly used by [Hello World Precompile Tutorial](https://docs.avax.network/subnets/hello-world-precompile-tutorial).

On below you can find the original README.md of the precompile template:

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 <https://docs.avax.network/subnets/hello-world-precompile-tutorial> 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`
74 changes: 74 additions & 0 deletions helloworld/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package helloworld

import (
"math/big"

"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] as members of the allowlist .
func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *Config {
return &Config{
AllowListConfig: allowlist.AllowListConfig{
AdminAddresses: admins,
EnabledAddresses: enableds,
},
Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp},
}
}

// NewDisableConfig returns config for a network upgrade at [blockTimestamp]
// that disables HelloWorld.
func NewDisableConfig(blockTimestamp *big.Int) *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() error {
// Verify AllowList first
if err := c.AllowListConfig.Verify(); 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
}
Loading

0 comments on commit be6911a

Please sign in to comment.