Burrow, EVM precompiled contracts
There are several precompiled functions in Burrow EVM:
- sha256 @0x0000000000000000000000000000000000000002
- ripemd160 @0x0000000000000000000000000000000000000003
- identityFunc @0x0000000000000000000000000000000000000004
- expModFunc @0x0000000000000000000000000000000000000005
Native Contract
- Permission @0x0A758FEB535243577C1A79AE55BED8CA03E226EC
Permission can be accessed at 0x0A758FEB535243577C1A79AE55BED8CA03E226EC.
It has serveral functions:
- addRole
- removeRole
- hasRole
- setBase
- unsetBase
- hasBase
- setGlobal
Native Contract Call:
// Dispatch is designed to be called from the EVM once a native contract
// has been selected.
func (c *Contract) Call(state engine.State, params engine.CallParams) (output []byte, err error) {
if len(params.Input) < abi.FunctionIDSize {
return nil, errors.Errorf(errors.Codes.NativeFunction,
"Burrow Native dispatch requires a 4-byte function identifier but arguments are only %v bytes long",
len(params.Input))
}
var id abi.FunctionID
copy(id[:], params.Input)
function, err := c.FunctionByID(id)
if err != nil {
return nil, err
}
params.Input = params.Input[abi.FunctionIDSize:]
return function.Call(state, params)
}
Call stack:
github.com/hyperledger/burrow/execution/native.hasRole at permissions.go:229
runtime.call256 at asm_amd64.s:542
reflect.Value.call at value.go:460
reflect.Value.Call at value.go:321
github.com/hyperledger/burrow/execution/native.(*Function).execute at function.go:106
github.com/hyperledger/burrow/execution/native.(*Function).execute-fm at function.go:76
github.com/hyperledger/burrow/execution/native.Call at call.go:17
github.com/hyperledger/burrow/execution/native.(*Function).Call at function.go:73
github.com/hyperledger/burrow/execution/native.(*Contract).Call at contract.go:113
github.com/hyperledger/burrow/execution/evm.(*Contract).execute at contract.go:775
github.com/hyperledger/burrow/execution/evm.(*Contract).execute-fm at contract.go:36
github.com/hyperledger/burrow/execution/native.Call at call.go:17
github.com/hyperledger/burrow/execution/evm.(*Contract).Call at contract.go:32
github.com/hyperledger/burrow/execution/evm.(*EVM).Execute at evm.go:87
github.com/hyperledger/burrow/execution/contexts.(*CallContext).Deliver at call_context.go:222
github.com/hyperledger/burrow/execution/contexts.(*CallContext).Execute at call_context.go:49
github.com/hyperledger/burrow/execution.CallSim at simulated_call.go:42
github.com/hyperledger/burrow/rpc/rpctransact.(*transactServer).CallTxSim at transact_server.go:108
github.com/hyperledger/burrow/rpc/rpctransact._Transact_CallTxSim_Handler.func1 at rpctransact.pb.go:613
github.com/hyperledger/burrow/rpc.unaryInterceptor.func1 at grpc.go:104
github.com/hyperledger/burrow/rpc/rpctransact._Transact_CallTxSim_Handler at rpctransact.pb.go:615
google.golang.org/grpc.(*Server).processUnaryRPC at server.go:1024
google.golang.org/grpc.(*Server).handleStream at server.go:1313
google.golang.org/grpc.(*Server).serveStreams.func1.1 at server.go:722
runtime.goexit at asm_amd64.s:1357
- Async stack trace
google.golang.org/grpc.(*Server).serveStreams.func1 at server.go:720
Precompiled Native Functions
When executing the bytecode, EVM will try to dispatch the contact call for any opCode of Call/DelegateCall/STaticCall/CallCode, as below:
returnData, callErr = c.Dispatch(acc).Call(childState, calleeParams)
calleeParams are constructed from Stack, and acc is the address of the callee contract, which is for example, 0x0000000000000000000000000000000000000002, when it comes to precompiled sha256.
Therefore, c.Dispatch(acc) will look the external dispatcher and locate the callable Function of sha256Func:
callable = {github.com/hyperledger/burrow/execution/engine.Callable | *github.com/hyperledger/burrow/execution/native.Function}
Comment = {string} "Compute the sha256 hash of input"
PermFlag = {github.com/hyperledger/burrow/permission.PermFlag} None (0)
F = {interface {} | func(github.com/hyperledger/burrow/execution/native.Context) ([]uint8, error)} github.com/hyperledger/burrow/execution/native.sha256Func
contractName = {string} ""
name = {string} "sha256Func"
abi = {*github.com/hyperledger/burrow/execution/evm/abi.FunctionSpec} nil
address = {github.com/hyperledger/burrow/crypto.Address} len:20
externals = {github.com/hyperledger/burrow/execution/engine.Dispatcher | github.com/hyperledger/burrow/execution/engine.Dispatchers} len:1, cap:1
logger = {*github.com/hyperledger/burrow/logging.Logger} nil
See Dispatch() for more details:
func (vm *EVM) Dispatch(acc *acm.Account) engine.Callable {
// Try external calls then fallback to EVM
callable := vm.externals.Dispatch(acc)
if callable != nil {
return callable
}
// This supports empty code calls
return vm.Contract(acc.EVMCode)
}
sha256 example
Solidity code:
function testB() public {
address nnn = 0x0000000000000000000000000000000000000002;
nnn.call("123");
...
}
call stack
github.com/hyperledger/burrow/execution/native.sha256Func at precompiles.go:63
runtime.call256 at asm_amd64.s:542
reflect.Value.call at value.go:460
reflect.Value.Call at value.go:321
github.com/hyperledger/burrow/execution/native.(*Function).execute at function.go:106
github.com/hyperledger/burrow/execution/native.(*Function).execute-fm at function.go:76
github.com/hyperledger/burrow/execution/native.Call at call.go:17
github.com/hyperledger/burrow/execution/native.(*Function).Call at function.go:73
github.com/hyperledger/burrow/execution/evm.(*Contract).execute at contract.go:775
github.com/hyperledger/burrow/execution/evm.(*Contract).execute-fm at contract.go:36
github.com/hyperledger/burrow/execution/native.Call at call.go:17
github.com/hyperledger/burrow/execution/evm.(*Contract).Call at contract.go:32
github.com/hyperledger/burrow/execution/evm.(*EVM).Execute at evm.go:87
github.com/hyperledger/burrow/execution/contexts.(*CallContext).Deliver at call_context.go:222
github.com/hyperledger/burrow/execution/contexts.(*CallContext).Execute at call_context.go:49
github.com/hyperledger/burrow/execution.(*executor).Execute at execution.go:257
github.com/hyperledger/burrow/consensus/abci.ExecuteTx at execute_tx.go:30
github.com/hyperledger/burrow/consensus/abci.(*App).DeliverTx at app.go:212
github.com/tendermint/tendermint/abci/client.(*localClient).DeliverTxAsync at local_client.go:88
github.com/tendermint/tendermint/proxy.(*appConnConsensus).DeliverTxAsync at app_conn.go:73
github.com/tendermint/tendermint/state.execBlockOnProxyApp at execution.go:293
github.com/tendermint/tendermint/state.(*BlockExecutor).ApplyBlock at execution.go:131
github.com/tendermint/tendermint/consensus.(*State).finalizeCommit at state.go:1431
github.com/tendermint/tendermint/consensus.(*State).tryFinalizeCommit at state.go:1350
github.com/tendermint/tendermint/consensus.(*State).enterCommit.func1 at state.go:1285
github.com/tendermint/tendermint/consensus.(*State).enterCommit at state.go:1322
github.com/tendermint/tendermint/consensus.(*State).addVote at state.go:1819
github.com/tendermint/tendermint/consensus.(*State).tryAddVote at state.go:1642
github.com/tendermint/tendermint/consensus.(*State).handleMsg at state.go:709
github.com/tendermint/tendermint/consensus.(*State).receiveRoutine at state.go:660
runtime.goexit at asm_amd64.s:1357
- Async stack trace
github.com/tendermint/tendermint/consensus.(*State).OnStart at state.go:335
Call, DelegateCall, StaticCall, CallCode
case CALL, CALLCODE, DELEGATECALL, STATICCALL: // 0xF1, 0xF2, 0xF4, 0xFA
returnData = nil
if maybe.PushError(ensurePermission(st.CallFrame, params.Callee, permission.Call)) {
continue
}
// Pull arguments off stack:
gasLimit := stack.Pop64()
target := stack.PopAddress()
value := params.Value
// NOTE: for DELEGATECALL value is preserved from the original
// caller, as such it is not stored on stack as an argument
// for DELEGATECALL and should not be popped. Instead previous
// caller value is used. for CALL and CALLCODE value is stored
// on stack and needs to be overwritten from the given value.
if op != DELEGATECALL && op != STATICCALL {
value = stack.Pop64()
}
// inputs
inOffset, inSize := stack.PopBigInt(), stack.PopBigInt()
// outputs
retOffset := stack.PopBigInt()
retSize := stack.Pop64()
c.debugf(" => %v\n", target)
// Get the arguments from the memory
// EVM contract
maybe.PushError(useGasNegative(params.Gas, native.GasGetAccount))
// since CALL is used also for sending funds,
// acc may not exist yet. This is an errors.CodedError for
// CALLCODE, but not for CALL, though I don't think
// ethereum actually cares
acc := getAccount(st.CallFrame, maybe, target)
if acc == nil {
if op != CALL {
maybe.PushError(errors.Codes.UnknownAddress)
continue
}
// We're sending funds to a new account so we must create it first
if maybe.PushError(createAccount(st.CallFrame, params.Callee, target)) {
continue
}
acc = mustGetAccount(st.CallFrame, maybe, target)
}
// Establish a stack frame and perform the call
childCallFrame, err := st.CallFrame.NewFrame()
if maybe.PushError(err) {
continue
}
childState := engine.State{
CallFrame: childCallFrame,
Blockchain: st.Blockchain,
EventSink: st.EventSink,
}
// Ensure that gasLimit is reasonable
if *params.Gas < gasLimit {
// EIP150 - the 63/64 rule - rather than errors.CodedError we pass this specified fraction of the total available gas
gasLimit = *params.Gas - *params.Gas/64
}
// NOTE: we will return any used gas later.
*params.Gas -= gasLimit
// Setup callee params for call type
calleeParams := engine.CallParams{
Origin: params.Origin,
Input: memory.Read(inOffset, inSize),
Value: value,
Gas: &gasLimit,
}
// Set up the caller/callee context
switch op {
case CALL:
// Calls contract at target from this contract normally
// Value: transferred
// Caller: this contract
// Storage: target
// Code: from target
calleeParams.CallType = exec.CallTypeCall
calleeParams.Caller = params.Callee
calleeParams.Callee = target
case STATICCALL:
// Calls contract at target from this contract with no state mutation
// Value: not transferred
// Caller: this contract
// Storage: target (read-only)
// Code: from target
calleeParams.CallType = exec.CallTypeStatic
calleeParams.Caller = params.Callee
calleeParams.Callee = target
childState.CallFrame.ReadOnly()
childState.EventSink = exec.NewLogFreeEventSink(childState.EventSink)
case CALLCODE:
// Calling this contract from itself as if it had the code at target
// Value: transferred
// Caller: this contract
// Storage: this contract
// Code: from target
calleeParams.CallType = exec.CallTypeCode
calleeParams.Caller = params.Callee
calleeParams.Callee = params.Callee
case DELEGATECALL:
// Calling this contract from the original caller as if it had the code at target
// Value: not transferred
// Caller: original caller
// Storage: this contract
// Code: from target
calleeParams.CallType = exec.CallTypeDelegate
calleeParams.Caller = params.Caller
calleeParams.Callee = params.Callee
default:
panic(fmt.Errorf("switch statement should be exhaustive so this should not have been reached"))
}
var callErr error
returnData, callErr = c.Dispatch(acc).Call(childState, calleeParams)
if callErr == nil {
// Sync error is a hard stop
maybe.PushError(childState.CallFrame.Sync())
}
// Push result
if callErr != nil {
c.debugf("error from nested sub-call (depth: %v): %s\n", st.CallFrame.CallStackDepth(), callErr.Error())
// So we can return nested errors.CodedError if the top level return is an errors.CodedError
stack.Push(Zero256)
if errors.GetCode(callErr) == errors.Codes.ExecutionReverted {
memory.Write(retOffset, RightPadBytes(returnData, int(retSize)))
}
} else {
stack.Push(One256)
// Should probably only be necessary when there is no return value and
// returnData is empty, but since EVM expects retSize to be respected this will
// defensively pad or truncate the portion of returnData to be returned.
memory.Write(retOffset, RightPadBytes(returnData, int(retSize)))
}
// Handle remaining gas.
*params.Gas += *calleeParams.Gas
c.debugf("resume %s (%v)\n", params.Callee, params.Gas)