Skip to content

Commit

Permalink
Merge pull request #31 from ava-labs/function-selectors-precompile
Browse files Browse the repository at this point in the history
Function selectors precompile
  • Loading branch information
patrick-ogrady authored Mar 3, 2022
2 parents 505f039 + 914a615 commit 1d9bf7a
Show file tree
Hide file tree
Showing 31 changed files with 1,626 additions and 68 deletions.
92 changes: 92 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,98 @@ Next, you'll need to update your [chain config](https://docs.avax.network/build/
_Note: If you enable this feature but a validator doesn't specify
a "feeRecipient", the fees will be burned in blocks they produce._

## Restricting Smart Contract Deployers
If you'd like to restrict who has the ability to deploy contracts on your
subnet, you can provide an `AllowList` configuration in your genesis file:
```json
{
"config": {
"chainId": 99999,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"subnetEVMTimestamp": 0,
"feeConfig": {
"gasLimit": 20000000,
"minBaseFee": 1000000000,
"targetGas": 100000000,
"baseFeeChangeDenominator": 48,
"minBlockGasCost": 0,
"maxBlockGasCost": 10000000,
"targetBlockRate": 2,
"blockGasCostStep": 500000
},
"contractDeployerAllowListConfig": {
"blockTimestamp": 0,
"adminAddresses":["0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"]
}
},
"alloc": {
"8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": {
"balance": "0x52B7D2DCC80CD2E4000000"
}
},
"nonce": "0x0",
"timestamp": "0x0",
"extraData": "0x00",
"gasLimit": "0x1312D00",
"difficulty": "0x0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
```

In this example, `0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC` is named as the
`Admin` of the `ContractDeployerAllowList`. This enables them to add other `Admins` or to add
`Deployers`. Both `Admins` and `Deployers` can deploy contracts. To provide
a great UX with factory contracts, the `tx.Origin` is checked for being a valid
deployer instead of the caller of `CREATE`.

The `Stateful Precompile` powering the `ContractDeployerAllowList` adheres to the following
Solidity interface at `0x0200000000000000000000000000000000000000` (you can
load this interface and interact directly in Remix):
```solidity
// (c) 2022-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface AllowListInterface {
// Set [addr] to have the admin role over the allow list
function setAdmin(address addr) external;
// Set [addr] to have the deployer role over the allow list
function setDeployer(address addr) external;
// Set [addr] to have no role over the allow list
function setNone(address addr) external;
// Read the status of [addr]
function readAllowList(address addr) external view returns (uint256);
}
```

If you attempt to add a `Deployer` and you are not an `Admin`, you will see
something like:
![admin fail](./imgs/admin_fail.png)

If you attempt to deploy a contract but you are not an `Admin` not
a `Deployer`, you will see something like:
![deploy fail](./imgs/deploy_fail.png)


## Run Local Network
[`scripts/run.sh`](scripts/run.sh) automatically installs [avalanchego], sets up a local network,
and creates a `subnet-evm` genesis file.
Expand Down
8 changes: 2 additions & 6 deletions chain/subnet_evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/ava-labs/avalanchego/utils/timer/mockable"
"github.com/ava-labs/subnet-evm/constants"
"github.com/ava-labs/subnet-evm/core"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/types"
Expand All @@ -19,11 +20,6 @@ import (
"github.com/ethereum/go-ethereum/log"
)

var BlackholeAddr = common.Address{
1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}

type (
Tx = types.Transaction
Block = types.Block
Expand All @@ -47,7 +43,7 @@ func NewETHChain(config *eth.Config, nodecfg *node.Config, chainDB ethdb.Databas
chain := &ETHChain{backend: backend}
if config.Miner.Etherbase == (common.Address{}) { // used for testing
log.Warn("Etherbase not set. Falling back to blackhole address.")
backend.SetEtherbase(BlackholeAddr)
backend.SetEtherbase(constants.BlackholeAddr)
} else {
backend.SetEtherbase(config.Miner.Etherbase)
}
Expand Down
13 changes: 13 additions & 0 deletions constants/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// (c) 2021-2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package constants

import "github.com/ethereum/go-ethereum/common"

var (
BlackholeAddr = common.Address{
1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}
)
9 changes: 8 additions & 1 deletion core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
"t", time.Since(t),
)
}

genesisTimestamp := new(big.Int).SetUint64(g.Timestamp)
// Configure any stateful precompiles that should be enabled in the genesis.
g.Config.CheckConfigurePrecompiles(nil, genesisTimestamp, statedb)

// Do cusotm allocation after airdrop in case an address shows up in standard
// allocation
for addr, account := range g.Alloc {
Expand Down Expand Up @@ -306,7 +311,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
}
}
statedb.Commit(false)
statedb.Database().TrieDB().Commit(root, true, nil)
if err := statedb.Database().TrieDB().Commit(root, true, nil); err != nil {
panic(fmt.Sprintf("unable to commit genesis block: %v", err))
}

return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
}
Expand Down
58 changes: 58 additions & 0 deletions core/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ import (

"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/ethdb"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
)

func setupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
Expand Down Expand Up @@ -165,3 +168,58 @@ func TestSetupGenesis(t *testing.T) {
})
}
}

func TestStatefulPrecompilesConfigure(t *testing.T) {
type test struct {
getConfig func() *params.ChainConfig // Return the config that enables the stateful precompile at the genesis for the test
assertState func(t *testing.T, sdb *state.StateDB) // Check that the stateful precompiles were configured correctly
}

addr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC")

// Test suite to ensure that stateful precompiles are configured correctly in the genesis.
for name, test := range map[string]test{
"allow list enabled in genesis": {
getConfig: func() *params.ChainConfig {
config := *params.TestChainConfig
config.ContractDeployerAllowListConfig = precompile.ContractDeployerAllowListConfig{
AllowListConfig: precompile.AllowListConfig{
BlockTimestamp: big.NewInt(0),
AllowListAdmins: []common.Address{addr},
},
}
return &config
},
assertState: func(t *testing.T, sdb *state.StateDB) {
assert.Equal(t, precompile.AllowListAdmin, precompile.GetContractDeployerAllowListStatus(sdb, addr), "unexpected allow list status for modified address")
assert.Equal(t, uint64(1), sdb.GetNonce(precompile.ContractDeployerAllowListAddress))
},
},
} {
t.Run(name, func(t *testing.T) {
config := test.getConfig()

genesis := &Genesis{
Config: config,
Alloc: GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
}

db := rawdb.NewMemoryDatabase()
_, err := SetupGenesisBlock(db, genesis)
if err != nil {
t.Fatal(err)
}

genesisBlock := genesis.ToBlock(nil)
genesisRoot := genesisBlock.Root()

statedb, err := state.New(genesisRoot, state.NewDatabase(db), nil)
if err != nil {
t.Fatal(err)
}
test.assertState(t, statedb)
})
}
}
6 changes: 5 additions & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,17 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state
blockNumber = block.Number()
allLogs []*types.Log
gp = new(GasPool).AddGas(block.GasLimit())
timestamp = new(big.Int).SetUint64(header.Time)
)

// Configure any stateful precompiles that should go into effect during this block.
p.config.CheckConfigurePrecompiles(new(big.Int).SetUint64(parent.Time), timestamp, statedb)

blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number, new(big.Int).SetUint64(header.Time)), header.BaseFee)
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number, timestamp), header.BaseFee)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
Expand Down
6 changes: 3 additions & 3 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,9 @@ func (st *StateTransition) preCheck() error {
return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA,
st.msg.From().Hex(), codeHash)
}
// Make sure the sender is not the Blackhole
if st.msg.From() == st.evm.Context.Coinbase {
return fmt.Errorf("%w: address %v", vm.ErrNoSenderBlackhole, st.msg.From())
// Make sure the sender is not prohibited
if vm.IsProhibited(st.msg.From()) {
return fmt.Errorf("%w: address %v", vm.ErrAddrProhibited, st.msg.From())
}
}
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
Expand Down
Loading

0 comments on commit 1d9bf7a

Please sign in to comment.