This document is a guide listing detailed steps to integrate fhevm-go
into or any other implementations that follow the same architecture.
Steps
Step 1: update core/state_transition.go
Copy func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool, isEIP3860 bool) (uint64, error)
replace the last return with:
Copy return fhevm.TxDataFractionalGas(gas), nil
which will impact tests as the returned gas won’t be the same.
Step 2: update core/vm/contracts.go
update the PrecompiledContract
interface to:
Copy type PrecompileAccessibleState interface {
Interpreter() *EVMInterpreter
}
type PrecompiledContract interface {
RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64
Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error)
}
Update all previous uses of this interface
Add:
Copy common.BytesToAddress([]byte{93}): &fheLib{}
to all precompiled contract maps (e.g. PrecompiledContractsHomestead
)
Implement the fheLib
precompile
Copy // fheLib calls into the different precompile functions available in the fhevm
type fheLib struct{}
func (c *fheLib) RequiredGas(accessibleState PrecompileAccessibleState, input []byte) uint64 {
return fhevm.FheLibRequiredGas(accessibleState.Interpreter().evm.FhevmEnvironment(), input)
}
func (c *fheLib) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
return fhevm.FheLibRun(accessibleState.Interpreter().evm.FhevmEnvironment(), caller, addr, input, readOnly)
}
Rewrite RunPrecompiledContract
Copy func RunPrecompiledContract(p PrecompiledContract, accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(accessibleState, input)
if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas
}
suppliedGas -= gasCost
output, err := p.Run(accessibleState, caller, addr, input, accessibleState.Interpreter().readOnly)
return output, suppliedGas, err
}
Step 3: update core/vm/errors.go
Register errors at initialization in fhevm-go
to be recognized at runtime
Copy func init() {
fhevm.RegisterErrors(ErrOutOfGas, ErrCodeStoreOutOfGas, ErrDepth, ErrInsufficientBalance,
ErrContractAddressCollision, ErrExecutionReverted, ErrMaxInitCodeSizeExceeded, ErrMaxCodeSizeExceeded,
ErrInvalidJump, ErrWriteProtection, ErrReturnDataOutOfBounds, ErrGasUintOverflow, ErrInvalidCode,
ErrNonceUintOverflow, nil, nil, nil)
}
Step 4: update core/vm/evm.go
Update EVM
struct with new fields
Copy fhevmEnvironment FhevmImplementation
isGasEstimation bool
isEthCall bool
While implementing fhevmEnvironment
as:
Copy type FhevmImplementation struct {
interpreter *EVMInterpreter
data fhevm.FhevmData
logger fhevm.Logger
params fhevm.FhevmParams
}
Update NewEVM
In:
Copy func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM
Initialize isGasEstimation
using config.IsGasEstimation
Initialize isEthCall
using config.IsEthCall
Initialize fhevmEnvironment
with FhevmImplementation{interpreter: nil, logger: &fhevm.DefaultLogger{}, data: fhevm.NewFhevmData(), params: fhevm.DefaultFhevmParams()}
After initializing evm.interpreter
make sure to point fhevmEnvironment
to it evm.fhevmEnvironment.interpreter = evm.interpreter
then initialize it fhevm.InitFhevm(&evm.fhevmEnvironment)
Update RunPrecompiledContract
After changing precompiled contract interface in 2, we have to change usages of:
Copy RunPrecompiledContract(p, input, gas)
to:
Copy RunPrecompiledContract(p, evm, caller.Address(), addr, input, gas)
Implement EVMEnvironment interface
Now implement the fhevm.EVMEnvironment
interface for FhevmImplementation
:
Copy func (evm *EVM) FhevmEnvironment() fhevm.EVMEnvironment { return &evm.fhevmEnvironment }
// If you are using OpenTelemetry, you can return a context that the precompiled fhelib will use
// to trace its internal functions. Otherwise, just return nil
func (evm *FhevmImplementation) OtelContext() context.Context {
return nil
}
func (evm *FhevmImplementation) GetState(addr common.Address, hash common.Hash) common.Hash {
return evm.interpreter.evm.StateDB.GetState(addr, hash)
}
func (evm *FhevmImplementation) SetState(addr common.Address, hash common.Hash, input common.Hash) {
evm.interpreter.evm.StateDB.SetState(addr, hash, input)
}
func (evm *FhevmImplementation) GetNonce(addr common.Address) uint64 {
return evm.interpreter.evm.StateDB.GetNonce(addr)
}
func (evm *FhevmImplementation) AddBalance(addr common.Address, value *big.Int) {
evm.interpreter.evm.StateDB.AddBalance(addr, value)
}
func (evm *FhevmImplementation) GetBalance(addr common.Address) *big.Int {
return evm.interpreter.evm.StateDB.GetBalance(addr)
}
func (evm *FhevmImplementation) Suicide(addr common.Address) bool {
evm.interpreter.evm.StateDB.SelfDestruct(addr)
return evm.interpreter.evm.StateDB.HasSelfDestructed(addr)
}
func (evm *FhevmImplementation) GetDepth() int {
return evm.interpreter.evm.depth
}
func (evm *FhevmImplementation) IsCommitting() bool {
return !evm.interpreter.evm.isGasEstimation
}
func (evm *FhevmImplementation) IsEthCall() bool {
return evm.interpreter.evm.isEthCall
}
func (evm *FhevmImplementation) IsReadOnly() bool {
return evm.interpreter.readOnly
}
func (evm *FhevmImplementation) GetLogger() fhevm.Logger {
return evm.logger
}
func (evm *FhevmImplementation) FhevmData() *fhevm.FhevmData {
return &evm.data
}
func (evm *FhevmImplementation) FhevmParams() *fhevm.FhevmParams {
return &evm.params
}
func (evm *FhevmImplementation) CreateContract(caller common.Address, code []byte, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
return evm.interpreter.evm.create(AccountRef(caller), &codeAndHash{code: code}, gas, value, address, CREATE)
}
func (evm *FhevmImplementation) CreateContract2(caller common.Address, code []byte, codeHash common.Hash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
return evm.interpreter.evm.create(AccountRef(caller), &codeAndHash{code: code, hash: codeHash}, gas, value, address, CREATE2)
}
Step 5: update core/vm/instructions.go
Update opSstore
Rewrite opSstore
by a call to their fhevm implementation:
Copy func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
return fhevm.OpSstore(pc, interpreter.evm.FhevmEnvironment(), scope)
}
Step 6: update core/vm/interpreter.go
Update Config
struct with new fields
Copy IsEthCall bool
IsGasEstimation bool
Implements the GetMemory
, GetStack
and GetContract
methods
Copy func (s *ScopeContext) GetMemory() fhevm.Memory {
return s.Memory
}
func (s *ScopeContext) GetStack() fhevm.Stack {
return s.Stack
}
func (s *ScopeContext) GetContract() fhevm.Contract {
return s.Contract
}
Update Run
method
In:
Copy func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error)
Step 7: update core/vm/stack.go
Implement the following methods
Copy func (st *Stack) Pop() uint256.Int {
return st.pop()
}
func (st *Stack) Peek() *uint256.Int {
return st.peek()
}
Step 8: update core/vm/operations_acl.go
Implement gas cost for storing a ciphertext
In func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
Just before original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32())
, add this block to increase SSTORE gas cost for storing a ciphertext:
Copy ct := fhevm.GetCiphertextFromMemory(evm.FhevmEnvironment(), value)
if ct != nil {
cost += evm.fhevmEnvironment.params.GasCosts.FheStorageSstoreGas[ct.Type()]
}
Step 9: update internal/ethapi/api.go
Add isGasEstimation, isEthCall bool
arguments to func doCall
and pass them in vm.Config
during EVM creation:
Copy evm, vmError := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true, IsGasEstimation: isGasEstimation, IsEthCall: isEthCall}, &blockCtx)
Add isGasEstimation, isEthCall bool
arguments to func DoCall
and forward them in the call to doCall
Update usages of doCall
and DoCall
by simply setting IsEthCall
to true
when it’s a call, and IsGasEstimation
to true
when it’s estimating gas
Step 10: update graphql/graphql.go
Update usages of doCall
and DoCall
by simply setting IsEthCall
to true
when it’s a call, and IsGasEstimation
to true
when it’s estimating gas