github.com/MetalBlockchain/subnet-evm@v0.4.9/precompile/allow_list.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package precompile
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/MetalBlockchain/subnet-evm/vmerrs"
    11  	"github.com/ethereum/go-ethereum/common"
    12  )
    13  
    14  const (
    15  	SetAdminFuncKey      = "setAdmin"
    16  	SetEnabledFuncKey    = "setEnabled"
    17  	SetNoneFuncKey       = "setNone"
    18  	ReadAllowListFuncKey = "readAllowList"
    19  
    20  	ModifyAllowListGasCost = writeGasCostPerSlot
    21  	ReadAllowListGasCost   = readGasCostPerSlot
    22  )
    23  
    24  var (
    25  	// No role assigned - this is equivalent to common.Hash{} and deletes the key from the DB when set
    26  	AllowListNoRole = AllowListRole(common.BigToHash(common.Big0))
    27  	// Enabled - allowed to use state-changing precompile functions without modifying status of other admins or enableds
    28  	AllowListEnabled = AllowListRole(common.BigToHash(common.Big1))
    29  	// Admin - allowed to modify both the admin and enabled list, as well as to use state-changing precompile functions
    30  	AllowListAdmin = AllowListRole(common.BigToHash(common.Big2))
    31  
    32  	// AllowList function signatures
    33  	setAdminSignature      = CalculateFunctionSelector("setAdmin(address)")
    34  	setEnabledSignature    = CalculateFunctionSelector("setEnabled(address)")
    35  	setNoneSignature       = CalculateFunctionSelector("setNone(address)")
    36  	readAllowListSignature = CalculateFunctionSelector("readAllowList(address)")
    37  	// Error returned when an invalid write is attempted
    38  	ErrCannotModifyAllowList = errors.New("non-admin cannot modify allow list")
    39  
    40  	allowListInputLen = common.HashLength
    41  )
    42  
    43  // AllowListConfig specifies the initial set of allow list admins.
    44  type AllowListConfig struct {
    45  	AllowListAdmins  []common.Address `json:"adminAddresses"`
    46  	EnabledAddresses []common.Address `json:"enabledAddresses"` // initial enabled addresses
    47  }
    48  
    49  // Configure initializes the address space of [precompileAddr] by initializing the role of each of
    50  // the addresses in [AllowListAdmins].
    51  func (c *AllowListConfig) Configure(state StateDB, precompileAddr common.Address) {
    52  	for _, enabledAddr := range c.EnabledAddresses {
    53  		setAllowListRole(state, precompileAddr, enabledAddr, AllowListEnabled)
    54  	}
    55  	for _, adminAddr := range c.AllowListAdmins {
    56  		setAllowListRole(state, precompileAddr, adminAddr, AllowListAdmin)
    57  	}
    58  }
    59  
    60  // Equal returns true iff [other] has the same admins in the same order in its allow list.
    61  func (c *AllowListConfig) Equal(other *AllowListConfig) bool {
    62  	if other == nil {
    63  		return false
    64  	}
    65  	if !areEqualAddressLists(c.AllowListAdmins, other.AllowListAdmins) {
    66  		return false
    67  	}
    68  
    69  	return areEqualAddressLists(c.EnabledAddresses, other.EnabledAddresses)
    70  }
    71  
    72  // areEqualAddressLists returns true iff [a] and [b] have the same addresses in the same order.
    73  func areEqualAddressLists(current []common.Address, other []common.Address) bool {
    74  	if len(current) != len(other) {
    75  		return false
    76  	}
    77  	for i, address := range current {
    78  		if address != other[i] {
    79  			return false
    80  		}
    81  	}
    82  	return true
    83  }
    84  
    85  // Verify returns an error if there is an overlapping address between admin and enabled roles
    86  func (c *AllowListConfig) Verify() error {
    87  	// return early if either list is empty
    88  	if len(c.EnabledAddresses) == 0 || len(c.AllowListAdmins) == 0 {
    89  		return nil
    90  	}
    91  
    92  	addressMap := make(map[common.Address]bool)
    93  	for _, enabledAddr := range c.EnabledAddresses {
    94  		// check for duplicates
    95  		if _, ok := addressMap[enabledAddr]; ok {
    96  			return fmt.Errorf("duplicate address %s in enabled list", enabledAddr)
    97  		}
    98  		addressMap[enabledAddr] = false
    99  	}
   100  
   101  	for _, adminAddr := range c.AllowListAdmins {
   102  		// check for overlap between enabled and admin lists
   103  		if inAdmin, ok := addressMap[adminAddr]; ok {
   104  			if inAdmin {
   105  				return fmt.Errorf("duplicate address %s in admin list", adminAddr)
   106  			} else {
   107  				return fmt.Errorf("cannot set address %s as both admin and enabled", adminAddr)
   108  			}
   109  		}
   110  		addressMap[adminAddr] = true
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  // getAllowListStatus returns the allow list role of [address] for the precompile
   117  // at [precompileAddr]
   118  func getAllowListStatus(state StateDB, precompileAddr common.Address, address common.Address) AllowListRole {
   119  	// Generate the state key for [address]
   120  	addressKey := address.Hash()
   121  	return AllowListRole(state.GetState(precompileAddr, addressKey))
   122  }
   123  
   124  // setAllowListRole sets the permissions of [address] to [role] for the precompile
   125  // at [precompileAddr].
   126  // assumes [role] has already been verified as valid.
   127  func setAllowListRole(stateDB StateDB, precompileAddr, address common.Address, role AllowListRole) {
   128  	// Generate the state key for [address]
   129  	addressKey := address.Hash()
   130  	// Assign [role] to the address
   131  	// This stores the [role] in the contract storage with address [precompileAddr]
   132  	// and [addressKey] hash. It means that any reusage of the [addressKey] for different value
   133  	// conflicts with the same slot [role] is stored.
   134  	// Precompile implementations must use a different key than [addressKey]
   135  	stateDB.SetState(precompileAddr, addressKey, common.Hash(role))
   136  }
   137  
   138  // PackModifyAllowList packs [address] and [role] into the appropriate arguments for modifying the allow list.
   139  // Note: [role] is not packed in the input value returned, but is instead used as a selector for the function
   140  // selector that should be encoded in the input.
   141  func PackModifyAllowList(address common.Address, role AllowListRole) ([]byte, error) {
   142  	// function selector (4 bytes) + hash for address
   143  	input := make([]byte, 0, selectorLen+common.HashLength)
   144  
   145  	switch role {
   146  	case AllowListAdmin:
   147  		input = append(input, setAdminSignature...)
   148  	case AllowListEnabled:
   149  		input = append(input, setEnabledSignature...)
   150  	case AllowListNoRole:
   151  		input = append(input, setNoneSignature...)
   152  	default:
   153  		return nil, fmt.Errorf("cannot pack modify list input with invalid role: %s", role)
   154  	}
   155  
   156  	input = append(input, address.Hash().Bytes()...)
   157  	return input, nil
   158  }
   159  
   160  // PackReadAllowList packs [address] into the input data to the read allow list function
   161  func PackReadAllowList(address common.Address) []byte {
   162  	input := make([]byte, 0, selectorLen+common.HashLength)
   163  	input = append(input, readAllowListSignature...)
   164  	input = append(input, address.Hash().Bytes()...)
   165  	return input
   166  }
   167  
   168  // createAllowListRoleSetter returns an execution function for setting the allow list status of the input address argument to [role].
   169  // This execution function is speciifc to [precompileAddr].
   170  func createAllowListRoleSetter(precompileAddr common.Address, role AllowListRole) RunStatefulPrecompileFunc {
   171  	return func(evm PrecompileAccessibleState, callerAddr, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
   172  		if remainingGas, err = deductGas(suppliedGas, ModifyAllowListGasCost); err != nil {
   173  			return nil, 0, err
   174  		}
   175  
   176  		if len(input) != allowListInputLen {
   177  			return nil, remainingGas, fmt.Errorf("invalid input length for modifying allow list: %d", len(input))
   178  		}
   179  
   180  		modifyAddress := common.BytesToAddress(input)
   181  
   182  		if readOnly {
   183  			return nil, remainingGas, vmerrs.ErrWriteProtection
   184  		}
   185  
   186  		stateDB := evm.GetStateDB()
   187  
   188  		// Verify that the caller is in the allow list and therefore has the right to modify it
   189  		callerStatus := getAllowListStatus(stateDB, precompileAddr, callerAddr)
   190  		if !callerStatus.IsAdmin() {
   191  			return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotModifyAllowList, callerAddr)
   192  		}
   193  
   194  		setAllowListRole(stateDB, precompileAddr, modifyAddress, role)
   195  		// Return an empty output and the remaining gas
   196  		return []byte{}, remainingGas, nil
   197  	}
   198  }
   199  
   200  // createReadAllowList returns an execution function that reads the allow list for the given [precompileAddr].
   201  // The execution function parses the input into a single address and returns the 32 byte hash that specifies the
   202  // designated role of that address
   203  func createReadAllowList(precompileAddr common.Address) RunStatefulPrecompileFunc {
   204  	return func(evm PrecompileAccessibleState, callerAddr common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
   205  		if remainingGas, err = deductGas(suppliedGas, ReadAllowListGasCost); err != nil {
   206  			return nil, 0, err
   207  		}
   208  
   209  		if len(input) != allowListInputLen {
   210  			return nil, remainingGas, fmt.Errorf("invalid input length for read allow list: %d", len(input))
   211  		}
   212  
   213  		readAddress := common.BytesToAddress(input)
   214  		role := getAllowListStatus(evm.GetStateDB(), precompileAddr, readAddress)
   215  		roleBytes := common.Hash(role).Bytes()
   216  		return roleBytes, remainingGas, nil
   217  	}
   218  }
   219  
   220  // createAllowListPrecompile returns a StatefulPrecompiledContract with R/W control of an allow list at [precompileAddr]
   221  func createAllowListPrecompile(precompileAddr common.Address) StatefulPrecompiledContract {
   222  	// Construct the contract with no fallback function.
   223  	allowListFuncs := createAllowListFunctions(precompileAddr)
   224  	contract := newStatefulPrecompileWithFunctionSelectors(nil, allowListFuncs)
   225  	return contract
   226  }
   227  
   228  func createAllowListFunctions(precompileAddr common.Address) []*statefulPrecompileFunction {
   229  	setAdmin := newStatefulPrecompileFunction(setAdminSignature, createAllowListRoleSetter(precompileAddr, AllowListAdmin))
   230  	setEnabled := newStatefulPrecompileFunction(setEnabledSignature, createAllowListRoleSetter(precompileAddr, AllowListEnabled))
   231  	setNone := newStatefulPrecompileFunction(setNoneSignature, createAllowListRoleSetter(precompileAddr, AllowListNoRole))
   232  	read := newStatefulPrecompileFunction(readAllowListSignature, createReadAllowList(precompileAddr))
   233  
   234  	return []*statefulPrecompileFunction{setAdmin, setEnabled, setNone, read}
   235  }