[Ethereum] ethereumjs-abi equivalent for Golang

abiencodinggo-ethereumgolang

ethereumjs-abi is great for easily ABI encoding and decoding in javascript. Is there an equivalently simple package for Golang?

Geth's accounts/abipackage does this, but there's a lot of setup and it's hard to change the types, because you have to specify a full ABI-JSON. I imagine this is a common need and solved. Any suggestions?

Example of ethereumjs-abi:

let encoded = abi.rawEncode(["bytes32", "bytes"], [val1, val2])

Best Answer

Using Ethereum's official golangs 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

Related Topic