Add support for Uint128
Closed this issue · 7 comments
This should JSON parse/serialize as a string, but be a usable u128 number to do math in Go.
We need this for proper Coin compatibility (not just using u64 with json string and hoping for no overflow)
What are the plans on uint128, I've looked at the issue and I think we could use https://github.com/lukechampine/uint128
If we use the library, we'd need to create an intermediary type for human representation in json ser/de
I think we could also consider to pull the code inside cosmwasm-go and just implement the json marshaller interface and avoid creating the intermediary type.
Sounds like the proper library.
They do comment about just copying the code as well, which I am happy to do with some attribution comments.
Manually implementing de/ser with tinyjson is a bit special, as they use stream encoding/decoding internally and the MarshalJSON()
and UnmarshalJSON()
methods are really just for external users (not other structs that embed this)
uint128
is an object that we're going to unmarshal from a string and marshal back to a string, so actually it is a json primitive type that we want to deal with at code level as if it was an object.
This is something that would need to be addressed from the codegen software (IIRC we maintain a fork of tinyjson?).
I can see two paths:
-
Make uint128 a 'well-known' type from tinyjson codegen perspective.
Pros: easier from developers perspective ( no need to signal to tinyjson codegen that this is a custom type)
Cons: devs cannot create their own custom types (which is not even always necessarily bad). -
Add a feature to tinyjson that allows to parse primitive types (strings, floats) as custom types.
Pros: more customisation.
Cons: we need to signal to tinyjson codegen when a type is actually custom. Custom types can be abused.
Now regarding solution 2:
We can signal to tinyjson codegen that this isa custom type with a struct tag:
type Balance struct {
Amount math.Uint128 string `json:"amount" tinyjson:"customtype"`,
Denom string `json:"denom"`,
}
The problem with this approach is that you'd find a lot of structures with "customtype" which is annoying with common types such as Uin128, Dec128 etc
Another solution would be to check if a type inside a struct generated with tinyjson implements the custom type interface (can this be done from AST?). This would be more complex to develop but far easier from dev perspective.
You can simply use //tinyjson:skip
on top of uint128.
That will avoid the auto code-gen, and allow you to implement the methods yourself.
// MarshalJSON supports json.Marshaler interface
func (v MigrateMsg) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
v. MarshalTinyJSON(&w)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalTinyJSON supports tinyjson.Marshaler interface
func (v MigrateMsg) MarshalTinyJSON(w *jwriter.Writer) {
// TODO
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *MigrateMsg) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
v. UnmarshalTinyJSON(&r)
return r.Error()
}
// UnmarshalTinyJSON supports tinyjson.Unmarshaler interface
func (v *MigrateMsg) UnmarshalTinyJSON(l *jlexer.Lexer) {
// TODO
}
But yes, this will involve copying uint128 into this repo
Code looks a bit ugly, but something like this (from msg_tinyjson.go
), look at any other *_tinyjson.go
for "inspiration", but I am sure you could hand-craft cleaner code.
func tinyjsonF5cd6cf9DecodeGithubComCosmwasmCosmwasmGoStdTypes(in *jlexer.Lexer, out *WithdrawDelegatorRewardMsg) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "validator":
out.Validator = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func tinyjsonF5cd6cf9EncodeGithubComCosmwasmCosmwasmGoStdTypes(out *jwriter.Writer, in WithdrawDelegatorRewardMsg) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"validator\":"
out.RawString(prefix[1:])
out.String(string(in.Validator))
}
out.RawByte('}')
}