This document is a guide listing detailed steps to integrate fhevm-go
into go-ethereum or any other implementations that follow the same architecture.
This document is based on go-ethereum v1.13.5
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
)
We used 93 as the address of the precompile here, but you can choose any other address as far as client libraries know where to find it.
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