Skip to content

Commit

Permalink
Generalized upgrades rb (#434)
Browse files Browse the repository at this point in the history
* introduce precompiles as registrable modules

* add precompile specific contract tests

* remove print debug

* add unmarshal tests

* remove unnecessary func

* fix initial disabled value

* register all modules in core/evm/contract_stateful

* more refactor & test fix

* sync template

* fix more tests

* rename file

* add comment

* rename

* fix linter

* use require error contains

* remove whitespace

* trim mock interface

* sort steps

* reviews

* Update precompile/stateful_precompile_module.go

* Update params/precompile_config.go

* Update params/precompile_config.go

* fix reviews

* add new module to configs and group module functions

* generalized-upgrades-rb review (#474)

* keep genesis disabled fix

* nits

* nits

* nit

* review fixes

* Update precompile/allowlist/allowlist.go

* use address in map

* fix linter for embedded keys

* update err messages

* more err update

* remove unnecessary function (#478)

* Start work on breaking cyclic dependency (#496)

* Update core/state_processor.go

* fix reviews

* Update precompile/contracts/txallowlist/contract_test.go

* Generalized upgrades rb nits0 (#512)

* Minor improvements

* restore readOnly

* more updates

* Add back readOnly to allow list tests
  • Loading branch information
ceyonur authored Feb 16, 2023
1 parent 357d9e6 commit 1add41a
Show file tree
Hide file tree
Showing 90 changed files with 4,127 additions and 3,241 deletions.
8 changes: 5 additions & 3 deletions accounts/abi/bind/precompile_bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []m

configBind, err := bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configHook)
if err != nil {
return "", "", err
return "", "", fmt.Errorf("failed to generate config binding: %w", err)
}
contractBind, err := bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractHook)

return configBind, contractBind, err
if err != nil {
return "", "", fmt.Errorf("failed to generate contract binding: %w", err)
}
return configBind, contractBind, nil
}

func createPrecompileHook(abifilename string, template string) BindHook {
Expand Down
7 changes: 3 additions & 4 deletions accounts/abi/bind/precompile_bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ package bind

import (
"testing"

"github.com/stretchr/testify/require"
)

var bindFailedTests = []struct {
Expand Down Expand Up @@ -100,10 +102,7 @@ func golangBindingsFailure(t *testing.T) {
if err == nil {
t.Fatalf("test %d: no error occurred but was expected", i)
}

if tt.errorMsg != err.Error() {
t.Fatalf("test %d: expected Err %s but got actual Err: %s", i, tt.errorMsg, err.Error())
}
require.ErrorContains(t, err, tt.errorMsg)
})
}
}
69 changes: 43 additions & 26 deletions accounts/abi/bind/precompile_config_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ const tmplSourcePrecompileConfigGo = `
// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders.
/* General guidelines for precompile development:
1- Read the comment and set a suitable contract address in precompile/params.go. E.g:
{{.Contract.Type}}Address = common.HexToAddress("ASUITABLEHEXADDRESS")
2- Set gas costs in contract.go
3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM.
1- Read the comment and set a suitable contract address in generated contract.go. E.g:
ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS")
2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM.
Typically, custom codes are required in only those areas.
4- Add your upgradable config in params/precompile_config.go
5- Add your precompile upgrade in params/config.go
6- Add your config unit test in {generatedpkg}/config_test.go
3- Set gas costs in generated contract.go
4- Register your precompile module in params/precompile_modules.go
5- Add your config unit tests under generated package config_test.go
6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go
7- Add your solidity interface and test contract to contract-examples/contracts
8- Write solidity tests for your precompile in contract-examples/test
9- Create your genesis with your precompile enabled in tests/e2e/genesis/
Expand All @@ -33,24 +33,27 @@ Typically, custom codes are required in only those areas.
package {{.Package}}
import (
"encoding/json"
"math/big"
"github.com/ava-labs/subnet-evm/precompile"
{{- if .Contract.AllowList}}
"github.com/ava-labs/subnet-evm/precompile/allowlist"
{{- end}}
"github.com/ethereum/go-ethereum/common"
)
{{$contract := .Contract}}
var (
_ precompile.StatefulPrecompileConfig = &{{.Contract.Type}}Config{}
)
var _ precompile.StatefulPrecompileConfig = &{{.Contract.Type}}Config{}
// ConfigKey is the key used in json config files to specify this precompile config.
// Must be unique across all precompiles.
const ConfigKey = "{{decapitalise .Contract.Type}}Config"
// {{.Contract.Type}}Config implements the StatefulPrecompileConfig
// interface while adding in the {{.Contract.Type}} specific precompile address.
type {{.Contract.Type}}Config struct {
{{- if .Contract.AllowList}}
precompile.AllowListConfig
allowlist.AllowListConfig
{{- end}}
precompile.UpgradeableConfig
}
Expand Down Expand Up @@ -81,7 +84,7 @@ type {{capitalise .Normalized.Name}}Output struct{
// {{.Contract.Type}} {{if .Contract.AllowList}} with the given [admins] as members of the allowlist {{end}}.
func New{{.Contract.Type}}Config(blockTimestamp *big.Int{{if .Contract.AllowList}}, admins []common.Address{{end}}) *{{.Contract.Type}}Config {
return &{{.Contract.Type}}Config{
{{if .Contract.AllowList}}AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins},{{end}}
{{if .Contract.AllowList}}AllowListConfig: allowlist.AllowListConfig{AdminAddresses: admins},{{end}}
UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp},
}
}
Expand Down Expand Up @@ -125,27 +128,41 @@ func (c *{{.Contract.Type}}Config) Equal(s precompile.StatefulPrecompileConfig)
return equals
}
// Address returns the address of the {{.Contract.Type}}. Addresses reside under the precompile/params.go
// Select a non-conflicting address and set it in the params.go.
func (c *{{.Contract.Type}}Config) Address() common.Address {
return {{.Contract.Type}}Address
}
// Configure configures [state] with the initial configuration.
func (c *{{.Contract.Type}}Config) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error {
{{if .Contract.AllowList}}c.AllowListConfig.Configure(state, {{.Contract.Type}}Address){{end}}
{{if .Contract.AllowList}}c.AllowListConfig.Configure(state, ContractAddress){{end}}
// CUSTOM CODE STARTS HERE
return nil
}
// Required module functions for {{.Contract.Type}}Config
// These functions mostly do not require any custom code.
// NewModule returns a new module for {{.Contract.Type}}.
func NewModule() precompile.StatefulPrecompileModule {
return &{{.Contract.Type}}Config{}
}
// Address returns the address of the {{.Contract.Type}}.
// Select a non-conflicting address and set it in generated contract.go
func ({{.Contract.Type}}Config) Address() common.Address {
return ContractAddress
}
// Contract returns the singleton stateful precompiled contract to be used for {{.Contract.Type}}.
func (c *{{.Contract.Type}}Config) Contract() precompile.StatefulPrecompiledContract {
func ({{.Contract.Type}}Config) Contract() precompile.StatefulPrecompiledContract {
return {{.Contract.Type}}Precompile
}
// String returns a string representation of the {{.Contract.Type}}Config.
func (c *{{.Contract.Type}}Config) String() string {
bytes, _ := json.Marshal(c)
return string(bytes)
// Key returns the key used in json config files to specify this precompile config.
func ({{.Contract.Type}}Config) Key() string {
return ConfigKey
}
// New returns a new {{.Contract.Type}}Config.
// This is used by the json parser to create a new instance of the {{.Contract.Type}}Config.
func ({{.Contract.Type}}Config) NewConfig() precompile.StatefulPrecompileConfig {
return new({{.Contract.Type}}Config)
}
`
58 changes: 29 additions & 29 deletions accounts/abi/bind/precompile_contract_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ type tmplPrecompileContract struct {
// tmplSourcePrecompileContractGo is the Go precompiled contract source template.
const tmplSourcePrecompileContractGo = `
// Code generated
// This file is a generated precompile contract with stubbed abstract functions.
// 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.
// There are some must-be-done changes waiting in the 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 core/stateful_precompile_test.go
// For testing take a look at other precompile tests in tests/statefulprecompiles/ and config_test.go in other precompile folders.
/* General guidelines for precompile development:
1- Read the comment and set a suitable contract address in precompile/params.go. E.g:
{{.Contract.Type}}Address = common.HexToAddress("ASUITABLEHEXADDRESS")
2- Set gas costs in contract.go
1- Read the comment and set a suitable contract address in generated contract.go. E.g:
ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS")
2- Set gas costs in generated contract.go
3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM.
Typically, custom codes are required in only those areas.
4- Add your upgradable config in params/precompile_config.go
5- Add your precompile upgrade in params/config.go
6- Add your config unit test in {generatedpkg}/config_test.go
4- Register your precompile module in params/precompile_modules.go
5- Add your config unit tests under generated package config_test.go
6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go
7- Add your solidity interface and test contract to contract-examples/contracts
8- Write solidity tests for your precompile in contract-examples/test
9- Create your genesis with your precompile enabled in tests/e2e/genesis/
Expand All @@ -48,14 +48,16 @@ Typically, custom codes are required in only those areas.
package {{.Package}}
import (
"encoding/json"
"math/big"
"errors"
"fmt"
"strings"
"github.com/ava-labs/subnet-evm/accounts/abi"
"github.com/ava-labs/subnet-evm/precompile"
{{- if .Contract.AllowList}}
"github.com/ava-labs/subnet-evm/precompile/allowlist"
{{- end}}
"github.com/ava-labs/subnet-evm/vmerrs"
_ "embed"
Expand All @@ -77,14 +79,16 @@ const (
var (
_ = errors.New
_ = big.NewInt
_ = strings.NewReader
_ = fmt.Printf
_ = json.Unmarshal
)
{{$contract := .Contract}}
// Singleton StatefulPrecompiledContract and signatures.
var (
// ContractAddress is the defined address of the precompile contract.
// This should be unique across all precompile contracts.
// See params/precompile_modules.go for registered precompile contracts and more information.
ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE
{{- range .Contract.Funcs}}
{{- if not .Original.IsConstant | and $contract.AllowList}}
Expand All @@ -107,10 +111,6 @@ var (
{{.Contract.Type}}ABI abi.ABI // will be initialized by init function
{{.Contract.Type}}Precompile precompile.StatefulPrecompiledContract // will be initialized by init function
// CUSTOM CODE STARTS HERE
// THIS SHOULD BE MOVED TO precompile/params.go with a suitable hex address.
{{.Contract.Type}}Address = common.HexToAddress("ASUITABLEHEXADDRESS")
)
{{$structs := .Structs}}
Expand Down Expand Up @@ -142,26 +142,26 @@ func init() {
}
{{.Contract.Type}}ABI = parsed
{{.Contract.Type}}Precompile, err = create{{.Contract.Type}}Precompile({{.Contract.Type}}Address)
{{.Contract.Type}}Precompile, err = create{{.Contract.Type}}Precompile()
if err != nil {
panic(err)
}
}
{{if .Contract.AllowList}}
// Get{{.Contract.Type}}AllowListStatus returns the role of [address] for the {{.Contract.Type}} list.
func Get{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address) precompile.AllowListRole {
return precompile.GetAllowListStatus(stateDB, {{.Contract.Type}}Address, address)
func Get{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole {
return allowlist.GetAllowListStatus(stateDB, ContractAddress, address)
}
// Set{{.Contract.Type}}AllowListStatus sets the permissions of [address] to [role] for the
// {{.Contract.Type}} list. Assumes [role] has already been verified as valid.
// This stores the [role] in the contract storage with address [{{.Contract.Type}}Address]
// 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 Set{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address, role precompile.AllowListRole) {
precompile.SetAllowListRole(stateDB, {{.Contract.Type}}Address, address, role)
func Set{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) {
allowlist.SetAllowListRole(stateDB, ContractAddress, address, role)
}
{{end}}
Expand Down Expand Up @@ -257,8 +257,8 @@ func {{decapitalise .Normalized.Name}}(accessibleState precompile.PrecompileAcce
// 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 modify it
callerStatus := precompile.GetAllowListStatus(stateDB, {{$contract.Type}}Address, caller)
// 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", ErrCannot{{.Normalized.Name}}, caller)
}
Expand Down Expand Up @@ -308,8 +308,8 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState precompile.Precomp
// 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 modify it
callerStatus := precompile.GetAllowListStatus(stateDB, {{$contract.Type}}Address, caller)
// 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", Err{{$contract.Type}}CannotFallback, caller)
}
Expand All @@ -329,11 +329,11 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState precompile.Precomp
{{- end}}
// create{{.Contract.Type}}Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile.
{{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for [precompileAddr].{{end}}
func create{{.Contract.Type}}Precompile(precompileAddr common.Address) (precompile.StatefulPrecompiledContract, error) {
{{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for ContractAddress.{{end}}
func create{{.Contract.Type}}Precompile() (precompile.StatefulPrecompiledContract, error) {
var functions []*precompile.StatefulPrecompileFunction
{{- if .Contract.AllowList}}
functions = append(functions, precompile.CreateAllowListFunctions(precompileAddr)...)
functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...)
{{- end}}
abiFunctionMap := map[string]precompile.RunStatefulPrecompileFunc{
Expand Down
6 changes: 3 additions & 3 deletions cmd/precompilegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func precompilegen(c *cli.Context) error {
// Generate the contract precompile
configCode, contractCode, err := bind.PrecompileBind(types, abis, bins, sigs, pkg, lang, libs, aliases, abifilename)
if err != nil {
utils.Fatalf("Failed to generate ABI precompile: %v", err)
utils.Fatalf("Failed to generate precompile: %v", err)
}

// Either flush it out to a file or display on the standard output
Expand All @@ -165,13 +165,13 @@ func precompilegen(c *cli.Context) error {
configCodeOut := filepath.Join(outFlagStr, "config.go")

if err := os.WriteFile(configCodeOut, []byte(configCode), 0o600); err != nil {
utils.Fatalf("Failed to write generated precompile: %v", err)
utils.Fatalf("Failed to write generated config code: %v", err)
}

contractCodeOut := filepath.Join(outFlagStr, "contract.go")

if err := os.WriteFile(contractCodeOut, []byte(contractCode), 0o600); err != nil {
utils.Fatalf("Failed to write generated precompile: %v", err)
utils.Fatalf("Failed to write generated contract code: %v", err)
}

if err := os.WriteFile(abipath, []byte(abis[0]), 0o600); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions contract-examples/contracts/ExampleTxAllowList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./AllowList.sol";

// ExampleDeployerList shows how ContractDeployerAllowList precompile can be used in a smart contract
// ExampleTxAllowList shows how TxAllowList precompile can be used in a smart contract
// All methods of [allowList] can be directly called. There are example calls as tasks in hardhat.config.ts file.
contract ExampleTxAllowList is AllowList {
// Precompiled Allow List Contract Address
address constant DEPLOYER_LIST = 0x0200000000000000000000000000000000000002;
address constant TX_ALLOW_LIST = 0x0200000000000000000000000000000000000002;

constructor() AllowList(DEPLOYER_LIST) {}
constructor() AllowList(TX_ALLOW_LIST) {}
}
9 changes: 4 additions & 5 deletions core/blockchain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ import (
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile"
"github.com/ava-labs/subnet-evm/precompile/feemanager"
"github.com/ava-labs/subnet-evm/precompile/rewardmanager"
"github.com/ava-labs/subnet-evm/precompile/contracts/feemanager"
"github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
)
Expand Down Expand Up @@ -350,7 +349,7 @@ func (bc *BlockChain) SubscribeAcceptedTransactionEvent(ch chan<- NewTxsEvent) e
func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) {
config := bc.Config()
bigTime := new(big.Int).SetUint64(parent.Time)
if !config.IsPrecompileEnabled(precompile.FeeManagerAddress, bigTime) {
if !config.IsPrecompileEnabled(feemanager.ContractAddress, bigTime) {
return config.FeeConfig, common.Big0, nil
}

Expand Down Expand Up @@ -394,7 +393,7 @@ func (bc *BlockChain) GetCoinbaseAt(parent *types.Header) (common.Address, bool,
return constants.BlackholeAddr, false, nil
}

if !config.IsPrecompileEnabled(precompile.RewardManagerAddress, bigTime) {
if !config.IsPrecompileEnabled(rewardmanager.ContractAddress, bigTime) {
if bc.chainConfig.AllowFeeRecipients {
return common.Address{}, true, nil
} else {
Expand Down
2 changes: 1 addition & 1 deletion core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
}

// Configure any stateful precompiles that should be enabled in the genesis.
err = g.Config.ConfigurePrecompiles(nil, types.NewBlockWithHeader(head), statedb)
err = ApplyPrecompileActivations(g.Config, nil, types.NewBlockWithHeader(head), statedb)
if err != nil {
panic(fmt.Sprintf("unable to configure precompiles in genesis block: %v", err))
}
Expand Down
Loading

0 comments on commit 1add41a

Please sign in to comment.