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 }