Skip to content

Commit

Permalink
add durango changes
Browse files Browse the repository at this point in the history
  • Loading branch information
ceyonur committed Feb 27, 2024
1 parent c22192c commit cc441ad
Show file tree
Hide file tree
Showing 11 changed files with 516 additions and 253 deletions.
1 change: 0 additions & 1 deletion contracts/abis/IAllowList.abi

This file was deleted.

1 change: 0 additions & 1 deletion contracts/abis/IHelloWorld.abi

This file was deleted.

1 change: 1 addition & 0 deletions contracts/contracts/interfaces/IHelloWorld.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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);

Expand Down
7 changes: 1 addition & 6 deletions helloworld/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
# 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:
Expand Down
13 changes: 9 additions & 4 deletions helloworld/config.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// 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 (
Expand All @@ -19,12 +23,13 @@ type Config struct {
}

// 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 *uint64, admins []common.Address, enableds []common.Address) *Config {
// 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},
}
Expand All @@ -46,9 +51,9 @@ func NewDisableConfig(blockTimestamp *uint64) *Config {
func (*Config) Key() string { return ConfigKey }

// Verify tries to verify Config and returns an error accordingly.
func (c *Config) Verify(p precompileconfig.ChainConfig) error {
func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error {
// Verify AllowList first
if err := c.AllowListConfig.Verify(p, c.Upgrade); err != nil {
if err := c.AllowListConfig.Verify(chainConfig, c.Upgrade); err != nil {
return err
}
// CUSTOM CODE STARTS HERE
Expand Down
41 changes: 24 additions & 17 deletions helloworld/config_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// 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 (
Expand All @@ -7,29 +11,36 @@ import (
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
"github.com/ava-labs/subnet-evm/precompile/testutils"
"github.com/ava-labs/subnet-evm/utils"
"go.uber.org/mock/gomock"

"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),
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),
// Config: NewConfig(utils.NewUint64(3), admins, enableds, managers),
// ExpectedError: ErrYourCustomError.Error(),
// },
// We don't have a custom verification logic for HelloWorld
// so we just test the allowlist verification logic with a nil custom verifyTests input.
// VerifyPrecompileWithAllowListTests will add the allowlist verification logic tests for us.
"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
Expand All @@ -43,34 +54,30 @@ func TestVerify(t *testing.T) {
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),
Config: NewConfig(utils.NewUint64(3), admins, enableds, managers),
Other: nil,
Expected: false,
},
"different type": {
Config: NewConfig(utils.NewUint64(3), admins, enableds),
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),
Other: NewConfig(utils.NewUint64(4), admins, enableds),
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),
Other: NewConfig(utils.NewUint64(3), admins, enableds),
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
"different enabled": {
Config: NewConfig(utils.NewUint64(3), admins, nil),
Other: NewConfig(utils.NewUint64(3), admins, enableds),
Expected: false,
},
}
// Run allow list equal tests.
// This adds allowlist equal tests to your custom tests
Expand Down
2 changes: 1 addition & 1 deletion helloworld/contract.abi
Original file line number Diff line number Diff line change
@@ -1 +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"}]
[{"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"}]
76 changes: 64 additions & 12 deletions helloworld/contract.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// 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 (
Expand All @@ -15,12 +19,12 @@ import (
)

const (
// Gas costs for each function. These are set to 0 by default.
// 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.}
// You should also increase gas costs of functions that read from AllowList storage.
SayHelloGasCost uint64 = contract.ReadGasCostPerSlot
SetGreetingGasCost uint64 = contract.WriteGasCostPerSlot + allowlist.ReadAllowListGasCost
)
Expand All @@ -38,10 +42,6 @@ var (

HelloWorldPrecompile = createHelloWorldPrecompile()

// storageKeyHash is the hash of the storage key "storageKey" in the contract storage.
// This is used to store the value of the greeting in the contract storage.
// It is important to use a unique key here to avoid conflicts with other storage keys
// like addresses, AllowList, etc.
storageKeyHash = common.BytesToHash([]byte("storageKey"))
)

Expand Down Expand Up @@ -72,6 +72,17 @@ 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.
Expand Down Expand Up @@ -105,8 +116,16 @@ func sayHello(accessibleState contract.AccessibleState, caller common.Address, a

// UnpackSetGreetingInput attempts to unpack [input] into the string type argument
// assumes that [input] does not include selector (omits first 4 func signature bytes)
func UnpackSetGreetingInput(input []byte) (string, error) {
res, err := HelloWorldABI.UnpackInput("setGreeting", input, false)
// 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
}
Expand Down Expand Up @@ -137,10 +156,12 @@ func setGreeting(accessibleState contract.AccessibleState, caller common.Address
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)
inputStruct, err := UnpackSetGreetingInput(input, useStrictMode)
if err != nil {
return nil, remainingGas, err
}
Expand All @@ -157,9 +178,40 @@ func setGreeting(accessibleState contract.AccessibleState, caller common.Address
// allow list code ends here.

// CUSTOM CODE STARTS HERE
// Check if the input string is longer than HashLength
if len(inputStruct) > common.HashLength {
return nil, 0, ErrInputExceedsLimit
// 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
Expand Down
Loading

0 comments on commit cc441ad

Please sign in to comment.