tendermint / go-amino

Protobuf3 with Interface support - Designed for blockchains (deterministic, upgradeable, fast, and compact)
Other
259 stars 78 forks source link

Amino cannot encode big.Int from math/big correctly #241

Closed nnkken closed 5 years ago

nnkken commented 5 years ago
package main

import "fmt"
import "math/big"
import "github.com/tendermint/go-amino"

func main() {
    n := big.NewInt(1234)
    bs, err := amino.MarshalBinaryBare(n)
    if err != nil {
        panic(err)
    }
    fmt.Println(bs)
    var n2 *big.Int
    err = amino.UnmarshalBinaryBare(bs, &n)
    if err != nil {
        panic(err)
    }
    fmt.Println(n2.String())
}

Expected result: printing some binary representation of 1234 as []byte, and getting back 1234 in n2 Actual result: printing [] and <nil>

It this a bug or by design?

liamsi commented 5 years ago

big.Ints aren't supported: although they are a struct (and hence could be encoded without an error), their fields are not-exported:

type Int struct {
    neg bool // sign
    abs nat  // absolute value of the integer
}

If you really need to encode them you could use their string representation though. This should work for instance:

package main

import "fmt"
import "math/big"
import "github.com/tendermint/go-amino"

func main() {
    type BigInt struct {
        N string
    }
    n := big.NewInt(1234)
    bi := BigInt{N: n.String()}
    bs, err := amino.MarshalBinaryBare(bi)
    if err != nil {
        panic(err)
    }
    fmt.Println(bs)
    var n2 BigInt
    err = amino.UnmarshalBinaryBare(bs, &n2)
    if err != nil {
        panic(err)
    }
        fmt.Println(n2.N)
        n3, ok := big.NewInt(0).SetString(n2.N, 10)
    if !ok {
        panic("couldn't parse big.Int")
    }
    fmt.Println(n3)
}
nnkken commented 5 years ago

Got it. I'm now working around by having a struct wrapping *big.Int and implementing MarshalAmino and UnmarshalAmino:

// BigInt is an adaptor of big.Int, implementing AminoMarshaler and AminoUnmarshaler
type BigInt struct {
    *big.Int
}

// MarshalAmino implements AminoMarshaler
func (n BigInt) MarshalAmino() ([]byte, error) {
    return n.Bytes(), nil
}

// UnmarshalAmino implements AminoUnmarshaler
func (n *BigInt) UnmarshalAmino(bs []byte) error {
    n.Int = new(big.Int).SetBytes(bs)
    return nil
}

(I don't need to support negative numbers, so using .Bytes() is OK for me)

I cannot find the document of MarshalAmino and UnmarshalAmino, so I'm not sure that this is correct, but at least MarshalBinaryBare and UnmarshalBinaryBare works for me.

liamsi commented 5 years ago

That works as well. MarshalAmino and UnmarshalAmino will be called when calling MarshalBinaryBare and UnmarshalBinaryBare.