From cefa1e0fb5e56e8a47dfa7cd9a0f125753616171 Mon Sep 17 00:00:00 2001 From: Petar Ivanov <29689712+dartdart26@users.noreply.github.com> Date: Wed, 23 Nov 2022 12:59:06 +0200 Subject: [PATCH] Add in-memory state and restrict SLOAD/SSTORE --- core/vm/contracts.go | 10 ++++++++++ core/vm/contracts_stateful.go | 20 ++++++++++++++++++++ core/vm/evm.go | 19 ++++++++++++++++++- core/vm/instructions.go | 17 +++++++++++++++-- 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 8fe99bdcc56a9..774a77a4ff8d6 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -48,6 +48,8 @@ var PrecompiledContractsHomestead = map[common.Address]StatefulPrecompiledContra common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), common.BytesToAddress([]byte{19}): &fheAdd{}, + common.BytesToAddress([]byte{20}): &setMemoryState{}, + common.BytesToAddress([]byte{21}): &getMemoryState{}, } // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum @@ -62,6 +64,8 @@ var PrecompiledContractsByzantium = map[common.Address]StatefulPrecompiledContra common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulByzantium{}), common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingByzantium{}), common.BytesToAddress([]byte{19}): &fheAdd{}, + common.BytesToAddress([]byte{20}): &setMemoryState{}, + common.BytesToAddress([]byte{21}): &getMemoryState{}, } // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum @@ -77,6 +81,8 @@ var PrecompiledContractsIstanbul = map[common.Address]StatefulPrecompiledContrac common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), common.BytesToAddress([]byte{19}): &fheAdd{}, + common.BytesToAddress([]byte{20}): &setMemoryState{}, + common.BytesToAddress([]byte{21}): &getMemoryState{}, } // PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum @@ -92,6 +98,8 @@ var PrecompiledContractsBerlin = map[common.Address]StatefulPrecompiledContract{ common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), common.BytesToAddress([]byte{19}): &fheAdd{}, + common.BytesToAddress([]byte{20}): &setMemoryState{}, + common.BytesToAddress([]byte{21}): &getMemoryState{}, } // PrecompiledContractsBLS contains the set of pre-compiled Ethereum @@ -107,6 +115,8 @@ var PrecompiledContractsBLS = map[common.Address]StatefulPrecompiledContract{ common.BytesToAddress([]byte{17}): newWrappedPrecompiledContract(&bls12381MapG1{}), common.BytesToAddress([]byte{18}): newWrappedPrecompiledContract(&bls12381MapG2{}), common.BytesToAddress([]byte{19}): &fheAdd{}, + common.BytesToAddress([]byte{20}): &setMemoryState{}, + common.BytesToAddress([]byte{21}): &getMemoryState{}, } var ( diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go index ee6c14fb28361..af1cea2b733e9 100644 --- a/core/vm/contracts_stateful.go +++ b/core/vm/contracts_stateful.go @@ -62,3 +62,23 @@ func (e *fheAdd) Run(accessibleState PrecompileAccessibleState, caller common.Ad return input, suppliedGas, nil } + +type setMemoryState struct{} + +func (c *setMemoryState) RequiredGas(input []byte) uint64 { + return 1 +} + +func (c *setMemoryState) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + return []byte{}, suppliedGas, nil +} + +type getMemoryState struct{} + +func (c *getMemoryState) RequiredGas(input []byte) uint64 { + return 1 +} + +func (c *getMemoryState) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + return []byte{}, suppliedGas, nil +} diff --git a/core/vm/evm.go b/core/vm/evm.go index b17871cb6b8c3..2efc1689ba43a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -121,6 +121,8 @@ type EVM struct { // available gas is calculated in gasCall* according to the 63/64 rule and later // applied in opCall*. callGasTemp uint64 + // some arbitrary in-memory state that lives as long as the enclosing EVM + inMemoryState []byte } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -143,6 +145,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { evm.TxContext = txCtx evm.StateDB = statedb + evm.inMemoryState = []byte{} } // Cancel cancels any running EVM operation. This may be called concurrently and @@ -222,6 +225,11 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } if isPrecompile { + // If it is the set memory state call, set the input to the EVM's in-memory state. + // Return whatever the precompiled contract returns. + if addr == common.BytesToAddress([]byte{20}) { + evm.inMemoryState = input + } ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) } else { // Initialise a new contract and set the code that is to be used by the EVM. @@ -375,7 +383,16 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) + // If it is the get memory state call, return the EVM's in-memory state. + if addr == common.BytesToAddress([]byte{21}) { + if gas < p.RequiredGas(input) { + ret, gas, err = nil, 0, ErrOutOfGas + } else { + ret, gas, err = evm.inMemoryState, gas-p.RequiredGas(input), nil + } + } else { + ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) + } } else { // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 92be3bf259a3d..61224abe0901c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -513,9 +513,22 @@ func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] return nil, nil } +var locationModulus *uint256.Int + +func init() { + locationModulus = uint256.NewInt(0) + locationModulus.SetAllOne() + locationModulus[3] = 0 +} + +func unprotectedLocation(loc uint256.Int) uint256.Int { + return *loc.Mod(&loc, locationModulus) +} + func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { loc := scope.Stack.peek() - hash := common.Hash(loc.Bytes32()) + actual_loc := unprotectedLocation(*loc) + hash := common.Hash(actual_loc.Bytes32()) val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) loc.SetBytes(val.Bytes()) return nil, nil @@ -525,7 +538,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b if interpreter.readOnly { return nil, ErrWriteProtection } - loc := scope.Stack.pop() + loc := unprotectedLocation(scope.Stack.pop()) val := scope.Stack.pop() interpreter.evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32())