Skip to content

Commit

Permalink
Evm 785: enable and disable access lists in the runtime (#1839)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Igor Crevar <crewce@gmail.com>
Co-authored-by: Victor Castell <victor@victorcastell.com>
  • Loading branch information
3 people committed Aug 25, 2023
1 parent 2fea208 commit 85af1b3
Show file tree
Hide file tree
Showing 14 changed files with 554 additions and 82 deletions.
1 change: 1 addition & 0 deletions chain/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Params struct {
BlockGasTarget uint64 `json:"blockGasTarget"`

// Access control configuration
AccessListsOwner *types.Address `json:"accessListsOwner,omitempty"`
ContractDeployerAllowList *AddressListConfig `json:"contractDeployerAllowList,omitempty"`
ContractDeployerBlockList *AddressListConfig `json:"contractDeployerBlockList,omitempty"`
TransactionsAllowList *AddressListConfig `json:"transactionsAllowList,omitempty"`
Expand Down
7 changes: 7 additions & 0 deletions command/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@ func setFlags(cmd *cobra.Command) {
[]string{},
"list of addresses to enable by default in the bridge block list",
)

cmd.Flags().StringVar(
&params.accessListsOwner,
accessListsOwnerFlag,
"",
"owner for all allow and block lists",
)
}
}

Expand Down
1 change: 1 addition & 0 deletions command/genesis/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ type genesisParams struct {
bridgeAllowListEnabled []string
bridgeBlockListAdmin []string
bridgeBlockListEnabled []string
accessListsOwner string

nativeTokenConfigRaw string
nativeTokenConfig *polybft.TokenConfig
Expand Down
8 changes: 7 additions & 1 deletion command/genesis/polybft_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
defaultEpochReward = 1
defaultBlockTimeDrift = uint64(10)

accessListsOwnerFlag = "access-lists-owner" // #nosec G101
contractDeployerAllowListAdminFlag = "contract-deployer-allow-list-admin"
contractDeployerAllowListEnabledFlag = "contract-deployer-allow-list-enabled"
contractDeployerBlockListAdminFlag = "contract-deployer-block-list-admin"
Expand Down Expand Up @@ -296,6 +297,11 @@ func (p *genesisParams) generatePolyBftChainConfig(o command.OutputFormatter) er
}
}

if p.accessListsOwner != "" {
value := types.StringToAddress(p.accessListsOwner)
chainConfig.Params.AccessListsOwner = &value
}

if p.isBurnContractEnabled() {
// only populate base fee and base fee multiplier values if burn contract(s)
// is provided
Expand Down Expand Up @@ -386,7 +392,7 @@ func (p *genesisParams) deployContracts(
})
}

if len(params.bridgeAllowListAdmin) != 0 || len(params.bridgeBlockListAdmin) != 0 {
if len(p.bridgeAllowListAdmin) != 0 || len(p.bridgeBlockListAdmin) != 0 || p.accessListsOwner != "" {
// rootchain originated tokens predicates (with access lists)
genesisContracts = append(genesisContracts,
&contractInfo{
Expand Down
16 changes: 10 additions & 6 deletions consensus/polybft/polybft.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,20 @@ func GenesisPostHookFactory(config *chain.Chain, engineName string) func(txn *st
bridgeBlockListAdmin = config.Params.BridgeBlockList.AdminAddresses[0]
}

hasOwner := config.Params.AccessListsOwner != nil
useBridgeAllowList := bridgeAllowListAdmin != types.ZeroAddress
useBridgeBlockList := bridgeBlockListAdmin != types.ZeroAddress

// initialize Predicate SCs
if bridgeAllowListAdmin != types.ZeroAddress || bridgeBlockListAdmin != types.ZeroAddress {
if hasOwner || useBridgeAllowList || useBridgeBlockList {
// The owner of the contract will be the allow list admin or the block list admin, if any of them is set.
owner := contracts.SystemCaller
useBridgeAllowList := bridgeAllowListAdmin != types.ZeroAddress
useBridgeBlockList := bridgeBlockListAdmin != types.ZeroAddress
var owner types.Address

if bridgeAllowListAdmin != types.ZeroAddress {
if hasOwner {
owner = *config.Params.AccessListsOwner
} else if useBridgeAllowList {
owner = bridgeAllowListAdmin
} else if bridgeBlockListAdmin != types.ZeroAddress {
} else {
owner = bridgeBlockListAdmin
}

Expand Down
151 changes: 142 additions & 9 deletions e2e-polybft/e2e/acls_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package e2e

import (
"encoding/hex"
"fmt"
"math/big"
"testing"

"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi"
"github.com/0xPolygon/polygon-edge/contracts"
"github.com/0xPolygon/polygon-edge/e2e-polybft/framework"
"github.com/0xPolygon/polygon-edge/helper/hex"
"github.com/0xPolygon/polygon-edge/state/runtime/addresslist"
"github.com/0xPolygon/polygon-edge/types"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -53,9 +54,7 @@ func TestE2E_AllowList_ContractDeployment(t *testing.T) {

cluster.WaitForReady(t)

// bytecode for an empty smart contract
bytecode, err := hex.DecodeString("608060405234801561001057600080fd5b506103db806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635e9f13f81461003b578063d78bca6914610059575b600080fd5b610043610089565b6040516100509190610217565b60405180910390f35b610073600480360381019061006e9190610263565b6100a1565b60405161008091906102a9565b60405180910390f35b73020000000000000000000000000000000000000081565b600080600073020000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16846040516024016100e29190610217565b6040516020818303038152906040527fd78bca69000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161016c9190610335565b6000604051808303816000865af19150503d80600081146101a9576040519150601f19603f3d011682016040523d82523d6000602084013e6101ae565b606091505b50915091506000818060200190518101906101c99190610378565b9050809350505050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610201826101d6565b9050919050565b610211816101f6565b82525050565b600060208201905061022c6000830184610208565b92915050565b600080fd5b610240816101f6565b811461024b57600080fd5b50565b60008135905061025d81610237565b92915050565b60006020828403121561027957610278610232565b5b60006102878482850161024e565b91505092915050565b6000819050919050565b6102a381610290565b82525050565b60006020820190506102be600083018461029a565b92915050565b600081519050919050565b600081905092915050565b60005b838110156102f85780820151818401526020810190506102dd565b60008484015250505050565b600061030f826102c4565b61031981856102cf565b93506103298185602086016102da565b80840191505092915050565b60006103418284610304565b915081905092915050565b61035581610290565b811461036057600080fd5b50565b6000815190506103728161034c565b92915050565b60006020828403121561038e5761038d610232565b5b600061039c84828501610363565b9150509291505056fea264697066735822122035f391b5c3dbf9a5e31072167a4ada71e9ba762650849f6320bc6150fa45aa9564736f6c63430008110033")
require.NoError(t, err)
bytecode := getDummySmartContract(t) // this test requires custom proxy smart contract

{
// Step 0. Check the role of both accounts
Expand Down Expand Up @@ -152,9 +151,7 @@ func TestE2E_BlockList_ContractDeployment(t *testing.T) {

cluster.WaitForReady(t)

// bytecode for an empty smart contract
bytecode, err := hex.DecodeString("608060405234801561001057600080fd5b506103db806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635e9f13f81461003b578063d78bca6914610059575b600080fd5b610043610089565b6040516100509190610217565b60405180910390f35b610073600480360381019061006e9190610263565b6100a1565b60405161008091906102a9565b60405180910390f35b73020000000000000000000000000000000000000081565b600080600073020000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16846040516024016100e29190610217565b6040516020818303038152906040527fd78bca69000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161016c9190610335565b6000604051808303816000865af19150503d80600081146101a9576040519150601f19603f3d011682016040523d82523d6000602084013e6101ae565b606091505b50915091506000818060200190518101906101c99190610378565b9050809350505050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610201826101d6565b9050919050565b610211816101f6565b82525050565b600060208201905061022c6000830184610208565b92915050565b600080fd5b610240816101f6565b811461024b57600080fd5b50565b60008135905061025d81610237565b92915050565b60006020828403121561027957610278610232565b5b60006102878482850161024e565b91505092915050565b6000819050919050565b6102a381610290565b82525050565b60006020820190506102be600083018461029a565b92915050565b600081519050919050565b600081905092915050565b60005b838110156102f85780820151818401526020810190506102dd565b60008484015250505050565b600061030f826102c4565b61031981856102cf565b93506103298185602086016102da565b80840191505092915050565b60006103418284610304565b915081905092915050565b61035581610290565b811461036057600080fd5b50565b6000815190506103728161034c565b92915050565b60006020828403121561038e5761038d610232565b5b600061039c84828501610363565b9150509291505056fea264697066735822122035f391b5c3dbf9a5e31072167a4ada71e9ba762650849f6320bc6150fa45aa9564736f6c63430008110033")
require.NoError(t, err)
bytecode := contractsapi.TestSimple.Bytecode

{
// Step 0. Check the role of accounts
Expand Down Expand Up @@ -236,8 +233,7 @@ func TestE2E_AllowList_Transactions(t *testing.T) {

cluster.WaitForReady(t)

// bytecode for an empty smart contract
bytecode, _ := hex.DecodeString("608060405234801561001057600080fd5b506103db806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635e9f13f81461003b578063d78bca6914610059575b600080fd5b610043610089565b6040516100509190610217565b60405180910390f35b610073600480360381019061006e9190610263565b6100a1565b60405161008091906102a9565b60405180910390f35b73020000000000000000000000000000000000000081565b600080600073020000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16846040516024016100e29190610217565b6040516020818303038152906040527fd78bca69000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161016c9190610335565b6000604051808303816000865af19150503d80600081146101a9576040519150601f19603f3d011682016040523d82523d6000602084013e6101ae565b606091505b50915091506000818060200190518101906101c99190610378565b9050809350505050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610201826101d6565b9050919050565b610211816101f6565b82525050565b600060208201905061022c6000830184610208565b92915050565b600080fd5b610240816101f6565b811461024b57600080fd5b50565b60008135905061025d81610237565b92915050565b60006020828403121561027957610278610232565b5b60006102878482850161024e565b91505092915050565b6000819050919050565b6102a381610290565b82525050565b60006020820190506102be600083018461029a565b92915050565b600081519050919050565b600081905092915050565b60005b838110156102f85780820151818401526020810190506102dd565b60008484015250505050565b600061030f826102c4565b61031981856102cf565b93506103298185602086016102da565b80840191505092915050565b60006103418284610304565b915081905092915050565b61035581610290565b811461036057600080fd5b50565b6000815190506103728161034c565b92915050565b60006020828403121561038e5761038d610232565b5b600061039c84828501610363565b9150509291505056fea264697066735822122035f391b5c3dbf9a5e31072167a4ada71e9ba762650849f6320bc6150fa45aa9564736f6c63430008110033")
bytecode := contractsapi.TestSimple.Bytecode

{
// Step 0. Check the role of both accounts
Expand Down Expand Up @@ -413,3 +409,140 @@ func TestE2E_AddressLists_Bridge(t *testing.T) {
expectRole(t, cluster, contracts.BlockListBridgeAddr, otherAddr, addresslist.EnabledRole)
}
}

func TestE2E_AccessListsOwner(t *testing.T) {
owner, _ := wallet.GenerateKey()
admin, _ := wallet.GenerateKey()
rndUser, _ := wallet.GenerateKey()

adminAddr := types.Address(admin.Address())
rndUserAddress := types.Address(rndUser.Address())

cluster := framework.NewTestCluster(t, 5,
framework.WithNativeTokenConfig(fmt.Sprintf(nativeTokenMintableTestCfg, adminAddr)),
framework.WithPremine(adminAddr, rndUserAddress),
framework.WithAccessListsOwner(types.Address(owner.Address())),
)
defer cluster.Stop()

cluster.WaitForReady(t)

{
// Step 0. allow lists should be disabled
expectIsEnabled(t, cluster, contracts.AllowListTransactionsAddr, false)
expectIsEnabled(t, cluster, contracts.AllowListContractsAddr, false)
}

bytecode := contractsapi.TestSimple.Bytecode

{
// Step 1. anyone can send normal transaction or deploy smart contract because lists are disabled
tx := cluster.Transfer(t, admin, types.ZeroAddress, big.NewInt(1))
require.NoError(t, tx.Wait())
require.True(t, tx.Succeed())

tx = cluster.Deploy(t, rndUser, bytecode)
require.NoError(t, tx.Wait())
require.True(t, tx.Succeed())
require.True(t, cluster.ExistsCode(t, tx.Receipt().ContractAddress))
}

{
// Step 2. owner enables allow tx list and allow contracts list
input, _ := addresslist.SetListEnabledFunc.Encode([]interface{}{true})

saTxn := cluster.MethodTxn(t, owner, contracts.AllowListTransactionsAddr, input)
require.NoError(t, saTxn.Wait())
require.False(t, saTxn.Failed())

input, _ = addresslist.SetListEnabledFunc.Encode([]interface{}{true})

saTxn = cluster.MethodTxn(t, owner, contracts.AllowListContractsAddr, input)
require.NoError(t, saTxn.Wait())
require.False(t, saTxn.Failed())
}

{
// Step 3. future admin or rndUser can not send transaction or deploy smart contract because they are not in the lists yet
tx := cluster.Transfer(t, rndUser, types.ZeroAddress, big.NewInt(1))
require.NoError(t, tx.Wait())
require.False(t, tx.Succeed())

tx = cluster.Deploy(t, admin, bytecode)
require.NoError(t, tx.Wait())
require.False(t, tx.Succeed())
require.False(t, cluster.ExistsCode(t, tx.Receipt().ContractAddress))
}

{
// Step 4. add two roles by owner
input, _ := addresslist.SetAdminFunc.Encode([]interface{}{adminAddr})

saTxn := cluster.MethodTxn(t, owner, contracts.AllowListTransactionsAddr, input)
require.NoError(t, saTxn.Wait())
require.True(t, saTxn.Succeed())

input, _ = addresslist.SetEnabledFunc.Encode([]interface{}{rndUserAddress})

saTxn = cluster.MethodTxn(t, owner, contracts.AllowListContractsAddr, input)
require.NoError(t, saTxn.Wait())
require.True(t, saTxn.Succeed())

// add one more role by admin
input, _ = addresslist.SetEnabledFunc.Encode([]interface{}{rndUserAddress})

saTxn = cluster.MethodTxn(t, admin, contracts.AllowListTransactionsAddr, input)
require.NoError(t, saTxn.Wait())
require.True(t, saTxn.Succeed())

// adding role by normal user should fail
input, _ = addresslist.SetEnabledFunc.Encode([]interface{}{adminAddr})

saTxn = cluster.MethodTxn(t, rndUser, contracts.AllowListContractsAddr, input)
require.NoError(t, saTxn.Wait())
require.True(t, saTxn.Failed())

expectRole(t, cluster, contracts.AllowListTransactionsAddr, adminAddr, addresslist.AdminRole)
expectRole(t, cluster, contracts.AllowListContractsAddr, rndUserAddress, addresslist.EnabledRole)
expectRole(t, cluster, contracts.AllowListTransactionsAddr, rndUserAddress, addresslist.EnabledRole)
expectRole(t, cluster, contracts.AllowListContractsAddr, adminAddr, addresslist.NoRole)
}

{
// Step 4. admin address can send normal transaction, user can deploy contract...
tx := cluster.Transfer(t, admin, types.ZeroAddress, big.NewInt(1))
require.NoError(t, tx.Wait())
require.True(t, tx.Succeed())

tx = cluster.Deploy(t, rndUser, bytecode)
require.NoError(t, tx.Wait())
require.True(t, tx.Succeed())
require.True(t, cluster.ExistsCode(t, tx.Receipt().ContractAddress))

// ... but admin address is not in deployment allow list
tx = cluster.Deploy(t, admin, bytecode)
require.NoError(t, tx.Wait())
require.True(t, tx.Reverted())
require.False(t, cluster.ExistsCode(t, tx.Receipt().ContractAddress))

// remove rndUser address from tx allow list so that address can not send transactions anymore
input, _ := addresslist.SetNoneFunc.Encode([]interface{}{rndUserAddress})

saTxn := cluster.MethodTxn(t, admin, contracts.AllowListTransactionsAddr, input)
require.NoError(t, saTxn.Wait())
require.True(t, saTxn.Succeed())

tx = cluster.Transfer(t, rndUser, types.ZeroAddress, big.NewInt(1))
require.NoError(t, tx.Wait())
require.True(t, tx.Reverted())
}
}

func getDummySmartContract(t *testing.T) []byte {
t.Helper()

bytecode, err := hex.DecodeString("608060405234801561001057600080fd5b506103db806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635e9f13f81461003b578063d78bca6914610059575b600080fd5b610043610089565b6040516100509190610217565b60405180910390f35b610073600480360381019061006e9190610263565b6100a1565b60405161008091906102a9565b60405180910390f35b73020000000000000000000000000000000000000081565b600080600073020000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16846040516024016100e29190610217565b6040516020818303038152906040527fd78bca69000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161016c9190610335565b6000604051808303816000865af19150503d80600081146101a9576040519150601f19603f3d011682016040523d82523d6000602084013e6101ae565b606091505b50915091506000818060200190518101906101c99190610378565b9050809350505050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610201826101d6565b9050919050565b610211816101f6565b82525050565b600060208201905061022c6000830184610208565b92915050565b600080fd5b610240816101f6565b811461024b57600080fd5b50565b60008135905061025d81610237565b92915050565b60006020828403121561027957610278610232565b5b60006102878482850161024e565b91505092915050565b6000819050919050565b6102a381610290565b82525050565b60006020820190506102be600083018461029a565b92915050565b600081519050919050565b600081905092915050565b60005b838110156102f85780820151818401526020810190506102dd565b60008484015250505050565b600061030f826102c4565b61031981856102cf565b93506103298185602086016102da565b80840191505092915050565b60006103418284610304565b915081905092915050565b61035581610290565b811461036057600080fd5b50565b6000815190506103728161034c565b92915050565b60006020828403121561038e5761038d610232565b5b600061039c84828501610363565b9150509291505056fea264697066735822122035f391b5c3dbf9a5e31072167a4ada71e9ba762650849f6320bc6150fa45aa9564736f6c63430008110033")
require.NoError(t, err)

return bytecode
}
14 changes: 11 additions & 3 deletions e2e-polybft/e2e/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,21 @@ func expectRole(t *testing.T, cluster *framework.TestCluster, contract types.Add
out := cluster.Call(t, contract, addresslist.ReadAddressListFunc, addr)

num, ok := out["0"].(*big.Int)
if !ok {
t.Fatal("unexpected")
}

require.True(t, ok)
require.Equal(t, role.Uint64(), num.Uint64())
}

func expectIsEnabled(t *testing.T, cluster *framework.TestCluster, contract types.Address, value bool) {
t.Helper()
out := cluster.Call(t, contract, addresslist.GetListEnabledFunc)

num, ok := out["0"].(bool)

require.True(t, ok)
require.Equal(t, value, num)
}

// getFilteredLogs retrieves Ethereum logs, described by event signature within the block range
func getFilteredLogs(eventSig ethgo.Hash, startBlock, endBlock uint64,
ethEndpoint *jsonrpc.Eth) ([]*ethgo.Log, error) {
Expand Down
11 changes: 11 additions & 0 deletions e2e-polybft/framework/test-cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type TestClusterConfig struct {
NativeTokenConfigRaw string
SecretsCallback func([]types.Address, *TestClusterConfig)

AccessListsOwner *types.Address
ContractDeployerAllowListAdmin []types.Address
ContractDeployerAllowListEnabled []types.Address
ContractDeployerBlockListAdmin []types.Address
Expand Down Expand Up @@ -266,6 +267,12 @@ func WithNumBlockConfirmations(numBlockConfirmations uint64) ClusterOption {
}
}

func WithAccessListsOwner(addr types.Address) ClusterOption {
return func(h *TestClusterConfig) {
h.AccessListsOwner = &addr
}
}

func WithContractDeployerAllowListAdmin(addr types.Address) ClusterOption {
return func(h *TestClusterConfig) {
h.ContractDeployerAllowListAdmin = append(h.ContractDeployerAllowListAdmin, addr)
Expand Down Expand Up @@ -489,6 +496,10 @@ func NewTestCluster(t *testing.T, validatorsCount int, opts ...ClusterOption) *T
}
}

if cluster.Config.AccessListsOwner != nil {
args = append(args, "--access-lists-owner", cluster.Config.AccessListsOwner.String())
}

if len(cluster.Config.ContractDeployerAllowListAdmin) != 0 {
args = append(args, "--contract-deployer-allow-list-admin",
strings.Join(sliceAddressToSliceString(cluster.Config.ContractDeployerAllowListAdmin), ","))
Expand Down
Loading

0 comments on commit 85af1b3

Please sign in to comment.