-
Notifications
You must be signed in to change notification settings - Fork 127
Abi Generation V2 #1459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Abi Generation V2 #1459
Changes from all commits
Commits
Show all changes
192 commits
Select commit
Hold shift + click to select a range
92e08ec
naive first marshalling attemmpt
containerman17 6a3d006
int 8-int64 supported
containerman17 5618d6c
negative numbers support
containerman17 9fbd36c
support maps
containerman17 9b0c5c2
funky benchmark
containerman17 0f4d1c6
structs reflection caching attempt
containerman17 1c5b8a2
some fuzz tests
containerman17 fe39c66
fix speed comment
containerman17 66c86f8
relocate implementation out of test
containerman17 02c3643
benchmark
containerman17 8fb31da
rename test to TestMakeSureMarshalUnmarshalIsNotTooSlow
containerman17 acb7e45
fix fuzz test
containerman17 529d1c4
spec tests for js implant
containerman17 1a38839
update spec tests
containerman17 bfa9aea
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 55e9bcd
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 d31dc72
remove fuzz test
containerman17 5c08120
simplify to 2 types
containerman17 d7ec6e4
restore original logic
containerman17 103db88
rewrite marshal with avalanchego's wrappers.Packer
containerman17 2df75a4
pack bytes with uint32 and everything else with uint16
containerman17 c2c0e64
check for long arrays and strings, marshal maps with uint16
containerman17 3279d3d
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 0d62a73
update benchmark results
containerman17 1229c35
move to codec
containerman17 917335b
come back to codec.packer
containerman17 c7b49bd
speed up TestMakeSureMarshalUnmarshalIsNotTooSlow a bit
containerman17 dfe750b
support pointer to a struct
containerman17 1eada5e
auto marshaller integration
containerman17 6d48163
lint
containerman17 d78337a
remove a slow test breaking CI
containerman17 08ca5fa
fix linter errors
containerman17 a760610
faster reflection cache
containerman17 d0b9b33
minimize test to exclude testing errors
containerman17 2b61fee
unsafe type caching
containerman17 88ad966
simplify benchmark
containerman17 ebf421e
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 9d48156
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 364ea97
update benchmarks
containerman17 5aa6aa6
add benchmark results
containerman17 70f5185
add benchmem
containerman17 55e9e90
add benchmem results
containerman17 bc99331
deprecate string operations
containerman17 bbd80e0
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 2dcfe25
move empty address error
containerman17 38a6380
empty file
containerman17 efc1970
lint
containerman17 82cb7f8
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 f92b4f5
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 c2a7741
Merge branch 'containerman/standardize-marshal-function' of https://g…
containerman17 4146154
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 43d55f8
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 6b932ab
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 5370f77
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 30e4201
remove .prof
containerman17 2fdd6fe
correct 'marshall' to 'marshal' according to Go conventions
containerman17 cf0e771
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 b43de9a
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 0383d88
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 7ec6da9
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 a330078
simplify codec.Packer
containerman17 15b5a44
get chainid from tmpnet instead of the platform (#1458)
containerman17 630efb0
lint
containerman17 dc1a194
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 10c51b2
change to linearcodec
containerman17 2aaac18
lint
containerman17 66df721
go mod tidy
containerman17 e8aed2e
add serialize tag to Transfer
containerman17 ef5be50
abi generation
containerman17 0b0ecc5
spec tests
containerman17 81472c7
ABI in RPC
containerman17 4f02e00
Merge branch 'main' into containerman/standardize-marshal-function
containerman17 0116d32
Merge branch 'containerman/standardize-marshal-function' into abi
containerman17 bb29003
HasTypeID as a separate iface
containerman17 f8865fa
add TypeParser.GetRegisteredTypes method
containerman17 efc7670
move ABI to core API
containerman17 b9406f3
remove unused errors
containerman17 0afbe10
auto size calculation
containerman17 bb3b03e
Merge branch 'containerman/standardize-marshal-function' into abi
containerman17 5b49eda
rename LinearCodecInstance
containerman17 534e82d
lint
containerman17 cb157e0
update from #1198
containerman17 9a22c00
from clean slate
containerman17 195b570
Merge branch 'main' into abi
containerman17 0a9ecf7
return abi logic
containerman17 3c0317b
nit: remove function name in panics
containerman17 f758d2b
transaction test nit
containerman17 74a2147
catch up with main
containerman17 848fa01
Merge branch 'main' into abi
containerman17 05b9a84
lint
containerman17 e3b4976
rename HasTypeID to Typed
containerman17 3a35d42
Merge branch 'main' into abi
containerman17 f960449
separate package for abi
containerman17 76762f9
restore spec tests
containerman17 9c38001
treat codec.Address as a byte array while serializing
containerman17 2dde532
comments and nits
containerman17 9018981
use set.Set instead of map[reflect.Type]bool
containerman17 0b4c679
Merge branch 'main' into abi
containerman17 c3daf14
lint
containerman17 52f44b5
Merge branch 'main' into abi
containerman17 0941d9b
remove memo field
containerman17 c92fc27
require serialize=true
containerman17 3bbe982
remove a breaker
containerman17 43e273c
calculate ABI in place
containerman17 d508fb3
use ABI as struct in implementation
containerman17 6448d62
stable ABI hash
containerman17 c74da71
ABI wants its own ABI
containerman17 60d3bdf
rename abi to vmabi
containerman17 90c1a23
remove ABI for ABI
containerman17 888678b
redo map as an array
containerman17 52f40ae
clean up test specs a bit
containerman17 1f627d0
basic codegen and refactor tests WIP
containerman17 8c0e813
trying different naming
containerman17 012c9be
spec simplification WIP
containerman17 1c6e9de
go generate
containerman17 6ea5052
lower case json
containerman17 b49e7dd
proper codegen test
containerman17 90d416f
further simplify spec tests
containerman17 913be5a
transfer test
containerman17 e26553d
file based spec test
containerman17 0d7fe63
simplify tests
containerman17 192d312
ABI of ABI
containerman17 65a7961
Outer/Inner struct tests for TS debug
containerman17 9386601
Merge branch 'main' into abi
containerman17 af3aaa1
lint
containerman17 80f26b1
check ABI
containerman17 2422191
Merge branch 'main' into abi
containerman17 063371c
lost in merge
containerman17 6b20600
remove comment lines in abi test
containerman17 6ea64b7
remove a debug statement
containerman17 357b943
Merge branch 'main' into abi
containerman17 e853d67
Merge branch 'main' into abi
containerman17 044f78f
move mock abi file
containerman17 df03e64
rename to abigen
containerman17 ecd2dcb
use cobra
containerman17 ce6316f
Update codec/address.go
containerman17 5056833
require that the " characters in address string
containerman17 e00eeb0
rename ABI-related stuff
containerman17 34e53bf
fix tests after renaming
containerman17 9256382
test full marshal cycle
containerman17 0f3467d
TestDescribeVM
containerman17 1a4fbd3
Update abi/codegen.go
containerman17 7cfce68
remove StringAsBytes
containerman17 4e5ab4d
Merge branch 'abi' of https://github.com/ava-labs/hypersdk into abi
containerman17 e28f5ca
add unicode package
containerman17 f191e3c
lint
containerman17 179729f
go mod tidy
containerman17 6733ad7
Merge branch 'main' into abi
containerman17 0ed9b62
flatten types def in abi
containerman17 a17b7a5
don't use mixed receivers
containerman17 8eb134a
inline vm.Hash into a test
containerman17 e58bea0
rename abi.VM to abi.ABI
containerman17 a08ca8d
remove embed
containerman17 0af8928
funish renaming
containerman17 8bf7870
get rid of vm.getabi
containerman17 6867827
nit avoid redundant import alias
containerman17 7c214da
DescribeVM -> NewABI
containerman17 a072bba
lint
containerman17 0738ff9
mock gen
containerman17 c8bc483
share typesAlreadyProcessed across describing multiple actions
containerman17 89bfae5
put a comment on each test
containerman17 ad0cdc7
Update abi/auto_marshal_abi_spec_test.go
containerman17 4e91369
nit: objectBytes
containerman17 790c1ea
Merge branch 'abi' of https://github.com/ava-labs/hypersdk into abi
containerman17 8769ec1
remove mustPrintOrderedJSON
containerman17 814122e
comment on empty names
containerman17 4505daf
comment on IsUpper
containerman17 23ef4b8
revert typealias
containerman17 58b77bb
TODO here to switch to the new address format
containerman17 9e918c6
We should follow the style of funcName does X
containerman17 a9a4a1d
use rune in cobra
containerman17 2d80339
comment on Dereference
containerman17 be0a7f6
comment on serialize tag
containerman17 0e10aa0
use %s and t instead of t.String()
containerman17 51ef70b
Merge branch 'main' into abi
containerman17 771cc44
readme
containerman17 2ed650e
rename mockabi_test
containerman17 0179e43
lint
containerman17 b2aa9df
Update api/jsonrpc/server.go
aaronbuchwald 157c619
expectedABIJSON capitalization
containerman17 4a34fda
Merge branch 'main' into abi
containerman17 96b7446
adopt vm with contracts
containerman17 0d9c004
readme update
containerman17 dd093d4
readme fix
containerman17 69edc0f
formatting
containerman17 3abbc04
readme
containerman17 85f4e3a
typo
containerman17 dcaa47a
type table
containerman17 976ad57
explain why we need ABI
containerman17 75da953
Testing algo
containerman17 8de8534
whitespace
containerman17 2322462
ABI readme nits (#1548)
aaronbuchwald File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
# ABI Package | ||
|
||
## Overview | ||
The ABI package provides functionality for marshaling and unmarshaling actions. It is designed to work across different language implementations. | ||
|
||
## ABI Format | ||
The ABI is defined in JSON format, as shown in the `abi.json` file: | ||
```json | ||
{ | ||
"actions": [ | ||
{ | ||
"id": 1, | ||
"action": "MockObjectSingleNumber" | ||
}, | ||
], | ||
"types": [ | ||
{ | ||
"name": "MockObjectSingleNumber", | ||
"fields": [ | ||
{ | ||
"name": "Field1", | ||
"type": "uint16" | ||
} | ||
] | ||
}, | ||
] | ||
} | ||
``` | ||
|
||
The ABI consists of two main sections: | ||
- actions: A list of action definitions, each with a typeID and action name (action name specifies the type) | ||
- types: A dictionary of types including their name and corresponding fields | ||
|
||
The type in each field must either be included in the ABI's `types` or in the list of [Supported Primitive Types](#supported-primitive-types). | ||
|
||
## Test Vectors | ||
This implementation provides `testdata/` for implementations in any other language. | ||
|
||
To verify correctness, an implementation can implement the following pseudocode: | ||
``` | ||
abi = abi.json | ||
|
||
for filename in testdata/*.hex: | ||
if filename.endswith(".hash.hex"): | ||
continue | ||
expectedHex = readFile(filename) | ||
json = readFile(filename.replace(".hex", ".json")) | ||
|
||
actualHex = Marshal(abi, json) | ||
if actualHex != expectedHex: | ||
raise "Hex values do not match" | ||
|
||
``` | ||
|
||
## ABI Verification | ||
Frontends can use the ABI to display proper action and field names. For a wallet to verify it knows what it's signing, it must ensure that a canonical hash of the ABI is included in the message it signs. | ||
|
||
A correct VM will verify the signature against the same ABI hash, such that verification fails if the wallet signed an action against a different than expected ABI. | ||
|
||
This enables frontends to provide a verifiable display of what they are asking users to sign. | ||
|
||
## Constraints | ||
- Actions require an ID, other structs / types do not require one | ||
- Multiple structs with the same name from different packages are not supported | ||
- Maps are not supported; use slices (arrays) instead | ||
- Built-in types include the special case type aliases: `codec.Address` and `codec.Bytes` | ||
|
||
## Generating Golang Bindings | ||
Use cmd/abigen to automatically generate Go bindings from an ABI's JSON. | ||
|
||
For example, to auto-generate golang bindings for the test ABI provided in `./abi/testdata/abi.json` run: | ||
|
||
```sh | ||
go run ./cmd/abigen/ ./abi/testdata/abi.json ./example.go --package=testpackage | ||
``` | ||
|
||
This should generate the same code that is present in `./abi/mockabi_test.go`. | ||
|
||
## Supported Primitive Types | ||
|
||
| Type | Range/Description | JSON Serialization | Binary Serialization | | ||
|----------|----------------------------------------------------------|--------------------|---------------------------------------| | ||
| `bool` | true or false | boolean | 1 byte | | ||
| `uint8` | numbers from 0 to 255 | number | 1 byte | | ||
| `uint16` | numbers from 0 to 65535 | number | 2 bytes | | ||
| `uint32` | numbers from 0 to 4294967295 | number | 4 bytes | | ||
| `uint64` | numbers from 0 to 18446744073709551615 | number | 8 bytes | | ||
| `int8` | numbers from -128 to 127 | number | 1 byte | | ||
| `int16` | numbers from -32768 to 32767 | number | 2 bytes | | ||
| `int32` | numbers from -2147483648 to 2147483647 | number | 4 bytes | | ||
| `int64` | numbers from -9223372036854775808 to 9223372036854775807 | number | 8 bytes | | ||
| `Address`| 33 byte array | base64 | 33 bytes | | ||
| `Bytes` | byte array | base64 | uint32 length + bytes | | ||
| `string` | string | string | uint16 length + bytes | | ||
| `[]T` | for any `T` in the above list, serialized as an array | array | uint32 length + elements | | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package abi | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
|
||
"github.com/ava-labs/avalanchego/utils/set" | ||
|
||
"github.com/ava-labs/hypersdk/codec" | ||
) | ||
|
||
type ABI struct { | ||
Actions []Action `serialize:"true" json:"actions"` | ||
Types []Type `serialize:"true" json:"types"` | ||
} | ||
|
||
var _ codec.Typed = (*ABI)(nil) | ||
|
||
func (ABI) GetTypeID() uint8 { | ||
return 0 | ||
} | ||
|
||
type Field struct { | ||
Name string `serialize:"true" json:"name"` | ||
Type string `serialize:"true" json:"type"` | ||
} | ||
|
||
type Action struct { | ||
ID uint8 `serialize:"true" json:"id"` | ||
Action string `serialize:"true" json:"action"` | ||
} | ||
|
||
type Type struct { | ||
Name string `serialize:"true" json:"name"` | ||
Fields []Field `serialize:"true" json:"fields"` | ||
} | ||
|
||
func NewABI(actions []codec.Typed) (ABI, error) { | ||
vmActions := make([]Action, 0) | ||
vmTypes := make([]Type, 0) | ||
typesSet := set.Set[string]{} | ||
typesAlreadyProcessed := set.Set[reflect.Type]{} | ||
|
||
for _, action := range actions { | ||
actionABI, typeABI, err := describeAction(action, typesAlreadyProcessed) | ||
if err != nil { | ||
return ABI{}, err | ||
} | ||
vmActions = append(vmActions, actionABI) | ||
for _, t := range typeABI { | ||
if !typesSet.Contains(t.Name) { | ||
aaronbuchwald marked this conversation as resolved.
Show resolved
Hide resolved
|
||
vmTypes = append(vmTypes, t) | ||
typesSet.Add(t.Name) | ||
} | ||
} | ||
} | ||
return ABI{Actions: vmActions, Types: vmTypes}, nil | ||
} | ||
|
||
// describeAction generates the Action and Types for a single action. | ||
// It handles both struct and pointer types, and recursively processes nested structs. | ||
// Does not support maps or interfaces - only standard go types, slices, arrays and structs | ||
func describeAction(action codec.Typed, typesAlreadyProcessed set.Set[reflect.Type]) (Action, []Type, error) { | ||
t := reflect.TypeOf(action) | ||
if t.Kind() == reflect.Ptr { | ||
t = t.Elem() | ||
} | ||
|
||
actionABI := Action{ | ||
ID: action.GetTypeID(), | ||
Action: t.Name(), | ||
} | ||
|
||
typesABI := make([]Type, 0) | ||
typesLeft := []reflect.Type{t} | ||
|
||
// Process all types, including nested ones | ||
for { | ||
var nextType reflect.Type | ||
nextTypeFound := false | ||
for _, anotherType := range typesLeft { | ||
if !typesAlreadyProcessed.Contains(anotherType) { | ||
nextType = anotherType | ||
nextTypeFound = true | ||
break | ||
} | ||
} | ||
if !nextTypeFound { | ||
break | ||
} | ||
|
||
fields, moreTypes, err := describeStruct(nextType) | ||
if err != nil { | ||
return Action{}, nil, err | ||
} | ||
|
||
typesABI = append(typesABI, Type{ | ||
Name: nextType.Name(), | ||
Fields: fields, | ||
}) | ||
typesLeft = append(typesLeft, moreTypes...) | ||
|
||
typesAlreadyProcessed.Add(nextType) | ||
} | ||
|
||
return actionABI, typesABI, nil | ||
} | ||
|
||
// describeStruct analyzes a struct type and returns its fields and any nested struct types it found | ||
func describeStruct(t reflect.Type) ([]Field, []reflect.Type, error) { | ||
kind := t.Kind() | ||
|
||
if kind != reflect.Struct { | ||
return nil, nil, fmt.Errorf("type %s is not a struct", t) | ||
} | ||
|
||
fields := make([]Field, 0) | ||
otherStructsSeen := make([]reflect.Type, 0) | ||
|
||
for i := 0; i < t.NumField(); i++ { | ||
field := t.Field(i) | ||
fieldType := field.Type | ||
fieldName := field.Name | ||
|
||
// Skip any field that will not be serialized by the codec | ||
serializeTag := field.Tag.Get("serialize") | ||
if serializeTag != "true" { | ||
continue | ||
} | ||
aaronbuchwald marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Handle JSON tag for field name override | ||
jsonTag := field.Tag.Get("json") | ||
if jsonTag != "" { | ||
parts := strings.Split(jsonTag, ",") | ||
fieldName = parts[0] | ||
} | ||
|
||
if field.Anonymous && fieldType.Kind() == reflect.Struct { | ||
// Handle embedded struct by flattening its fields | ||
embeddedFields, moreTypes, err := describeStruct(fieldType) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
fields = append(fields, embeddedFields...) | ||
otherStructsSeen = append(otherStructsSeen, moreTypes...) | ||
} else { | ||
arrayPrefix := "" | ||
|
||
// Here we assume that all types without a name are slices. | ||
// We completely ignore the fact that maps exist as we don't support them. | ||
// Types like `type Bytes = []byte` are slices technically, but they have a name | ||
// and we need them to be named types instead of slices. | ||
for fieldType.Name() == "" { | ||
arrayPrefix += "[]" | ||
fieldType = fieldType.Elem() | ||
} | ||
Comment on lines
+157
to
+160
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we add a comment explaining the exact assumption we're using here ie. exactly what set of types will return their name as empty string? |
||
|
||
typeName := arrayPrefix + fieldType.Name() | ||
|
||
// Add nested structs and pointers to structs to the list for processing | ||
if fieldType.Kind() == reflect.Struct { | ||
otherStructsSeen = append(otherStructsSeen, fieldType) | ||
} else if fieldType.Kind() == reflect.Ptr { | ||
otherStructsSeen = append(otherStructsSeen, fieldType.Elem()) | ||
} | ||
|
||
fields = append(fields, Field{ | ||
Name: fieldName, | ||
Type: typeName, | ||
}) | ||
} | ||
} | ||
|
||
return fields, otherStructsSeen, nil | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package abi | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ava-labs/hypersdk/codec" | ||
) | ||
|
||
func TestNewABI(t *testing.T) { | ||
require := require.New(t) | ||
|
||
actualABI, err := NewABI([]codec.Typed{ | ||
MockObjectSingleNumber{}, | ||
MockActionTransfer{}, | ||
MockObjectAllNumbers{}, | ||
MockObjectStringAndBytes{}, | ||
MockObjectArrays{}, | ||
MockActionWithTransfer{}, | ||
MockActionWithTransferArray{}, | ||
Outer{}, | ||
}) | ||
require.NoError(err) | ||
|
||
expectedABIJSON := mustReadFile(t, "testdata/abi.json") | ||
expectedABI := mustJSONParse[ABI](t, string(expectedABIJSON)) | ||
|
||
require.Equal(expectedABI, actualABI) | ||
} | ||
|
||
func TestGetABIofABI(t *testing.T) { | ||
require := require.New(t) | ||
|
||
actualABI, err := NewABI([]codec.Typed{ABI{}}) | ||
require.NoError(err) | ||
|
||
expectedABIJSON := mustReadFile(t, "testdata/abi.abi.json") | ||
expectedABI := mustJSONParse[ABI](t, string(expectedABIJSON)) | ||
|
||
require.Equal(expectedABI, actualABI) | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.