github.com/dominant-strategies/go-quai@v0.28.2/core/vm/operations_acl.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package vm 18 19 import ( 20 "errors" 21 22 "github.com/dominant-strategies/go-quai/common" 23 "github.com/dominant-strategies/go-quai/common/math" 24 "github.com/dominant-strategies/go-quai/params" 25 ) 26 27 func makeGasSStoreFunc(clearingRefund uint64) gasFunc { 28 return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { 29 // If we fail the minimum gas availability invariant, fail (0) 30 if contract.Gas <= params.SstoreSentryGas { 31 return 0, errors.New("not enough gas for reentrancy sentry") 32 } 33 // Gas sentry honoured, do the actual gas calculation based on the stored value 34 var ( 35 y, x = stack.Back(1), stack.peek() 36 slot = common.Hash(x.Bytes32()) 37 cost = uint64(0) 38 internalAddr, err = contract.Address().InternalAddress() 39 ) 40 if err != nil { 41 return 0, err 42 } 43 current := evm.StateDB.GetState(internalAddr, slot) 44 // Check slot presence in the access list 45 if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { 46 cost = params.ColdSloadCost 47 // If the caller cannot afford the cost, this change will be rolled back 48 evm.StateDB.AddSlotToAccessList(contract.Address(), slot) 49 if !addrPresent { 50 // Once we're done with YOLOv2 and schedule this for mainnet, might 51 // be good to remove this panic here, which is just really a 52 // canary to have during testing 53 panic("impossible case: address was not present in access list during sstore op") 54 } 55 } 56 value := common.Hash(y.Bytes32()) 57 58 if current == value { // noop (1) 59 return cost + params.WarmStorageReadCost, nil // SLOAD_GAS 60 } 61 original := evm.StateDB.GetCommittedState(internalAddr, x.Bytes32()) 62 if original == current { 63 if original == (common.Hash{}) { // create slot (2.1.1) 64 return cost + params.SstoreSetGas, nil 65 } 66 if value == (common.Hash{}) { // delete slot (2.1.2b) 67 evm.StateDB.AddRefund(clearingRefund) 68 } 69 return cost + (params.SstoreResetGas - params.ColdSloadCost), nil // write existing slot (2.1.2) 70 } 71 if original != (common.Hash{}) { 72 if current == (common.Hash{}) { // recreate slot (2.2.1.1) 73 evm.StateDB.SubRefund(clearingRefund) 74 } else if value == (common.Hash{}) { // delete slot (2.2.1.2) 75 evm.StateDB.AddRefund(clearingRefund) 76 } 77 } 78 if original == value { 79 if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) 80 evm.StateDB.AddRefund(params.SstoreSetGas - params.WarmStorageReadCost) 81 } else { // reset to original existing slot (2.2.2.2) 82 evm.StateDB.AddRefund((params.SstoreResetGas - params.ColdSloadCost) - params.WarmStorageReadCost) 83 } 84 } 85 return cost + params.WarmStorageReadCost, nil // dirty update (2.2) 86 } 87 } 88 89 // gasSLoad calculates dynamic gas for SLOAD 90 // For SLOAD, if the (address, storage_key) pair (where address is the address of the contract 91 // whose storage is being read) is not yet in accessed_storage_keys, 92 // charge 2100 gas and add the pair to accessed_storage_keys. 93 // If the pair is already in accessed_storage_keys, charge 100 gas. 94 func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { 95 loc := stack.peek() 96 slot := common.Hash(loc.Bytes32()) 97 // Check slot presence in the access list 98 if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { 99 // If the caller cannot afford the cost, this change will be rolled back 100 // If he does afford it, we can skip checking the same thing later on, during execution 101 evm.StateDB.AddSlotToAccessList(contract.Address(), slot) 102 return params.ColdSloadCost, nil 103 } 104 return params.WarmStorageReadCost, nil 105 } 106 107 // gasExtCodeCopy implements extcodecopy gas calculation 108 // > If the target is not in accessed_addresses, 109 // > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses. 110 // > Otherwise, charge WARM_STORAGE_READ_COST gas. 111 func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { 112 // memory expansion first 113 gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) 114 if err != nil { 115 return 0, err 116 } 117 addr := common.Bytes20ToAddress(stack.peek().Bytes20()) 118 // Check slot presence in the access list 119 if !evm.StateDB.AddressInAccessList(addr) { 120 evm.StateDB.AddAddressToAccessList(addr) 121 var overflow bool 122 // We charge (cold-warm), since 'warm' is already charged as constantGas 123 if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCost-params.WarmStorageReadCost); overflow { 124 return 0, ErrGasUintOverflow 125 } 126 return gas, nil 127 } 128 return gas, nil 129 } 130 131 // gasAccountCheck checks whether the first stack item (as address) is present in the access list. 132 // If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it 133 // is also using 'warm' as constant factor. 134 // This method is used by: 135 // - extcodehash, 136 // - extcodesize, 137 // - (ext) balance 138 func gasAccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { 139 addr := common.Bytes20ToAddress(stack.peek().Bytes20()) 140 // Check slot presence in the access list 141 if !evm.StateDB.AddressInAccessList(addr) { 142 // If the caller cannot afford the cost, this change will be rolled back 143 evm.StateDB.AddAddressToAccessList(addr) 144 // The warm storage read cost is already charged as constantGas 145 return params.ColdAccountAccessCost - params.WarmStorageReadCost, nil 146 } 147 return 0, nil 148 } 149 150 func makeCallVariantGasCall(oldCalculator gasFunc) gasFunc { 151 return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { 152 addr := common.Bytes20ToAddress(stack.Back(1).Bytes20()) 153 // Check slot presence in the access list 154 warmAccess := evm.StateDB.AddressInAccessList(addr) 155 // The WarmStorageReadCost (100) is already deducted in the form of a constant cost, so 156 // the cost to charge for cold access, if any, is Cold - Warm 157 coldCost := params.ColdAccountAccessCost - params.WarmStorageReadCost 158 if !warmAccess { 159 evm.StateDB.AddAddressToAccessList(addr) 160 // Charge the remaining difference here already, to correctly calculate available 161 // gas for call 162 if !contract.UseGas(coldCost) { 163 return 0, ErrOutOfGas 164 } 165 } 166 // Now call the old calculator, which takes into account 167 // - create new account 168 // - transfer value 169 // - memory expansion 170 // - 63/64ths rule 171 gas, err := oldCalculator(evm, contract, stack, mem, memorySize) 172 if warmAccess || err != nil { 173 return gas, err 174 } 175 // In case of a cold access, we temporarily add the cold charge back, and also 176 // add it to the returned gas. By adding it to the return, it will be charged 177 // outside of this function, as part of the dynamic gas, and that will make it 178 // also become correctly reported to tracers. 179 contract.Gas += coldCost 180 return gas + coldCost, nil 181 } 182 } 183 184 var ( 185 gasCallVariant = makeCallVariantGasCall(gasCall) 186 gasDelegateCallVariant = makeCallVariantGasCall(gasDelegateCall) 187 gasStaticCallVariant = makeCallVariantGasCall(gasStaticCall) 188 gasCallCodeVariant = makeCallVariantGasCall(gasCallCode) 189 // gasSelfdestructVariant implements self destruct with no refunds 190 gasSelfdestructVariant = makeSelfdestructGasFn(false) 191 192 // gasSStoreVariant implements gas cost for SSTORE 193 // Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800) 194 gasSStoreVariant = makeGasSStoreFunc(params.SstoreClearsScheduleRefund) 195 ) 196 197 // makeSelfdestructGasFn can create the selfdestruct dynamic gas function 198 func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { 199 gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { 200 var ( 201 gas uint64 202 address = common.Bytes20ToAddress(stack.peek().Bytes20()) 203 internalAddress, err = address.InternalAddress() 204 ) 205 if err != nil { 206 return 0, err 207 } 208 contractAddress, err := contract.Address().InternalAddress() 209 if err != nil { 210 return 0, err 211 } 212 if !evm.StateDB.AddressInAccessList(address) { 213 // If the caller cannot afford the cost, this change will be rolled back 214 evm.StateDB.AddAddressToAccessList(address) 215 gas = params.ColdAccountAccessCost 216 } 217 // if empty and transfers value 218 if evm.StateDB.Empty(internalAddress) && evm.StateDB.GetBalance(contractAddress).Sign() != 0 { 219 gas += params.CreateBySelfdestructGas 220 } 221 if refundsEnabled && !evm.StateDB.HasSuicided(contractAddress) { 222 evm.StateDB.AddRefund(params.SelfdestructRefundGas) 223 } 224 return gas, nil 225 } 226 return gasFunc 227 }