2

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])

2

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

  • Thanks for your answer, but that's over 100 lines of code. I'm looking for something less verbose, ideally single digits in terms of LOC. – Steve Ellis Aug 29 '18 at 1:15
  • it is 1 line too. the 100 lines is a use case for a C++ app – Nulik Aug 29 '18 at 1:17
  • OK, possibly more what I was looking for. Can you explain how you got contract_abi? That variable seems to appear out of nowhere, and it seems to be the important part. – Steve Ellis Aug 30 '18 at 4:32
  • contract_abi is the object of type abi.ABI defined in ./accounts/abi/abi.go , you get it feeding the abi string , for example: erc20_methods_abi, err = abi.JSON(strings.NewReader(erc20_methods_abi_json_str)) (erc20_methods_abi_json_str is an ASCII string with the abi definition) – Nulik Aug 30 '18 at 15:43
  • Could you include that in your answer? The C++ part is misleading because it's not really clear what it really does without reading it, and it's a lot of code. – Steve Ellis Sep 4 '18 at 17:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.