This blob is focus on the generate, encode and storage of abi. If you want to know more about abi, you can refer solidity abi spec
abi
The chart above show the rule of abi in a process of contract invoke.
show
json
type specJSON struct {
Name string
Type string
Inputs []argumentJSON
Outputs []argumentJSON
Constant bool
Payable bool
StateMutability string
Anonymous bool
}
// function input/output argument
type argumentJSON struct {
Name string
Type string
Components []argumentJSON
Indexed bool
}
example
part abi of storage.sol
[{
"constant": true,
"inputs": [],
"name": "getAddress",
"outputs": [{
"internalType": "address",
"name": "retAddress",
"type": "address"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
}, {
"constant": false,
"inputs": [{
"internalType": "address",
"name": "x",
"type": "address"
}],
"name": "setAddress",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}]
go structure
// Spec is the ABI for contract decoded.
type Spec struct {
Constructor *FunctionSpec
Fallback *FunctionSpec
Functions map[string]*FunctionSpec
EventsByName map[string]*EventSpec
EventsByID map[EventID]*EventSpec
}
// function spec
type FunctionSpec struct {
Name string
FunctionID FunctionID
Constant bool
Inputs []Argument
Outputs []Argument
}
// event spec
type EventSpec struct {
ID EventID
Inputs []Argument
Name string
Anonymous bool
}
generate
Abi is auto generated after compile, Of course, you can also write your abi file by hand.
The code below show respones of compiler, Abi is located in resp.Objects[].Contract.Abi and Contract.MetadataMap[].Metadata.Abi, which the latter is stored in blockchain.
type Response struct {
Objects []ResponseItem `json:"objects"`
Warning string `json:"warning"`
Version string `json:"version"`
Error string `json:"error"`
}
// Compile response object
type ResponseItem struct {
Filename string `json:"filename"`
Objectname string `json:"objectname"`
Contract SolidityContract `json:"binary"`
}
// SolidityContract is defined for each contract defined in the solidity source code
type SolidityContract struct {
Abi json.RawMessage
Evm struct {
Bytecode ContractCode
DeployedBytecode ContractCode
}
EWasm struct {
Wasm string
}
Devdoc json.RawMessage
Userdoc json.RawMessage
Metadata string
// This is not present in the solidity output, but we add it ourselves
// This is map from DeployedBytecode to Metadata. A Solidity contract can create any number
// of contracts, which have distinct metadata. This is a map for the deployed code to metdata,
// including the first contract itself.
MetadataMap []MetadataMap `json:",omitempty"`
}
type Metadata struct {
ContractName string
SourceFile string
CompilerVersion string
Abi json.RawMessage
}
type MetadataMap struct {
DeployedBytecode ContractCode
Metadata Metadata
}
encode
To construct a tx, we should encode invocation args.
First of all, we should know data type which encode from and to.
From type is implemented via reflect; To type is retrieved from contract abi.
setStorageAddress call stack
github.com/hyperledger/burrow/execution/evm/abi.EVMAddress.pack at primitives.go:536
<autogenerated>:2
github.com/hyperledger/burrow/execution/evm/abi.pack.func1 at packing.go:185
github.com/hyperledger/burrow/execution/evm/abi.pack at packing.go:240
github.com/hyperledger/burrow/execution/evm/abi.Pack at packing.go:16
github.com/hyperledger/burrow/execution/evm/abi.(*Spec).Pack at spec.go:157
github.com/hyperledger/burrow/execution/evm/abi.EncodeFunctionCall at abi.go:97
github.com/hyperledger/burrow/deploy/jobs.FormulateCallJob at jobs_contracts.go:456
github.com/hyperledger/burrow/deploy/jobs.doJobs at job_manager.go:241
github.com/hyperledger/burrow/deploy/jobs.ExecutePlaybook at job_manager.go:332
github.com/hyperledger/burrow/deploy.worker.func1 at run_deploy.go:63
github.com/hyperledger/burrow/deploy.worker at run_deploy.go:68
runtime.goexit at asm_amd64.s:1357
- Async stack trace
github.com/hyperledger/burrow/deploy.RunPlaybooks at run_deploy.go:97
EVMType
Each data type is packed respectively, their pack func is defined in EVMType.pack.
Now there is 7 kinds of types.
type EVMType interface {
GetSignature() string
getGoType() interface{}
pack(v interface{}) ([]byte, error)
unpack(data []byte, offset int, v interface{}) (int, error)
Dynamic() bool
ImplicitCast(o EVMType) bool
}
implement
EVMInt
EVMUint
EVMBool
EVMString
EVMBytes
EVMFixed
EVMAddress
delivery
Metadata of contract is delivery to calltx.ContractMeta and then send tx to full node.
// A instruction to run smart contract code in the EVM
type CallTx struct {
// The caller's input
Input *TxInput `protobuf:"bytes,1,opt,name=Input,proto3" json:"Input,omitempty"`
// The contract address to call or nil if we are creating a contract
Address *github_com_hyperledger_burrow_crypto.Address `protobuf:"bytes,2,opt,name=Address,proto3,customtype=github.com/hyperledger/burrow/crypto.Address" json:"Address,omitempty"`
// The upper bound on the amount of gas (and therefore EVM execution steps) this CallTx may generate
GasLimit uint64 `protobuf:"varint,3,opt,name=GasLimit,proto3" json:"GasLimit,omitempty"`
// Fee to offer validators for processing transaction
Fee uint64 `protobuf:"varint,4,opt,name=Fee,proto3" json:"Fee,omitempty"`
// EVM bytecode
Data github_com_hyperledger_burrow_binary.HexBytes `protobuf:"bytes,5,opt,name=Data,proto3,customtype=github.com/hyperledger/burrow/binary.HexBytes" json:"Data"`
// WASM bytecode
WASM github_com_hyperledger_burrow_binary.HexBytes `protobuf:"bytes,6,opt,name=WASM,proto3,customtype=github.com/hyperledger/burrow/binary.HexBytes" json:"tags,omitempty"`
// Set of contracts this code will deploy
ContractMeta []*ContractMeta `protobuf:"bytes,7,rep,name=ContractMeta,proto3" json:"ContractMeta,omitempty"`
// The upper bound on the price per unit of gas
GasPrice uint64 `protobuf:"varint,8,opt,name=GasPrice,proto3" json:"GasPrice,omitempty"`
}
type ContractMeta struct {
CodeHash github_com_hyperledger_burrow_binary.HexBytes `protobuf:"bytes,1,opt,name=CodeHash,proto3,customtype=github.com/hyperledger/burrow/binary.HexBytes" json:"CodeHash"`
Meta string `protobuf:"bytes,2,opt,name=Meta,proto3" json:"Meta,omitempty"`
}
backen store
If the metadata is already memoised, it was stored in account.ContractMeta.Metadata. Otherwise, it was stored in leveldb.
type Account struct {
Address github_com_hyperledger_burrow_crypto.Address `protobuf:"bytes,1,opt,name=Address,proto3,customtype=github.com/hyperledger/burrow/crypto.Address" json:"Address"`
PublicKey crypto.PublicKey `protobuf:"bytes,2,opt,name=PublicKey,proto3" json:"PublicKey"`
// Sequence counts the number of transactions that have been accepted from this account
Sequence uint64 `protobuf:"varint,3,opt,name=Sequence,proto3" json:"Sequence,omitempty"`
// The account's current native token balance
Balance uint64 `protobuf:"varint,4,opt,name=Balance,proto3" json:"Balance,omitempty"`
// We expect exactly one of EVMCode, WASMCode, and NativeName to be non-empty
// EVM bytecode
EVMCode Bytecode `protobuf:"bytes,5,opt,name=EVMCode,proto3,customtype=Bytecode" json:"EVMCode"`
Permissions permission.AccountPermissions `protobuf:"bytes,6,opt,name=Permissions,proto3" json:"Permissions"`
// WASM bytecode
WASMCode Bytecode `protobuf:"bytes,7,opt,name=WASMCode,proto3,customtype=Bytecode" json:",omitempty"`
// Fully qualified (`<contract name>.<function name>`) name of native contract this for which this account object
// is a sentinel value. Which is to say this account object is a pointer to compiled code and does not contain
// the contract logic in its entirety
NativeName string `protobuf:"bytes,11,opt,name=NativeName,proto3" json:",omitempty"`
// The sha3 hash of the code associated with the account
CodeHash github_com_hyperledger_burrow_binary.HexBytes `protobuf:"bytes,8,opt,name=CodeHash,proto3,customtype=github.com/hyperledger/burrow/binary.HexBytes" json:"-"`
// Pointer to the Metadata associated with this account
ContractMeta []*ContractMeta `protobuf:"bytes,9,rep,name=ContractMeta,proto3" json:"ContractMeta,omitempty"`
// The metadata is stored in the deployed account. When the deployed account creates new account
// (from Solidity/EVM), they point to the original deployed account where the metadata is stored.
// This original account is called the forebear.
Forebear *github_com_hyperledger_burrow_crypto.Address `protobuf:"bytes,10,opt,name=Forebear,proto3,customtype=github.com/hyperledger/burrow/crypto.Address" json:"Forebear,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
type ContractMeta struct {
CodeHash github_com_hyperledger_burrow_binary.HexBytes `protobuf:"bytes,1,opt,name=CodeHash,proto3,customtype=github.com/hyperledger/burrow/binary.HexBytes" json:"CodeHash"`
MetadataHash github_com_hyperledger_burrow_binary.HexBytes `protobuf:"bytes,2,opt,name=MetadataHash,proto3,customtype=github.com/hyperledger/burrow/binary.HexBytes" json:"MetadataHash"`
// In the dump format we would like the ABI rather than its hash
Metadata string `protobuf:"bytes,3,opt,name=Metadata,proto3" json:"Metadata,omitempty"`
}
In my debug, metadata is always stored in leveldb.
rpc server call stack
github.com/tendermint/tm-db.(*GoLevelDB).Get at go_level_db.go:44
github.com/hyperledger/burrow/storage.(*PrefixDB).Get at prefix_db.go:23
github.com/hyperledger/burrow/execution/state.(*ReadState).GetMetadata at abi.go:8
github.com/hyperledger/burrow/rpc/rpcquery.(*queryServer).GetMetadata at query_server.go:121
github.com/hyperledger/burrow/rpc/rpcquery._Query_GetMetadata_Handler.func1 at rpcquery.pb.go:1419
github.com/hyperledger/burrow/rpc.unaryInterceptor.func1 at grpc.go:29
github.com/hyperledger/burrow/rpc/rpcquery._Query_GetMetadata_Handler at rpcquery.pb.go:1421
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