Using Ethereum's official golang
s ABI is easy too:
This is how you extract encoded values from transaction's input:
m,exists:=erc20_token_abi.Methods[method_name]
if exists {
method_input,err:=m.Inputs.UnpackValues(input_data_bytes)
}
This is how you encode values for transaction's input :
encoded_input,err:=contract_abi.Pack(method_name,method_params...)
This is how you decode the output
returned by the EVM once the contract Call is finished:
ret,gas,_,failed,err:=core.ApplyMessage(evm_obj,msg,gp)
output_int:=big.NewInt(0)
if !((err!=nil) || (failed)) {
err=erc20_token_abi.Unpack(&output_int,"totalSupply",ret)
if err!=nil {
log.Info(fmt.Sprintf("Contract %v: can;t upack total supply: %v",hex.EncodeToString(addr.Bytes()),err))
} else {
log.Info(fmt.Sprintf("totalSupply=%v",output_int.String()))
}
}
To create ABI object:
erc20_methods_abi, err = abi.JSON(strings.NewReader(erc20_methods_abi_json_str)) (erc20_methods_abi_json_st
For example, this is the code in golang
I am using to encode a generic input (received as string with comma separated values, and an ABI in a string) from a C++ app:
//export EncodeInput4ContractCall
func EncodeInput4ContractCall(out_encoded_input unsafe.Pointer,output_len_ptr *C.int,in_output_max_len C.int,in_contract_abi *C.char,in_method_name *C.char,in_method_params *C.char) C.int {
contract_abi_str:=C.GoString(in_contract_abi)
method_name:=C.GoString(in_method_name)
method_params_str:=C.GoString(in_method_params)
contract_abi,err:=abi.JSON(strings.NewReader(contract_abi_str))
if err!=nil {
write_back_error(out_encoded_input,output_len_ptr,err)
return 1;
}
method,exists:=contract_abi.Methods[method_name]
if !exists {
err:=errors.New(fmt.Sprintf("Method '%v' not found in the ABI",method_name))
write_back_error(out_encoded_input,output_len_ptr,err)
return 1
}
params:=strings.Split(method_params_str,",")
if len(params)!=len(method.Inputs) {
err:=errors.New(fmt.Sprintf("Invalid number of parameters. Method requires %v, but %v provided",len(method.Inputs),len(params)))
write_back_error(out_encoded_input,output_len_ptr,err)
return 1
}
method_params:=make([]interface{},0,8)
for i,input:=range method.Inputs {
v,err:=param_decode(params[i],&input)
if err!=nil {
err:=errors.New(fmt.Sprintf("Failed to decode parameter %v (%v): %v",(i+1),input.Name,err))
write_back_error(out_encoded_input,output_len_ptr,err)
return 1
}
method_params=append(method_params,v)
}
encoded_input,err:=contract_abi.Pack(method_name,method_params...)
if err!=nil {
write_back_error(out_encoded_input,output_len_ptr,err)
return 1;
}
encoded_input_hex:=hex.EncodeToString(encoded_input)
output_bytes:=[]byte(encoded_input_hex);
length:=_Ctype_ulong(len(output_bytes))
if (length>_Ctype_ulong(in_output_max_len)) {
err:=errors.New(fmt.Sprintf("Output buffer too small for generated output (%v>%v)",length,in_output_max_len))
write_back_error(out_encoded_input,output_len_ptr,err)
return 1
}
*output_len_ptr=C.int(length)
var c_bytes unsafe.Pointer
c_bytes=C.CBytes(output_bytes)
C.memcpy(out_encoded_input,c_bytes,length)
C.free(c_bytes)
return 0;
}
func param_decode(param string,arg *abi.Argument) (v interface{},err error) {
param=strings.TrimSpace(param)
switch(arg.Type.T) {
case abi.StringTy:
str_val:=new(string)
v=str_val
err=json.Unmarshal([]byte(param),v)
case abi.UintTy,abi.IntTy:
val:=big.NewInt(0)
_,success:=val.SetString(param,10)
if !success {
err=errors.New(fmt.Sprintf("Invalid numeric (base 10) value: %v",param))
}
v=val
case abi.AddressTy:
if !((len(param)==(common.AddressLength*2+2)) || (len(param)==common.AddressLength*2)) {
err=errors.New(fmt.Sprintf("Invalid address length (%v), must be 40 (unprefixed) or 42 (prefixed) chars",len(param)))
} else {
var addr common.Address
if len(param)==(common.AddressLength*2+2) {
addr=common.HexToAddress(param)
} else {
var data []byte
data,err=hex.DecodeString(param)
addr.SetBytes(data)
}
v=addr
}
case abi.HashTy:
if !((len(param)==(common.HashLength*2+2)) || (len(param)==common.HashLength*2)) {
err=errors.New(fmt.Sprintf("Invalid hash length, must be 64 (unprefixed) or 66 (prefixed) chars"))
} else {
var hash common.Hash
if len(param)==(common.HashLength*2+2) {
hash=common.HexToHash(param)
} else {
var data []byte
data,err=hex.DecodeString(param)
hash.SetBytes(data)
}
v=hash
}
case abi.BytesTy:
if len(param)>2 {
if (param[0]=='0') && (param[1]=='x') {
param=param[2:] // cut 0x prefix
}
}
decoded_bytes,tmperr:=hex.DecodeString(param)
v=decoded_bytes
err=tmperr
case abi.BoolTy:
val:=new(bool)
v=val
err=json.Unmarshal([]byte(param),v)
default:
err=errors.New(fmt.Sprintf("Not supported parameter type: %v",arg.Type))
}
return v,err
}
Full tutorial on how to pack and upack values for a contract is here:
https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts