mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
211 lines
8.4 KiB
Go
211 lines
8.4 KiB
Go
// (c) 2020-2023, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package allowlist
|
|
|
|
import (
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"github.com/luxfi/evm/precompile/contract"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/evm/core/types"
|
|
"github.com/luxfi/evm/vmerrs"
|
|
)
|
|
|
|
// AllowList is an abstraction that allows other precompiles to manage
|
|
// which addresses can call the precompile by maintaining an allowlist
|
|
// in the storage trie.
|
|
|
|
const (
|
|
ModifyAllowListGasCost = contract.WriteGasCostPerSlot
|
|
ReadAllowListGasCost = contract.ReadGasCostPerSlot
|
|
|
|
allowListInputLen = common.HashLength
|
|
)
|
|
|
|
var (
|
|
// Error returned when an invalid write is attempted
|
|
ErrCannotModifyAllowList = errors.New("cannot modify allow list")
|
|
|
|
// AllowListRawABI contains the raw ABI of AllowList library interface.
|
|
//go:embed allowlist.abi
|
|
AllowListRawABI string
|
|
|
|
AllowListABI = contract.ParseABI(AllowListRawABI)
|
|
)
|
|
|
|
// GetAllowListStatus returns the allow list role of [address] for the precompile
|
|
// at [precompileAddr]
|
|
func GetAllowListStatus(state contract.StateReader, precompileAddr common.Address, address common.Address) Role {
|
|
// Generate the state key for [address]
|
|
addressKey := common.BytesToHash(address.Bytes())
|
|
return Role(state.GetState(precompileAddr, addressKey))
|
|
}
|
|
|
|
// SetAllowListRole sets the permissions of [address] to [role] for the precompile
|
|
// at [precompileAddr].
|
|
// assumes [role] has already been verified as valid.
|
|
func SetAllowListRole(stateDB contract.StateDB, precompileAddr, address common.Address, role Role) {
|
|
// Generate the state key for [address]
|
|
addressKey := common.BytesToHash(address.Bytes())
|
|
// Assign [role] to the address
|
|
// This stores the [role] in the contract storage with address [precompileAddr]
|
|
// and [addressKey] hash. It means that any reusage of the [addressKey] for different value
|
|
// conflicts with the same slot [role] is stored.
|
|
// Precompile implementations must use a different key than [addressKey]
|
|
stateDB.SetState(precompileAddr, addressKey, role.Hash())
|
|
}
|
|
|
|
func PackModifyAllowList(address common.Address, role Role) ([]byte, error) {
|
|
funcName, err := role.GetSetterFunctionName()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return AllowListABI.Pack(funcName, address)
|
|
}
|
|
|
|
func UnpackModifyAllowListInput(input []byte, r Role, useStrictMode bool) (common.Address, error) {
|
|
if useStrictMode && len(input) != allowListInputLen {
|
|
return common.Address{}, fmt.Errorf("invalid input length for modifying allow list: %d", len(input))
|
|
}
|
|
|
|
funcName, err := r.GetSetterFunctionName()
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
var modifyAddress common.Address
|
|
err = AllowListABI.UnpackInputIntoInterface(&modifyAddress, funcName, input, useStrictMode)
|
|
return modifyAddress, err
|
|
}
|
|
|
|
// createAllowListRoleSetter returns an execution function for setting the allow list status of the input address argument to [role].
|
|
// This execution function is specific to [precompileAddr].
|
|
func createAllowListRoleSetter(precompileAddr common.Address, role Role) contract.RunStatefulPrecompileFunc {
|
|
return func(evm contract.AccessibleState, callerAddr, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
|
|
if remainingGas, err = contract.DeductGas(suppliedGas, ModifyAllowListGasCost); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// do not use strict mode after Durango
|
|
useStrictMode := !contract.IsDurangoActivated(evm)
|
|
modifyAddress, err := UnpackModifyAllowListInput(input, role, useStrictMode)
|
|
|
|
if err != nil {
|
|
return nil, remainingGas, err
|
|
}
|
|
|
|
if readOnly {
|
|
return nil, remainingGas, vmerrs.ErrWriteProtection
|
|
}
|
|
|
|
stateDB := evm.GetStateDB()
|
|
|
|
// Verify that the caller is an admin with permission to modify the allow list
|
|
callerStatus := GetAllowListStatus(stateDB, precompileAddr, callerAddr)
|
|
// Verify that the address we are trying to modify has a status that allows it to be modified
|
|
modifyStatus := GetAllowListStatus(stateDB, precompileAddr, modifyAddress)
|
|
if !callerStatus.CanModify(modifyStatus, role) {
|
|
return nil, remainingGas, fmt.Errorf("%w: modify address: %s, from role: %s, to role: %s", ErrCannotModifyAllowList, callerAddr, modifyStatus, role)
|
|
}
|
|
if contract.IsDurangoActivated(evm) {
|
|
if remainingGas, err = contract.DeductGas(remainingGas, AllowListEventGasCost); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
topics, data, err := PackRoleSetEvent(role, modifyAddress, callerAddr, modifyStatus)
|
|
if err != nil {
|
|
return nil, remainingGas, err
|
|
}
|
|
stateDB.AddLog(&types.Log{
|
|
Address: precompileAddr,
|
|
Topics: topics,
|
|
Data: data,
|
|
BlockNumber: evm.GetBlockContext().Number().Uint64(),
|
|
})
|
|
}
|
|
|
|
SetAllowListRole(stateDB, precompileAddr, modifyAddress, role)
|
|
|
|
return []byte{}, remainingGas, nil
|
|
}
|
|
}
|
|
|
|
// PackReadAllowList packs [address] into the input data to the read allow list function
|
|
func PackReadAllowList(address common.Address) ([]byte, error) {
|
|
return AllowListABI.Pack("readAllowList", address)
|
|
}
|
|
|
|
func UnpackReadAllowListInput(input []byte, useStrictMode bool) (common.Address, error) {
|
|
if useStrictMode && len(input) != allowListInputLen {
|
|
return common.Address{}, fmt.Errorf("invalid input length for read allow list: %d", len(input))
|
|
}
|
|
|
|
var modifyAddress common.Address
|
|
err := AllowListABI.UnpackInputIntoInterface(&modifyAddress, "readAllowList", input, useStrictMode)
|
|
return modifyAddress, err
|
|
}
|
|
|
|
func PackReadAllowListOutput(roleNumber *big.Int) ([]byte, error) {
|
|
return AllowListABI.PackOutput("readAllowList", roleNumber)
|
|
}
|
|
|
|
// createReadAllowList returns an execution function that reads the allow list for the given [precompileAddr].
|
|
// The execution function parses the input into a single address and returns the 32 byte hash that specifies the
|
|
// designated role of that address
|
|
func createReadAllowList(precompileAddr common.Address) contract.RunStatefulPrecompileFunc {
|
|
return func(evm contract.AccessibleState, callerAddr common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
|
|
if remainingGas, err = contract.DeductGas(suppliedGas, ReadAllowListGasCost); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// We skip the fixed length check with Durango
|
|
useStrictMode := !contract.IsDurangoActivated(evm)
|
|
readAddress, err := UnpackReadAllowListInput(input, useStrictMode)
|
|
if err != nil {
|
|
return nil, remainingGas, err
|
|
}
|
|
|
|
role := GetAllowListStatus(evm.GetStateDB(), precompileAddr, readAddress)
|
|
packedOutput, err := PackReadAllowListOutput(role.Big())
|
|
if err != nil {
|
|
return nil, remainingGas, err
|
|
}
|
|
return packedOutput, remainingGas, nil
|
|
}
|
|
}
|
|
|
|
// CreateAllowListPrecompile returns a StatefulPrecompiledContract with R/W control of an allow list at [precompileAddr]
|
|
func CreateAllowListPrecompile(precompileAddr common.Address) contract.StatefulPrecompiledContract {
|
|
// Construct the contract with no fallback function.
|
|
allowListFuncs := CreateAllowListFunctions(precompileAddr)
|
|
contract, err := contract.NewStatefulPrecompileContract(nil, allowListFuncs)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return contract
|
|
}
|
|
|
|
func CreateAllowListFunctions(precompileAddr common.Address) []*contract.StatefulPrecompileFunction {
|
|
var functions []*contract.StatefulPrecompileFunction
|
|
|
|
for name, method := range AllowListABI.Methods {
|
|
var fn *contract.StatefulPrecompileFunction
|
|
if name == "readAllowList" {
|
|
fn = contract.NewStatefulPrecompileFunction(method.ID, createReadAllowList(precompileAddr))
|
|
} else if adminFnName, _ := AdminRole.GetSetterFunctionName(); name == adminFnName {
|
|
fn = contract.NewStatefulPrecompileFunction(method.ID, createAllowListRoleSetter(precompileAddr, AdminRole))
|
|
} else if enabledFnName, _ := EnabledRole.GetSetterFunctionName(); name == enabledFnName {
|
|
fn = contract.NewStatefulPrecompileFunction(method.ID, createAllowListRoleSetter(precompileAddr, EnabledRole))
|
|
} else if noRoleFnName, _ := NoRole.GetSetterFunctionName(); name == noRoleFnName {
|
|
fn = contract.NewStatefulPrecompileFunction(method.ID, createAllowListRoleSetter(precompileAddr, NoRole))
|
|
} else if managerFnName, _ := ManagerRole.GetSetterFunctionName(); name == managerFnName {
|
|
fn = contract.NewStatefulPrecompileFunctionWithActivator(method.ID, createAllowListRoleSetter(precompileAddr, ManagerRole), contract.IsDurangoActivated)
|
|
} else {
|
|
panic(fmt.Sprintf("unexpected method name: %s", name))
|
|
}
|
|
functions = append(functions, fn)
|
|
}
|
|
return functions
|
|
}
|