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)
Rewrite Create
and Create2
by a call to their fhevm implementation
Copy func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
return fhevm.Create(evm.FhevmEnvironment(), caller.Address(), code, gas, value)
}
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
return fhevm.Create2(evm.FhevmEnvironment(), caller.Address(), code, gas, endowment, salt)
}
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 opSload
, opSstore
and opReturn
Rewrite opSload
, opSstore
and opReturn
by a call to their fhevm implementation:
Copy func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
return fhevm.OpSload(pc, interpreter.evm.FhevmEnvironment(), scope)
}
func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
return fhevm.OpSstore(pc, interpreter.evm.FhevmEnvironment(), scope)
}
func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
return fhevm.OpReturn(pc, interpreter.evm.FhevmEnvironment(), scope), errStopToken
}
Update opCall
, opCallCode
and opStaticCall
In opCall
, opCallCode
and opStaticCall
, add lines to delegate ciphertexts before the call and to restore at the end of function (using defer
)
Add the 2 following lines:
Copy verifiedBefore := fhevm.DelegateCiphertextHandlesInArgs(interpreter.evm.FhevmEnvironment(), args)
defer fhevm.RestoreVerifiedDepths(interpreter.evm.FhevmEnvironment(), verifiedBefore)
The call function is named differently in the 3 functions to update:
Copy ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal)
Update opSelfdestruct
In:
Copy func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error)
Replace the following lines:
Copy beneficiary := scope.Stack.pop()
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address())
with this call to the fhevm:
Copy beneficiary, balance := fhevm.OpSelfdestruct(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)
Add the deletion of verified ciphertexts at current depth in the defer
at the top:
Copy defer func() {
fhevm.RemoveVerifiedCipherextsAtCurrentDepth(in.evm.FhevmEnvironment())
in.evm.depth--
}()
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 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 9: 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