From 0e6b2ba73f1b9bbb70ca6daaf6b398fbaf151dc8 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 19:48:51 -0400 Subject: [PATCH 01/78] add generic --- heap/heap.go | 30 ++++++++++++++---------------- heap/heap_test.go | 28 ++++++++++++++-------------- heap/inner_heap.go | 44 +++++++++++++++++++++----------------------- 3 files changed, 49 insertions(+), 53 deletions(-) diff --git a/heap/heap.go b/heap/heap.go index b56f030d57..fe263011cd 100644 --- a/heap/heap.go +++ b/heap/heap.go @@ -6,74 +6,72 @@ package heap import ( "cmp" "container/heap" - - "github.com/ava-labs/avalanchego/ids" ) // Heap[I,V] is used to track objects of [I] by [Val]. // // This data structure does not perform any synchronization and is not // safe to use concurrently without external locking. -type Heap[I any, V cmp.Ordered] struct { - ih *innerHeap[I, V] +type Heap[T comparable, I any, V cmp.Ordered] struct { + ih *innerHeap[T, I, V] } // New returns an instance of Heap[I,V] -func New[I any, V cmp.Ordered](items int, isMinHeap bool) *Heap[I, V] { - return &Heap[I, V]{newInnerHeap[I, V](items, isMinHeap)} +func New[T comparable, I any, V cmp.Ordered](items int, isMinHeap bool) *Heap[T, I, V] { + return &Heap[T, I, V]{newInnerHeap[T, I, V](items, isMinHeap)} } // Len returns the number of items in ih. -func (h *Heap[I, V]) Len() int { return h.ih.Len() } +func (h *Heap[T, I, V]) Len() int { return h.ih.Len() } // Get returns the entry in th associated with [id], and a bool if [id] was // found in th. -func (h *Heap[I, V]) Get(id ids.ID) (*Entry[I, V], bool) { +func (h *Heap[T, I, V]) Get(id T) (*Entry[T, I, V], bool) { return h.ih.Get(id) } // Has returns whether [id] is found in th. -func (h *Heap[I, V]) Has(id ids.ID) bool { +func (h *Heap[T, I, V]) Has(id T) bool { return h.ih.Has(id) } // Items returns all items in the heap in sorted order. You should not modify // the response. -func (h *Heap[I, V]) Items() []*Entry[I, V] { +func (h *Heap[T, I, V]) Items() []*Entry[T, I, V] { return h.ih.items } // Push can be called by external users instead of using `containers.heap`, // which makes using this heap less error-prone. -func (h *Heap[I, V]) Push(e *Entry[I, V]) { +func (h *Heap[T, I, V]) Push(e *Entry[T, I, V]) { heap.Push(h.ih, e) } // Pop can be called by external users to remove an object from the heap at // a specific index instead of using `containers.heap`, // which makes using this heap less error-prone. -func (h *Heap[I, V]) Pop() *Entry[I, V] { +func (h *Heap[T, I, V]) Pop() *Entry[T, I, V] { if len(h.ih.items) == 0 { return nil } - return heap.Pop(h.ih).(*Entry[I, V]) + return heap.Pop(h.ih).(*Entry[T, I, V]) } // Remove can be called by external users to remove an object from the heap at // a specific index instead of using `containers.heap`, // which makes using this heap less error-prone. -func (h *Heap[I, V]) Remove(index int) *Entry[I, V] { +func (h *Heap[T, I, V]) Remove(index int) *Entry[T, I, V] { if index >= len(h.ih.items) { return nil } - return heap.Remove(h.ih, index).(*Entry[I, V]) + return heap.Remove(h.ih, index).(*Entry[T, I, V]) } // First returns the first item in the heap. This is the smallest item in // a minHeap and the largest item in a maxHeap. // // If no items are in the heap, it will return nil. -func (h *Heap[I, V]) First() *Entry[I, V] { +func (h *Heap[T, I, V]) First() *Entry[T, I, V] { if len(h.ih.items) == 0 { return nil } diff --git a/heap/heap_test.go b/heap/heap_test.go index bdf3198411..77ec5ac1de 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -17,28 +17,28 @@ type testItem struct { func TestUnit64HeapPushPopMin(t *testing.T) { require := require.New(t) - minHeap := New[*testItem, uint64](0, true) + minHeap := New[ids.ID, *testItem, uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") mempoolItem1 := &testItem{ids.GenerateTestID(), 10} mempoolItem2 := &testItem{ids.GenerateTestID(), 7} mempoolItem3 := &testItem{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[*testItem, uint64]{ + med := &Entry[ids.ID, *testItem, uint64]{ ID: mempoolItem1.id, Item: mempoolItem1, Val: mempoolItem1.value, Index: minHeap.Len(), } // Lesser UnitPrice - low := &Entry[*testItem, uint64]{ + low := &Entry[ids.ID, *testItem, uint64]{ ID: mempoolItem2.id, Item: mempoolItem2, Val: mempoolItem2.value, Index: minHeap.Len(), } // Greatest UnitPrice - high := &Entry[*testItem, uint64]{ + high := &Entry[ids.ID, *testItem, uint64]{ ID: mempoolItem3.id, Item: mempoolItem3, Val: mempoolItem3.value, @@ -67,7 +67,7 @@ func TestUnit64HeapPushPopMin(t *testing.T) { func TestUnit64HeapPushPopMax(t *testing.T) { require := require.New(t) - maxHeap := New[*testItem, uint64](0, false) + maxHeap := New[ids.ID, *testItem, uint64](0, false) require.Zero(maxHeap.Len(), "heap not initialized properly.") mempoolItem1 := &testItem{ids.GenerateTestID(), 10} @@ -75,21 +75,21 @@ func TestUnit64HeapPushPopMax(t *testing.T) { mempoolItem3 := &testItem{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[*testItem, uint64]{ + med := &Entry[ids.ID, *testItem, uint64]{ ID: mempoolItem1.id, Item: mempoolItem1, Val: mempoolItem1.value, Index: maxHeap.Len(), } // Lesser UnitPrice - low := &Entry[*testItem, uint64]{ + low := &Entry[ids.ID, *testItem, uint64]{ ID: mempoolItem2.id, Item: mempoolItem2, Val: mempoolItem2.value, Index: maxHeap.Len(), } // Greatest UnitPrice - high := &Entry[*testItem, uint64]{ + high := &Entry[ids.ID, *testItem, uint64]{ ID: mempoolItem3.id, Item: mempoolItem3, Val: mempoolItem3.value, @@ -119,10 +119,10 @@ func TestUnit64HeapPushPopMax(t *testing.T) { func TestUnit64HeapPushExists(t *testing.T) { // Push an item already in heap require := require.New(t) - minHeap := New[*testItem, uint64](0, true) + minHeap := New[ids.ID, *testItem, uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") mempoolItem := &testItem{ids.GenerateTestID(), 10} - entry := &Entry[*testItem, uint64]{ + entry := &Entry[ids.ID, *testItem, uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -142,11 +142,11 @@ func TestUnit64HeapPushExists(t *testing.T) { func TestUnit64HeapGetID(t *testing.T) { // Push an item and grab its ID require := require.New(t) - minHeap := New[*testItem, uint64](0, true) + minHeap := New[ids.ID, *testItem, uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") mempoolItem := &testItem{ids.GenerateTestID(), 10} - entry := &Entry[*testItem, uint64]{ + entry := &Entry[ids.ID, *testItem, uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -164,10 +164,10 @@ func TestUnit64HeapGetID(t *testing.T) { func TestUnit64HeapHasID(t *testing.T) { require := require.New(t) - minHeap := New[*testItem, uint64](0, true) + minHeap := New[ids.ID, *testItem, uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") mempoolItem := &testItem{ids.GenerateTestID(), 10} - entry := &Entry[*testItem, uint64]{ + entry := &Entry[ids.ID, *testItem, uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, diff --git a/heap/inner_heap.go b/heap/inner_heap.go index 4c8058c0d7..2e99366076 100644 --- a/heap/inner_heap.go +++ b/heap/inner_heap.go @@ -7,43 +7,41 @@ import ( "cmp" "container/heap" "fmt" - - "github.com/ava-labs/avalanchego/ids" ) -var _ heap.Interface = (*innerHeap[any, uint64])(nil) +var _ heap.Interface = (*innerHeap[any, any, uint64])(nil) -type Entry[I any, V cmp.Ordered] struct { - ID ids.ID // id of entry - Item I // associated item - Val V // Value to be prioritized +type Entry[T comparable, I any, V cmp.Ordered] struct { + ID T // id of entry + Item I // associated item + Val V // Value to be prioritized Index int // Index of the entry in heap } -type innerHeap[I any, V cmp.Ordered] struct { - isMinHeap bool // true for Min-Heap, false for Max-Heap - items []*Entry[I, V] // items in this heap - lookup map[ids.ID]*Entry[I, V] // ids in the heap mapping to an entry +type innerHeap[T comparable, I any, V cmp.Ordered] struct { + isMinHeap bool // true for Min-Heap, false for Max-Heap + items []*Entry[T, I, V] // items in this heap + lookup map[T]*Entry[T, I, V] // ids in the heap mapping to an entry } -func newInnerHeap[I any, V cmp.Ordered](items int, isMinHeap bool) *innerHeap[I, V] { - return &innerHeap[I, V]{ +func newInnerHeap[T comparable, I any, V cmp.Ordered](items int, isMinHeap bool) *innerHeap[T, I, V] { + return &innerHeap[T, I, V]{ isMinHeap: isMinHeap, - items: make([]*Entry[I, V], 0, items), - lookup: make(map[ids.ID]*Entry[I, V], items), + items: make([]*Entry[T, I, V], 0, items), + lookup: make(map[T]*Entry[T, I, V], items), } } // Len returns the number of items in ih. -func (ih *innerHeap[I, V]) Len() int { return len(ih.items) } +func (ih *innerHeap[T, I, V]) Len() int { return len(ih.items) } // Less compares the priority of [i] and [j] based on th.isMinHeap. // // This should never be called by an external caller and is required to // confirm to `heap.Interface`. -func (ih *innerHeap[I, V]) Less(i, j int) bool { +func (ih *innerHeap[T, I, V]) Less(i, j int) bool { if ih.isMinHeap { return ih.items[i].Val < ih.items[j].Val } @@ -54,7 +52,7 @@ func (ih *innerHeap[I, V]) Less(i, j int) bool { // // This should never be called by an external caller and is required to // confirm to `heap.Interface`. -func (ih *innerHeap[I, V]) Swap(i, j int) { +func (ih *innerHeap[T, I, V]) Swap(i, j int) { ih.items[i], ih.items[j] = ih.items[j], ih.items[i] ih.items[i].Index = i ih.items[j].Index = j @@ -65,8 +63,8 @@ func (ih *innerHeap[I, V]) Swap(i, j int) { // // This should never be called by an external caller and is required to // confirm to `heap.Interface`. -func (ih *innerHeap[I, V]) Push(x any) { - entry, ok := x.(*Entry[I, V]) +func (ih *innerHeap[T, I, V]) Push(x any) { + entry, ok := x.(*Entry[T, I, V]) if !ok { panic(fmt.Errorf("unexpected %T, expected *Entry", x)) } @@ -82,7 +80,7 @@ func (ih *innerHeap[I, V]) Push(x any) { // // This should never be called by an external caller and is required to // confirm to `heap.Interface`. -func (ih *innerHeap[I, V]) Pop() any { +func (ih *innerHeap[T, I, V]) Pop() any { n := len(ih.items) item := ih.items[n-1] ih.items[n-1] = nil // avoid memory leak @@ -93,13 +91,13 @@ func (ih *innerHeap[I, V]) Pop() any { // Get returns the entry in th associated with [id], and a bool if [id] was // found in th. -func (ih *innerHeap[I, V]) Get(id ids.ID) (*Entry[I, V], bool) { +func (ih *innerHeap[T, I, V]) Get(id T) (*Entry[T, I, V], bool) { entry, ok := ih.lookup[id] return entry, ok } // Has returns whether [id] is found in th. -func (ih *innerHeap[I, V]) Has(id ids.ID) bool { +func (ih *innerHeap[T, I, V]) Has(id T) bool { _, has := ih.Get(id) return has } From 18580b0c57b3d5778913616419424cb3f751ea40 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 19:51:27 -0400 Subject: [PATCH 02/78] emap passes --- eheap/eheap.go | 2 +- emap/emap.go | 6 +++--- emap/emap_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eheap/eheap.go b/eheap/eheap.go index 453ee316da..dbcef28a7a 100644 --- a/eheap/eheap.go +++ b/eheap/eheap.go @@ -29,7 +29,7 @@ type ExpiryHeap[T Item] struct { // containing [items]. func New[T Item](items int) *ExpiryHeap[T] { return &ExpiryHeap[T]{ - minHeap: heap.New[T, int64](items, true), + minHeap: heap.New[ids.ID, T, int64](items, true), } } diff --git a/emap/emap.go b/emap/emap.go index a5d1d3e083..68f6f642a2 100644 --- a/emap/emap.go +++ b/emap/emap.go @@ -29,7 +29,7 @@ type Item interface { type EMap[T Item] struct { mu sync.RWMutex - bh *heap.Heap[*bucket, int64] + bh *heap.Heap[ids.ID, *bucket, int64] seen set.Set[ids.ID] // Stores a set of unique tx ids times map[int64]*bucket // Uses timestamp as keys to map to buckets of ids. } @@ -39,7 +39,7 @@ func NewEMap[T Item]() *EMap[T] { return &EMap[T]{ seen: set.Set[ids.ID]{}, times: make(map[int64]*bucket), - bh: heap.New[*bucket, int64](120, true), + bh: heap.New[ids.ID, *bucket, int64](120, true), } } @@ -81,7 +81,7 @@ func (e *EMap[T]) add(id ids.ID, t int64) { items: []ids.ID{id}, } e.times[t] = b - e.bh.Push(&heap.Entry[*bucket, int64]{ + e.bh.Push(&heap.Entry[ids.ID, *bucket, int64]{ ID: id, Val: t, Item: b, diff --git a/emap/emap_test.go b/emap/emap_test.go index bb04963a87..09dc2a177d 100644 --- a/emap/emap_test.go +++ b/emap/emap_test.go @@ -27,7 +27,7 @@ func TestEmapNew(t *testing.T) { emptyE := &EMap[*TestTx]{ seen: set.Set[ids.ID]{}, times: make(map[int64]*bucket), - bh: heap.New[*bucket, int64](0, true), + bh: heap.New[ids.ID, *bucket, int64](0, true), } require.Equal(emptyE.seen, e.seen, "Emap did not return an empty emap struct.") require.Equal(emptyE.times, e.times, "Emap did not return an empty emap struct.") From d9bfb3aa602b3e303087a88341b275e9f758d958 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 19:52:16 -0400 Subject: [PATCH 03/78] eheap passes --- eheap/eheap.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eheap/eheap.go b/eheap/eheap.go index dbcef28a7a..6e1d823a6c 100644 --- a/eheap/eheap.go +++ b/eheap/eheap.go @@ -22,7 +22,7 @@ type Item interface { // instead of grouping by expiry to support this feature, which makes it // less efficient). type ExpiryHeap[T Item] struct { - minHeap *heap.Heap[T, int64] + minHeap *heap.Heap[ids.ID, T, int64] } // New returns an instance of ExpiryHeap with minHeap and maxHeap @@ -37,7 +37,7 @@ func New[T Item](items int) *ExpiryHeap[T] { func (eh *ExpiryHeap[T]) Add(item T) { itemID := item.ID() poolLen := eh.minHeap.Len() - eh.minHeap.Push(&heap.Entry[T, int64]{ + eh.minHeap.Push(&heap.Entry[ids.ID, T, int64]{ ID: itemID, Val: item.Expiry(), Item: item, From 25775c671cf1db66bcf854ba3ffa5275d8f85a72 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 19:57:13 -0400 Subject: [PATCH 04/78] fix tokenvm orderbook --- examples/tokenvm/orderbook/orderbook.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/tokenvm/orderbook/orderbook.go b/examples/tokenvm/orderbook/orderbook.go index 28f518c9a2..cf717ee705 100644 --- a/examples/tokenvm/orderbook/orderbook.go +++ b/examples/tokenvm/orderbook/orderbook.go @@ -35,7 +35,7 @@ type OrderBook struct { // dust orders from filling the heap. // // TODO: Allow operator to specify min creation supply per pair to be tracked - orders map[string]*heap.Heap[*Order, float64] + orders map[string]*heap.Heap[ids.ID, *Order, float64] orderToPair map[ids.ID]string // needed to delete from [CloseOrder] actions maxOrdersPerPair int l sync.RWMutex @@ -44,7 +44,7 @@ type OrderBook struct { } func New(c Controller, trackedPairs []string, maxOrdersPerPair int) *OrderBook { - m := map[string]*heap.Heap[*Order, float64]{} + m := map[string]*heap.Heap[ids.ID, *Order, float64]{} trackAll := false if len(trackedPairs) == 1 && trackedPairs[0] == allPairs { trackAll = true @@ -52,7 +52,7 @@ func New(c Controller, trackedPairs []string, maxOrdersPerPair int) *OrderBook { } else { for _, pair := range trackedPairs { // We use a max heap so we return the best rates in order. - m[pair] = heap.New[*Order, float64](maxOrdersPerPair+1, true) + m[pair] = heap.New[ids.ID, *Order, float64](maxOrdersPerPair+1, true) c.Logger().Info("tracking order book", zap.String("pair", pair)) } } @@ -86,10 +86,10 @@ func (o *OrderBook) Add(txID ids.ID, actor codec.Address, action *actions.Create return case !ok && o.trackAll: o.c.Logger().Info("tracking order book", zap.String("pair", pair)) - h = heap.New[*Order, float64](o.maxOrdersPerPair+1, true) + h = heap.New[ids.ID, *Order, float64](o.maxOrdersPerPair+1, true) o.orders[pair] = h } - h.Push(&heap.Entry[*Order, float64]{ + h.Push(&heap.Entry[ids.ID, *Order, float64]{ ID: order.ID, Val: float64(order.InTick) / float64(order.OutTick), Item: order, From 2de3e56561ebfcf009967749b16d995fb986dc6f Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 16:20:12 -0400 Subject: [PATCH 05/78] intro ActionID --- chain/dependencies.go | 4 ++++ chain/transaction.go | 25 +++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/chain/dependencies.go b/chain/dependencies.go index 6ca1057feb..d66e4cfe29 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -217,6 +217,10 @@ type Object interface { type Action interface { Object + // GetActionID returns the ActionID for an [Action] in a [Transaction]. There may be + // multiple [Action]s, so we pass its index in the [Action] array along with the txID. + GetActionID(i uint8, txID ids.ID) codec.Address + // MaxComputeUnits is the maximum amount of compute a given [Action] could use. This is // used to determine whether the [Action] can be included in a given block and to compute // the required fee to execute. diff --git a/chain/transaction.go b/chain/transaction.go index 5357692cea..f440aa353e 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -29,9 +29,8 @@ var ( type Transaction struct { Base *Base `json:"base"` - // TODO: turn [Action] into an array (#335) - Action Action `json:"action"` - Auth Auth `json:"auth"` + Actions []Action `json:"action"` + Auth Auth `json:"auth"` digest []byte bytes []byte @@ -40,10 +39,10 @@ type Transaction struct { stateKeys state.Keys } -func NewTx(base *Base, act Action) *Transaction { +func NewTx(base *Base, actions []Action) *Transaction { return &Transaction{ - Base: base, - Action: act, + Base: base, + Actions: actions, } } @@ -51,11 +50,17 @@ func (t *Transaction) Digest() ([]byte, error) { if len(t.digest) > 0 { return t.digest, nil } - actionID := t.Action.GetTypeID() - p := codec.NewWriter(t.Base.Size()+consts.ByteLen+t.Action.Size(), consts.NetworkSizeLimit) + size := t.Base.Size() + for _, action := range t.Actions { + size += consts.ByteLen + action.Size() + } + p := codec.NewWriter(size, consts.NetworkSizeLimit) t.Base.Marshal(p) - p.PackByte(actionID) - t.Action.Marshal(p) + p.PackInt(len(t.Actions)) + for i, action := range t.Actions { + p.PackByte(action.GetActionID(uint8(i), t.id)) + action.Marshal(p) + } return p.Bytes(), p.Err() } From 965b3133ef3519f4988643a506f228a829fb1d6a Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 16:21:24 -0400 Subject: [PATCH 06/78] intro max actions --- chain/dependencies.go | 2 ++ examples/morpheusvm/genesis/genesis.go | 6 ++++++ examples/morpheusvm/genesis/rules.go | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/chain/dependencies.go b/chain/dependencies.go index d66e4cfe29..0e598abdff 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -152,6 +152,8 @@ type Rules interface { GetStorageValueWriteUnits() uint64 // per chunk FetchCustom(string) (any, bool) + + GetMaxActionsPerTx() uint8 } type MetadataManager interface { diff --git a/examples/morpheusvm/genesis/genesis.go b/examples/morpheusvm/genesis/genesis.go index bfe536e32b..adeedf5696 100644 --- a/examples/morpheusvm/genesis/genesis.go +++ b/examples/morpheusvm/genesis/genesis.go @@ -54,6 +54,9 @@ type Genesis struct { StorageKeyWriteUnits uint64 `json:"storageKeyWriteUnits"` StorageValueWriteUnits uint64 `json:"storageValueWriteUnits"` // per chunk + // Action Per Tx + MaxActionsPerTx uint8 `json:"maxActionsPerTx"` + // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` } @@ -88,6 +91,9 @@ func Default() *Genesis { StorageValueAllocateUnits: 5, StorageKeyWriteUnits: 10, StorageValueWriteUnits: 3, + + // Action Per Tx + MaxActionsPerTx: 1, } } diff --git a/examples/morpheusvm/genesis/rules.go b/examples/morpheusvm/genesis/rules.go index bb375af7d3..1bded55a3e 100644 --- a/examples/morpheusvm/genesis/rules.go +++ b/examples/morpheusvm/genesis/rules.go @@ -95,3 +95,7 @@ func (r *Rules) GetWindowTargetUnits() fees.Dimensions { func (*Rules) FetchCustom(string) (any, bool) { return nil, false } + +func (*Rules) GetMaxActionsPerTx() uint8 { + return r.g.MaxActionsPerTx +} From 5351388a6430a994332250279383f83a46949423 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 16:46:55 -0400 Subject: [PATCH 07/78] enforce max actions + loop thru statekeys --- chain/dependencies.go | 2 +- chain/errors.go | 1 + chain/transaction.go | 41 ++++++++++++++++++++++++----------------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/chain/dependencies.go b/chain/dependencies.go index 0e598abdff..66a193b25d 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -244,7 +244,7 @@ type Action interface { // key (formatted as a big-endian uint16). This is used to automatically calculate storage usage. // // If any key is removed and then re-created, this will count as a creation instead of a modification. - StateKeys(actor codec.Address, txID ids.ID) state.Keys + StateKeys(actor codec.Address, actionID codec.Address) state.Keys // Execute actually runs the [Action]. Any state changes that the [Action] performs should // be done here. diff --git a/chain/errors.go b/chain/errors.go index a88d8ccb37..e131f4cc86 100644 --- a/chain/errors.go +++ b/chain/errors.go @@ -50,6 +50,7 @@ var ( ErrMisalignedTime = errors.New("misaligned time") ErrInvalidActor = errors.New("invalid actor") ErrInvalidSponsor = errors.New("invalid sponsor") + ErrTooManyActions = errors.New("too many actions") // Execution Correctness ErrInvalidBalance = errors.New("invalid balance") diff --git a/chain/transaction.go b/chain/transaction.go index f440aa353e..9f1d46850a 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -58,7 +58,7 @@ func (t *Transaction) Digest() ([]byte, error) { t.Base.Marshal(p) p.PackInt(len(t.Actions)) for i, action := range t.Actions { - p.PackByte(action.GetActionID(uint8(i), t.id)) + p.PackAddress(action.GetActionID(uint8(i), t.id)) action.Marshal(p) } return p.Bytes(), p.Err() @@ -107,17 +107,19 @@ func (t *Transaction) StateKeys(sm StateManager) (state.Keys, error) { if t.stateKeys != nil { return t.stateKeys, nil } + stateKeys := make(state.Keys) // Verify the formatting of state keys passed by the controller - actionKeys := t.Action.StateKeys(t.Auth.Actor(), t.ID()) - sponsorKeys := sm.SponsorStateKeys(t.Auth.Sponsor()) - stateKeys := make(state.Keys) - for _, m := range []state.Keys{actionKeys, sponsorKeys} { - for k, v := range m { - if !keys.Valid(k) { - return nil, ErrInvalidKeyValue + for i, action := range t.Actions { + actionKeys := action.StateKeys(t.Auth.Actor(), action.GetActionID(uint8(i), t.id)) + sponsorKeys := sm.SponsorStateKeys(t.Auth.Sponsor()) + for _, m := range []state.Keys{actionKeys, sponsorKeys} { + for k, v := range m { + if !keys.Valid(k) { + return nil, ErrInvalidKeyValue + } + stateKeys.Add(k, v) } - stateKeys.Add(k, v) } } @@ -259,6 +261,9 @@ func (t *Transaction) PreExecute( if end >= 0 && timestamp > end { return ErrAuthNotActivated } + if len(t.Actions) > r.GetMaxActionsPerTx() { + return ErrTooManyActions + } maxUnits, err := t.MaxUnits(s, r) if err != nil { return err @@ -419,13 +424,15 @@ func (t *Transaction) Marshal(p *codec.Packer) error { return p.Err() } - actionID := t.Action.GetTypeID() - authID := t.Auth.GetTypeID() - t.Base.Marshal(p) - p.PackByte(actionID) - t.Action.Marshal(p) - p.PackByte(authID) - t.Auth.Marshal(p) + for i, action := range t.Action { + actionID := action.GetActionID(uint8(i), t.id) + authID := t.Auth.GetTypeID() + t.Base.Marshal(p) + p.PackAddress(actionID) + t.Action.Marshal(p) + p.PackByte(authID) + t.Auth.Marshal(p) + } return p.Err() } @@ -507,7 +514,7 @@ func UnmarshalTx( var tx Transaction tx.Base = base - tx.Action = action + tx.Actions = []Action{action} tx.Auth = auth if err := p.Err(); err != nil { return nil, p.Err() From 2e3dc39834011504adbf8623655398be6429ab13 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 16:56:11 -0400 Subject: [PATCH 08/78] fix some tx errors --- chain/dependencies.go | 2 +- chain/transaction.go | 213 ++++++++++++++++++++++-------------------- 2 files changed, 112 insertions(+), 103 deletions(-) diff --git a/chain/dependencies.go b/chain/dependencies.go index 66a193b25d..ebf9d16967 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -153,7 +153,7 @@ type Rules interface { FetchCustom(string) (any, bool) - GetMaxActionsPerTx() uint8 + GetMaxActionsPerTx() int } type MetadataManager interface { diff --git a/chain/transaction.go b/chain/transaction.go index 9f1d46850a..2169352cdb 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -136,8 +136,10 @@ func (t *Transaction) Sponsor() codec.Address { return t.Auth.Sponsor() } func (t *Transaction) MaxUnits(sm StateManager, r Rules) (fees.Dimensions, error) { // Cacluate max compute costs maxComputeUnitsOp := math.NewUint64Operator(r.GetBaseComputeUnits()) - maxComputeUnitsOp.Add(t.Action.MaxComputeUnits(r)) - maxComputeUnitsOp.Add(t.Auth.ComputeUnits(r)) + for _, action := range t.Actions { + maxComputeUnitsOp.Add(action.MaxComputeUnits(r)) + maxComputeUnitsOp.Add(t.Auth.ComputeUnits(r)) + } maxComputeUnits, err := maxComputeUnitsOp.Value() if err != nil { return fees.Dimensions{}, err @@ -247,14 +249,16 @@ func (t *Transaction) PreExecute( if err := t.Base.Execute(r.ChainID(), r, timestamp); err != nil { return err } - start, end := t.Action.ValidRange(r) - if start >= 0 && timestamp < start { - return ErrActionNotActivated - } - if end >= 0 && timestamp > end { - return ErrActionNotActivated + for _, action := range t.Actions { + start, end := action.ValidRange(r) + if start >= 0 && timestamp < start { + return ErrActionNotActivated + } + if end >= 0 && timestamp > end { + return ErrActionNotActivated + } } - start, end = t.Auth.ValidRange(r) + start, end := t.Auth.ValidRange(r) if start >= 0 && timestamp < start { return ErrAuthNotActivated } @@ -290,7 +294,7 @@ func (t *Transaction) Execute( r Rules, ts *tstate.TStateView, timestamp int64, -) (*Result, error) { +) ([]*Result, error) { // Always charge fee first (in case [Action] moves funds) maxUnits, err := t.MaxUnits(s, r) if err != nil { @@ -315,107 +319,112 @@ func (t *Transaction) Execute( // are set when this function is defined. If any of them are // modified later, they will not be used here. ts.Rollback(ctx, actionStart) - return &Result{false, utils.ErrBytes(rerr), maxUnits, maxFee}, nil - } - success, actionCUs, output, err := t.Action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), t.id) - if err != nil { - return handleRevert(err) - } - if len(output) == 0 && output != nil { - // Enforce object standardization (this is a VM bug and we should fail - // fast) - return handleRevert(ErrInvalidObject) - } - if !success { - ts.Rollback(ctx, actionStart) - } - - // Calculate units used - computeUnitsOp := math.NewUint64Operator(r.GetBaseComputeUnits()) - computeUnitsOp.Add(t.Auth.ComputeUnits(r)) - computeUnitsOp.Add(actionCUs) - computeUnits, err := computeUnitsOp.Value() - if err != nil { - return handleRevert(err) + return []*Result{{false, utils.ErrBytes(rerr), maxUnits, maxFee}}, nil } + results := make([]*Result, 0) + for _, action := range t.Actions { + success, actionCUs, output, err := t.Actions.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), t.id) + if err != nil { + return handleRevert(err) + } + if len(output) == 0 && output != nil { + // Enforce object standardization (this is a VM bug and we should fail + // fast) + return handleRevert(ErrInvalidObject) + } + if !success { + ts.Rollback(ctx, actionStart) + } - // Because the key database is abstracted from [Auth]/[Actions], we can compute - // all storage use in the background. KeyOperations is unique to a view. - allocates, writes := ts.KeyOperations() - - // Because we compute the fee before [Auth.Refund] is called, we need - // to pessimistically precompute the storage it will change. - for key := range s.SponsorStateKeys(t.Auth.Sponsor()) { - // maxChunks will be greater than the chunks read in any of these keys, - // so we don't need to check for pre-existing values. - maxChunks, ok := keys.MaxChunks([]byte(key)) - if !ok { - return handleRevert(ErrInvalidKeyValue) + // Calculate units used + computeUnitsOp := math.NewUint64Operator(r.GetBaseComputeUnits()) + computeUnitsOp.Add(t.Auth.ComputeUnits(r)) + computeUnitsOp.Add(actionCUs) + computeUnits, err := computeUnitsOp.Value() + if err != nil { + return handleRevert(err) } - writes[key] = maxChunks - } - // We only charge for the chunks read from disk instead of charging for the max chunks - // specified by the key. - readsOp := math.NewUint64Operator(0) - for _, chunksRead := range reads { - readsOp.Add(r.GetStorageKeyReadUnits()) - readsOp.MulAdd(uint64(chunksRead), r.GetStorageValueReadUnits()) - } - readUnits, err := readsOp.Value() - if err != nil { - return handleRevert(err) - } - allocatesOp := math.NewUint64Operator(0) - for _, chunksStored := range allocates { - allocatesOp.Add(r.GetStorageKeyAllocateUnits()) - allocatesOp.MulAdd(uint64(chunksStored), r.GetStorageValueAllocateUnits()) - } - allocateUnits, err := allocatesOp.Value() - if err != nil { - return handleRevert(err) - } - writesOp := math.NewUint64Operator(0) - for _, chunksModified := range writes { - writesOp.Add(r.GetStorageKeyWriteUnits()) - writesOp.MulAdd(uint64(chunksModified), r.GetStorageValueWriteUnits()) - } - writeUnits, err := writesOp.Value() - if err != nil { - return handleRevert(err) - } - used := fees.Dimensions{uint64(t.Size()), computeUnits, readUnits, allocateUnits, writeUnits} + // Because the key database is abstracted from [Auth]/[Actions], we can compute + // all storage use in the background. KeyOperations is unique to a view. + allocates, writes := ts.KeyOperations() + + // Because we compute the fee before [Auth.Refund] is called, we need + // to pessimistically precompute the storage it will change. + for key := range s.SponsorStateKeys(t.Auth.Sponsor()) { + // maxChunks will be greater than the chunks read in any of these keys, + // so we don't need to check for pre-existing values. + maxChunks, ok := keys.MaxChunks([]byte(key)) + if !ok { + return handleRevert(ErrInvalidKeyValue) + } + writes[key] = maxChunks + } - // Check to see if the units consumed are greater than the max units - // - // This should never be the case but erroring here is better than - // underflowing the refund. - if !maxUnits.Greater(used) { - return nil, fmt.Errorf("%w: max=%+v consumed=%+v", ErrInvalidUnitsConsumed, maxUnits, used) - } + // We only charge for the chunks read from disk instead of charging for the max chunks + // specified by the key. + readsOp := math.NewUint64Operator(0) + for _, chunksRead := range reads { + readsOp.Add(r.GetStorageKeyReadUnits()) + readsOp.MulAdd(uint64(chunksRead), r.GetStorageValueReadUnits()) + } + readUnits, err := readsOp.Value() + if err != nil { + return handleRevert(err) + } + allocatesOp := math.NewUint64Operator(0) + for _, chunksStored := range allocates { + allocatesOp.Add(r.GetStorageKeyAllocateUnits()) + allocatesOp.MulAdd(uint64(chunksStored), r.GetStorageValueAllocateUnits()) + } + allocateUnits, err := allocatesOp.Value() + if err != nil { + return handleRevert(err) + } + writesOp := math.NewUint64Operator(0) + for _, chunksModified := range writes { + writesOp.Add(r.GetStorageKeyWriteUnits()) + writesOp.MulAdd(uint64(chunksModified), r.GetStorageValueWriteUnits()) + } + writeUnits, err := writesOp.Value() + if err != nil { + return handleRevert(err) + } + used := fees.Dimensions{uint64(t.Size()), computeUnits, readUnits, allocateUnits, writeUnits} + + // Check to see if the units consumed are greater than the max units + // + // This should never be the case but erroring here is better than + // underflowing the refund. + if !maxUnits.Greater(used) { + return nil, fmt.Errorf("%w: max=%+v consumed=%+v", ErrInvalidUnitsConsumed, maxUnits, used) + } - // Return any funds from unused units - // - // To avoid storage abuse of [Auth.Refund], we precharge for possible usage. - feeRequired, err := feeManager.MaxFee(used) - if err != nil { - return handleRevert(err) - } - refund := maxFee - feeRequired - if refund > 0 { - ts.DisableAllocation() - defer ts.EnableAllocation() - if err := s.Refund(ctx, t.Auth.Sponsor(), ts, refund); err != nil { + // Return any funds from unused units + // + // To avoid storage abuse of [Auth.Refund], we precharge for possible usage. + feeRequired, err := feeManager.MaxFee(used) + if err != nil { return handleRevert(err) } - } - return &Result{ - Success: success, - Output: output, + refund := maxFee - feeRequired + if refund > 0 { + ts.DisableAllocation() + defer ts.EnableAllocation() + if err := s.Refund(ctx, t.Auth.Sponsor(), ts, refund); err != nil { + return handleRevert(err) + } + } - Consumed: used, - Fee: feeRequired, - }, nil + results = append(results, &Result{ + Success: success, + Output: output, + + Consumed: used, + Fee: feeRequired, + }) + } + return results, nil } func (t *Transaction) Marshal(p *codec.Packer) error { From 89986fad448ba024e75f3232313fb8b8268dcd9e Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 16:57:38 -0400 Subject: [PATCH 09/78] fix builder errors --- chain/builder.go | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/chain/builder.go b/chain/builder.go index c6122563b1..2b028458dc 100644 --- a/chain/builder.go +++ b/chain/builder.go @@ -294,7 +294,7 @@ func BuildBlock( log.Warn("invalid tx: invalid state keys") return nil } - result, err := tx.Execute( + txResults, err := tx.Execute( ctx, feeManager, reads, @@ -315,29 +315,31 @@ func BuildBlock( defer blockLock.Unlock() // Ensure block isn't too big - if ok, dimension := feeManager.Consume(result.Consumed, maxUnits); !ok { - log.Debug( - "skipping tx: too many units", - zap.Int("dimension", int(dimension)), - zap.Uint64("tx", result.Consumed[dimension]), - zap.Uint64("block units", feeManager.LastConsumed(dimension)), - zap.Uint64("max block units", maxUnits[dimension]), - ) - restore = true + for _, result := range txResults { + if ok, dimension := feeManager.Consume(result.Consumed, maxUnits); !ok { + log.Debug( + "skipping tx: too many units", + zap.Int("dimension", int(dimension)), + zap.Uint64("tx", result.Consumed[dimension]), + zap.Uint64("block units", feeManager.LastConsumed(dimension)), + zap.Uint64("max block units", maxUnits[dimension]), + ) + restore = true - // If we are above the target for the dimension we can't consume, we will - // stop building. This prevents a full mempool iteration looking for the - // "perfect fit". - if feeManager.LastConsumed(dimension) >= targetUnits[dimension] { - stop = true - return errBlockFull + // If we are above the target for the dimension we can't consume, we will + // stop building. This prevents a full mempool iteration looking for the + // "perfect fit". + if feeManager.LastConsumed(dimension) >= targetUnits[dimension] { + stop = true + return errBlockFull + } } } // Update block with new transaction tsv.Commit() b.Txs = append(b.Txs, tx) - results = append(results, result) + results = append(results, txResults...) return nil }) } From 06d62fe6b5141b750282f0b22a27c36a20d5e111 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 16:59:31 -0400 Subject: [PATCH 10/78] fix processor errors --- chain/processor.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/chain/processor.go b/chain/processor.go index 0b44215923..ea7bb404a0 100644 --- a/chain/processor.go +++ b/chain/processor.go @@ -38,15 +38,15 @@ func (b *StatelessBlock) Execute( numTxs = len(b.Txs) t = b.GetTimestamp() - f = fetcher.New(im, numTxs, b.vm.GetStateFetchConcurrency()) - e = executor.New(numTxs, b.vm.GetTransactionExecutionCores(), MaxKeyDependencies, b.vm.GetExecutorVerifyRecorder()) - ts = tstate.New(numTxs * 2) // TODO: tune this heuristic - results = make([]*Result, numTxs) + f = fetcher.New(im, numTxs, b.vm.GetStateFetchConcurrency()) + e = executor.New(numTxs, b.vm.GetTransactionExecutionCores(), MaxKeyDependencies, b.vm.GetExecutorVerifyRecorder()) + ts = tstate.New(numTxs * 2) // TODO: tune this heuristic + // TODO: specify len? + results = []*Result{} ) // Fetch required keys and execute transactions - for li, ltx := range b.Txs { - i := li + for _, ltx := range b.Txs { tx := ltx stateKeys, err := tx.StateKeys(sm) @@ -79,16 +79,18 @@ func (b *StatelessBlock) Execute( return err } - result, err := tx.Execute(ctx, feeManager, reads, sm, r, tsv, t) + txResults, err := tx.Execute(ctx, feeManager, reads, sm, r, tsv, t) if err != nil { return err } - results[i] = result - - // Update block metadata with units actually consumed (if more is consumed than block allows, we will non-deterministically - // exit with an error based on which tx over the limit is processed first) - if ok, d := feeManager.Consume(result.Consumed, r.GetMaxBlockUnits()); !ok { - return fmt.Errorf("%w: %d too large", ErrInvalidUnitsConsumed, d) + results = append(results, txResults...) + + for _, result := range txResults { + // Update block metadata with units actually consumed (if more is consumed than block allows, we will non-deterministically + // exit with an error based on which tx over the limit is processed first) + if ok, d := feeManager.Consume(result.Consumed, r.GetMaxBlockUnits()); !ok { + return fmt.Errorf("%w: %d too large", ErrInvalidUnitsConsumed, d) + } } // Commit results to parent [TState] From 5dcf31d8aaed25c5053649c296887ecfcaf75127 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 17:00:49 -0400 Subject: [PATCH 11/78] fix more tx errors --- chain/transaction.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 2169352cdb..7e976e2a24 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -314,7 +314,7 @@ func (t *Transaction) Execute( // We create a temp state checkpoint to ensure we don't commit failed actions to state. actionStart := ts.OpIndex() - handleRevert := func(rerr error) (*Result, error) { + handleRevert := func(rerr error) ([]*Result, error) { // Be warned that the variables captured in this function // are set when this function is defined. If any of them are // modified later, they will not be used here. @@ -323,7 +323,7 @@ func (t *Transaction) Execute( } results := make([]*Result, 0) for _, action := range t.Actions { - success, actionCUs, output, err := t.Actions.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), t.id) + success, actionCUs, output, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), t.id) if err != nil { return handleRevert(err) } @@ -433,12 +433,12 @@ func (t *Transaction) Marshal(p *codec.Packer) error { return p.Err() } - for i, action := range t.Action { + for i, action := range t.Actions { actionID := action.GetActionID(uint8(i), t.id) authID := t.Auth.GetTypeID() t.Base.Marshal(p) p.PackAddress(actionID) - t.Action.Marshal(p) + action.Marshal(p) p.PackByte(authID) t.Auth.Marshal(p) } From 5c84c690baa4c1a0014fae325a172248d77cddb8 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 17:01:45 -0400 Subject: [PATCH 12/78] fix client rpc error --- rpc/jsonrpc_client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/jsonrpc_client.go b/rpc/jsonrpc_client.go index fedccd7d7d..929438e0c7 100644 --- a/rpc/jsonrpc_client.go +++ b/rpc/jsonrpc_client.go @@ -170,7 +170,7 @@ func (cli *JSONRPCClient) GenerateTransactionManual( // Build transaction actionRegistry, authRegistry := parser.Registry() - tx := chain.NewTx(base, action) + tx := chain.NewTx(base, []chain.Action{action}) tx, err := tx.Sign(authFactory, actionRegistry, authRegistry) if err != nil { return nil, nil, fmt.Errorf("%w: failed to sign transaction", err) From 197ed913f301a81a3769356efaa8403a1810b8ab Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 17:03:32 -0400 Subject: [PATCH 13/78] fix morpheus transfer action --- examples/morpheusvm/actions/transfer.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/morpheusvm/actions/transfer.go b/examples/morpheusvm/actions/transfer.go index d478e698a6..f6c94ec5c4 100644 --- a/examples/morpheusvm/actions/transfer.go +++ b/examples/morpheusvm/actions/transfer.go @@ -30,7 +30,11 @@ func (*Transfer) GetTypeID() uint8 { return mconsts.TransferID } -func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys { +func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.Address { + return codec.CreateAddress(i, txID) +} + +func (t *Transfer) StateKeys(actor codec.Address, _ codec.Address) state.Keys { return state.Keys{ string(storage.BalanceKey(actor)): state.Read | state.Write, string(storage.BalanceKey(t.To)): state.All, From d5f589cea45066d11af00d352c4bbb8df4aa5427 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 17:06:02 -0400 Subject: [PATCH 14/78] fix more morpheus --- examples/morpheusvm/controller/controller.go | 8 +++++--- examples/morpheusvm/genesis/genesis.go | 2 +- examples/morpheusvm/genesis/rules.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/morpheusvm/controller/controller.go b/examples/morpheusvm/controller/controller.go index a4e5a9d980..7fb305ceee 100644 --- a/examples/morpheusvm/controller/controller.go +++ b/examples/morpheusvm/controller/controller.go @@ -168,9 +168,11 @@ func (c *Controller) Accepted(ctx context.Context, blk *chain.StatelessBlock) er } } if result.Success { - switch tx.Action.(type) { //nolint:gocritic - case *actions.Transfer: - c.metrics.transfer.Inc() + for _, action := range tx.Actions { + switch action.(type) { //nolint:gocritic + case *actions.Transfer: + c.metrics.transfer.Inc() + } } } } diff --git a/examples/morpheusvm/genesis/genesis.go b/examples/morpheusvm/genesis/genesis.go index adeedf5696..58aaa5bfc1 100644 --- a/examples/morpheusvm/genesis/genesis.go +++ b/examples/morpheusvm/genesis/genesis.go @@ -55,7 +55,7 @@ type Genesis struct { StorageValueWriteUnits uint64 `json:"storageValueWriteUnits"` // per chunk // Action Per Tx - MaxActionsPerTx uint8 `json:"maxActionsPerTx"` + MaxActionsPerTx int `json:"maxActionsPerTx"` // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` diff --git a/examples/morpheusvm/genesis/rules.go b/examples/morpheusvm/genesis/rules.go index 1bded55a3e..f6eefb8398 100644 --- a/examples/morpheusvm/genesis/rules.go +++ b/examples/morpheusvm/genesis/rules.go @@ -96,6 +96,6 @@ func (*Rules) FetchCustom(string) (any, bool) { return nil, false } -func (*Rules) GetMaxActionsPerTx() uint8 { +func (r *Rules) GetMaxActionsPerTx() int { return r.g.MaxActionsPerTx } From 0ca6b1c8ab434e6491d20b9af73ed43ecdfaae56 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 20:07:41 -0400 Subject: [PATCH 15/78] morpheusVM compiles --- examples/morpheusvm/tests/integration/integration_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/morpheusvm/tests/integration/integration_test.go b/examples/morpheusvm/tests/integration/integration_test.go index 0955c9c2c2..545b93f764 100644 --- a/examples/morpheusvm/tests/integration/integration_test.go +++ b/examples/morpheusvm/tests/integration/integration_test.go @@ -366,10 +366,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Timestamp: 0, MaxFee: 1000, }, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr2, Value: 110, - }, + }}, ) // Must do manual construction to avoid `tx.Sign` error (would fail with // 0 timestamp) @@ -777,7 +777,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { blk, lresults, prices, err := cli.ListenBlock(context.TODO(), parser) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(len(blk.Txs)).Should(gomega.Equal(1)) - tx := blk.Txs[0].Action.(*actions.Transfer) + tx := blk.Txs[0].Actions[0].(*actions.Transfer) gomega.Ω(tx.Value).To(gomega.Equal(uint64(1))) gomega.Ω(lresults).Should(gomega.Equal(results)) gomega.Ω(prices).Should(gomega.Equal(fees.Dimensions{1, 1, 1, 1, 1})) From def86ad0aeda94b7eadeda4a3c38ed9c66c7ee10 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 20:12:03 -0400 Subject: [PATCH 16/78] fix mock gen --- chain/mock_action.go | 16 +++++++++++++++- chain/mock_rules.go | 14 ++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/chain/mock_action.go b/chain/mock_action.go index 920b763dbd..7ccb1a4604 100644 --- a/chain/mock_action.go +++ b/chain/mock_action.go @@ -62,6 +62,20 @@ func (mr *MockActionMockRecorder) Execute(arg0, arg1, arg2, arg3, arg4, arg5 any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockAction)(nil).Execute), arg0, arg1, arg2, arg3, arg4, arg5) } +// GetActionID mocks base method. +func (m *MockAction) GetActionID(arg0 byte, arg1 ids.ID) codec.Address { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetActionID", arg0, arg1) + ret0, _ := ret[0].(codec.Address) + return ret0 +} + +// GetActionID indicates an expected call of GetActionID. +func (mr *MockActionMockRecorder) GetActionID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActionID", reflect.TypeOf((*MockAction)(nil).GetActionID), arg0, arg1) +} + // GetTypeID mocks base method. func (m *MockAction) GetTypeID() byte { m.ctrl.T.Helper() @@ -117,7 +131,7 @@ func (mr *MockActionMockRecorder) Size() *gomock.Call { } // StateKeys mocks base method. -func (m *MockAction) StateKeys(arg0 codec.Address, arg1 ids.ID) state.Keys { +func (m *MockAction) StateKeys(arg0, arg1 codec.Address) state.Keys { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "StateKeys", arg0, arg1) ret0, _ := ret[0].(state.Keys) diff --git a/chain/mock_rules.go b/chain/mock_rules.go index feabddc7a7..003a4ac970 100644 --- a/chain/mock_rules.go +++ b/chain/mock_rules.go @@ -86,6 +86,20 @@ func (mr *MockRulesMockRecorder) GetBaseComputeUnits() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaseComputeUnits", reflect.TypeOf((*MockRules)(nil).GetBaseComputeUnits)) } +// GetMaxActionsPerTx mocks base method. +func (m *MockRules) GetMaxActionsPerTx() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMaxActionsPerTx") + ret0, _ := ret[0].(int) + return ret0 +} + +// GetMaxActionsPerTx indicates an expected call of GetMaxActionsPerTx. +func (mr *MockRulesMockRecorder) GetMaxActionsPerTx() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMaxActionsPerTx", reflect.TypeOf((*MockRules)(nil).GetMaxActionsPerTx)) +} + // GetMaxBlockUnits mocks base method. func (m *MockRules) GetMaxBlockUnits() fees.Dimensions { m.ctrl.T.Helper() From 26088fd9f9df46d1f11892e75003aa3125619f11 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 20:14:02 -0400 Subject: [PATCH 17/78] fix morpheus static lint --- .../morpheusvm/cmd/morpheus-cli/cmd/resolutions.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go index 675fc47e19..78eb50b8b2 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go @@ -62,9 +62,11 @@ func handleTx(tx *chain.Transaction, result *chain.Result) { status := "❌" if result.Success { status = "✅" - switch action := tx.Action.(type) { //nolint:gocritic - case *actions.Transfer: - summaryStr = fmt.Sprintf("%s %s -> %s", utils.FormatBalance(action.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, action.To)) + for _, action := range tx.Actions { + switch act := action.(type) { //nolint:gocritic + case *actions.Transfer: + summaryStr = fmt.Sprintf("%s %s -> %s", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) + } } } utils.Outf( @@ -72,7 +74,7 @@ func handleTx(tx *chain.Transaction, result *chain.Result) { status, tx.ID(), codec.MustAddressBech32(consts.HRP, actor), - reflect.TypeOf(tx.Action), + reflect.TypeOf(tx.Actions), summaryStr, float64(result.Fee)/float64(tx.Base.MaxFee)*100, utils.FormatBalance(result.Fee, consts.Decimals), From 583388fe84bdeda38108de1119905dd152b643a9 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 20:23:49 -0400 Subject: [PATCH 18/78] fix lint and function signatures --- .../morpheusvm/cmd/morpheus-cli/cmd/action.go | 4 +- .../cmd/morpheus-cli/cmd/resolutions.go | 4 +- .../morpheusvm/cmd/morpheus-cli/cmd/spam.go | 10 ++-- examples/morpheusvm/tests/e2e/e2e_test.go | 12 ++-- .../tests/integration/integration_test.go | 60 +++++++++---------- examples/morpheusvm/tests/load/load_test.go | 4 +- rpc/jsonrpc_client.go | 10 ++-- 7 files changed, 52 insertions(+), 52 deletions(-) diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go index 9fd10c3d28..104b615eb0 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go @@ -52,10 +52,10 @@ var transferCmd = &cobra.Command{ } // Generate transaction - _, _, err = sendAndWait(ctx, &actions.Transfer{ + _, _, err = sendAndWait(ctx, []chain.Action{&actions.Transfer{ To: recipient, Value: amount, - }, cli, bcli, ws, factory, true) + }}, cli, bcli, ws, factory, true) return err }, } diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go index 78eb50b8b2..215a419c99 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go @@ -21,14 +21,14 @@ import ( // sendAndWait may not be used concurrently func sendAndWait( - ctx context.Context, action chain.Action, cli *rpc.JSONRPCClient, + ctx context.Context, actions []chain.Action, cli *rpc.JSONRPCClient, bcli *brpc.JSONRPCClient, ws *rpc.WebSocketClient, factory chain.AuthFactory, printStatus bool, ) (bool, ids.ID, error) { //nolint:unparam parser, err := bcli.Parser(ctx) if err != nil { return false, ids.Empty, err } - _, tx, _, err := cli.GenerateTransaction(ctx, parser, action, factory) + _, tx, _, err := cli.GenerateTransaction(ctx, parser, actions, factory) if err != nil { return false, ids.Empty, err } diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/spam.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/spam.go index 1ac060733c..b1feacd422 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/spam.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/spam.go @@ -94,11 +94,11 @@ var runSpamCmd = &cobra.Command{ func(ctx context.Context, chainID ids.ID) (chain.Parser, error) { // getParser return bclient.Parser(ctx) }, - func(addr codec.Address, amount uint64) chain.Action { // getTransfer - return &actions.Transfer{ + func(addr codec.Address, amount uint64) []chain.Action { // getTransfer + return []chain.Action{&actions.Transfer{ To: addr, Value: amount, - } + }} }, func(cli *rpc.JSONRPCClient, priv *cli.PrivateKey) func(context.Context, uint64) error { // submitDummy return func(ictx context.Context, count uint64) error { @@ -106,10 +106,10 @@ var runSpamCmd = &cobra.Command{ if err != nil { return err } - _, _, err = sendAndWait(ictx, &actions.Transfer{ + _, _, err = sendAndWait(ictx, []chain.Action{&actions.Transfer{ To: priv.Address, Value: count, // prevent duplicate txs - }, cli, bclient, wclient, factory, false) + }}, cli, bclient, wclient, factory, false) return err } }, diff --git a/examples/morpheusvm/tests/e2e/e2e_test.go b/examples/morpheusvm/tests/e2e/e2e_test.go index bf352848bc..85a91234f9 100644 --- a/examples/morpheusvm/tests/e2e/e2e_test.go +++ b/examples/morpheusvm/tests/e2e/e2e_test.go @@ -424,10 +424,10 @@ var _ = ginkgo.Describe("[Test]", func() { submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: aother, Value: sendAmount, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -716,10 +716,10 @@ func generateBlocks( submit, _, _, err := instances[cumulativeTxs%len(instances)].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: aother, Value: 1, - }, + }}, factory, ) if failOnError { @@ -784,10 +784,10 @@ func acceptTransaction(cli *rpc.JSONRPCClient, lcli *lrpc.JSONRPCClient) { submit, tx, maxFee, err := cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: aother, Value: 1, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) diff --git a/examples/morpheusvm/tests/integration/integration_test.go b/examples/morpheusvm/tests/integration/integration_test.go index 545b93f764..8239bab8d8 100644 --- a/examples/morpheusvm/tests/integration/integration_test.go +++ b/examples/morpheusvm/tests/integration/integration_test.go @@ -334,10 +334,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, transferTx, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr2, Value: 100_000, // must be more than StateLockup - }, + }}, factory, ) transferTxRoot = transferTx @@ -458,10 +458,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr2, Value: 101, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -498,10 +498,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr2, Value: 102, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -509,10 +509,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err = instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr2, Value: 103, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -520,10 +520,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err = instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr3, Value: 104, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -531,10 +531,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err = instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr3, Value: 105, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -630,10 +630,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr2, Value: 200, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -643,10 +643,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err = instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr2, Value: 201, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -671,10 +671,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr2, Value: 203, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -751,10 +751,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // Send tx other, err := ed25519.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) - transfer := &actions.Transfer{ + transfer := []chain.Action{&actions.Transfer{ To: auth.NewED25519Address(other.PublicKey()), Value: 1, - } + }} parser, err := instances[0].lcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) @@ -799,10 +799,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // Create tx other, err := ed25519.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) - transfer := &actions.Transfer{ + transfer := []chain.Action{&actions.Transfer{ To: auth.NewED25519Address(other.PublicKey()), Value: 1, - } + }} parser, err := instances[0].lcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) _, tx, _, err := instances[0].cli.GenerateTransaction( @@ -856,10 +856,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: r1addr, Value: 2000, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -880,10 +880,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr, Value: 100, - }, + }}, r1factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -908,10 +908,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: r1addr, Value: 2000, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -932,10 +932,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: addr, Value: 100, - }, + }}, r1factory, ) gomega.Ω(err).Should(gomega.BeNil()) diff --git a/examples/morpheusvm/tests/load/load_test.go b/examples/morpheusvm/tests/load/load_test.go index 634826ccd1..3fef5700cc 100644 --- a/examples/morpheusvm/tests/load/load_test.go +++ b/examples/morpheusvm/tests/load/load_test.go @@ -542,10 +542,10 @@ func issueSimpleTx( ChainID: i.chainID, MaxFee: maxFee, }, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: to, Value: amount, - }, + }}, ) tx, err := tx.Sign(factory, consts.ActionRegistry, consts.AuthRegistry) gomega.Ω(err).To(gomega.BeNil()) diff --git a/rpc/jsonrpc_client.go b/rpc/jsonrpc_client.go index 929438e0c7..43815026cb 100644 --- a/rpc/jsonrpc_client.go +++ b/rpc/jsonrpc_client.go @@ -122,7 +122,7 @@ type Modifier interface { func (cli *JSONRPCClient) GenerateTransaction( ctx context.Context, parser chain.Parser, - action chain.Action, + actions []chain.Action, authFactory chain.AuthFactory, modifiers ...Modifier, ) (func(context.Context) error, *chain.Transaction, uint64, error) { @@ -132,7 +132,7 @@ func (cli *JSONRPCClient) GenerateTransaction( return nil, nil, 0, err } - maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), action, authFactory) + maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), actions, authFactory) if err != nil { return nil, nil, 0, err } @@ -140,7 +140,7 @@ func (cli *JSONRPCClient) GenerateTransaction( if err != nil { return nil, nil, 0, err } - f, tx, err := cli.GenerateTransactionManual(parser, action, authFactory, maxFee, modifiers...) + f, tx, err := cli.GenerateTransactionManual(parser, actions, authFactory, maxFee, modifiers...) if err != nil { return nil, nil, 0, err } @@ -149,7 +149,7 @@ func (cli *JSONRPCClient) GenerateTransaction( func (cli *JSONRPCClient) GenerateTransactionManual( parser chain.Parser, - action chain.Action, + actions []chain.Action, authFactory chain.AuthFactory, maxFee uint64, modifiers ...Modifier, @@ -170,7 +170,7 @@ func (cli *JSONRPCClient) GenerateTransactionManual( // Build transaction actionRegistry, authRegistry := parser.Registry() - tx := chain.NewTx(base, []chain.Action{action}) + tx := chain.NewTx(base, actions) tx, err := tx.Sign(authFactory, actionRegistry, authRegistry) if err != nil { return nil, nil, fmt.Errorf("%w: failed to sign transaction", err) From a8e3a7c14d2f882c04df8c4e1b56b89e3c4c8bed Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 20:34:04 -0400 Subject: [PATCH 19/78] fix rpc GenerateTx --- rpc/jsonrpc_client.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/rpc/jsonrpc_client.go b/rpc/jsonrpc_client.go index 43815026cb..8a722f8ff6 100644 --- a/rpc/jsonrpc_client.go +++ b/rpc/jsonrpc_client.go @@ -132,11 +132,18 @@ func (cli *JSONRPCClient) GenerateTransaction( return nil, nil, 0, err } - maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), actions, authFactory) - if err != nil { - return nil, nil, 0, err + var totalUnits fees.Dimensions + for _, action := range actions { + maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), actions, authFactory) + if err != nil { + return nil, nil, 0, err + } + totalUnits, err = fees.Add(totalUnits, maxUnits) + if err != nil { + return nil, nil, 0, err + } } - maxFee, err := fees.MulSum(unitPrices, maxUnits) + maxFee, err := fees.MulSum(unitPrices, totalUnits) if err != nil { return nil, nil, 0, err } From 487f294430732e031f3df09b1942a18f81eebd37 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 20:35:50 -0400 Subject: [PATCH 20/78] import chain into morpheus actions --- examples/morpheusvm/cmd/morpheus-cli/cmd/action.go | 1 + examples/morpheusvm/tests/e2e/e2e_test.go | 1 + rpc/jsonrpc_client.go | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go index 104b615eb0..a3bcd5b9f4 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go @@ -6,6 +6,7 @@ package cmd import ( "context" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" "github.com/spf13/cobra" diff --git a/examples/morpheusvm/tests/e2e/e2e_test.go b/examples/morpheusvm/tests/e2e/e2e_test.go index 85a91234f9..1b1ae5c82b 100644 --- a/examples/morpheusvm/tests/e2e/e2e_test.go +++ b/examples/morpheusvm/tests/e2e/e2e_test.go @@ -16,6 +16,7 @@ import ( "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" diff --git a/rpc/jsonrpc_client.go b/rpc/jsonrpc_client.go index 8a722f8ff6..00fded0e98 100644 --- a/rpc/jsonrpc_client.go +++ b/rpc/jsonrpc_client.go @@ -134,7 +134,7 @@ func (cli *JSONRPCClient) GenerateTransaction( var totalUnits fees.Dimensions for _, action := range actions { - maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), actions, authFactory) + maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), action, authFactory) if err != nil { return nil, nil, 0, err } From c643f90090a660d093c35cc724545aadb9406407 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 24 Apr 2024 20:41:06 -0400 Subject: [PATCH 21/78] more lints --- cli/spam.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/spam.go b/cli/spam.go index 16978d538f..41a5b3a1b9 100644 --- a/cli/spam.go +++ b/cli/spam.go @@ -52,7 +52,7 @@ func (h *Handler) Spam( createAccount func() (*PrivateKey, error), lookupBalance func(int, string) (uint64, error), getParser func(context.Context, ids.ID) (chain.Parser, error), - getTransfer func(codec.Address, uint64) chain.Action, + getTransfer func(codec.Address, uint64) []chain.Action, submitDummy func(*rpc.JSONRPCClient, *PrivateKey) func(context.Context, uint64) error, ) error { ctx := context.Background() @@ -110,8 +110,8 @@ func (h *Handler) Spam( if err != nil { return err } - action := getTransfer(keys[0].Address, 0) - maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), action, factory) + actions := getTransfer(keys[0].Address, 0) + maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), actions[0], factory) if err != nil { return err } @@ -307,7 +307,7 @@ func (h *Handler) Spam( } v := selected[recipient] + 1 selected[recipient] = v - action := getTransfer(recipient, uint64(v)) + actions := getTransfer(recipient, uint64(v)) fee, err := fees.MulSum(unitPrices, maxUnits) if err != nil { utils.Outf("{{orange}}failed to estimate max fee:{{/}} %v\n", err) @@ -316,7 +316,7 @@ func (h *Handler) Spam( if maxFee != nil { fee = *maxFee } - _, tx, err := issuer.c.GenerateTransactionManual(parser, action, factory, fee, tm) + _, tx, err := issuer.c.GenerateTransactionManual(parser, actions, factory, fee, tm) if err != nil { utils.Outf("{{orange}}failed to generate tx:{{/}} %v\n", err) continue From e55bf3717ff190c68e4fac19202dab91fd7971f6 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 11:11:29 -0400 Subject: [PATCH 22/78] marhsal unmarshal Actions --- chain/transaction.go | 53 +++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 7e976e2a24..0f400b93cf 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -433,18 +433,46 @@ func (t *Transaction) Marshal(p *codec.Packer) error { return p.Err() } - for i, action := range t.Actions { - actionID := action.GetActionID(uint8(i), t.id) - authID := t.Auth.GetTypeID() - t.Base.Marshal(p) - p.PackAddress(actionID) + return t.marshalActions(p) +} + +func (t *Transaction) marshalActions(p *codec.Packer) error { + t.Base.Marshal(p) + p.PackInt(len(t.Actions)) + for _, action := range t.Actions { + actionID := action.GetTypeID() + p.PackByte(actionID) action.Marshal(p) - p.PackByte(authID) - t.Auth.Marshal(p) } + authID := t.Auth.GetTypeID() + p.PackByte(authID) + t.Auth.Marshal(p) return p.Err() } +// todo: move below UnmarshalTx +func unmarshalActions( + p *codec.Packer, + actionRegistry *codec.TypeParser[Action, bool], + authRegistry *codec.TypeParser[Auth, bool], +) ([]Action, error) { + actionCount := p.UnpackInt(true) + actions := make([]Action, 0) + for i := 0; i < actionCount; i++ { + actionType := p.UnpackByte() + unmarshalAction, ok := actionRegistry.LookupIndex(actionType) + if !ok { + return nil, fmt.Errorf("%w: %d is unknown action type", ErrInvalidObject, actionType) + } + action, err := unmarshalAction(p) + if err != nil { + return nil, fmt.Errorf("%w: could not unmarshal action", err) + } + actions = append(actions, action) + } + return actions, nil +} + func MarshalTxs(txs []*Transaction) ([]byte, error) { if len(txs) == 0 { return nil, ErrNoTxs @@ -495,14 +523,9 @@ func UnmarshalTx( if err != nil { return nil, fmt.Errorf("%w: could not unmarshal base", err) } - actionType := p.UnpackByte() - unmarshalAction, ok := actionRegistry.LookupIndex(actionType) - if !ok { - return nil, fmt.Errorf("%w: %d is unknown action type", ErrInvalidObject, actionType) - } - action, err := unmarshalAction(p) + actions, err := unmarshalActions(p, actionRegistry, authRegistry) if err != nil { - return nil, fmt.Errorf("%w: could not unmarshal action", err) + return nil, fmt.Errorf("%w: could not unmarshal actions", err) } digest := p.Offset() authType := p.UnpackByte() @@ -523,7 +546,7 @@ func UnmarshalTx( var tx Transaction tx.Base = base - tx.Actions = []Action{action} + tx.Actions = actions tx.Auth = auth if err := p.Err(); err != nil { return nil, p.Err() From e8023738901971485d28dff7f7922b4f036b6986 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 11:17:56 -0400 Subject: [PATCH 23/78] totalUnits fees.Add --- chain/transaction.go | 3 +-- rpc/jsonrpc_client.go | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 0f400b93cf..a392362779 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -454,7 +454,6 @@ func (t *Transaction) marshalActions(p *codec.Packer) error { func unmarshalActions( p *codec.Packer, actionRegistry *codec.TypeParser[Action, bool], - authRegistry *codec.TypeParser[Auth, bool], ) ([]Action, error) { actionCount := p.UnpackInt(true) actions := make([]Action, 0) @@ -523,7 +522,7 @@ func UnmarshalTx( if err != nil { return nil, fmt.Errorf("%w: could not unmarshal base", err) } - actions, err := unmarshalActions(p, actionRegistry, authRegistry) + actions, err := unmarshalActions(p, actionRegistry) if err != nil { return nil, fmt.Errorf("%w: could not unmarshal actions", err) } diff --git a/rpc/jsonrpc_client.go b/rpc/jsonrpc_client.go index 00fded0e98..41fb384449 100644 --- a/rpc/jsonrpc_client.go +++ b/rpc/jsonrpc_client.go @@ -132,16 +132,17 @@ func (cli *JSONRPCClient) GenerateTransaction( return nil, nil, 0, err } - var totalUnits fees.Dimensions + totalUnits := fees.Dimensions{} for _, action := range actions { maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), action, authFactory) if err != nil { return nil, nil, 0, err } - totalUnits, err = fees.Add(totalUnits, maxUnits) + nTotalUnits, err := fees.Add(totalUnits, maxUnits) if err != nil { return nil, nil, 0, err } + totalUnits = nTotalUnits } maxFee, err := fees.MulSum(unitPrices, totalUnits) if err != nil { From 719c2c4f3e654367bd82f2e99ee0cca5f23dac5f Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 13:10:35 -0400 Subject: [PATCH 24/78] fix invalid signature --- chain/transaction.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index a392362779..22de51fb92 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -57,8 +57,8 @@ func (t *Transaction) Digest() ([]byte, error) { p := codec.NewWriter(size, consts.NetworkSizeLimit) t.Base.Marshal(p) p.PackInt(len(t.Actions)) - for i, action := range t.Actions { - p.PackAddress(action.GetActionID(uint8(i), t.id)) + for _, action := range t.Actions { + p.PackByte(action.GetTypeID()) action.Marshal(p) } return p.Bytes(), p.Err() From 92d9902e066f54ffd121f11c07941be582439bd0 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 14:01:53 -0400 Subject: [PATCH 25/78] fix some tx fees and packing bytes --- chain/transaction.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 22de51fb92..40f50ef27e 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -50,9 +50,9 @@ func (t *Transaction) Digest() ([]byte, error) { if len(t.digest) > 0 { return t.digest, nil } - size := t.Base.Size() + size := t.Base.Size() + consts.ByteLen for _, action := range t.Actions { - size += consts.ByteLen + action.Size() + size += action.Size() } p := codec.NewWriter(size, consts.NetworkSizeLimit) t.Base.Marshal(p) @@ -138,8 +138,8 @@ func (t *Transaction) MaxUnits(sm StateManager, r Rules) (fees.Dimensions, error maxComputeUnitsOp := math.NewUint64Operator(r.GetBaseComputeUnits()) for _, action := range t.Actions { maxComputeUnitsOp.Add(action.MaxComputeUnits(r)) - maxComputeUnitsOp.Add(t.Auth.ComputeUnits(r)) } + maxComputeUnitsOp.Add(t.Auth.ComputeUnits(r)) maxComputeUnits, err := maxComputeUnitsOp.Value() if err != nil { return fees.Dimensions{}, err From 9a0a5dfc37d5473cc891dd7e1f39e34fbd98ede6 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 14:20:31 -0400 Subject: [PATCH 26/78] check maxActions earlier --- chain/transaction.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 40f50ef27e..64d6edf55e 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -249,6 +249,9 @@ func (t *Transaction) PreExecute( if err := t.Base.Execute(r.ChainID(), r, timestamp); err != nil { return err } + if len(t.Actions) > r.GetMaxActionsPerTx() { + return ErrTooManyActions + } for _, action := range t.Actions { start, end := action.ValidRange(r) if start >= 0 && timestamp < start { @@ -265,9 +268,6 @@ func (t *Transaction) PreExecute( if end >= 0 && timestamp > end { return ErrAuthNotActivated } - if len(t.Actions) > r.GetMaxActionsPerTx() { - return ErrTooManyActions - } maxUnits, err := t.MaxUnits(s, r) if err != nil { return err From 2a546b77e894b543fa8d9940693e137cbd1c97f4 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 14:47:25 -0400 Subject: [PATCH 27/78] morpheus integration passes --- chain/transaction.go | 4 +-- .../tests/integration/integration_test.go | 26 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 64d6edf55e..a113a8aebe 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -50,9 +50,9 @@ func (t *Transaction) Digest() ([]byte, error) { if len(t.digest) > 0 { return t.digest, nil } - size := t.Base.Size() + consts.ByteLen + size := t.Base.Size() for _, action := range t.Actions { - size += action.Size() + size += consts.ByteLen + action.Size() } p := codec.NewWriter(size, consts.NetworkSizeLimit) t.Base.Marshal(p) diff --git a/examples/morpheusvm/tests/integration/integration_test.go b/examples/morpheusvm/tests/integration/integration_test.go index 8239bab8d8..714bfe8fd0 100644 --- a/examples/morpheusvm/tests/integration/integration_test.go +++ b/examples/morpheusvm/tests/integration/integration_test.go @@ -432,19 +432,19 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // read: 2 keys reads, 1 had 0 chunks // allocate: 1 key created with 1 chunk // write: 2 keys modified (new + old) - transferTxConsumed := fees.Dimensions{187, 7, 12, 25, 26} + transferTxConsumed := fees.Dimensions{191, 7, 12, 25, 26} gomega.Ω(results[0].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(257))) + gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(261))) }) ginkgo.By("ensure balance is updated", func() { balance, err := instances[1].lcli.Balance(context.Background(), addrStr) gomega.Ω(err).To(gomega.BeNil()) - gomega.Ω(balance).To(gomega.Equal(uint64(9899743))) + gomega.Ω(balance).To(gomega.Equal(uint64(9899739))) balance2, err := instances[1].lcli.Balance(context.Background(), addrStr2) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100000))) @@ -478,13 +478,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // read: 2 keys reads, 1 chunk each // allocate: 0 key created // write: 2 key modified - transferTxConsumed := fees.Dimensions{187, 7, 14, 0, 26} + transferTxConsumed := fees.Dimensions{191, 7, 14, 0, 26} gomega.Ω(results[0].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(234))) + gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(238))) balance2, err := instances[1].lcli.Balance(context.Background(), addrStr2) gomega.Ω(err).To(gomega.BeNil()) @@ -559,12 +559,12 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // allocate: 0 key created // write: 2 key modified gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - transferTxConsumed := fees.Dimensions{187, 7, 14, 0, 26} + transferTxConsumed := fees.Dimensions{191, 7, 14, 0, 26} gomega.Ω(results[0].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(234))) + gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(238))) // Unit explanation // @@ -574,12 +574,12 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // allocate: 0 key created // write: 2 keys modified gomega.Ω(results[1].Success).Should(gomega.BeTrue()) - transferTxConsumed = fees.Dimensions{187, 7, 14, 0, 26} + transferTxConsumed = fees.Dimensions{191, 7, 14, 0, 26} gomega.Ω(results[1].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[1].Fee).Should(gomega.Equal(uint64(234))) + gomega.Ω(results[1].Fee).Should(gomega.Equal(uint64(238))) // Unit explanation // @@ -589,12 +589,12 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // allocate: 1 key created (1 chunk) // write: 2 key modified (1 chunk), both previously modified gomega.Ω(results[2].Success).Should(gomega.BeTrue()) - transferTxConsumed = fees.Dimensions{187, 7, 12, 25, 26} + transferTxConsumed = fees.Dimensions{191, 7, 12, 25, 26} gomega.Ω(results[2].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[2].Fee).Should(gomega.Equal(uint64(257))) + gomega.Ω(results[2].Fee).Should(gomega.Equal(uint64(261))) // Unit explanation // @@ -604,12 +604,12 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // allocate: 0 key created // write: 2 keys modified (1 chunk) gomega.Ω(results[3].Success).Should(gomega.BeTrue()) - transferTxConsumed = fees.Dimensions{187, 7, 12, 0, 26} + transferTxConsumed = fees.Dimensions{191, 7, 12, 0, 26} gomega.Ω(results[3].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[3].Fee).Should(gomega.Equal(uint64(232))) + gomega.Ω(results[3].Fee).Should(gomega.Equal(uint64(236))) // Check end balance balance2, err := instances[1].lcli.Balance(context.Background(), addrStr2) From a847c7395ef1ab851cf3fa622ff86dd9aeb9d0f5 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 25 Apr 2024 16:01:58 -0400 Subject: [PATCH 28/78] make action.Execute use actionID --- chain/dependencies.go | 2 +- chain/transaction.go | 5 +++-- examples/morpheusvm/actions/transfer.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/chain/dependencies.go b/chain/dependencies.go index ebf9d16967..7aad3fc460 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -260,7 +260,7 @@ type Action interface { mu state.Mutable, timestamp int64, actor codec.Address, - txID ids.ID, + actionID codec.Address, ) (success bool, computeUnits uint64, output []byte, err error) } diff --git a/chain/transaction.go b/chain/transaction.go index a113a8aebe..f0b18d75d5 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -322,8 +322,9 @@ func (t *Transaction) Execute( return []*Result{{false, utils.ErrBytes(rerr), maxUnits, maxFee}}, nil } results := make([]*Result, 0) - for _, action := range t.Actions { - success, actionCUs, output, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), t.id) + for i, action := range t.Actions { + actionID := action.GetActionID(uint8(i), t.id) + success, actionCUs, output, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) if err != nil { return handleRevert(err) } diff --git a/examples/morpheusvm/actions/transfer.go b/examples/morpheusvm/actions/transfer.go index f6c94ec5c4..ce90652731 100644 --- a/examples/morpheusvm/actions/transfer.go +++ b/examples/morpheusvm/actions/transfer.go @@ -51,7 +51,7 @@ func (t *Transfer) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ ids.ID, + _ codec.Address, ) (bool, uint64, []byte, error) { if t.Value == 0 { return false, 1, OutputValueZero, nil From 574d0abca133e102fa5018056c85a42f71e408fd Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 26 Apr 2024 10:27:39 -0400 Subject: [PATCH 29/78] intro codec.ActionID --- chain/dependencies.go | 6 +++--- codec/address.go | 9 +++++++++ examples/morpheusvm/actions/transfer.go | 8 ++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/chain/dependencies.go b/chain/dependencies.go index 7aad3fc460..a1c57ac1ab 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -221,7 +221,7 @@ type Action interface { // GetActionID returns the ActionID for an [Action] in a [Transaction]. There may be // multiple [Action]s, so we pass its index in the [Action] array along with the txID. - GetActionID(i uint8, txID ids.ID) codec.Address + GetActionID(i uint8, txID ids.ID) codec.ActionID // MaxComputeUnits is the maximum amount of compute a given [Action] could use. This is // used to determine whether the [Action] can be included in a given block and to compute @@ -244,7 +244,7 @@ type Action interface { // key (formatted as a big-endian uint16). This is used to automatically calculate storage usage. // // If any key is removed and then re-created, this will count as a creation instead of a modification. - StateKeys(actor codec.Address, actionID codec.Address) state.Keys + StateKeys(actor codec.Address, actionID codec.ActionID) state.Keys // Execute actually runs the [Action]. Any state changes that the [Action] performs should // be done here. @@ -260,7 +260,7 @@ type Action interface { mu state.Mutable, timestamp int64, actor codec.Address, - actionID codec.Address, + actionID codec.ActionID, ) (success bool, computeUnits uint64, output []byte, err error) } diff --git a/codec/address.go b/codec/address.go index 4ececa92a0..a9e81214a0 100644 --- a/codec/address.go +++ b/codec/address.go @@ -21,6 +21,8 @@ const ( maxBech32Size = 90 ) +type ActionID Address + type Address [AddressLen]byte var EmptyAddress = [AddressLen]byte{} @@ -78,3 +80,10 @@ func ParseAddressBech32(hrp, saddr string) (Address, error) { } return Address(p[:AddressLen]), nil } + +func CreateActionID(idx uint8, txID ids.ID) ActionID { + a := make([]byte, AddressLen) + a[0] = idx + copy(a[1:], txID[:]) + return ActionID(a) +} diff --git a/examples/morpheusvm/actions/transfer.go b/examples/morpheusvm/actions/transfer.go index ce90652731..43ad28763c 100644 --- a/examples/morpheusvm/actions/transfer.go +++ b/examples/morpheusvm/actions/transfer.go @@ -30,11 +30,11 @@ func (*Transfer) GetTypeID() uint8 { return mconsts.TransferID } -func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.Address { - return codec.CreateAddress(i, txID) +func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.ActionID { + return codec.CreateActionID(i, txID) } -func (t *Transfer) StateKeys(actor codec.Address, _ codec.Address) state.Keys { +func (t *Transfer) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor)): state.Read | state.Write, string(storage.BalanceKey(t.To)): state.All, @@ -51,7 +51,7 @@ func (t *Transfer) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.Address, + _ codec.ActionID, ) (bool, uint64, []byte, error) { if t.Value == 0 { return false, 1, OutputValueZero, nil From 4d6be79d296c25b6fb821d91cfcd4734ad53d73a Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 26 Apr 2024 11:20:05 -0400 Subject: [PATCH 30/78] fix tokenvm --- codec/address.go | 8 ++ codec/packer.go | 11 ++ examples/tokenvm/actions/burn_asset.go | 14 ++- examples/tokenvm/actions/close_order.go | 20 ++-- examples/tokenvm/actions/create_asset.go | 12 +- examples/tokenvm/actions/create_order.go | 29 +++-- examples/tokenvm/actions/fill_order.go | 26 +++-- examples/tokenvm/actions/mint_asset.go | 16 ++- examples/tokenvm/actions/transfer.go | 14 ++- examples/tokenvm/controller/controller.go | 52 ++++----- examples/tokenvm/controller/resolutions.go | 12 +- examples/tokenvm/controller/state_manager.go | 9 +- examples/tokenvm/genesis/genesis.go | 11 +- examples/tokenvm/genesis/rules.go | 4 + examples/tokenvm/orderbook/orderbook.go | 37 +++--- examples/tokenvm/rpc/dependencies.go | 12 +- examples/tokenvm/rpc/jsonrpc_client.go | 15 +-- examples/tokenvm/rpc/jsonrpc_server.go | 12 +- examples/tokenvm/storage/storage.go | 112 +++++++++---------- 19 files changed, 242 insertions(+), 184 deletions(-) diff --git a/codec/address.go b/codec/address.go index a9e81214a0..1becf317b2 100644 --- a/codec/address.go +++ b/codec/address.go @@ -87,3 +87,11 @@ func CreateActionID(idx uint8, txID ids.ID) ActionID { copy(a[1:], txID[:]) return ActionID(a) } + +func ActionToString(hrp string, a ActionID) string { + addr, err := AddressBech32(hrp, Address(a)) + if err != nil { + panic(err) + } + return addr +} diff --git a/codec/packer.go b/codec/packer.go index 2f282c81c3..911d89905a 100644 --- a/codec/packer.go +++ b/codec/packer.go @@ -81,6 +81,17 @@ func (p *Packer) UnpackAddress(dest *Address) { } } +func (p *Packer) PackActionID(a ActionID) { + p.p.PackFixedBytes(a[:]) +} + +func (p *Packer) UnpackActionID(required bool, dest *ActionID) { + copy((*dest)[:], p.p.UnpackFixedBytes(AddressLen)) + if required && *dest == EmptyAddress { + p.addErr(fmt.Errorf("%w: Address field is not populated", ErrFieldNotPopulated)) + } +} + func (p *Packer) PackBytes(b []byte) { p.p.PackBytes(b) } diff --git a/examples/tokenvm/actions/burn_asset.go b/examples/tokenvm/actions/burn_asset.go index b72b49a577..6817113da8 100644 --- a/examples/tokenvm/actions/burn_asset.go +++ b/examples/tokenvm/actions/burn_asset.go @@ -21,7 +21,7 @@ var _ chain.Action = (*BurnAsset)(nil) type BurnAsset struct { // Asset is the [TxID] that created the asset. - Asset ids.ID `json:"asset"` + Asset codec.ActionID `json:"asset"` // Number of assets to mint to [To]. Value uint64 `json:"value"` @@ -31,7 +31,11 @@ func (*BurnAsset) GetTypeID() uint8 { return burnAssetID } -func (b *BurnAsset) StateKeys(actor codec.Address, _ ids.ID) state.Keys { +func (*BurnAsset) GetActionID(i uint8, txID ids.ID) codec.ActionID { + return codec.CreateActionID(i, txID) +} + +func (b *BurnAsset) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { return state.Keys{ string(storage.AssetKey(b.Asset)): state.Read | state.Write, string(storage.BalanceKey(actor, b.Asset)): state.Read | state.Write, @@ -48,7 +52,7 @@ func (b *BurnAsset) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ ids.ID, + _ codec.ActionID, ) (bool, uint64, []byte, error) { if b.Value == 0 { return false, BurnComputeUnits, OutputValueZero, nil @@ -82,13 +86,13 @@ func (*BurnAsset) Size() int { } func (b *BurnAsset) Marshal(p *codec.Packer) { - p.PackID(b.Asset) + p.PackActionID(b.Asset) p.PackUint64(b.Value) } func UnmarshalBurnAsset(p *codec.Packer) (chain.Action, error) { var burn BurnAsset - p.UnpackID(false, &burn.Asset) // can burn native asset + p.UnpackActionID(false, &burn.Asset) // can burn native asset burn.Value = p.UnpackUint64(true) return &burn, p.Err() } diff --git a/examples/tokenvm/actions/close_order.go b/examples/tokenvm/actions/close_order.go index 862d142752..b1f8e45b30 100644 --- a/examples/tokenvm/actions/close_order.go +++ b/examples/tokenvm/actions/close_order.go @@ -19,18 +19,22 @@ var _ chain.Action = (*CloseOrder)(nil) type CloseOrder struct { // [Order] is the OrderID you wish to close. - Order ids.ID `json:"order"` + Order codec.ActionID `json:"order"` // [Out] is the asset locked up in the order. We need to provide this to // populate [StateKeys]. - Out ids.ID `json:"out"` + Out codec.ActionID `json:"out"` } func (*CloseOrder) GetTypeID() uint8 { return closeOrderID } -func (c *CloseOrder) StateKeys(actor codec.Address, _ ids.ID) state.Keys { +func (*CloseOrder) GetActionID(i uint8, txID ids.ID) codec.ActionID { + return codec.CreateActionID(i, txID) +} + +func (c *CloseOrder) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { return state.Keys{ string(storage.OrderKey(c.Order)): state.Read | state.Write, string(storage.BalanceKey(actor, c.Out)): state.Read | state.Write, @@ -47,7 +51,7 @@ func (c *CloseOrder) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ ids.ID, + _ codec.ActionID, ) (bool, uint64, []byte, error) { exists, _, _, out, _, remaining, owner, err := storage.GetOrder(ctx, mu, c.Order) if err != nil { @@ -80,14 +84,14 @@ func (*CloseOrder) Size() int { } func (c *CloseOrder) Marshal(p *codec.Packer) { - p.PackID(c.Order) - p.PackID(c.Out) + p.PackActionID(c.Order) + p.PackActionID(c.Out) } func UnmarshalCloseOrder(p *codec.Packer) (chain.Action, error) { var cl CloseOrder - p.UnpackID(true, &cl.Order) - p.UnpackID(false, &cl.Out) // empty ID is the native asset + p.UnpackActionID(true, &cl.Order) + p.UnpackActionID(false, &cl.Out) // empty ID is the native asset return &cl, p.Err() } diff --git a/examples/tokenvm/actions/create_asset.go b/examples/tokenvm/actions/create_asset.go index a044e3bacd..1de4fc1b40 100644 --- a/examples/tokenvm/actions/create_asset.go +++ b/examples/tokenvm/actions/create_asset.go @@ -27,9 +27,13 @@ func (*CreateAsset) GetTypeID() uint8 { return createAssetID } -func (*CreateAsset) StateKeys(_ codec.Address, txID ids.ID) state.Keys { +func (*CreateAsset) GetActionID(i uint8, txID ids.ID) codec.ActionID { + return codec.CreateActionID(i, txID) +} + +func (*CreateAsset) StateKeys(_ codec.Address, actionID codec.ActionID) state.Keys { return state.Keys{ - string(storage.AssetKey(txID)): state.Allocate | state.Write, + string(storage.AssetKey(actionID)): state.Allocate | state.Write, } } @@ -43,7 +47,7 @@ func (c *CreateAsset) Execute( mu state.Mutable, _ int64, actor codec.Address, - txID ids.ID, + actionID codec.ActionID, ) (bool, uint64, []byte, error) { if len(c.Symbol) == 0 { return false, CreateAssetComputeUnits, OutputSymbolEmpty, nil @@ -62,7 +66,7 @@ func (c *CreateAsset) Execute( } // It should only be possible to overwrite an existing asset if there is // a hash collision. - if err := storage.SetAsset(ctx, mu, txID, c.Symbol, c.Decimals, c.Metadata, 0, actor); err != nil { + if err := storage.SetAsset(ctx, mu, actionID, c.Symbol, c.Decimals, c.Metadata, 0, actor); err != nil { return false, CreateAssetComputeUnits, utils.ErrBytes(err), nil } return true, CreateAssetComputeUnits, nil, nil diff --git a/examples/tokenvm/actions/create_order.go b/examples/tokenvm/actions/create_order.go index 243e4ad9fb..e40870408f 100644 --- a/examples/tokenvm/actions/create_order.go +++ b/examples/tokenvm/actions/create_order.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" + tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/utils" @@ -20,7 +21,7 @@ var _ chain.Action = (*CreateOrder)(nil) type CreateOrder struct { // [In] is the asset you trade for [Out]. - In ids.ID `json:"in"` + In codec.ActionID `json:"in"` // [InTick] is the amount of [In] required to purchase // [OutTick] of [Out]. @@ -29,7 +30,7 @@ type CreateOrder struct { // [Out] is the asset you receive when trading for [In]. // // This is the asset that is actually provided by the creator. - Out ids.ID `json:"out"` + Out codec.ActionID `json:"out"` // [OutTick] is the amount of [Out] the counterparty gets per [InTick] of // [In]. @@ -50,10 +51,14 @@ func (*CreateOrder) GetTypeID() uint8 { return createOrderID } -func (c *CreateOrder) StateKeys(actor codec.Address, txID ids.ID) state.Keys { +func (*CreateOrder) GetActionID(i uint8, txID ids.ID) codec.ActionID { + return codec.CreateActionID(i, txID) +} + +func (c *CreateOrder) StateKeys(actor codec.Address, actionID codec.ActionID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor, c.Out)): state.Read | state.Write, - string(storage.OrderKey(txID)): state.Allocate | state.Write, + string(storage.OrderKey(actionID)): state.Allocate | state.Write, } } @@ -67,7 +72,7 @@ func (c *CreateOrder) Execute( mu state.Mutable, _ int64, actor codec.Address, - txID ids.ID, + actionID codec.ActionID, ) (bool, uint64, []byte, error) { if c.In == c.Out { return false, CreateOrderComputeUnits, OutputSameInOut, nil @@ -87,7 +92,7 @@ func (c *CreateOrder) Execute( if err := storage.SubBalance(ctx, mu, actor, c.Out, c.Supply); err != nil { return false, CreateOrderComputeUnits, utils.ErrBytes(err), nil } - if err := storage.SetOrder(ctx, mu, txID, c.In, c.InTick, c.Out, c.OutTick, c.Supply, actor); err != nil { + if err := storage.SetOrder(ctx, mu, actionID, c.In, c.InTick, c.Out, c.OutTick, c.Supply, actor); err != nil { return false, CreateOrderComputeUnits, utils.ErrBytes(err), nil } return true, CreateOrderComputeUnits, nil, nil @@ -102,18 +107,18 @@ func (*CreateOrder) Size() int { } func (c *CreateOrder) Marshal(p *codec.Packer) { - p.PackID(c.In) + p.PackActionID(c.In) p.PackUint64(c.InTick) - p.PackID(c.Out) + p.PackActionID(c.Out) p.PackUint64(c.OutTick) p.PackUint64(c.Supply) } func UnmarshalCreateOrder(p *codec.Packer) (chain.Action, error) { var create CreateOrder - p.UnpackID(false, &create.In) // empty ID is the native asset + p.UnpackActionID(false, &create.In) // empty ID is the native asset create.InTick = p.UnpackUint64(true) - p.UnpackID(false, &create.Out) // empty ID is the native asset + p.UnpackActionID(false, &create.Out) // empty ID is the native asset create.OutTick = p.UnpackUint64(true) create.Supply = p.UnpackUint64(true) return &create, p.Err() @@ -124,6 +129,6 @@ func (*CreateOrder) ValidRange(chain.Rules) (int64, int64) { return -1, -1 } -func PairID(in ids.ID, out ids.ID) string { - return fmt.Sprintf("%s-%s", in.String(), out.String()) +func PairID(in codec.ActionID, out codec.ActionID) string { + return fmt.Sprintf("%s-%s", codec.ActionToString(tconsts.HRP, in), codec.ActionToString(tconsts.HRP, out)) } diff --git a/examples/tokenvm/actions/fill_order.go b/examples/tokenvm/actions/fill_order.go index 0c16454642..6f0ab8211a 100644 --- a/examples/tokenvm/actions/fill_order.go +++ b/examples/tokenvm/actions/fill_order.go @@ -21,7 +21,7 @@ var _ chain.Action = (*FillOrder)(nil) type FillOrder struct { // [Order] is the OrderID you wish to close. - Order ids.ID `json:"order"` + Order codec.ActionID `json:"order"` // [Owner] is the owner of the order and the recipient of the trade // proceeds. @@ -29,11 +29,11 @@ type FillOrder struct { // [In] is the asset that will be sent to the owner from the fill. We need to provide this to // populate [StateKeys]. - In ids.ID `json:"in"` + In codec.ActionID `json:"in"` // [Out] is the asset that will be received from the fill. We need to provide this to // populate [StateKeys]. - Out ids.ID `json:"out"` + Out codec.ActionID `json:"out"` // [Value] is the max amount of [In] that will be swapped for [Out]. Value uint64 `json:"value"` @@ -43,7 +43,11 @@ func (*FillOrder) GetTypeID() uint8 { return fillOrderID } -func (f *FillOrder) StateKeys(actor codec.Address, _ ids.ID) state.Keys { +func (*FillOrder) GetActionID(i uint8, txID ids.ID) codec.ActionID { + return codec.CreateActionID(i, txID) +} + +func (f *FillOrder) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { return state.Keys{ string(storage.OrderKey(f.Order)): state.Read | state.Write, string(storage.BalanceKey(f.Owner, f.In)): state.All, @@ -62,7 +66,7 @@ func (f *FillOrder) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ ids.ID, + _ codec.ActionID, ) (bool, uint64, []byte, error) { exists, in, inTick, out, outTick, remaining, owner, err := storage.GetOrder(ctx, mu, f.Order) if err != nil { @@ -158,19 +162,19 @@ func (*FillOrder) Size() int { } func (f *FillOrder) Marshal(p *codec.Packer) { - p.PackID(f.Order) + p.PackActionID(f.Order) p.PackAddress(f.Owner) - p.PackID(f.In) - p.PackID(f.Out) + p.PackActionID(f.In) + p.PackActionID(f.Out) p.PackUint64(f.Value) } func UnmarshalFillOrder(p *codec.Packer) (chain.Action, error) { var fill FillOrder - p.UnpackID(true, &fill.Order) + p.UnpackActionID(true, &fill.Order) p.UnpackAddress(&fill.Owner) - p.UnpackID(false, &fill.In) // empty ID is the native asset - p.UnpackID(false, &fill.Out) // empty ID is the native asset + p.UnpackActionID(false, &fill.In) // empty ID is the native asset + p.UnpackActionID(false, &fill.Out) // empty ID is the native asset fill.Value = p.UnpackUint64(true) return &fill, p.Err() } diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index 52e05ca576..75e3d2fab0 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -24,7 +24,7 @@ type MintAsset struct { To codec.Address `json:"to"` // Asset is the [TxID] that created the asset. - Asset ids.ID `json:"asset"` + Asset codec.ActionID `json:"asset"` // Number of assets to mint to [To]. Value uint64 `json:"value"` @@ -34,7 +34,11 @@ func (*MintAsset) GetTypeID() uint8 { return mintAssetID } -func (m *MintAsset) StateKeys(codec.Address, ids.ID) state.Keys { +func (*MintAsset) GetActionID(i uint8, txID ids.ID) codec.ActionID { + return codec.CreateActionID(i, txID) +} + +func (m *MintAsset) StateKeys(codec.Address, codec.ActionID) state.Keys { return state.Keys{ string(storage.AssetKey(m.Asset)): state.Read | state.Write, string(storage.BalanceKey(m.To, m.Asset)): state.All, @@ -51,9 +55,9 @@ func (m *MintAsset) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ ids.ID, + _ codec.ActionID, ) (bool, uint64, []byte, error) { - if m.Asset == ids.Empty { + if m.Asset == codec.EmptyAddress { return false, MintAssetComputeUnits, OutputAssetIsNative, nil } if m.Value == 0 { @@ -92,14 +96,14 @@ func (*MintAsset) Size() int { func (m *MintAsset) Marshal(p *codec.Packer) { p.PackAddress(m.To) - p.PackID(m.Asset) + p.PackActionID(m.Asset) p.PackUint64(m.Value) } func UnmarshalMintAsset(p *codec.Packer) (chain.Action, error) { var mint MintAsset p.UnpackAddress(&mint.To) - p.UnpackID(true, &mint.Asset) // empty ID is the native asset + p.UnpackActionID(true, &mint.Asset) // empty ID is the native asset mint.Value = p.UnpackUint64(true) return &mint, p.Err() } diff --git a/examples/tokenvm/actions/transfer.go b/examples/tokenvm/actions/transfer.go index f1630f10c0..f2ca2ddf56 100644 --- a/examples/tokenvm/actions/transfer.go +++ b/examples/tokenvm/actions/transfer.go @@ -22,7 +22,7 @@ type Transfer struct { To codec.Address `json:"to"` // Asset to transfer to [To]. - Asset ids.ID `json:"asset"` + Asset codec.ActionID `json:"asset"` // Amount are transferred to [To]. Value uint64 `json:"value"` @@ -35,7 +35,11 @@ func (*Transfer) GetTypeID() uint8 { return transferID } -func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys { +func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.ActionID { + return codec.CreateActionID(i, txID) +} + +func (t *Transfer) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor, t.Asset)): state.Read | state.Write, string(storage.BalanceKey(t.To, t.Asset)): state.All, @@ -52,7 +56,7 @@ func (t *Transfer) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ ids.ID, + _ codec.ActionID, ) (bool, uint64, []byte, error) { if t.Value == 0 { return false, TransferComputeUnits, OutputValueZero, nil @@ -80,7 +84,7 @@ func (t *Transfer) Size() int { func (t *Transfer) Marshal(p *codec.Packer) { p.PackAddress(t.To) - p.PackID(t.Asset) + p.PackActionID(t.Asset) p.PackUint64(t.Value) p.PackBytes(t.Memo) } @@ -88,7 +92,7 @@ func (t *Transfer) Marshal(p *codec.Packer) { func UnmarshalTransfer(p *codec.Packer) (chain.Action, error) { var transfer Transfer p.UnpackAddress(&transfer.To) - p.UnpackID(false, &transfer.Asset) // empty ID is the native asset + p.UnpackActionID(false, &transfer.Asset) // empty ID is the native asset transfer.Value = p.UnpackUint64(true) p.UnpackBytes(MaxMemoSize, false, &transfer.Memo) return &transfer, p.Err() diff --git a/examples/tokenvm/controller/controller.go b/examples/tokenvm/controller/controller.go index a84dcdc5d4..53a3cd6508 100644 --- a/examples/tokenvm/controller/controller.go +++ b/examples/tokenvm/controller/controller.go @@ -179,33 +179,35 @@ func (c *Controller) Accepted(ctx context.Context, blk *chain.StatelessBlock) er } } if result.Success { - switch action := tx.Action.(type) { - case *actions.CreateAsset: - c.metrics.createAsset.Inc() - case *actions.MintAsset: - c.metrics.mintAsset.Inc() - case *actions.BurnAsset: - c.metrics.burnAsset.Inc() - case *actions.Transfer: - c.metrics.transfer.Inc() - case *actions.CreateOrder: - c.metrics.createOrder.Inc() - c.orderBook.Add(tx.ID(), tx.Auth.Actor(), action) - case *actions.FillOrder: - c.metrics.fillOrder.Inc() - orderResult, err := actions.UnmarshalOrderResult(result.Output) - if err != nil { - // This should never happen - return err - } - if orderResult.Remaining == 0 { + for i, act := range tx.Actions { + switch action := act.(type) { + case *actions.CreateAsset: + c.metrics.createAsset.Inc() + case *actions.MintAsset: + c.metrics.mintAsset.Inc() + case *actions.BurnAsset: + c.metrics.burnAsset.Inc() + case *actions.Transfer: + c.metrics.transfer.Inc() + case *actions.CreateOrder: + c.metrics.createOrder.Inc() + c.orderBook.Add(action.GetActionID(uint8(i), tx.ID()), tx.Auth.Actor(), action) + case *actions.FillOrder: + c.metrics.fillOrder.Inc() + orderResult, err := actions.UnmarshalOrderResult(result.Output) + if err != nil { + // This should never happen + return err + } + if orderResult.Remaining == 0 { + c.orderBook.Remove(action.Order) + continue + } + c.orderBook.UpdateRemaining(action.Order, orderResult.Remaining) + case *actions.CloseOrder: + c.metrics.closeOrder.Inc() c.orderBook.Remove(action.Order) - continue } - c.orderBook.UpdateRemaining(action.Order, orderResult.Remaining) - case *actions.CloseOrder: - c.metrics.closeOrder.Inc() - c.orderBook.Remove(action.Order) } } } diff --git a/examples/tokenvm/controller/resolutions.go b/examples/tokenvm/controller/resolutions.go index ccdd47882e..ac1f5099e7 100644 --- a/examples/tokenvm/controller/resolutions.go +++ b/examples/tokenvm/controller/resolutions.go @@ -37,7 +37,7 @@ func (c *Controller) GetTransaction( func (c *Controller) GetAssetFromState( ctx context.Context, - asset ids.ID, + asset codec.ActionID, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { return storage.GetAssetFromState(ctx, c.inner.ReadState, asset) } @@ -45,7 +45,7 @@ func (c *Controller) GetAssetFromState( func (c *Controller) GetBalanceFromState( ctx context.Context, addr codec.Address, - asset ids.ID, + asset codec.ActionID, ) (uint64, error) { return storage.GetBalanceFromState(ctx, c.inner.ReadState, addr, asset) } @@ -56,12 +56,12 @@ func (c *Controller) Orders(pair string, limit int) []*orderbook.Order { func (c *Controller) GetOrderFromState( ctx context.Context, - orderID ids.ID, + orderID codec.ActionID, ) ( bool, // exists - ids.ID, // in + codec.ActionID, // in uint64, // inTick - ids.ID, // out + codec.ActionID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -72,7 +72,7 @@ func (c *Controller) GetOrderFromState( func (c *Controller) GetLoanFromState( ctx context.Context, - asset ids.ID, + asset codec.ActionID, destination ids.ID, ) (uint64, error) { return storage.GetLoanFromState(ctx, c.inner.ReadState, asset, destination) diff --git a/examples/tokenvm/controller/state_manager.go b/examples/tokenvm/controller/state_manager.go index 3a1fe82dce..27b859ac11 100644 --- a/examples/tokenvm/controller/state_manager.go +++ b/examples/tokenvm/controller/state_manager.go @@ -6,7 +6,6 @@ package controller import ( "context" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" @@ -31,7 +30,7 @@ func (*StateManager) FeeKey() []byte { func (*StateManager) SponsorStateKeys(addr codec.Address) state.Keys { return state.Keys{ - string(storage.BalanceKey(addr, ids.Empty)): state.Read | state.Write, + string(storage.BalanceKey(addr, codec.EmptyAddress)): state.Read | state.Write, } } @@ -41,7 +40,7 @@ func (*StateManager) CanDeduct( im state.Immutable, amount uint64, ) error { - bal, err := storage.GetBalance(ctx, im, addr, ids.Empty) + bal, err := storage.GetBalance(ctx, im, addr, codec.EmptyAddress) if err != nil { return err } @@ -57,7 +56,7 @@ func (*StateManager) Deduct( mu state.Mutable, amount uint64, ) error { - return storage.SubBalance(ctx, mu, addr, ids.Empty, amount) + return storage.SubBalance(ctx, mu, addr, codec.EmptyAddress, amount) } func (*StateManager) Refund( @@ -67,5 +66,5 @@ func (*StateManager) Refund( amount uint64, ) error { // Don't create account if it doesn't exist (may have sent all funds). - return storage.AddBalance(ctx, mu, addr, ids.Empty, amount, false) + return storage.AddBalance(ctx, mu, addr, codec.EmptyAddress, amount, false) } diff --git a/examples/tokenvm/genesis/genesis.go b/examples/tokenvm/genesis/genesis.go index 4632bd332f..eb95a527bd 100644 --- a/examples/tokenvm/genesis/genesis.go +++ b/examples/tokenvm/genesis/genesis.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" smath "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/x/merkledb" @@ -55,6 +54,9 @@ type Genesis struct { StorageKeyWriteUnits uint64 `json:"storageKeyWriteUnits"` StorageValueWriteUnits uint64 `json:"storageValueWriteUnits"` // per chunk + // Action Per Tx + MaxActionsPerTx int `json:"maxActionsPerTx"` + // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` } @@ -89,6 +91,9 @@ func Default() *Genesis { StorageValueAllocateUnits: 5, StorageKeyWriteUnits: 10, StorageValueWriteUnits: 3, + + // Action Per Tx + MaxActionsPerTx: 1, } } @@ -120,14 +125,14 @@ func (g *Genesis) Load(ctx context.Context, tracer trace.Tracer, mu state.Mutabl if err != nil { return err } - if err := storage.SetBalance(ctx, mu, pk, ids.Empty, alloc.Balance); err != nil { + if err := storage.SetBalance(ctx, mu, pk, codec.EmptyAddress, alloc.Balance); err != nil { return fmt.Errorf("%w: addr=%s, bal=%d", err, alloc.Address, alloc.Balance) } } return storage.SetAsset( ctx, mu, - ids.Empty, + codec.EmptyAddress, []byte(consts.Symbol), consts.Decimals, []byte(consts.Name), diff --git a/examples/tokenvm/genesis/rules.go b/examples/tokenvm/genesis/rules.go index c787c5ac6f..d2681650e1 100644 --- a/examples/tokenvm/genesis/rules.go +++ b/examples/tokenvm/genesis/rules.go @@ -95,3 +95,7 @@ func (r *Rules) GetWindowTargetUnits() fees.Dimensions { func (*Rules) FetchCustom(string) (any, bool) { return nil, false } + +func (r *Rules) GetMaxActionsPerTx() int { + return r.g.MaxActionsPerTx +} diff --git a/examples/tokenvm/orderbook/orderbook.go b/examples/tokenvm/orderbook/orderbook.go index cf717ee705..a382fa9551 100644 --- a/examples/tokenvm/orderbook/orderbook.go +++ b/examples/tokenvm/orderbook/orderbook.go @@ -6,7 +6,6 @@ package orderbook import ( "sync" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" @@ -17,13 +16,13 @@ import ( const allPairs = "*" type Order struct { - ID ids.ID `json:"id"` - Owner string `json:"owner"` // we always send address over RPC - InAsset ids.ID `json:"inAsset"` - InTick uint64 `json:"inTick"` - OutAsset ids.ID `json:"outAsset"` - OutTick uint64 `json:"outTick"` - Remaining uint64 `json:"remaining"` + ID codec.ActionID `json:"id"` + Owner string `json:"owner"` // we always send address over RPC + InAsset codec.ActionID `json:"inAsset"` + InTick uint64 `json:"inTick"` + OutAsset codec.ActionID `json:"outAsset"` + OutTick uint64 `json:"outTick"` + Remaining uint64 `json:"remaining"` owner codec.Address } @@ -35,8 +34,8 @@ type OrderBook struct { // dust orders from filling the heap. // // TODO: Allow operator to specify min creation supply per pair to be tracked - orders map[string]*heap.Heap[ids.ID, *Order, float64] - orderToPair map[ids.ID]string // needed to delete from [CloseOrder] actions + orders map[string]*heap.Heap[codec.ActionID, *Order, float64] + orderToPair map[codec.ActionID]string // needed to delete from [CloseOrder] actions maxOrdersPerPair int l sync.RWMutex @@ -44,7 +43,7 @@ type OrderBook struct { } func New(c Controller, trackedPairs []string, maxOrdersPerPair int) *OrderBook { - m := map[string]*heap.Heap[ids.ID, *Order, float64]{} + m := map[string]*heap.Heap[codec.ActionID, *Order, float64]{} trackAll := false if len(trackedPairs) == 1 && trackedPairs[0] == allPairs { trackAll = true @@ -52,23 +51,23 @@ func New(c Controller, trackedPairs []string, maxOrdersPerPair int) *OrderBook { } else { for _, pair := range trackedPairs { // We use a max heap so we return the best rates in order. - m[pair] = heap.New[ids.ID, *Order, float64](maxOrdersPerPair+1, true) + m[pair] = heap.New[codec.ActionID, *Order, float64](maxOrdersPerPair+1, true) c.Logger().Info("tracking order book", zap.String("pair", pair)) } } return &OrderBook{ c: c, orders: m, - orderToPair: map[ids.ID]string{}, + orderToPair: map[codec.ActionID]string{}, maxOrdersPerPair: maxOrdersPerPair, trackAll: trackAll, } } -func (o *OrderBook) Add(txID ids.ID, actor codec.Address, action *actions.CreateOrder) { +func (o *OrderBook) Add(actionID codec.ActionID, actor codec.Address, action *actions.CreateOrder) { pair := actions.PairID(action.In, action.Out) order := &Order{ - txID, + actionID, codec.MustAddressBech32(consts.HRP, actor), action.In, action.InTick, @@ -86,10 +85,10 @@ func (o *OrderBook) Add(txID ids.ID, actor codec.Address, action *actions.Create return case !ok && o.trackAll: o.c.Logger().Info("tracking order book", zap.String("pair", pair)) - h = heap.New[ids.ID, *Order, float64](o.maxOrdersPerPair+1, true) + h = heap.New[codec.ActionID, *Order, float64](o.maxOrdersPerPair+1, true) o.orders[pair] = h } - h.Push(&heap.Entry[ids.ID, *Order, float64]{ + h.Push(&heap.Entry[codec.ActionID, *Order, float64]{ ID: order.ID, Val: float64(order.InTick) / float64(order.OutTick), Item: order, @@ -105,7 +104,7 @@ func (o *OrderBook) Add(txID ids.ID, actor codec.Address, action *actions.Create } } -func (o *OrderBook) Remove(id ids.ID) { +func (o *OrderBook) Remove(id codec.ActionID) { o.l.Lock() defer o.l.Unlock() @@ -127,7 +126,7 @@ func (o *OrderBook) Remove(id ids.ID) { h.Remove(entry.Index) // O(log N) } -func (o *OrderBook) UpdateRemaining(id ids.ID, remaining uint64) { +func (o *OrderBook) UpdateRemaining(id codec.ActionID, remaining uint64) { o.l.Lock() defer o.l.Unlock() diff --git a/examples/tokenvm/rpc/dependencies.go b/examples/tokenvm/rpc/dependencies.go index fd43a854fa..595fbb9db6 100644 --- a/examples/tokenvm/rpc/dependencies.go +++ b/examples/tokenvm/rpc/dependencies.go @@ -18,18 +18,18 @@ type Controller interface { Genesis() *genesis.Genesis Tracer() trace.Tracer GetTransaction(context.Context, ids.ID) (bool, int64, bool, fees.Dimensions, uint64, error) - GetAssetFromState(context.Context, ids.ID) (bool, []byte, uint8, []byte, uint64, codec.Address, error) - GetBalanceFromState(context.Context, codec.Address, ids.ID) (uint64, error) + GetAssetFromState(context.Context, codec.ActionID) (bool, []byte, uint8, []byte, uint64, codec.Address, error) + GetBalanceFromState(context.Context, codec.Address, codec.ActionID) (uint64, error) Orders(pair string, limit int) []*orderbook.Order - GetOrderFromState(context.Context, ids.ID) ( + GetOrderFromState(context.Context, codec.ActionID) ( bool, // exists - ids.ID, // in + codec.ActionID, // in uint64, // inTick - ids.ID, // out + codec.ActionID, // out uint64, // outTick uint64, // remaining codec.Address, // owner error, ) - GetLoanFromState(context.Context, ids.ID, ids.ID) (uint64, error) + GetLoanFromState(context.Context, codec.ActionID, ids.ID) (uint64, error) } diff --git a/examples/tokenvm/rpc/jsonrpc_client.go b/examples/tokenvm/rpc/jsonrpc_client.go index 934ef3700f..3d3eaa652c 100644 --- a/examples/tokenvm/rpc/jsonrpc_client.go +++ b/examples/tokenvm/rpc/jsonrpc_client.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" "github.com/ava-labs/hypersdk/examples/tokenvm/orderbook" @@ -28,7 +29,7 @@ type JSONRPCClient struct { chainID ids.ID g *genesis.Genesis assetsL sync.Mutex - assets map[ids.ID]*AssetReply + assets map[codec.ActionID]*AssetReply } // New creates a new client object. @@ -40,7 +41,7 @@ func NewJSONRPCClient(uri string, networkID uint32, chainID ids.ID) *JSONRPCClie requester: req, networkID: networkID, chainID: chainID, - assets: map[ids.ID]*AssetReply{}, + assets: map[codec.ActionID]*AssetReply{}, } } @@ -84,7 +85,7 @@ func (cli *JSONRPCClient) Tx(ctx context.Context, id ids.ID) (bool, bool, int64, func (cli *JSONRPCClient) Asset( ctx context.Context, - asset ids.ID, + asset codec.ActionID, useCache bool, ) (bool, []byte, uint8, []byte, uint64, string, error) { cli.assetsL.Lock() @@ -116,7 +117,7 @@ func (cli *JSONRPCClient) Asset( return true, resp.Symbol, resp.Decimals, resp.Metadata, resp.Supply, resp.Owner, nil } -func (cli *JSONRPCClient) Balance(ctx context.Context, addr string, asset ids.ID) (uint64, error) { +func (cli *JSONRPCClient) Balance(ctx context.Context, addr string, asset codec.ActionID) (uint64, error) { resp := new(BalanceReply) err := cli.requester.SendRequest( ctx, @@ -143,7 +144,7 @@ func (cli *JSONRPCClient) Orders(ctx context.Context, pair string) ([]*orderbook return resp.Orders, err } -func (cli *JSONRPCClient) GetOrder(ctx context.Context, orderID ids.ID) (*orderbook.Order, error) { +func (cli *JSONRPCClient) GetOrder(ctx context.Context, orderID codec.ActionID) (*orderbook.Order, error) { resp := new(GetOrderReply) err := cli.requester.SendRequest( ctx, @@ -158,7 +159,7 @@ func (cli *JSONRPCClient) GetOrder(ctx context.Context, orderID ids.ID) (*orderb func (cli *JSONRPCClient) Loan( ctx context.Context, - asset ids.ID, + asset codec.ActionID, destination ids.ID, ) (uint64, error) { resp := new(LoanReply) @@ -177,7 +178,7 @@ func (cli *JSONRPCClient) Loan( func (cli *JSONRPCClient) WaitForBalance( ctx context.Context, addr string, - asset ids.ID, + asset codec.ActionID, min uint64, ) error { exists, symbol, decimals, _, _, _, err := cli.Asset(ctx, asset, true) diff --git a/examples/tokenvm/rpc/jsonrpc_server.go b/examples/tokenvm/rpc/jsonrpc_server.go index da64956056..566199553f 100644 --- a/examples/tokenvm/rpc/jsonrpc_server.go +++ b/examples/tokenvm/rpc/jsonrpc_server.go @@ -62,7 +62,7 @@ func (j *JSONRPCServer) Tx(req *http.Request, args *TxArgs, reply *TxReply) erro } type AssetArgs struct { - Asset ids.ID `json:"asset"` + Asset codec.ActionID `json:"asset"` } type AssetReply struct { @@ -93,8 +93,8 @@ func (j *JSONRPCServer) Asset(req *http.Request, args *AssetArgs, reply *AssetRe } type BalanceArgs struct { - Address string `json:"address"` - Asset ids.ID `json:"asset"` + Address string `json:"address"` + Asset codec.ActionID `json:"asset"` } type BalanceReply struct { @@ -134,7 +134,7 @@ func (j *JSONRPCServer) Orders(req *http.Request, args *OrdersArgs, reply *Order } type GetOrderArgs struct { - OrderID ids.ID `json:"orderID"` + OrderID codec.ActionID `json:"orderID"` } type GetOrderReply struct { @@ -165,8 +165,8 @@ func (j *JSONRPCServer) GetOrder(req *http.Request, args *GetOrderArgs, reply *G } type LoanArgs struct { - Destination ids.ID `json:"destination"` - Asset ids.ID `json:"asset"` + Destination ids.ID `json:"destination"` + Asset codec.ActionID `json:"asset"` } type LoanReply struct { diff --git a/examples/tokenvm/storage/storage.go b/examples/tokenvm/storage/storage.go index e8382a8e16..b002a8fab1 100644 --- a/examples/tokenvm/storage/storage.go +++ b/examples/tokenvm/storage/storage.go @@ -131,12 +131,12 @@ func GetTransaction( } // [accountPrefix] + [address] + [asset] -func BalanceKey(addr codec.Address, asset ids.ID) (k []byte) { +func BalanceKey(addr codec.Address, asset codec.ActionID) (k []byte) { k = balanceKeyPool.Get().([]byte) k[0] = balancePrefix copy(k[1:], addr[:]) copy(k[1+codec.AddressLen:], asset[:]) - binary.BigEndian.PutUint16(k[1+codec.AddressLen+consts.IDLen:], BalanceChunks) + binary.BigEndian.PutUint16(k[1+codec.AddressLen*2:], BalanceChunks) return } @@ -145,7 +145,7 @@ func GetBalance( ctx context.Context, im state.Immutable, addr codec.Address, - asset ids.ID, + asset codec.ActionID, ) (uint64, error) { key, bal, _, err := getBalance(ctx, im, addr, asset) balanceKeyPool.Put(key) @@ -156,7 +156,7 @@ func getBalance( ctx context.Context, im state.Immutable, addr codec.Address, - asset ids.ID, + asset codec.ActionID, ) ([]byte, uint64, bool, error) { k := BalanceKey(addr, asset) bal, exists, err := innerGetBalance(im.GetValue(ctx, k)) @@ -168,7 +168,7 @@ func GetBalanceFromState( ctx context.Context, f ReadState, addr codec.Address, - asset ids.ID, + asset codec.ActionID, ) (uint64, error) { k := BalanceKey(addr, asset) values, errs := f(ctx, [][]byte{k}) @@ -194,7 +194,7 @@ func SetBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset ids.ID, + asset codec.ActionID, balance uint64, ) error { k := BalanceKey(addr, asset) @@ -214,7 +214,7 @@ func DeleteBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset ids.ID, + asset codec.ActionID, ) error { return mu.Remove(ctx, BalanceKey(addr, asset)) } @@ -223,7 +223,7 @@ func AddBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset ids.ID, + asset codec.ActionID, amount uint64, create bool, ) error { @@ -254,7 +254,7 @@ func SubBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset ids.ID, + asset codec.ActionID, amount uint64, ) error { key, bal, _, err := getBalance(ctx, mu, addr, asset) @@ -281,11 +281,11 @@ func SubBalance( } // [assetPrefix] + [address] -func AssetKey(asset ids.ID) (k []byte) { - k = make([]byte, 1+consts.IDLen+consts.Uint16Len) +func AssetKey(asset codec.ActionID) (k []byte) { + k = make([]byte, 1+codec.AddressLen+consts.Uint16Len) k[0] = assetPrefix copy(k[1:], asset[:]) - binary.BigEndian.PutUint16(k[1+consts.IDLen:], AssetChunks) + binary.BigEndian.PutUint16(k[1+codec.AddressLen:], AssetChunks) return } @@ -293,7 +293,7 @@ func AssetKey(asset ids.ID) (k []byte) { func GetAssetFromState( ctx context.Context, f ReadState, - asset ids.ID, + asset codec.ActionID, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { values, errs := f(ctx, [][]byte{AssetKey(asset)}) return innerGetAsset(values[0], errs[0]) @@ -302,7 +302,7 @@ func GetAssetFromState( func GetAsset( ctx context.Context, im state.Immutable, - asset ids.ID, + asset codec.ActionID, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { k := AssetKey(asset) return innerGetAsset(im.GetValue(ctx, k)) @@ -332,7 +332,7 @@ func innerGetAsset( func SetAsset( ctx context.Context, mu state.Mutable, - asset ids.ID, + asset codec.ActionID, symbol []byte, decimals uint8, metadata []byte, @@ -353,51 +353,51 @@ func SetAsset( return mu.Insert(ctx, k, v) } -func DeleteAsset(ctx context.Context, mu state.Mutable, asset ids.ID) error { +func DeleteAsset(ctx context.Context, mu state.Mutable, asset codec.ActionID) error { k := AssetKey(asset) return mu.Remove(ctx, k) } -// [orderPrefix] + [txID] -func OrderKey(txID ids.ID) (k []byte) { - k = make([]byte, 1+consts.IDLen+consts.Uint16Len) +// [orderPrefix] + [actionID] +func OrderKey(actionID codec.ActionID) (k []byte) { + k = make([]byte, 1+codec.AddressLen+consts.Uint16Len) k[0] = orderPrefix - copy(k[1:], txID[:]) - binary.BigEndian.PutUint16(k[1+consts.IDLen:], OrderChunks) + copy(k[1:], actionID[:]) + binary.BigEndian.PutUint16(k[1+codec.AddressLen:], OrderChunks) return } func SetOrder( ctx context.Context, mu state.Mutable, - txID ids.ID, - in ids.ID, + actionID codec.ActionID, + in codec.ActionID, inTick uint64, - out ids.ID, + out codec.ActionID, outTick uint64, supply uint64, owner codec.Address, ) error { - k := OrderKey(txID) - v := make([]byte, consts.IDLen*2+consts.Uint64Len*3+codec.AddressLen) + k := OrderKey(actionID) + v := make([]byte, codec.AddressLen*2+consts.Uint64Len*3+codec.AddressLen) copy(v, in[:]) - binary.BigEndian.PutUint64(v[consts.IDLen:], inTick) - copy(v[consts.IDLen+consts.Uint64Len:], out[:]) - binary.BigEndian.PutUint64(v[consts.IDLen*2+consts.Uint64Len:], outTick) - binary.BigEndian.PutUint64(v[consts.IDLen*2+consts.Uint64Len*2:], supply) - copy(v[consts.IDLen*2+consts.Uint64Len*3:], owner[:]) + binary.BigEndian.PutUint64(v[codec.AddressLen:], inTick) + copy(v[codec.AddressLen+consts.Uint64Len:], out[:]) + binary.BigEndian.PutUint64(v[codec.AddressLen*2+consts.Uint64Len:], outTick) + binary.BigEndian.PutUint64(v[codec.AddressLen*2+consts.Uint64Len*2:], supply) + copy(v[codec.AddressLen*2+consts.Uint64Len*3:], owner[:]) return mu.Insert(ctx, k, v) } func GetOrder( ctx context.Context, im state.Immutable, - order ids.ID, + order codec.ActionID, ) ( bool, // exists - ids.ID, // in + codec.ActionID, // in uint64, // inTick - ids.ID, // out + codec.ActionID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -412,12 +412,12 @@ func GetOrder( func GetOrderFromState( ctx context.Context, f ReadState, - order ids.ID, + order codec.ActionID, ) ( bool, // exists - ids.ID, // in + codec.ActionID, // in uint64, // inTick - ids.ID, // out + codec.ActionID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -429,39 +429,39 @@ func GetOrderFromState( func innerGetOrder(v []byte, err error) ( bool, // exists - ids.ID, // in + codec.ActionID, // in uint64, // inTick - ids.ID, // out + codec.ActionID, // out uint64, // outTick uint64, // remaining codec.Address, // owner error, ) { if errors.Is(err, database.ErrNotFound) { - return false, ids.Empty, 0, ids.Empty, 0, 0, codec.EmptyAddress, nil + return false, codec.EmptyAddress, 0, codec.EmptyAddress, 0, 0, codec.EmptyAddress, nil } if err != nil { - return false, ids.Empty, 0, ids.Empty, 0, 0, codec.EmptyAddress, err + return false, codec.EmptyAddress, 0, codec.EmptyAddress, 0, 0, codec.EmptyAddress, err } - var in ids.ID - copy(in[:], v[:consts.IDLen]) - inTick := binary.BigEndian.Uint64(v[consts.IDLen:]) - var out ids.ID - copy(out[:], v[consts.IDLen+consts.Uint64Len:consts.IDLen*2+consts.Uint64Len]) - outTick := binary.BigEndian.Uint64(v[consts.IDLen*2+consts.Uint64Len:]) - supply := binary.BigEndian.Uint64(v[consts.IDLen*2+consts.Uint64Len*2:]) + var in codec.ActionID + copy(in[:], v[:codec.AddressLen]) + inTick := binary.BigEndian.Uint64(v[codec.AddressLen:]) + var out codec.ActionID + copy(out[:], v[codec.AddressLen+consts.Uint64Len:codec.AddressLen*2+consts.Uint64Len]) + outTick := binary.BigEndian.Uint64(v[codec.AddressLen*2+consts.Uint64Len:]) + supply := binary.BigEndian.Uint64(v[codec.AddressLen*2+consts.Uint64Len*2:]) var owner codec.Address - copy(owner[:], v[consts.IDLen*2+consts.Uint64Len*3:]) + copy(owner[:], v[codec.AddressLen*2+consts.Uint64Len*3:]) return true, in, inTick, out, outTick, supply, owner, nil } -func DeleteOrder(ctx context.Context, mu state.Mutable, order ids.ID) error { +func DeleteOrder(ctx context.Context, mu state.Mutable, order codec.ActionID) error { k := OrderKey(order) return mu.Remove(ctx, k) } // [loanPrefix] + [asset] + [destination] -func LoanKey(asset ids.ID, destination ids.ID) (k []byte) { +func LoanKey(asset codec.ActionID, destination ids.ID) (k []byte) { k = make([]byte, 1+consts.IDLen*2+consts.Uint16Len) k[0] = loanPrefix copy(k[1:], asset[:]) @@ -474,7 +474,7 @@ func LoanKey(asset ids.ID, destination ids.ID) (k []byte) { func GetLoanFromState( ctx context.Context, f ReadState, - asset ids.ID, + asset codec.ActionID, destination ids.ID, ) (uint64, error) { values, errs := f(ctx, [][]byte{LoanKey(asset, destination)}) @@ -494,7 +494,7 @@ func innerGetLoan(v []byte, err error) (uint64, error) { func GetLoan( ctx context.Context, im state.Immutable, - asset ids.ID, + asset codec.ActionID, destination ids.ID, ) (uint64, error) { k := LoanKey(asset, destination) @@ -505,7 +505,7 @@ func GetLoan( func SetLoan( ctx context.Context, mu state.Mutable, - asset ids.ID, + asset codec.ActionID, destination ids.ID, amount uint64, ) error { @@ -516,7 +516,7 @@ func SetLoan( func AddLoan( ctx context.Context, mu state.Mutable, - asset ids.ID, + asset codec.ActionID, destination ids.ID, amount uint64, ) error { @@ -540,7 +540,7 @@ func AddLoan( func SubLoan( ctx context.Context, mu state.Mutable, - asset ids.ID, + asset codec.ActionID, destination ids.ID, amount uint64, ) error { From 86f22f38c7f7407c618829bf5fedf22d7df438de Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 26 Apr 2024 11:33:32 -0400 Subject: [PATCH 31/78] tokevnm integration errors --- .../tests/integration/integration_test.go | 180 +++++++++--------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index 34e3a10be3..8e84d3f80a 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -104,15 +104,15 @@ var ( asset1 []byte asset1Symbol []byte asset1Decimals uint8 - asset1ID ids.ID + asset1ID codec.ActionID asset2 []byte asset2Symbol []byte asset2Decimals uint8 - asset2ID ids.ID + asset2ID codec.ActionID asset3 []byte asset3Symbol []byte asset3Decimals uint8 - asset3ID ids.ID + asset3ID codec.ActionID // when used with embedded VMs genesisBytes []byte @@ -262,12 +262,12 @@ var _ = ginkgo.BeforeSuite(func() { csupply := uint64(0) for _, alloc := range g.CustomAllocation { - balance, err := cli.Balance(context.Background(), alloc.Address, ids.Empty) + balance, err := cli.Balance(context.Background(), alloc.Address, codec.EmptyAddress) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(alloc.Balance)) csupply += alloc.Balance } - exists, symbol, decimals, metadata, supply, owner, err := cli.Asset(context.Background(), ids.Empty, false) + exists, symbol, decimals, metadata, supply, owner, err := cli.Asset(context.Background(), codec.EmptyAddress, false) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(exists).Should(gomega.BeTrue()) gomega.Ω(string(symbol)).Should(gomega.Equal(tconsts.Symbol)) @@ -333,10 +333,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, transferTx, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: rsender2, Value: 100_000, // must be more than StateLockup - }, + }}, factory, ) transferTxRoot = transferTx @@ -365,10 +365,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Timestamp: 0, MaxFee: 1000, }, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: rsender2, Value: 110, - }, + }}, ) // Must do manual construction to avoid `tx.Sign` error (would fail with // 0 timestamp) @@ -441,10 +441,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.By("ensure balance is updated", func() { - balance, err := instances[1].tcli.Balance(context.Background(), sender, ids.Empty) + balance, err := instances[1].tcli.Balance(context.Background(), sender, codec.EmptyAddress) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance).To(gomega.Equal(uint64(9899707))) - balance2, err := instances[1].tcli.Balance(context.Background(), sender2, ids.Empty) + balance2, err := instances[1].tcli.Balance(context.Background(), sender2, codec.EmptyAddress) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100000))) }) @@ -457,10 +457,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: rsender2, Value: 101, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -471,7 +471,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance2, err := instances[1].tcli.Balance(context.Background(), sender2, ids.Empty) + balance2, err := instances[1].tcli.Balance(context.Background(), sender2, codec.EmptyAddress) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100101))) }) @@ -486,10 +486,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: rsender2, Value: 200, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -500,10 +500,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err = instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: rsender2, Value: 201, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -529,10 +529,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: rsender2, Value: 203, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -614,16 +614,16 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { time.Sleep(2 * pubsub.MaxMessageWait) // Fetch balances - balance, err := instances[0].tcli.Balance(context.TODO(), sender, ids.Empty) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, codec.EmptyAddress) gomega.Ω(err).Should(gomega.BeNil()) // Send tx other, err := ed25519.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) - transfer := &actions.Transfer{ + transfer := []chain.Action{&actions.Transfer{ To: auth.NewED25519Address(other.PublicKey()), Value: 1, - } + }} parser, err := instances[0].tcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) @@ -646,14 +646,14 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { blk, lresults, prices, err := cli.ListenBlock(context.TODO(), parser) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(len(blk.Txs)).Should(gomega.Equal(1)) - tx := blk.Txs[0].Action.(*actions.Transfer) - gomega.Ω(tx.Asset).To(gomega.Equal(ids.Empty)) + tx := blk.Txs[0].Actions[0].(*actions.Transfer) + gomega.Ω(tx.Asset).To(gomega.Equal(codec.EmptyAddress)) gomega.Ω(tx.Value).To(gomega.Equal(uint64(1))) gomega.Ω(lresults).Should(gomega.Equal(results)) gomega.Ω(prices).Should(gomega.Equal(fees.Dimensions{1, 1, 1, 1, 1})) // Check balance modifications are correct - balancea, err := instances[0].tcli.Balance(context.TODO(), sender, ids.Empty) + balancea, err := instances[0].tcli.Balance(context.TODO(), sender, codec.EmptyAddress) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(balancea + lresults[0].Fee + 1)) @@ -669,10 +669,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // Create tx other, err := ed25519.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) - transfer := &actions.Transfer{ + transfer := []chain.Action{&actions.Transfer{ To: auth.NewED25519Address(other.PublicKey()), Value: 1, - } + }} parser, err := instances[0].tcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) _, tx, _, err := instances[0].cli.GenerateTransaction( @@ -721,11 +721,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: auth.NewED25519Address(other.PublicKey()), Value: 10, Memo: []byte("hello"), - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -746,11 +746,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Timestamp: hutils.UnixRMilli(-1, 5*consts.MillisecondsPerSecond), MaxFee: 1001, }, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: auth.NewED25519Address(other.PublicKey()), Value: 10, Memo: make([]byte, 1000), - }, + }}, ) // Must do manual construction to avoid `tx.Sign` error (would fail with // too large) @@ -772,17 +772,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { ginkgo.It("mint an asset that doesn't exist", func() { other, err := ed25519.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) - assetID := ids.GenerateTestID() + assetID := codec.CreateActionID(0, ids.GenerateTestID()) parser, err := instances[0].tcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.MintAsset{ + []chain.Action{&actions.MintAsset{ To: auth.NewED25519Address(other.PublicKey()), Asset: assetID, Value: 10, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -807,11 +807,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Timestamp: hutils.UnixRMilli(-1, 5*consts.MillisecondsPerSecond), MaxFee: 1001, }, - &actions.CreateAsset{ + []chain.Action{&actions.CreateAsset{ Symbol: []byte("s0"), Decimals: 0, Metadata: nil, - }, + }}, ) // Must do manual construction to avoid `tx.Sign` error (would fail with // too large) @@ -837,11 +837,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Timestamp: hutils.UnixRMilli(-1, 5*consts.MillisecondsPerSecond), MaxFee: 1001, }, - &actions.CreateAsset{ + []chain.Action{&actions.CreateAsset{ Symbol: nil, Decimals: 0, Metadata: []byte("m"), - }, + }}, ) // Must do manual construction to avoid `tx.Sign` error (would fail with // too large) @@ -867,11 +867,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Timestamp: hutils.UnixRMilli(-1, 5*consts.MillisecondsPerSecond), MaxFee: 1000, }, - &actions.CreateAsset{ + []chain.Action{&actions.CreateAsset{ Symbol: []byte("s0"), Decimals: 0, Metadata: make([]byte, actions.MaxMetadataSize*2), - }, + }}, ) // Must do manual construction to avoid `tx.Sign` error (would fail with // too large) @@ -896,11 +896,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CreateAsset{ + []chain.Action{&actions.CreateAsset{ Symbol: asset1Symbol, Decimals: asset1Decimals, Metadata: asset1, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -910,7 +910,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset1ID = tx.ID() + asset1ID = codec.CreateActionID(0, tx.ID()) balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) @@ -931,11 +931,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.MintAsset{ + []chain.Action{&actions.MintAsset{ To: rsender2, Asset: asset1ID, Value: 15, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -970,11 +970,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.MintAsset{ + []chain.Action{&actions.MintAsset{ To: auth.NewED25519Address(other.PublicKey()), Asset: asset1ID, Value: 10, - }, + }}, factory2, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1003,10 +1003,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.BurnAsset{ + []chain.Action{&actions.BurnAsset{ Asset: asset1ID, Value: 5, - }, + }}, factory2, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1039,10 +1039,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.BurnAsset{ + []chain.Action{&actions.BurnAsset{ Asset: asset1ID, Value: 10, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1074,10 +1074,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Timestamp: hutils.UnixRMilli(-1, 5*consts.MillisecondsPerSecond), MaxFee: 1000, }, - &actions.MintAsset{ + []chain.Action{&actions.MintAsset{ To: auth.NewED25519Address(other.PublicKey()), Asset: asset1ID, - }, + }}, ) // Must do manual construction to avoid `tx.Sign` error (would fail with // bad codec) @@ -1102,11 +1102,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.MintAsset{ + []chain.Action{&actions.MintAsset{ To: rsender2, Asset: asset1ID, Value: consts.MaxUint64, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1145,10 +1145,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Timestamp: hutils.UnixRMilli(-1, 5*consts.MillisecondsPerSecond), MaxFee: 1000, }, - &actions.MintAsset{ + []chain.Action{&actions.MintAsset{ To: auth.NewED25519Address(other.PublicKey()), Value: 10, - }, + }}, ) // Must do manual construction to avoid `tx.Sign` error (would fail with // bad codec) @@ -1173,11 +1173,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CreateAsset{ + []chain.Action{&actions.CreateAsset{ Symbol: asset2Symbol, Decimals: asset2Decimals, Metadata: asset2, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1186,16 +1186,16 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := accept(false) gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset2ID = tx.ID() + asset2ID = codec.CreateActionID(0, tx.ID()) submit, _, _, err = instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.MintAsset{ + []chain.Action{&actions.MintAsset{ To: rsender, Asset: asset2ID, Value: 10, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1216,11 +1216,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CreateAsset{ + []chain.Action{&actions.CreateAsset{ Symbol: asset3Symbol, Decimals: asset3Decimals, Metadata: asset3, - }, + }}, factory2, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1229,16 +1229,16 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := accept(false) gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset3ID = tx.ID() + asset3ID = codec.CreateActionID(0, tx.ID()) submit, _, _, err = instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.MintAsset{ + []chain.Action{&actions.MintAsset{ To: rsender2, Asset: asset3ID, Value: 10, - }, + }}, factory2, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1259,13 +1259,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CreateOrder{ + []chain.Action{&actions.CreateOrder{ In: asset3ID, InTick: 1, Out: asset2ID, OutTick: 2, Supply: 4, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1296,13 +1296,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CreateOrder{ + []chain.Action{&actions.CreateOrder{ In: asset2ID, InTick: 4, Out: asset3ID, OutTick: 2, Supply: 5, // put half of balance - }, + }}, factory2, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1322,13 +1322,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CreateOrder{ + []chain.Action{&actions.CreateOrder{ In: asset2ID, InTick: 4, Out: asset3ID, OutTick: 1, Supply: 5, // put half of balance - }, + }}, factory2, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1359,13 +1359,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CreateOrder{ + []chain.Action{&actions.CreateOrder{ In: asset2ID, InTick: 5, Out: asset3ID, OutTick: 1, Supply: 5, // put half of balance - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1391,13 +1391,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.FillOrder{ + []chain.Action{&actions.FillOrder{ Order: order.ID, Owner: owner, In: asset2ID, Out: asset3ID, Value: 10, // rate of this order is 4 asset2 = 1 asset3 - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1423,13 +1423,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.FillOrder{ + []chain.Action{&actions.FillOrder{ Order: order.ID, Owner: owner, In: asset2ID, Out: asset3ID, Value: 20, // rate of this order is 4 asset2 = 1 asset3 - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1455,13 +1455,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.FillOrder{ + []chain.Action{&actions.FillOrder{ Order: order.ID, Owner: owner, In: asset2ID, Out: asset3ID, Value: 4, // rate of this order is 4 asset2 = 1 asset3 - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1501,10 +1501,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CloseOrder{ + []chain.Action{&actions.CloseOrder{ Order: order.ID, Out: asset3ID, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1528,10 +1528,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CloseOrder{ + []chain.Action{&actions.CloseOrder{ Order: order.ID, Out: asset3ID, - }, + }}, factory2, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1566,13 +1566,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.CreateOrder{ + []chain.Action{&actions.CreateOrder{ In: asset2ID, InTick: 2, Out: asset3ID, OutTick: 1, Supply: 1, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -1609,13 +1609,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - &actions.FillOrder{ + []chain.Action{&actions.FillOrder{ Order: order.ID, Owner: owner, In: asset2ID, Out: asset3ID, Value: 4, - }, + }}, factory2, ) gomega.Ω(err).Should(gomega.BeNil()) From d167753708e3766a5932bf303714934a92b4dd45 Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 26 Apr 2024 12:00:47 -0400 Subject: [PATCH 32/78] fix tokenvm integration error --- codec/address.go | 2 +- codec/packer.go | 2 +- examples/tokenvm/storage/storage.go | 8 +- .../tests/integration/integration_test.go | 6 +- heap/heap_test.go | 103 +++++++++++++----- 5 files changed, 87 insertions(+), 34 deletions(-) diff --git a/codec/address.go b/codec/address.go index 1becf317b2..bf55b39de0 100644 --- a/codec/address.go +++ b/codec/address.go @@ -21,7 +21,7 @@ const ( maxBech32Size = 90 ) -type ActionID Address +type ActionID [AddressLen]byte type Address [AddressLen]byte diff --git a/codec/packer.go b/codec/packer.go index 911d89905a..d0e18eaeda 100644 --- a/codec/packer.go +++ b/codec/packer.go @@ -88,7 +88,7 @@ func (p *Packer) PackActionID(a ActionID) { func (p *Packer) UnpackActionID(required bool, dest *ActionID) { copy((*dest)[:], p.p.UnpackFixedBytes(AddressLen)) if required && *dest == EmptyAddress { - p.addErr(fmt.Errorf("%w: Address field is not populated", ErrFieldNotPopulated)) + p.addErr(fmt.Errorf("%w: ActionID field is not populated", ErrFieldNotPopulated)) } } diff --git a/examples/tokenvm/storage/storage.go b/examples/tokenvm/storage/storage.go index b002a8fab1..19926740e9 100644 --- a/examples/tokenvm/storage/storage.go +++ b/examples/tokenvm/storage/storage.go @@ -69,7 +69,7 @@ var ( balanceKeyPool = sync.Pool{ New: func() any { - return make([]byte, 1+codec.AddressLen+consts.IDLen+consts.Uint16Len) + return make([]byte, 1+codec.AddressLen*2+consts.Uint16Len) }, } ) @@ -462,11 +462,11 @@ func DeleteOrder(ctx context.Context, mu state.Mutable, order codec.ActionID) er // [loanPrefix] + [asset] + [destination] func LoanKey(asset codec.ActionID, destination ids.ID) (k []byte) { - k = make([]byte, 1+consts.IDLen*2+consts.Uint16Len) + k = make([]byte, 1+codec.AddressLen+consts.IDLen+consts.Uint16Len) k[0] = loanPrefix copy(k[1:], asset[:]) - copy(k[1+consts.IDLen:], destination[:]) - binary.BigEndian.PutUint16(k[1+consts.IDLen*2:], LoanChunks) + copy(k[1+codec.AddressLen:], destination[:]) + binary.BigEndian.PutUint16(k[1+codec.AddressLen+consts.IDLen:], LoanChunks) return } diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index 8e84d3f80a..7886c5401a 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -431,19 +431,19 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // read: 2 keys reads, 1 had 0 chunks // allocate: 1 key created // write: 1 key modified, 1 key new - transferTxConsumed := fees.Dimensions{223, 7, 12, 25, 26} + transferTxConsumed := fees.Dimensions{228, 7, 12, 25, 26} gomega.Ω(results[0].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(293))) + gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(298))) }) ginkgo.By("ensure balance is updated", func() { balance, err := instances[1].tcli.Balance(context.Background(), sender, codec.EmptyAddress) gomega.Ω(err).To(gomega.BeNil()) - gomega.Ω(balance).To(gomega.Equal(uint64(9899707))) + gomega.Ω(balance).To(gomega.Equal(uint64(9899702))) balance2, err := instances[1].tcli.Balance(context.Background(), sender2, codec.EmptyAddress) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100000))) diff --git a/heap/heap_test.go b/heap/heap_test.go index 77ec5ac1de..ab91948bea 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -6,39 +6,41 @@ package heap import ( "testing" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/avalanchego/ids" "github.com/stretchr/testify/require" ) -type testItem struct { - id ids.ID +type testItem[T comparable] struct { + id T value uint64 } func TestUnit64HeapPushPopMin(t *testing.T) { require := require.New(t) - minHeap := New[ids.ID, *testItem, uint64](0, true) + minHeap := New[ids.ID, *testItem[ids.ID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") - mempoolItem1 := &testItem{ids.GenerateTestID(), 10} - mempoolItem2 := &testItem{ids.GenerateTestID(), 7} - mempoolItem3 := &testItem{ids.GenerateTestID(), 15} + mempoolItem1 := &testItem[ids.ID]{ids.GenerateTestID(), 10} + mempoolItem2 := &testItem[ids.ID]{ids.GenerateTestID(), 7} + mempoolItem3 := &testItem[ids.ID]{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[ids.ID, *testItem, uint64]{ + med := &Entry[ids.ID, *testItem[ids.ID], uint64]{ ID: mempoolItem1.id, Item: mempoolItem1, Val: mempoolItem1.value, Index: minHeap.Len(), } // Lesser UnitPrice - low := &Entry[ids.ID, *testItem, uint64]{ + low := &Entry[ids.ID, *testItem[ids.ID], uint64]{ ID: mempoolItem2.id, Item: mempoolItem2, Val: mempoolItem2.value, Index: minHeap.Len(), } // Greatest UnitPrice - high := &Entry[ids.ID, *testItem, uint64]{ + high := &Entry[ids.ID, *testItem[ids.ID], uint64]{ ID: mempoolItem3.id, Item: mempoolItem3, Val: mempoolItem3.value, @@ -67,29 +69,29 @@ func TestUnit64HeapPushPopMin(t *testing.T) { func TestUnit64HeapPushPopMax(t *testing.T) { require := require.New(t) - maxHeap := New[ids.ID, *testItem, uint64](0, false) + maxHeap := New[ids.ID, *testItem[ids.ID], uint64](0, false) require.Zero(maxHeap.Len(), "heap not initialized properly.") - mempoolItem1 := &testItem{ids.GenerateTestID(), 10} - mempoolItem2 := &testItem{ids.GenerateTestID(), 7} - mempoolItem3 := &testItem{ids.GenerateTestID(), 15} + mempoolItem1 := &testItem[ids.ID]{ids.GenerateTestID(), 10} + mempoolItem2 := &testItem[ids.ID]{ids.GenerateTestID(), 7} + mempoolItem3 := &testItem[ids.ID]{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[ids.ID, *testItem, uint64]{ + med := &Entry[ids.ID, *testItem[ids.ID], uint64]{ ID: mempoolItem1.id, Item: mempoolItem1, Val: mempoolItem1.value, Index: maxHeap.Len(), } // Lesser UnitPrice - low := &Entry[ids.ID, *testItem, uint64]{ + low := &Entry[ids.ID, *testItem[ids.ID], uint64]{ ID: mempoolItem2.id, Item: mempoolItem2, Val: mempoolItem2.value, Index: maxHeap.Len(), } // Greatest UnitPrice - high := &Entry[ids.ID, *testItem, uint64]{ + high := &Entry[ids.ID, *testItem[ids.ID], uint64]{ ID: mempoolItem3.id, Item: mempoolItem3, Val: mempoolItem3.value, @@ -119,10 +121,10 @@ func TestUnit64HeapPushPopMax(t *testing.T) { func TestUnit64HeapPushExists(t *testing.T) { // Push an item already in heap require := require.New(t) - minHeap := New[ids.ID, *testItem, uint64](0, true) + minHeap := New[ids.ID, *testItem[ids.ID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") - mempoolItem := &testItem{ids.GenerateTestID(), 10} - entry := &Entry[ids.ID, *testItem, uint64]{ + mempoolItem := &testItem[ids.ID]{ids.GenerateTestID(), 10} + entry := &Entry[ids.ID, *testItem[ids.ID], uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -142,11 +144,11 @@ func TestUnit64HeapPushExists(t *testing.T) { func TestUnit64HeapGetID(t *testing.T) { // Push an item and grab its ID require := require.New(t) - minHeap := New[ids.ID, *testItem, uint64](0, true) + minHeap := New[ids.ID, *testItem[ids.ID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") - mempoolItem := &testItem{ids.GenerateTestID(), 10} - entry := &Entry[ids.ID, *testItem, uint64]{ + mempoolItem := &testItem[ids.ID]{ids.GenerateTestID(), 10} + entry := &Entry[ids.ID, *testItem[ids.ID], uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -164,10 +166,10 @@ func TestUnit64HeapGetID(t *testing.T) { func TestUnit64HeapHasID(t *testing.T) { require := require.New(t) - minHeap := New[ids.ID, *testItem, uint64](0, true) + minHeap := New[ids.ID, *testItem[ids.ID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") - mempoolItem := &testItem{ids.GenerateTestID(), 10} - entry := &Entry[ids.ID, *testItem, uint64]{ + mempoolItem := &testItem[ids.ID]{ids.GenerateTestID(), 10} + entry := &Entry[ids.ID, *testItem[ids.ID], uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -181,3 +183,54 @@ func TestUnit64HeapHasID(t *testing.T) { ok = minHeap.Has(mempoolItem.id) require.True(ok, "Entry was not found in heap.") } + +func TestUnit64HeapPushPopMinForActionID(t *testing.T) { + require := require.New(t) + minHeap := New[codec.ActionID, *testItem[codec.ActionID], uint64](0, true) + require.Zero(minHeap.Len(), "heap not initialized properly.") + txID := ids.GenerateTestID() + mempoolItem1 := &testItem[codec.ActionID]{codec.CreateActionID(0, txID), 10} + mempoolItem2 := &testItem[codec.ActionID]{codec.CreateActionID(1, txID), 7} + mempoolItem3 := &testItem[codec.ActionID]{codec.CreateActionID(2, txID), 15} + + // Middle UnitPrice + med := &Entry[codec.ActionID, *testItem[codec.ActionID], uint64]{ + ID: mempoolItem1.id, + Item: mempoolItem1, + Val: mempoolItem1.value, + Index: minHeap.Len(), + } + // Lesser UnitPrice + low := &Entry[codec.ActionID, *testItem[codec.ActionID], uint64]{ + ID: mempoolItem2.id, + Item: mempoolItem2, + Val: mempoolItem2.value, + Index: minHeap.Len(), + } + // Greatest UnitPrice + high := &Entry[codec.ActionID, *testItem[codec.ActionID], uint64]{ + ID: mempoolItem3.id, + Item: mempoolItem3, + Val: mempoolItem3.value, + Index: minHeap.Len(), + } + minHeap.Push(med) + minHeap.Push(low) + minHeap.Push(high) + // Added all three + require.Equal(3, minHeap.Len(), "Not pushed correctly.") + // Check if added to lookup table + ok := minHeap.Has(med.ID) + require.True(ok, "Item not found in lookup.") + ok = minHeap.Has(low.ID) + require.True(ok, "Item not found in lookup.") + ok = minHeap.Has(high.ID) + require.True(ok, "Item not found in lookup.") + // Pop and check popped correctly. Order should be 2, 1, 3 + popped := minHeap.Pop() + require.Equal(low, popped, "Incorrect item removed.") + popped = minHeap.Pop() + require.Equal(med, popped, "Incorrect item removed.") + popped = minHeap.Pop() + require.Equal(high, popped, "Incorrect item removed.") +} From 7d321c3ee5bb524849ced46b2f4f9af69502a336 Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 26 Apr 2024 14:01:59 -0400 Subject: [PATCH 33/78] tokenvm integration passes --- examples/tokenvm/tests/integration/integration_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index 7886c5401a..c03696a718 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -647,7 +647,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(len(blk.Txs)).Should(gomega.Equal(1)) tx := blk.Txs[0].Actions[0].(*actions.Transfer) - gomega.Ω(tx.Asset).To(gomega.Equal(codec.EmptyAddress)) + gomega.Ω(tx.Asset).To(gomega.Equal(codec.ActionID{})) gomega.Ω(tx.Value).To(gomega.Equal(uint64(1))) gomega.Ω(lresults).Should(gomega.Equal(results)) gomega.Ω(prices).Should(gomega.Equal(fees.Dimensions{1, 1, 1, 1, 1})) @@ -1283,7 +1283,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] - gomega.Ω(order.ID).Should(gomega.Equal(tx.ID())) + gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateActionID(0, tx.ID()))) gomega.Ω(order.InTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order.OutTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order.Owner).Should(gomega.Equal(sender)) @@ -1346,7 +1346,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] - gomega.Ω(order.ID).Should(gomega.Equal(tx.ID())) + gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateActionID(0, tx.ID()))) gomega.Ω(order.InTick).Should(gomega.Equal(uint64(4))) gomega.Ω(order.OutTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order.Owner).Should(gomega.Equal(sender2)) @@ -1590,7 +1590,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] - gomega.Ω(order.ID).Should(gomega.Equal(tx.ID())) + gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateActionID(0, tx.ID()))) gomega.Ω(order.InTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order.OutTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order.Owner).Should(gomega.Equal(sender)) From 651d51a523d370fbecb3fe6ab6eb2846b9a72e49 Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 26 Apr 2024 14:25:33 -0400 Subject: [PATCH 34/78] create long ID --- chain/dependencies.go | 8 +-- codec/address.go | 11 +-- codec/packer.go | 6 +- examples/morpheusvm/actions/transfer.go | 8 +-- examples/tokenvm/actions/burn_asset.go | 10 +-- examples/tokenvm/actions/close_order.go | 12 ++-- examples/tokenvm/actions/create_asset.go | 8 +-- examples/tokenvm/actions/create_order.go | 16 ++--- examples/tokenvm/actions/fill_order.go | 14 ++-- examples/tokenvm/actions/mint_asset.go | 10 +-- examples/tokenvm/actions/transfer.go | 10 +-- examples/tokenvm/controller/resolutions.go | 12 ++-- examples/tokenvm/orderbook/orderbook.go | 34 +++++----- examples/tokenvm/rpc/dependencies.go | 12 ++-- examples/tokenvm/rpc/jsonrpc_client.go | 14 ++-- examples/tokenvm/rpc/jsonrpc_server.go | 12 ++-- examples/tokenvm/storage/storage.go | 68 +++++++++---------- .../tests/integration/integration_test.go | 22 +++--- heap/heap_test.go | 14 ++-- 19 files changed, 151 insertions(+), 150 deletions(-) diff --git a/chain/dependencies.go b/chain/dependencies.go index a1c57ac1ab..54eace0cf0 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -219,9 +219,9 @@ type Object interface { type Action interface { Object - // GetActionID returns the ActionID for an [Action] in a [Transaction]. There may be + // GetActionID returns the LID for an [Action] in a [Transaction]. There may be // multiple [Action]s, so we pass its index in the [Action] array along with the txID. - GetActionID(i uint8, txID ids.ID) codec.ActionID + GetActionID(i uint8, txID ids.ID) codec.LID // MaxComputeUnits is the maximum amount of compute a given [Action] could use. This is // used to determine whether the [Action] can be included in a given block and to compute @@ -244,7 +244,7 @@ type Action interface { // key (formatted as a big-endian uint16). This is used to automatically calculate storage usage. // // If any key is removed and then re-created, this will count as a creation instead of a modification. - StateKeys(actor codec.Address, actionID codec.ActionID) state.Keys + StateKeys(actor codec.Address, actionID codec.LID) state.Keys // Execute actually runs the [Action]. Any state changes that the [Action] performs should // be done here. @@ -260,7 +260,7 @@ type Action interface { mu state.Mutable, timestamp int64, actor codec.Address, - actionID codec.ActionID, + actionID codec.LID, ) (success bool, computeUnits uint64, output []byte, err error) } diff --git a/codec/address.go b/codec/address.go index bf55b39de0..c86db3c961 100644 --- a/codec/address.go +++ b/codec/address.go @@ -21,9 +21,10 @@ const ( maxBech32Size = 90 ) -type ActionID [AddressLen]byte +type Address LID -type Address [AddressLen]byte +// Long ID +type LID [AddressLen]byte var EmptyAddress = [AddressLen]byte{} @@ -81,14 +82,14 @@ func ParseAddressBech32(hrp, saddr string) (Address, error) { return Address(p[:AddressLen]), nil } -func CreateActionID(idx uint8, txID ids.ID) ActionID { +func CreateLID(idx uint8, txID ids.ID) LID { a := make([]byte, AddressLen) a[0] = idx copy(a[1:], txID[:]) - return ActionID(a) + return LID(a) } -func ActionToString(hrp string, a ActionID) string { +func LIDToString(hrp string, a LID) string { addr, err := AddressBech32(hrp, Address(a)) if err != nil { panic(err) diff --git a/codec/packer.go b/codec/packer.go index d0e18eaeda..0120568b5d 100644 --- a/codec/packer.go +++ b/codec/packer.go @@ -81,14 +81,14 @@ func (p *Packer) UnpackAddress(dest *Address) { } } -func (p *Packer) PackActionID(a ActionID) { +func (p *Packer) PackActionID(a LID) { p.p.PackFixedBytes(a[:]) } -func (p *Packer) UnpackActionID(required bool, dest *ActionID) { +func (p *Packer) UnpackActionID(required bool, dest *LID) { copy((*dest)[:], p.p.UnpackFixedBytes(AddressLen)) if required && *dest == EmptyAddress { - p.addErr(fmt.Errorf("%w: ActionID field is not populated", ErrFieldNotPopulated)) + p.addErr(fmt.Errorf("%w: LID field is not populated", ErrFieldNotPopulated)) } } diff --git a/examples/morpheusvm/actions/transfer.go b/examples/morpheusvm/actions/transfer.go index 43ad28763c..851532a540 100644 --- a/examples/morpheusvm/actions/transfer.go +++ b/examples/morpheusvm/actions/transfer.go @@ -30,11 +30,11 @@ func (*Transfer) GetTypeID() uint8 { return mconsts.TransferID } -func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.ActionID { - return codec.CreateActionID(i, txID) +func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) } -func (t *Transfer) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { +func (t *Transfer) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor)): state.Read | state.Write, string(storage.BalanceKey(t.To)): state.All, @@ -51,7 +51,7 @@ func (t *Transfer) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.ActionID, + _ codec.LID, ) (bool, uint64, []byte, error) { if t.Value == 0 { return false, 1, OutputValueZero, nil diff --git a/examples/tokenvm/actions/burn_asset.go b/examples/tokenvm/actions/burn_asset.go index 6817113da8..f77bafa0a6 100644 --- a/examples/tokenvm/actions/burn_asset.go +++ b/examples/tokenvm/actions/burn_asset.go @@ -21,7 +21,7 @@ var _ chain.Action = (*BurnAsset)(nil) type BurnAsset struct { // Asset is the [TxID] that created the asset. - Asset codec.ActionID `json:"asset"` + Asset codec.LID `json:"asset"` // Number of assets to mint to [To]. Value uint64 `json:"value"` @@ -31,11 +31,11 @@ func (*BurnAsset) GetTypeID() uint8 { return burnAssetID } -func (*BurnAsset) GetActionID(i uint8, txID ids.ID) codec.ActionID { - return codec.CreateActionID(i, txID) +func (*BurnAsset) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) } -func (b *BurnAsset) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { +func (b *BurnAsset) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.AssetKey(b.Asset)): state.Read | state.Write, string(storage.BalanceKey(actor, b.Asset)): state.Read | state.Write, @@ -52,7 +52,7 @@ func (b *BurnAsset) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.ActionID, + _ codec.LID, ) (bool, uint64, []byte, error) { if b.Value == 0 { return false, BurnComputeUnits, OutputValueZero, nil diff --git a/examples/tokenvm/actions/close_order.go b/examples/tokenvm/actions/close_order.go index b1f8e45b30..6c21f56bcd 100644 --- a/examples/tokenvm/actions/close_order.go +++ b/examples/tokenvm/actions/close_order.go @@ -19,22 +19,22 @@ var _ chain.Action = (*CloseOrder)(nil) type CloseOrder struct { // [Order] is the OrderID you wish to close. - Order codec.ActionID `json:"order"` + Order codec.LID `json:"order"` // [Out] is the asset locked up in the order. We need to provide this to // populate [StateKeys]. - Out codec.ActionID `json:"out"` + Out codec.LID `json:"out"` } func (*CloseOrder) GetTypeID() uint8 { return closeOrderID } -func (*CloseOrder) GetActionID(i uint8, txID ids.ID) codec.ActionID { - return codec.CreateActionID(i, txID) +func (*CloseOrder) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) } -func (c *CloseOrder) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { +func (c *CloseOrder) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.OrderKey(c.Order)): state.Read | state.Write, string(storage.BalanceKey(actor, c.Out)): state.Read | state.Write, @@ -51,7 +51,7 @@ func (c *CloseOrder) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.ActionID, + _ codec.LID, ) (bool, uint64, []byte, error) { exists, _, _, out, _, remaining, owner, err := storage.GetOrder(ctx, mu, c.Order) if err != nil { diff --git a/examples/tokenvm/actions/create_asset.go b/examples/tokenvm/actions/create_asset.go index 1de4fc1b40..80a3b4afac 100644 --- a/examples/tokenvm/actions/create_asset.go +++ b/examples/tokenvm/actions/create_asset.go @@ -27,11 +27,11 @@ func (*CreateAsset) GetTypeID() uint8 { return createAssetID } -func (*CreateAsset) GetActionID(i uint8, txID ids.ID) codec.ActionID { - return codec.CreateActionID(i, txID) +func (*CreateAsset) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) } -func (*CreateAsset) StateKeys(_ codec.Address, actionID codec.ActionID) state.Keys { +func (*CreateAsset) StateKeys(_ codec.Address, actionID codec.LID) state.Keys { return state.Keys{ string(storage.AssetKey(actionID)): state.Allocate | state.Write, } @@ -47,7 +47,7 @@ func (c *CreateAsset) Execute( mu state.Mutable, _ int64, actor codec.Address, - actionID codec.ActionID, + actionID codec.LID, ) (bool, uint64, []byte, error) { if len(c.Symbol) == 0 { return false, CreateAssetComputeUnits, OutputSymbolEmpty, nil diff --git a/examples/tokenvm/actions/create_order.go b/examples/tokenvm/actions/create_order.go index e40870408f..88ace01edf 100644 --- a/examples/tokenvm/actions/create_order.go +++ b/examples/tokenvm/actions/create_order.go @@ -21,7 +21,7 @@ var _ chain.Action = (*CreateOrder)(nil) type CreateOrder struct { // [In] is the asset you trade for [Out]. - In codec.ActionID `json:"in"` + In codec.LID `json:"in"` // [InTick] is the amount of [In] required to purchase // [OutTick] of [Out]. @@ -30,7 +30,7 @@ type CreateOrder struct { // [Out] is the asset you receive when trading for [In]. // // This is the asset that is actually provided by the creator. - Out codec.ActionID `json:"out"` + Out codec.LID `json:"out"` // [OutTick] is the amount of [Out] the counterparty gets per [InTick] of // [In]. @@ -51,11 +51,11 @@ func (*CreateOrder) GetTypeID() uint8 { return createOrderID } -func (*CreateOrder) GetActionID(i uint8, txID ids.ID) codec.ActionID { - return codec.CreateActionID(i, txID) +func (*CreateOrder) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) } -func (c *CreateOrder) StateKeys(actor codec.Address, actionID codec.ActionID) state.Keys { +func (c *CreateOrder) StateKeys(actor codec.Address, actionID codec.LID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor, c.Out)): state.Read | state.Write, string(storage.OrderKey(actionID)): state.Allocate | state.Write, @@ -72,7 +72,7 @@ func (c *CreateOrder) Execute( mu state.Mutable, _ int64, actor codec.Address, - actionID codec.ActionID, + actionID codec.LID, ) (bool, uint64, []byte, error) { if c.In == c.Out { return false, CreateOrderComputeUnits, OutputSameInOut, nil @@ -129,6 +129,6 @@ func (*CreateOrder) ValidRange(chain.Rules) (int64, int64) { return -1, -1 } -func PairID(in codec.ActionID, out codec.ActionID) string { - return fmt.Sprintf("%s-%s", codec.ActionToString(tconsts.HRP, in), codec.ActionToString(tconsts.HRP, out)) +func PairID(in codec.LID, out codec.LID) string { + return fmt.Sprintf("%s-%s", codec.LIDToString(tconsts.HRP, in), codec.LIDToString(tconsts.HRP, out)) } diff --git a/examples/tokenvm/actions/fill_order.go b/examples/tokenvm/actions/fill_order.go index 6f0ab8211a..19e38f6a0b 100644 --- a/examples/tokenvm/actions/fill_order.go +++ b/examples/tokenvm/actions/fill_order.go @@ -21,7 +21,7 @@ var _ chain.Action = (*FillOrder)(nil) type FillOrder struct { // [Order] is the OrderID you wish to close. - Order codec.ActionID `json:"order"` + Order codec.LID `json:"order"` // [Owner] is the owner of the order and the recipient of the trade // proceeds. @@ -29,11 +29,11 @@ type FillOrder struct { // [In] is the asset that will be sent to the owner from the fill. We need to provide this to // populate [StateKeys]. - In codec.ActionID `json:"in"` + In codec.LID `json:"in"` // [Out] is the asset that will be received from the fill. We need to provide this to // populate [StateKeys]. - Out codec.ActionID `json:"out"` + Out codec.LID `json:"out"` // [Value] is the max amount of [In] that will be swapped for [Out]. Value uint64 `json:"value"` @@ -43,11 +43,11 @@ func (*FillOrder) GetTypeID() uint8 { return fillOrderID } -func (*FillOrder) GetActionID(i uint8, txID ids.ID) codec.ActionID { - return codec.CreateActionID(i, txID) +func (*FillOrder) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) } -func (f *FillOrder) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { +func (f *FillOrder) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.OrderKey(f.Order)): state.Read | state.Write, string(storage.BalanceKey(f.Owner, f.In)): state.All, @@ -66,7 +66,7 @@ func (f *FillOrder) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.ActionID, + _ codec.LID, ) (bool, uint64, []byte, error) { exists, in, inTick, out, outTick, remaining, owner, err := storage.GetOrder(ctx, mu, f.Order) if err != nil { diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index 75e3d2fab0..301da7666e 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -24,7 +24,7 @@ type MintAsset struct { To codec.Address `json:"to"` // Asset is the [TxID] that created the asset. - Asset codec.ActionID `json:"asset"` + Asset codec.LID `json:"asset"` // Number of assets to mint to [To]. Value uint64 `json:"value"` @@ -34,11 +34,11 @@ func (*MintAsset) GetTypeID() uint8 { return mintAssetID } -func (*MintAsset) GetActionID(i uint8, txID ids.ID) codec.ActionID { - return codec.CreateActionID(i, txID) +func (*MintAsset) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) } -func (m *MintAsset) StateKeys(codec.Address, codec.ActionID) state.Keys { +func (m *MintAsset) StateKeys(codec.Address, codec.LID) state.Keys { return state.Keys{ string(storage.AssetKey(m.Asset)): state.Read | state.Write, string(storage.BalanceKey(m.To, m.Asset)): state.All, @@ -55,7 +55,7 @@ func (m *MintAsset) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.ActionID, + _ codec.LID, ) (bool, uint64, []byte, error) { if m.Asset == codec.EmptyAddress { return false, MintAssetComputeUnits, OutputAssetIsNative, nil diff --git a/examples/tokenvm/actions/transfer.go b/examples/tokenvm/actions/transfer.go index f2ca2ddf56..14a27e3b87 100644 --- a/examples/tokenvm/actions/transfer.go +++ b/examples/tokenvm/actions/transfer.go @@ -22,7 +22,7 @@ type Transfer struct { To codec.Address `json:"to"` // Asset to transfer to [To]. - Asset codec.ActionID `json:"asset"` + Asset codec.LID `json:"asset"` // Amount are transferred to [To]. Value uint64 `json:"value"` @@ -35,11 +35,11 @@ func (*Transfer) GetTypeID() uint8 { return transferID } -func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.ActionID { - return codec.CreateActionID(i, txID) +func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) } -func (t *Transfer) StateKeys(actor codec.Address, _ codec.ActionID) state.Keys { +func (t *Transfer) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor, t.Asset)): state.Read | state.Write, string(storage.BalanceKey(t.To, t.Asset)): state.All, @@ -56,7 +56,7 @@ func (t *Transfer) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.ActionID, + _ codec.LID, ) (bool, uint64, []byte, error) { if t.Value == 0 { return false, TransferComputeUnits, OutputValueZero, nil diff --git a/examples/tokenvm/controller/resolutions.go b/examples/tokenvm/controller/resolutions.go index ac1f5099e7..eedddc7830 100644 --- a/examples/tokenvm/controller/resolutions.go +++ b/examples/tokenvm/controller/resolutions.go @@ -37,7 +37,7 @@ func (c *Controller) GetTransaction( func (c *Controller) GetAssetFromState( ctx context.Context, - asset codec.ActionID, + asset codec.LID, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { return storage.GetAssetFromState(ctx, c.inner.ReadState, asset) } @@ -45,7 +45,7 @@ func (c *Controller) GetAssetFromState( func (c *Controller) GetBalanceFromState( ctx context.Context, addr codec.Address, - asset codec.ActionID, + asset codec.LID, ) (uint64, error) { return storage.GetBalanceFromState(ctx, c.inner.ReadState, addr, asset) } @@ -56,12 +56,12 @@ func (c *Controller) Orders(pair string, limit int) []*orderbook.Order { func (c *Controller) GetOrderFromState( ctx context.Context, - orderID codec.ActionID, + orderID codec.LID, ) ( bool, // exists - codec.ActionID, // in + codec.LID, // in uint64, // inTick - codec.ActionID, // out + codec.LID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -72,7 +72,7 @@ func (c *Controller) GetOrderFromState( func (c *Controller) GetLoanFromState( ctx context.Context, - asset codec.ActionID, + asset codec.LID, destination ids.ID, ) (uint64, error) { return storage.GetLoanFromState(ctx, c.inner.ReadState, asset, destination) diff --git a/examples/tokenvm/orderbook/orderbook.go b/examples/tokenvm/orderbook/orderbook.go index a382fa9551..a1c773d226 100644 --- a/examples/tokenvm/orderbook/orderbook.go +++ b/examples/tokenvm/orderbook/orderbook.go @@ -16,13 +16,13 @@ import ( const allPairs = "*" type Order struct { - ID codec.ActionID `json:"id"` - Owner string `json:"owner"` // we always send address over RPC - InAsset codec.ActionID `json:"inAsset"` - InTick uint64 `json:"inTick"` - OutAsset codec.ActionID `json:"outAsset"` - OutTick uint64 `json:"outTick"` - Remaining uint64 `json:"remaining"` + ID codec.LID `json:"id"` + Owner string `json:"owner"` // we always send address over RPC + InAsset codec.LID `json:"inAsset"` + InTick uint64 `json:"inTick"` + OutAsset codec.LID `json:"outAsset"` + OutTick uint64 `json:"outTick"` + Remaining uint64 `json:"remaining"` owner codec.Address } @@ -34,8 +34,8 @@ type OrderBook struct { // dust orders from filling the heap. // // TODO: Allow operator to specify min creation supply per pair to be tracked - orders map[string]*heap.Heap[codec.ActionID, *Order, float64] - orderToPair map[codec.ActionID]string // needed to delete from [CloseOrder] actions + orders map[string]*heap.Heap[codec.LID, *Order, float64] + orderToPair map[codec.LID]string // needed to delete from [CloseOrder] actions maxOrdersPerPair int l sync.RWMutex @@ -43,7 +43,7 @@ type OrderBook struct { } func New(c Controller, trackedPairs []string, maxOrdersPerPair int) *OrderBook { - m := map[string]*heap.Heap[codec.ActionID, *Order, float64]{} + m := map[string]*heap.Heap[codec.LID, *Order, float64]{} trackAll := false if len(trackedPairs) == 1 && trackedPairs[0] == allPairs { trackAll = true @@ -51,20 +51,20 @@ func New(c Controller, trackedPairs []string, maxOrdersPerPair int) *OrderBook { } else { for _, pair := range trackedPairs { // We use a max heap so we return the best rates in order. - m[pair] = heap.New[codec.ActionID, *Order, float64](maxOrdersPerPair+1, true) + m[pair] = heap.New[codec.LID, *Order, float64](maxOrdersPerPair+1, true) c.Logger().Info("tracking order book", zap.String("pair", pair)) } } return &OrderBook{ c: c, orders: m, - orderToPair: map[codec.ActionID]string{}, + orderToPair: map[codec.LID]string{}, maxOrdersPerPair: maxOrdersPerPair, trackAll: trackAll, } } -func (o *OrderBook) Add(actionID codec.ActionID, actor codec.Address, action *actions.CreateOrder) { +func (o *OrderBook) Add(actionID codec.LID, actor codec.Address, action *actions.CreateOrder) { pair := actions.PairID(action.In, action.Out) order := &Order{ actionID, @@ -85,10 +85,10 @@ func (o *OrderBook) Add(actionID codec.ActionID, actor codec.Address, action *ac return case !ok && o.trackAll: o.c.Logger().Info("tracking order book", zap.String("pair", pair)) - h = heap.New[codec.ActionID, *Order, float64](o.maxOrdersPerPair+1, true) + h = heap.New[codec.LID, *Order, float64](o.maxOrdersPerPair+1, true) o.orders[pair] = h } - h.Push(&heap.Entry[codec.ActionID, *Order, float64]{ + h.Push(&heap.Entry[codec.LID, *Order, float64]{ ID: order.ID, Val: float64(order.InTick) / float64(order.OutTick), Item: order, @@ -104,7 +104,7 @@ func (o *OrderBook) Add(actionID codec.ActionID, actor codec.Address, action *ac } } -func (o *OrderBook) Remove(id codec.ActionID) { +func (o *OrderBook) Remove(id codec.LID) { o.l.Lock() defer o.l.Unlock() @@ -126,7 +126,7 @@ func (o *OrderBook) Remove(id codec.ActionID) { h.Remove(entry.Index) // O(log N) } -func (o *OrderBook) UpdateRemaining(id codec.ActionID, remaining uint64) { +func (o *OrderBook) UpdateRemaining(id codec.LID, remaining uint64) { o.l.Lock() defer o.l.Unlock() diff --git a/examples/tokenvm/rpc/dependencies.go b/examples/tokenvm/rpc/dependencies.go index 595fbb9db6..7bde2f0fb7 100644 --- a/examples/tokenvm/rpc/dependencies.go +++ b/examples/tokenvm/rpc/dependencies.go @@ -18,18 +18,18 @@ type Controller interface { Genesis() *genesis.Genesis Tracer() trace.Tracer GetTransaction(context.Context, ids.ID) (bool, int64, bool, fees.Dimensions, uint64, error) - GetAssetFromState(context.Context, codec.ActionID) (bool, []byte, uint8, []byte, uint64, codec.Address, error) - GetBalanceFromState(context.Context, codec.Address, codec.ActionID) (uint64, error) + GetAssetFromState(context.Context, codec.LID) (bool, []byte, uint8, []byte, uint64, codec.Address, error) + GetBalanceFromState(context.Context, codec.Address, codec.LID) (uint64, error) Orders(pair string, limit int) []*orderbook.Order - GetOrderFromState(context.Context, codec.ActionID) ( + GetOrderFromState(context.Context, codec.LID) ( bool, // exists - codec.ActionID, // in + codec.LID, // in uint64, // inTick - codec.ActionID, // out + codec.LID, // out uint64, // outTick uint64, // remaining codec.Address, // owner error, ) - GetLoanFromState(context.Context, codec.ActionID, ids.ID) (uint64, error) + GetLoanFromState(context.Context, codec.LID, ids.ID) (uint64, error) } diff --git a/examples/tokenvm/rpc/jsonrpc_client.go b/examples/tokenvm/rpc/jsonrpc_client.go index 3d3eaa652c..76818f34c0 100644 --- a/examples/tokenvm/rpc/jsonrpc_client.go +++ b/examples/tokenvm/rpc/jsonrpc_client.go @@ -29,7 +29,7 @@ type JSONRPCClient struct { chainID ids.ID g *genesis.Genesis assetsL sync.Mutex - assets map[codec.ActionID]*AssetReply + assets map[codec.LID]*AssetReply } // New creates a new client object. @@ -41,7 +41,7 @@ func NewJSONRPCClient(uri string, networkID uint32, chainID ids.ID) *JSONRPCClie requester: req, networkID: networkID, chainID: chainID, - assets: map[codec.ActionID]*AssetReply{}, + assets: map[codec.LID]*AssetReply{}, } } @@ -85,7 +85,7 @@ func (cli *JSONRPCClient) Tx(ctx context.Context, id ids.ID) (bool, bool, int64, func (cli *JSONRPCClient) Asset( ctx context.Context, - asset codec.ActionID, + asset codec.LID, useCache bool, ) (bool, []byte, uint8, []byte, uint64, string, error) { cli.assetsL.Lock() @@ -117,7 +117,7 @@ func (cli *JSONRPCClient) Asset( return true, resp.Symbol, resp.Decimals, resp.Metadata, resp.Supply, resp.Owner, nil } -func (cli *JSONRPCClient) Balance(ctx context.Context, addr string, asset codec.ActionID) (uint64, error) { +func (cli *JSONRPCClient) Balance(ctx context.Context, addr string, asset codec.LID) (uint64, error) { resp := new(BalanceReply) err := cli.requester.SendRequest( ctx, @@ -144,7 +144,7 @@ func (cli *JSONRPCClient) Orders(ctx context.Context, pair string) ([]*orderbook return resp.Orders, err } -func (cli *JSONRPCClient) GetOrder(ctx context.Context, orderID codec.ActionID) (*orderbook.Order, error) { +func (cli *JSONRPCClient) GetOrder(ctx context.Context, orderID codec.LID) (*orderbook.Order, error) { resp := new(GetOrderReply) err := cli.requester.SendRequest( ctx, @@ -159,7 +159,7 @@ func (cli *JSONRPCClient) GetOrder(ctx context.Context, orderID codec.ActionID) func (cli *JSONRPCClient) Loan( ctx context.Context, - asset codec.ActionID, + asset codec.LID, destination ids.ID, ) (uint64, error) { resp := new(LoanReply) @@ -178,7 +178,7 @@ func (cli *JSONRPCClient) Loan( func (cli *JSONRPCClient) WaitForBalance( ctx context.Context, addr string, - asset codec.ActionID, + asset codec.LID, min uint64, ) error { exists, symbol, decimals, _, _, _, err := cli.Asset(ctx, asset, true) diff --git a/examples/tokenvm/rpc/jsonrpc_server.go b/examples/tokenvm/rpc/jsonrpc_server.go index 566199553f..15a20f3a91 100644 --- a/examples/tokenvm/rpc/jsonrpc_server.go +++ b/examples/tokenvm/rpc/jsonrpc_server.go @@ -62,7 +62,7 @@ func (j *JSONRPCServer) Tx(req *http.Request, args *TxArgs, reply *TxReply) erro } type AssetArgs struct { - Asset codec.ActionID `json:"asset"` + Asset codec.LID `json:"asset"` } type AssetReply struct { @@ -93,8 +93,8 @@ func (j *JSONRPCServer) Asset(req *http.Request, args *AssetArgs, reply *AssetRe } type BalanceArgs struct { - Address string `json:"address"` - Asset codec.ActionID `json:"asset"` + Address string `json:"address"` + Asset codec.LID `json:"asset"` } type BalanceReply struct { @@ -134,7 +134,7 @@ func (j *JSONRPCServer) Orders(req *http.Request, args *OrdersArgs, reply *Order } type GetOrderArgs struct { - OrderID codec.ActionID `json:"orderID"` + OrderID codec.LID `json:"orderID"` } type GetOrderReply struct { @@ -165,8 +165,8 @@ func (j *JSONRPCServer) GetOrder(req *http.Request, args *GetOrderArgs, reply *G } type LoanArgs struct { - Destination ids.ID `json:"destination"` - Asset codec.ActionID `json:"asset"` + Destination ids.ID `json:"destination"` + Asset codec.LID `json:"asset"` } type LoanReply struct { diff --git a/examples/tokenvm/storage/storage.go b/examples/tokenvm/storage/storage.go index 19926740e9..94d6753169 100644 --- a/examples/tokenvm/storage/storage.go +++ b/examples/tokenvm/storage/storage.go @@ -131,7 +131,7 @@ func GetTransaction( } // [accountPrefix] + [address] + [asset] -func BalanceKey(addr codec.Address, asset codec.ActionID) (k []byte) { +func BalanceKey(addr codec.Address, asset codec.LID) (k []byte) { k = balanceKeyPool.Get().([]byte) k[0] = balancePrefix copy(k[1:], addr[:]) @@ -145,7 +145,7 @@ func GetBalance( ctx context.Context, im state.Immutable, addr codec.Address, - asset codec.ActionID, + asset codec.LID, ) (uint64, error) { key, bal, _, err := getBalance(ctx, im, addr, asset) balanceKeyPool.Put(key) @@ -156,7 +156,7 @@ func getBalance( ctx context.Context, im state.Immutable, addr codec.Address, - asset codec.ActionID, + asset codec.LID, ) ([]byte, uint64, bool, error) { k := BalanceKey(addr, asset) bal, exists, err := innerGetBalance(im.GetValue(ctx, k)) @@ -168,7 +168,7 @@ func GetBalanceFromState( ctx context.Context, f ReadState, addr codec.Address, - asset codec.ActionID, + asset codec.LID, ) (uint64, error) { k := BalanceKey(addr, asset) values, errs := f(ctx, [][]byte{k}) @@ -194,7 +194,7 @@ func SetBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset codec.ActionID, + asset codec.LID, balance uint64, ) error { k := BalanceKey(addr, asset) @@ -214,7 +214,7 @@ func DeleteBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset codec.ActionID, + asset codec.LID, ) error { return mu.Remove(ctx, BalanceKey(addr, asset)) } @@ -223,7 +223,7 @@ func AddBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset codec.ActionID, + asset codec.LID, amount uint64, create bool, ) error { @@ -254,7 +254,7 @@ func SubBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset codec.ActionID, + asset codec.LID, amount uint64, ) error { key, bal, _, err := getBalance(ctx, mu, addr, asset) @@ -281,7 +281,7 @@ func SubBalance( } // [assetPrefix] + [address] -func AssetKey(asset codec.ActionID) (k []byte) { +func AssetKey(asset codec.LID) (k []byte) { k = make([]byte, 1+codec.AddressLen+consts.Uint16Len) k[0] = assetPrefix copy(k[1:], asset[:]) @@ -293,7 +293,7 @@ func AssetKey(asset codec.ActionID) (k []byte) { func GetAssetFromState( ctx context.Context, f ReadState, - asset codec.ActionID, + asset codec.LID, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { values, errs := f(ctx, [][]byte{AssetKey(asset)}) return innerGetAsset(values[0], errs[0]) @@ -302,7 +302,7 @@ func GetAssetFromState( func GetAsset( ctx context.Context, im state.Immutable, - asset codec.ActionID, + asset codec.LID, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { k := AssetKey(asset) return innerGetAsset(im.GetValue(ctx, k)) @@ -332,7 +332,7 @@ func innerGetAsset( func SetAsset( ctx context.Context, mu state.Mutable, - asset codec.ActionID, + asset codec.LID, symbol []byte, decimals uint8, metadata []byte, @@ -353,13 +353,13 @@ func SetAsset( return mu.Insert(ctx, k, v) } -func DeleteAsset(ctx context.Context, mu state.Mutable, asset codec.ActionID) error { +func DeleteAsset(ctx context.Context, mu state.Mutable, asset codec.LID) error { k := AssetKey(asset) return mu.Remove(ctx, k) } // [orderPrefix] + [actionID] -func OrderKey(actionID codec.ActionID) (k []byte) { +func OrderKey(actionID codec.LID) (k []byte) { k = make([]byte, 1+codec.AddressLen+consts.Uint16Len) k[0] = orderPrefix copy(k[1:], actionID[:]) @@ -370,10 +370,10 @@ func OrderKey(actionID codec.ActionID) (k []byte) { func SetOrder( ctx context.Context, mu state.Mutable, - actionID codec.ActionID, - in codec.ActionID, + actionID codec.LID, + in codec.LID, inTick uint64, - out codec.ActionID, + out codec.LID, outTick uint64, supply uint64, owner codec.Address, @@ -392,12 +392,12 @@ func SetOrder( func GetOrder( ctx context.Context, im state.Immutable, - order codec.ActionID, + order codec.LID, ) ( bool, // exists - codec.ActionID, // in + codec.LID, // in uint64, // inTick - codec.ActionID, // out + codec.LID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -412,12 +412,12 @@ func GetOrder( func GetOrderFromState( ctx context.Context, f ReadState, - order codec.ActionID, + order codec.LID, ) ( bool, // exists - codec.ActionID, // in + codec.LID, // in uint64, // inTick - codec.ActionID, // out + codec.LID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -429,9 +429,9 @@ func GetOrderFromState( func innerGetOrder(v []byte, err error) ( bool, // exists - codec.ActionID, // in + codec.LID, // in uint64, // inTick - codec.ActionID, // out + codec.LID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -443,10 +443,10 @@ func innerGetOrder(v []byte, err error) ( if err != nil { return false, codec.EmptyAddress, 0, codec.EmptyAddress, 0, 0, codec.EmptyAddress, err } - var in codec.ActionID + var in codec.LID copy(in[:], v[:codec.AddressLen]) inTick := binary.BigEndian.Uint64(v[codec.AddressLen:]) - var out codec.ActionID + var out codec.LID copy(out[:], v[codec.AddressLen+consts.Uint64Len:codec.AddressLen*2+consts.Uint64Len]) outTick := binary.BigEndian.Uint64(v[codec.AddressLen*2+consts.Uint64Len:]) supply := binary.BigEndian.Uint64(v[codec.AddressLen*2+consts.Uint64Len*2:]) @@ -455,13 +455,13 @@ func innerGetOrder(v []byte, err error) ( return true, in, inTick, out, outTick, supply, owner, nil } -func DeleteOrder(ctx context.Context, mu state.Mutable, order codec.ActionID) error { +func DeleteOrder(ctx context.Context, mu state.Mutable, order codec.LID) error { k := OrderKey(order) return mu.Remove(ctx, k) } // [loanPrefix] + [asset] + [destination] -func LoanKey(asset codec.ActionID, destination ids.ID) (k []byte) { +func LoanKey(asset codec.LID, destination ids.ID) (k []byte) { k = make([]byte, 1+codec.AddressLen+consts.IDLen+consts.Uint16Len) k[0] = loanPrefix copy(k[1:], asset[:]) @@ -474,7 +474,7 @@ func LoanKey(asset codec.ActionID, destination ids.ID) (k []byte) { func GetLoanFromState( ctx context.Context, f ReadState, - asset codec.ActionID, + asset codec.LID, destination ids.ID, ) (uint64, error) { values, errs := f(ctx, [][]byte{LoanKey(asset, destination)}) @@ -494,7 +494,7 @@ func innerGetLoan(v []byte, err error) (uint64, error) { func GetLoan( ctx context.Context, im state.Immutable, - asset codec.ActionID, + asset codec.LID, destination ids.ID, ) (uint64, error) { k := LoanKey(asset, destination) @@ -505,7 +505,7 @@ func GetLoan( func SetLoan( ctx context.Context, mu state.Mutable, - asset codec.ActionID, + asset codec.LID, destination ids.ID, amount uint64, ) error { @@ -516,7 +516,7 @@ func SetLoan( func AddLoan( ctx context.Context, mu state.Mutable, - asset codec.ActionID, + asset codec.LID, destination ids.ID, amount uint64, ) error { @@ -540,7 +540,7 @@ func AddLoan( func SubLoan( ctx context.Context, mu state.Mutable, - asset codec.ActionID, + asset codec.LID, destination ids.ID, amount uint64, ) error { diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index c03696a718..7f0f2bbee4 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -104,15 +104,15 @@ var ( asset1 []byte asset1Symbol []byte asset1Decimals uint8 - asset1ID codec.ActionID + asset1ID codec.LID asset2 []byte asset2Symbol []byte asset2Decimals uint8 - asset2ID codec.ActionID + asset2ID codec.LID asset3 []byte asset3Symbol []byte asset3Decimals uint8 - asset3ID codec.ActionID + asset3ID codec.LID // when used with embedded VMs genesisBytes []byte @@ -647,7 +647,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(len(blk.Txs)).Should(gomega.Equal(1)) tx := blk.Txs[0].Actions[0].(*actions.Transfer) - gomega.Ω(tx.Asset).To(gomega.Equal(codec.ActionID{})) + gomega.Ω(tx.Asset).To(gomega.Equal(codec.LID{})) gomega.Ω(tx.Value).To(gomega.Equal(uint64(1))) gomega.Ω(lresults).Should(gomega.Equal(results)) gomega.Ω(prices).Should(gomega.Equal(fees.Dimensions{1, 1, 1, 1, 1})) @@ -772,7 +772,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { ginkgo.It("mint an asset that doesn't exist", func() { other, err := ed25519.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) - assetID := codec.CreateActionID(0, ids.GenerateTestID()) + assetID := codec.CreateLID(0, ids.GenerateTestID()) parser, err := instances[0].tcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( @@ -910,7 +910,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset1ID = codec.CreateActionID(0, tx.ID()) + asset1ID = codec.CreateLID(0, tx.ID()) balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) @@ -1186,7 +1186,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := accept(false) gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset2ID = codec.CreateActionID(0, tx.ID()) + asset2ID = codec.CreateLID(0, tx.ID()) submit, _, _, err = instances[0].cli.GenerateTransaction( context.Background(), @@ -1229,7 +1229,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := accept(false) gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset3ID = codec.CreateActionID(0, tx.ID()) + asset3ID = codec.CreateLID(0, tx.ID()) submit, _, _, err = instances[0].cli.GenerateTransaction( context.Background(), @@ -1283,7 +1283,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] - gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateActionID(0, tx.ID()))) + gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) gomega.Ω(order.InTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order.OutTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order.Owner).Should(gomega.Equal(sender)) @@ -1346,7 +1346,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] - gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateActionID(0, tx.ID()))) + gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) gomega.Ω(order.InTick).Should(gomega.Equal(uint64(4))) gomega.Ω(order.OutTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order.Owner).Should(gomega.Equal(sender2)) @@ -1590,7 +1590,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] - gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateActionID(0, tx.ID()))) + gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) gomega.Ω(order.InTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order.OutTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order.Owner).Should(gomega.Equal(sender)) diff --git a/heap/heap_test.go b/heap/heap_test.go index ab91948bea..59380fd860 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -186,29 +186,29 @@ func TestUnit64HeapHasID(t *testing.T) { func TestUnit64HeapPushPopMinForActionID(t *testing.T) { require := require.New(t) - minHeap := New[codec.ActionID, *testItem[codec.ActionID], uint64](0, true) + minHeap := New[codec.LID, *testItem[codec.LID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") txID := ids.GenerateTestID() - mempoolItem1 := &testItem[codec.ActionID]{codec.CreateActionID(0, txID), 10} - mempoolItem2 := &testItem[codec.ActionID]{codec.CreateActionID(1, txID), 7} - mempoolItem3 := &testItem[codec.ActionID]{codec.CreateActionID(2, txID), 15} + mempoolItem1 := &testItem[codec.LID]{codec.CreateLID(0, txID), 10} + mempoolItem2 := &testItem[codec.LID]{codec.CreateLID(1, txID), 7} + mempoolItem3 := &testItem[codec.LID]{codec.CreateLID(2, txID), 15} // Middle UnitPrice - med := &Entry[codec.ActionID, *testItem[codec.ActionID], uint64]{ + med := &Entry[codec.LID, *testItem[codec.LID], uint64]{ ID: mempoolItem1.id, Item: mempoolItem1, Val: mempoolItem1.value, Index: minHeap.Len(), } // Lesser UnitPrice - low := &Entry[codec.ActionID, *testItem[codec.ActionID], uint64]{ + low := &Entry[codec.LID, *testItem[codec.LID], uint64]{ ID: mempoolItem2.id, Item: mempoolItem2, Val: mempoolItem2.value, Index: minHeap.Len(), } // Greatest UnitPrice - high := &Entry[codec.ActionID, *testItem[codec.ActionID], uint64]{ + high := &Entry[codec.LID, *testItem[codec.LID], uint64]{ ID: mempoolItem3.id, Item: mempoolItem3, Val: mempoolItem3.value, From 24915a13a4b9087cd0aba2c149e1093cf086a152 Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 26 Apr 2024 14:54:31 -0400 Subject: [PATCH 35/78] fix token-cli lint --- cli/key.go | 4 +- cli/prompt.go | 38 ++++-- codec/address.go | 12 +- examples/tokenvm/cmd/token-cli/cmd/action.go | 38 +++--- examples/tokenvm/cmd/token-cli/cmd/handler.go | 4 +- examples/tokenvm/cmd/token-cli/cmd/key.go | 4 +- .../tokenvm/cmd/token-cli/cmd/resolutions.go | 127 +++++++++--------- examples/tokenvm/cmd/token-cli/cmd/spam.go | 14 +- 8 files changed, 134 insertions(+), 107 deletions(-) diff --git a/cli/key.go b/cli/key.go index f70fddb6bd..8f26c2dba8 100644 --- a/cli/key.go +++ b/cli/key.go @@ -51,7 +51,7 @@ func (h *Handler) SetKey(lookupBalance func(int, string, string, uint32, ids.ID) return h.StoreDefaultKey(key.Address) } -func (h *Handler) Balance(checkAllChains bool, promptAsset bool, printBalance func(codec.Address, string, uint32, ids.ID, ids.ID) error) error { +func (h *Handler) Balance(checkAllChains bool, promptAsset bool, printBalance func(codec.Address, string, uint32, ids.ID, codec.LID) error) error { addr, _, err := h.GetDefaultKey(true) if err != nil { return err @@ -60,7 +60,7 @@ func (h *Handler) Balance(checkAllChains bool, promptAsset bool, printBalance fu if err != nil { return err } - var assetID ids.ID + var assetID codec.LID if promptAsset { assetID, err = h.PromptAsset("assetID", true) if err != nil { diff --git a/cli/prompt.go b/cli/prompt.go index 127da4c73e..e3cbbfcb01 100644 --- a/cli/prompt.go +++ b/cli/prompt.go @@ -56,7 +56,7 @@ func (*Handler) PromptString(label string, min int, max int) (string, error) { return strings.TrimSpace(text), err } -func (h *Handler) PromptAsset(label string, allowNative bool) (ids.ID, error) { +func (h *Handler) PromptAsset(label string, allowNative bool) (codec.LID, error) { symbol := h.c.Symbol() text := fmt.Sprintf("%s (use %s for native token)", label, symbol) if !allowNative { @@ -71,24 +71,21 @@ func (h *Handler) PromptAsset(label string, allowNative bool) (ids.ID, error) { if allowNative && input == symbol { return nil } - _, err := ids.FromString(input) - return err + _ = codec.LIDFromString(input) + return nil }, } asset, err := promptText.Run() if err != nil { - return ids.Empty, err + return codec.EmptyAddress, err } asset = strings.TrimSpace(asset) - var assetID ids.ID + var assetID codec.LID if asset != symbol { - assetID, err = ids.FromString(asset) - if err != nil { - return ids.Empty, err - } + assetID = codec.LIDFromString(asset) } - if !allowNative && assetID == ids.Empty { - return ids.Empty, ErrInvalidChoice + if !allowNative && assetID == codec.EmptyAddress { + return codec.EmptyAddress, ErrInvalidChoice } return assetID, nil } @@ -277,6 +274,25 @@ func (*Handler) PromptID(label string) (ids.ID, error) { return id, nil } +func (*Handler) PromptLID(label string) (codec.LID, error) { + promptText := promptui.Prompt{ + Label: label, + Validate: func(input string) error { + if len(input) == 0 { + return ErrInputEmpty + } + _ = codec.LIDFromString(input) + return nil + }, + } + rawID, err := promptText.Run() + if err != nil { + return codec.EmptyAddress, err + } + rawID = strings.TrimSpace(rawID) + return codec.LIDFromString(rawID), nil +} + func (h *Handler) PromptChain(label string, excluded set.Set[ids.ID]) (ids.ID, []string, error) { chains, err := h.GetChains() if err != nil { diff --git a/codec/address.go b/codec/address.go index c86db3c961..cb0c3e625b 100644 --- a/codec/address.go +++ b/codec/address.go @@ -89,10 +89,18 @@ func CreateLID(idx uint8, txID ids.ID) LID { return LID(a) } -func LIDToString(hrp string, a LID) string { - addr, err := AddressBech32(hrp, Address(a)) +func LIDToString(hrp string, lid LID) string { + addr, err := AddressBech32(hrp, Address(lid)) if err != nil { panic(err) } return addr } + +func LIDFromString(lid string) LID { + addr, err := ParseAddressBech32("token", lid) + if err != nil { + panic(err) + } + return LID(addr) +} diff --git a/examples/tokenvm/cmd/token-cli/cmd/action.go b/examples/tokenvm/cmd/token-cli/cmd/action.go index d8cf34fa2c..4d36fb0145 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/action.go +++ b/examples/tokenvm/cmd/token-cli/cmd/action.go @@ -7,7 +7,7 @@ package cmd import ( "context" - "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" @@ -47,7 +47,7 @@ var fundFaucetCmd = &cobra.Command{ } // Get balance - _, decimals, balance, _, err := handler.GetAssetInfo(ctx, tcli, priv.Address, ids.Empty, true) + _, decimals, balance, _, err := handler.GetAssetInfo(ctx, tcli, priv.Address, codec.EmptyAddress, true) if balance == 0 || err != nil { return err } @@ -69,11 +69,11 @@ var fundFaucetCmd = &cobra.Command{ if err != nil { return err } - if err = sendAndWait(ctx, &actions.Transfer{ + if err = sendAndWait(ctx, []chain.Action{&actions.Transfer{ To: addr, - Asset: ids.Empty, + Asset: codec.EmptyAddress, Value: amount, - }, cli, scli, tcli, factory, true); err != nil { + }}, cli, scli, tcli, factory, true); err != nil { return err } hutils.Outf("{{green}}funded faucet:{{/}} %s\n", faucetAddress) @@ -119,11 +119,11 @@ var transferCmd = &cobra.Command{ } // Generate transaction - err = sendAndWait(ctx, &actions.Transfer{ + err = sendAndWait(ctx, []chain.Action{&actions.Transfer{ To: recipient, Asset: assetID, Value: amount, - }, cli, scli, tcli, factory, true) + }}, cli, scli, tcli, factory, true) return err }, } @@ -162,11 +162,11 @@ var createAssetCmd = &cobra.Command{ } // Generate transaction - err = sendAndWait(ctx, &actions.CreateAsset{ + err = sendAndWait(ctx, []chain.Action{&actions.CreateAsset{ Symbol: []byte(symbol), Decimals: uint8(decimals), // already constrain above to prevent overflow Metadata: []byte(metadata), - }, cli, scli, tcli, factory, true) + }}, cli, scli, tcli, factory, true) return err }, } @@ -226,11 +226,11 @@ var mintAssetCmd = &cobra.Command{ } // Generate transaction - err = sendAndWait(ctx, &actions.MintAsset{ + err = sendAndWait(ctx, []chain.Action{&actions.MintAsset{ Asset: assetID, To: recipient, Value: amount, - }, cli, scli, tcli, factory, true) + }}, cli, scli, tcli, factory, true) return err }, } @@ -245,7 +245,7 @@ var closeOrderCmd = &cobra.Command{ } // Select inbound token - orderID, err := handler.Root().PromptID("orderID") + orderID, err := handler.Root().PromptLID("orderID") if err != nil { return err } @@ -263,10 +263,10 @@ var closeOrderCmd = &cobra.Command{ } // Generate transaction - err = sendAndWait(ctx, &actions.CloseOrder{ + err = sendAndWait(ctx, []chain.Action{&actions.CloseOrder{ Order: orderID, Out: outAssetID, - }, cli, scli, tcli, factory, true) + }}, cli, scli, tcli, factory, true) return err }, } @@ -289,7 +289,7 @@ var createOrderCmd = &cobra.Command{ if err != nil { return err } - if inAssetID != ids.Empty { + if inAssetID != codec.EmptyAddress { if !exists { hutils.Outf("{{red}}%s does not exist{{/}}\n", inAssetID) hutils.Outf("{{red}}exiting...{{/}}\n") @@ -349,13 +349,13 @@ var createOrderCmd = &cobra.Command{ } // Generate transaction - err = sendAndWait(ctx, &actions.CreateOrder{ + err = sendAndWait(ctx, []chain.Action{&actions.CreateOrder{ In: inAssetID, InTick: inTick, Out: outAssetID, OutTick: outTick, Supply: supply, - }, cli, scli, tcli, factory, true) + }}, cli, scli, tcli, factory, true) return err }, } @@ -466,13 +466,13 @@ var fillOrderCmd = &cobra.Command{ if err != nil { return err } - err = sendAndWait(ctx, &actions.FillOrder{ + err = sendAndWait(ctx, []chain.Action{&actions.FillOrder{ Order: order.ID, Owner: owner, In: inAssetID, Out: outAssetID, Value: value, - }, cli, scli, tcli, factory, true) + }}, cli, scli, tcli, factory, true) return err }, } diff --git a/examples/tokenvm/cmd/token-cli/cmd/handler.go b/examples/tokenvm/cmd/token-cli/cmd/handler.go index 697189d4be..da055b37c3 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/handler.go +++ b/examples/tokenvm/cmd/token-cli/cmd/handler.go @@ -37,7 +37,7 @@ func (*Handler) GetAssetInfo( ctx context.Context, cli *trpc.JSONRPCClient, addr codec.Address, - assetID ids.ID, + assetID codec.LID, checkBalance bool, ) ([]byte, uint8, uint64, ids.ID, error) { var sourceChainID ids.ID @@ -45,7 +45,7 @@ func (*Handler) GetAssetInfo( if err != nil { return nil, 0, 0, ids.Empty, err } - if assetID != ids.Empty { + if assetID != codec.EmptyAddress { if !exists { hutils.Outf("{{red}}%s does not exist{{/}}\n", assetID) hutils.Outf("{{red}}exiting...{{/}}\n") diff --git a/examples/tokenvm/cmd/token-cli/cmd/key.go b/examples/tokenvm/cmd/token-cli/cmd/key.go index 8e766e1f17..76d84ee63e 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/key.go +++ b/examples/tokenvm/cmd/token-cli/cmd/key.go @@ -89,7 +89,7 @@ var importKeyCmd = &cobra.Command{ func lookupSetKeyBalance(choice int, address string, uri string, networkID uint32, chainID ids.ID) error { // TODO: just load once cli := trpc.NewJSONRPCClient(uri, networkID, chainID) - balance, err := cli.Balance(context.TODO(), address, ids.Empty) + balance, err := cli.Balance(context.TODO(), address, codec.EmptyAddress) if err != nil { return err } @@ -110,7 +110,7 @@ var setKeyCmd = &cobra.Command{ }, } -func lookupKeyBalance(addr codec.Address, uri string, networkID uint32, chainID ids.ID, assetID ids.ID) error { +func lookupKeyBalance(addr codec.Address, uri string, networkID uint32, chainID ids.ID, assetID codec.LID) error { _, _, _, _, err := handler.GetAssetInfo( context.TODO(), trpc.NewJSONRPCClient(uri, networkID, chainID), addr, assetID, true) diff --git a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go index 598151c7c9..b798ad6197 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go +++ b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go @@ -20,14 +20,14 @@ import ( // sendAndWait may not be used concurrently func sendAndWait( - ctx context.Context, action chain.Action, cli *rpc.JSONRPCClient, + ctx context.Context, actions []chain.Action, cli *rpc.JSONRPCClient, scli *rpc.WebSocketClient, tcli *trpc.JSONRPCClient, factory chain.AuthFactory, printStatus bool, ) error { parser, err := tcli.Parser(ctx) if err != nil { return err } - _, tx, _, err := cli.GenerateTransaction(ctx, parser, action, factory) + _, tx, _, err := cli.GenerateTransaction(ctx, parser, actions, factory) if err != nil { return err } @@ -62,68 +62,71 @@ func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result status := "❌" if result.Success { status = "✅" - switch action := tx.Action.(type) { - case *actions.CreateAsset: - summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", tx.ID(), action.Symbol, action.Decimals, action.Metadata) - case *actions.MintAsset: - _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - amountStr := utils.FormatBalance(action.Value, decimals) - summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - case *actions.BurnAsset: - summaryStr = fmt.Sprintf("%d %s -> 🔥", action.Value, action.Asset) + for i, act := range tx.Actions { + switch action := act.(type) { + case *actions.CreateAsset: + assetID := action.GetActionID(uint8(i), tx.ID()) + summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", assetID, action.Symbol, action.Decimals, action.Metadata) + case *actions.MintAsset: + _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + amountStr := utils.FormatBalance(action.Value, decimals) + summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + case *actions.BurnAsset: + summaryStr = fmt.Sprintf("%d %s -> 🔥", action.Value, action.Asset) - case *actions.Transfer: - _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - amountStr := utils.FormatBalance(action.Value, decimals) - summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - if len(action.Memo) > 0 { - summaryStr += fmt.Sprintf(" (memo: %s)", action.Memo) - } + case *actions.Transfer: + _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + amountStr := utils.FormatBalance(action.Value, decimals) + summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + if len(action.Memo) > 0 { + summaryStr += fmt.Sprintf(" (memo: %s)", action.Memo) + } - case *actions.CreateOrder: - _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - inTickStr := utils.FormatBalance(action.InTick, inDecimals) - _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - outTickStr := utils.FormatBalance(action.OutTick, outDecimals) - supplyStr := utils.FormatBalance(action.Supply, outDecimals) - summaryStr = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", inTickStr, inSymbol, outTickStr, outSymbol, supplyStr, outSymbol) - case *actions.FillOrder: - or, _ := actions.UnmarshalOrderResult(result.Output) - _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - inAmtStr := utils.FormatBalance(or.In, inDecimals) - _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return + case *actions.CreateOrder: + _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + inTickStr := utils.FormatBalance(action.InTick, inDecimals) + _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + outTickStr := utils.FormatBalance(action.OutTick, outDecimals) + supplyStr := utils.FormatBalance(action.Supply, outDecimals) + summaryStr = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", inTickStr, inSymbol, outTickStr, outSymbol, supplyStr, outSymbol) + case *actions.FillOrder: + or, _ := actions.UnmarshalOrderResult(result.Output) + _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + inAmtStr := utils.FormatBalance(or.In, inDecimals) + _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + outAmtStr := utils.FormatBalance(or.Out, outDecimals) + remainingStr := utils.FormatBalance(or.Remaining, outDecimals) + summaryStr = fmt.Sprintf( + "%s %s -> %s %s (remaining: %s %s)", + inAmtStr, inSymbol, outAmtStr, outSymbol, remainingStr, outSymbol, + ) + case *actions.CloseOrder: + summaryStr = fmt.Sprintf("orderID: %s", action.Order) } - outAmtStr := utils.FormatBalance(or.Out, outDecimals) - remainingStr := utils.FormatBalance(or.Remaining, outDecimals) - summaryStr = fmt.Sprintf( - "%s %s -> %s %s (remaining: %s %s)", - inAmtStr, inSymbol, outAmtStr, outSymbol, remainingStr, outSymbol, - ) - case *actions.CloseOrder: - summaryStr = fmt.Sprintf("orderID: %s", action.Order) } } utils.Outf( @@ -131,7 +134,7 @@ func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result status, tx.ID(), codec.MustAddressBech32(tconsts.HRP, actor), - reflect.TypeOf(tx.Action), + reflect.TypeOf(tx.Actions), summaryStr, float64(result.Fee)/float64(tx.Base.MaxFee)*100, utils.FormatBalance(result.Fee, tconsts.Decimals), diff --git a/examples/tokenvm/cmd/token-cli/cmd/spam.go b/examples/tokenvm/cmd/token-cli/cmd/spam.go index f3bf3b6660..77616d4cdb 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/spam.go +++ b/examples/tokenvm/cmd/token-cli/cmd/spam.go @@ -62,7 +62,7 @@ var runSpamCmd = &cobra.Command{ }, nil }, func(choice int, address string) (uint64, error) { // lookupBalance - balance, err := tclient.Balance(context.TODO(), address, ids.Empty) + balance, err := tclient.Balance(context.TODO(), address, codec.EmptyAddress) if err != nil { return 0, err } @@ -78,19 +78,19 @@ var runSpamCmd = &cobra.Command{ func(ctx context.Context, chainID ids.ID) (chain.Parser, error) { // getParser return tclient.Parser(ctx) }, - func(addr codec.Address, amount uint64) chain.Action { // getTransfer - return &actions.Transfer{ + func(addr codec.Address, amount uint64) []chain.Action { // getTransfer + return []chain.Action{&actions.Transfer{ To: addr, - Asset: ids.Empty, + Asset: codec.EmptyAddress, Value: amount, - } + }} }, func(cli *rpc.JSONRPCClient, priv *cli.PrivateKey) func(context.Context, uint64) error { // submitDummy return func(ictx context.Context, count uint64) error { - return sendAndWait(ictx, &actions.Transfer{ + return sendAndWait(ictx, []chain.Action{&actions.Transfer{ To: priv.Address, Value: count, // prevent duplicate txs - }, cli, sclient, tclient, auth.NewED25519Factory(ed25519.PrivateKey(priv.Bytes)), false) + }}, cli, sclient, tclient, auth.NewED25519Factory(ed25519.PrivateKey(priv.Bytes)), false) } }, ) From 3136c1cca37532d2d3f8102bfc44a511ed75580d Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 26 Apr 2024 14:58:54 -0400 Subject: [PATCH 36/78] fix token fauct and feed lint --- .../cmd/token-faucet/manager/manager.go | 12 +- .../tokenvm/cmd/token-feed/manager/manager.go | 116 +++++++++--------- 2 files changed, 66 insertions(+), 62 deletions(-) diff --git a/examples/tokenvm/cmd/token-faucet/manager/manager.go b/examples/tokenvm/cmd/token-faucet/manager/manager.go index 0c6586f518..775b7b00be 100644 --- a/examples/tokenvm/cmd/token-faucet/manager/manager.go +++ b/examples/tokenvm/cmd/token-faucet/manager/manager.go @@ -14,6 +14,8 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/timer" + + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" @@ -59,7 +61,7 @@ func New(logger logging.Logger, config *config.Config) (*Manager, error) { if err != nil { return nil, err } - bal, err := tcli.Balance(ctx, m.config.AddressBech32(), ids.Empty) + bal, err := tcli.Balance(ctx, m.config.AddressBech32(), codec.EmptyAddress) if err != nil { return nil, err } @@ -122,11 +124,11 @@ func (m *Manager) sendFunds(ctx context.Context, destination codec.Address, amou if err != nil { return ids.Empty, 0, err } - submit, tx, maxFee, err := m.cli.GenerateTransaction(ctx, parser, &actions.Transfer{ + submit, tx, maxFee, err := m.cli.GenerateTransaction(ctx, parser, []chain.Action{&actions.Transfer{ To: destination, - Asset: ids.Empty, + Asset: codec.EmptyAddress, Value: amount, - }, m.factory) + }}, m.factory) if err != nil { return ids.Empty, 0, err } @@ -134,7 +136,7 @@ func (m *Manager) sendFunds(ctx context.Context, destination codec.Address, amou m.log.Warn("abandoning airdrop because network fee is greater than amount", zap.String("maxFee", utils.FormatBalance(maxFee, consts.Decimals))) return ids.Empty, 0, errors.New("network fee too high") } - bal, err := m.tcli.Balance(ctx, m.config.AddressBech32(), ids.Empty) + bal, err := m.tcli.Balance(ctx, m.config.AddressBech32(), codec.EmptyAddress) if err != nil { return ids.Empty, 0, err } diff --git a/examples/tokenvm/cmd/token-feed/manager/manager.go b/examples/tokenvm/cmd/token-feed/manager/manager.go index c882a1daa0..d1bb6b9eb0 100644 --- a/examples/tokenvm/cmd/token-feed/manager/manager.go +++ b/examples/tokenvm/cmd/token-feed/manager/manager.go @@ -131,64 +131,66 @@ func (m *Manager) Run(ctx context.Context) error { // Look for transactions to recipient for i, tx := range blk.Txs { - action, ok := tx.Action.(*actions.Transfer) - if !ok { - continue + for _, act := range tx.Actions { + action, ok := act.(*actions.Transfer) + if !ok { + continue + } + if action.To != recipientAddr { + continue + } + if len(action.Memo) == 0 { + continue + } + result := results[i] + from := tx.Auth.Actor() + fromStr := codec.MustAddressBech32(consts.HRP, from) + if !result.Success { + m.log.Info("incoming message failed on-chain", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value), zap.Uint64("required", m.feeAmount)) + continue + } + if action.Value < m.feeAmount { + m.log.Info("incoming message did not pay enough", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value), zap.Uint64("required", m.feeAmount)) + continue + } + + var c FeedContent + if err := json.Unmarshal(action.Memo, &c); err != nil { + m.log.Info("incoming message could not be parsed", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value), zap.Error(err)) + continue + } + if len(c.Message) == 0 { + m.log.Info("incoming message was empty", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value)) + continue + } + // TODO: pre-verify URLs + m.l.Lock() + m.f.Lock() + m.feed = append([]*FeedObject{{ + Address: fromStr, + TxID: tx.ID(), // TODO: may need to change to action ID + Timestamp: blk.Tmstmp, + Fee: action.Value, + Content: &c, + }}, m.feed...) + if len(m.feed) > m.config.FeedSize { + // TODO: do this more efficiently using a rolling window + m.feed[m.config.FeedSize] = nil // prevent memory leak + m.feed = m.feed[:m.config.FeedSize] + } + m.epochMessages++ + if m.epochMessages >= m.config.MessagesPerEpoch { + m.feeAmount += m.config.FeeDelta + m.log.Info("increasing message fee", zap.Uint64("fee", m.feeAmount)) + m.epochMessages = 0 + m.epochStart = time.Now().Unix() + m.t.Cancel() + m.t.SetTimeoutIn(time.Duration(m.config.TargetDurationPerEpoch) * time.Second) + } + m.log.Info("received incoming message", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value), zap.Uint64("new required", m.feeAmount)) + m.f.Unlock() + m.l.Unlock() } - if action.To != recipientAddr { - continue - } - if len(action.Memo) == 0 { - continue - } - result := results[i] - from := tx.Auth.Actor() - fromStr := codec.MustAddressBech32(consts.HRP, from) - if !result.Success { - m.log.Info("incoming message failed on-chain", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value), zap.Uint64("required", m.feeAmount)) - continue - } - if action.Value < m.feeAmount { - m.log.Info("incoming message did not pay enough", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value), zap.Uint64("required", m.feeAmount)) - continue - } - - var c FeedContent - if err := json.Unmarshal(action.Memo, &c); err != nil { - m.log.Info("incoming message could not be parsed", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value), zap.Error(err)) - continue - } - if len(c.Message) == 0 { - m.log.Info("incoming message was empty", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value)) - continue - } - // TODO: pre-verify URLs - m.l.Lock() - m.f.Lock() - m.feed = append([]*FeedObject{{ - Address: fromStr, - TxID: tx.ID(), - Timestamp: blk.Tmstmp, - Fee: action.Value, - Content: &c, - }}, m.feed...) - if len(m.feed) > m.config.FeedSize { - // TODO: do this more efficiently using a rolling window - m.feed[m.config.FeedSize] = nil // prevent memory leak - m.feed = m.feed[:m.config.FeedSize] - } - m.epochMessages++ - if m.epochMessages >= m.config.MessagesPerEpoch { - m.feeAmount += m.config.FeeDelta - m.log.Info("increasing message fee", zap.Uint64("fee", m.feeAmount)) - m.epochMessages = 0 - m.epochStart = time.Now().Unix() - m.t.Cancel() - m.t.SetTimeoutIn(time.Duration(m.config.TargetDurationPerEpoch) * time.Second) - } - m.log.Info("received incoming message", zap.String("from", fromStr), zap.String("memo", string(action.Memo)), zap.Uint64("payment", action.Value), zap.Uint64("new required", m.feeAmount)) - m.f.Unlock() - m.l.Unlock() } } if ctx.Err() != nil { From c31792f653d27191dbc76d541ca54872b4ed6e2b Mon Sep 17 00:00:00 2001 From: William Law Date: Sat, 27 Apr 2024 20:16:36 -0400 Subject: [PATCH 37/78] introduce multiple result.Outputs and morpheus integration passes --- chain/builder.go | 36 ++++++++-------- chain/dependencies.go | 4 +- chain/processor.go | 28 ++++++------ chain/result.go | 43 ++++++++++++++++--- chain/transaction.go | 37 +++++++++------- examples/morpheusvm/actions/transfer.go | 10 ++--- examples/morpheusvm/controller/controller.go | 1 + .../tests/integration/integration_test.go | 2 +- 8 files changed, 99 insertions(+), 62 deletions(-) diff --git a/chain/builder.go b/chain/builder.go index 2b028458dc..c6122563b1 100644 --- a/chain/builder.go +++ b/chain/builder.go @@ -294,7 +294,7 @@ func BuildBlock( log.Warn("invalid tx: invalid state keys") return nil } - txResults, err := tx.Execute( + result, err := tx.Execute( ctx, feeManager, reads, @@ -315,31 +315,29 @@ func BuildBlock( defer blockLock.Unlock() // Ensure block isn't too big - for _, result := range txResults { - if ok, dimension := feeManager.Consume(result.Consumed, maxUnits); !ok { - log.Debug( - "skipping tx: too many units", - zap.Int("dimension", int(dimension)), - zap.Uint64("tx", result.Consumed[dimension]), - zap.Uint64("block units", feeManager.LastConsumed(dimension)), - zap.Uint64("max block units", maxUnits[dimension]), - ) - restore = true + if ok, dimension := feeManager.Consume(result.Consumed, maxUnits); !ok { + log.Debug( + "skipping tx: too many units", + zap.Int("dimension", int(dimension)), + zap.Uint64("tx", result.Consumed[dimension]), + zap.Uint64("block units", feeManager.LastConsumed(dimension)), + zap.Uint64("max block units", maxUnits[dimension]), + ) + restore = true - // If we are above the target for the dimension we can't consume, we will - // stop building. This prevents a full mempool iteration looking for the - // "perfect fit". - if feeManager.LastConsumed(dimension) >= targetUnits[dimension] { - stop = true - return errBlockFull - } + // If we are above the target for the dimension we can't consume, we will + // stop building. This prevents a full mempool iteration looking for the + // "perfect fit". + if feeManager.LastConsumed(dimension) >= targetUnits[dimension] { + stop = true + return errBlockFull } } // Update block with new transaction tsv.Commit() b.Txs = append(b.Txs, tx) - results = append(results, txResults...) + results = append(results, result) return nil }) } diff --git a/chain/dependencies.go b/chain/dependencies.go index 54eace0cf0..d10cb1a409 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -254,6 +254,8 @@ type Action interface { // // An error should only be returned if a fatal error was encountered, otherwise [success] should // be marked as false and fees will still be charged. + // + // TODO: Consider limiting number of outputs an [Action] can have Execute( ctx context.Context, r Rules, @@ -261,7 +263,7 @@ type Action interface { timestamp int64, actor codec.Address, actionID codec.LID, - ) (success bool, computeUnits uint64, output []byte, err error) + ) (success bool, computeUnits uint64, outputs [][]byte, err error) } type Auth interface { diff --git a/chain/processor.go b/chain/processor.go index ea7bb404a0..0b44215923 100644 --- a/chain/processor.go +++ b/chain/processor.go @@ -38,15 +38,15 @@ func (b *StatelessBlock) Execute( numTxs = len(b.Txs) t = b.GetTimestamp() - f = fetcher.New(im, numTxs, b.vm.GetStateFetchConcurrency()) - e = executor.New(numTxs, b.vm.GetTransactionExecutionCores(), MaxKeyDependencies, b.vm.GetExecutorVerifyRecorder()) - ts = tstate.New(numTxs * 2) // TODO: tune this heuristic - // TODO: specify len? - results = []*Result{} + f = fetcher.New(im, numTxs, b.vm.GetStateFetchConcurrency()) + e = executor.New(numTxs, b.vm.GetTransactionExecutionCores(), MaxKeyDependencies, b.vm.GetExecutorVerifyRecorder()) + ts = tstate.New(numTxs * 2) // TODO: tune this heuristic + results = make([]*Result, numTxs) ) // Fetch required keys and execute transactions - for _, ltx := range b.Txs { + for li, ltx := range b.Txs { + i := li tx := ltx stateKeys, err := tx.StateKeys(sm) @@ -79,18 +79,16 @@ func (b *StatelessBlock) Execute( return err } - txResults, err := tx.Execute(ctx, feeManager, reads, sm, r, tsv, t) + result, err := tx.Execute(ctx, feeManager, reads, sm, r, tsv, t) if err != nil { return err } - results = append(results, txResults...) - - for _, result := range txResults { - // Update block metadata with units actually consumed (if more is consumed than block allows, we will non-deterministically - // exit with an error based on which tx over the limit is processed first) - if ok, d := feeManager.Consume(result.Consumed, r.GetMaxBlockUnits()); !ok { - return fmt.Errorf("%w: %d too large", ErrInvalidUnitsConsumed, d) - } + results[i] = result + + // Update block metadata with units actually consumed (if more is consumed than block allows, we will non-deterministically + // exit with an error based on which tx over the limit is processed first) + if ok, d := feeManager.Consume(result.Consumed, r.GetMaxBlockUnits()); !ok { + return fmt.Errorf("%w: %d too large", ErrInvalidUnitsConsumed, d) } // Commit results to parent [TState] diff --git a/chain/result.go b/chain/result.go index 9e9964608d..9047f153d8 100644 --- a/chain/result.go +++ b/chain/result.go @@ -11,19 +11,37 @@ import ( type Result struct { Success bool - Output []byte + Outputs [][][]byte Consumed fees.Dimensions Fee uint64 } func (r *Result) Size() int { - return consts.BoolLen + codec.BytesLen(r.Output) + fees.DimensionsLen + consts.Uint64Len + outputSize := consts.IntLen + for _, action := range r.Outputs { + for _, output := range action { + outputSize += codec.BytesLen(output) + } + } + return consts.BoolLen + outputSize + fees.DimensionsLen + consts.Uint64Len } func (r *Result) Marshal(p *codec.Packer) error { p.PackBool(r.Success) - p.PackBytes(r.Output) + + numOutputs := 0 + for _, action := range r.Outputs { + numOutputs += len(action) + } + p.PackInt(len(r.Outputs)) + p.PackInt(numOutputs) + for _, action := range r.Outputs { + for _, output := range action { + p.PackBytes(output) + } + } + p.PackFixedBytes(r.Consumed.Bytes()) p.PackUint64(r.Fee) return nil @@ -45,10 +63,23 @@ func UnmarshalResult(p *codec.Packer) (*Result, error) { result := &Result{ Success: p.UnpackBool(), } - p.UnpackBytes(consts.MaxInt, false, &result.Output) - if len(result.Output) == 0 { + + totalOutputs := [][][]byte{} + numActions := p.UnpackInt(false) + numOutputs := p.UnpackInt(false) + for i := 0; i < numActions; i++ { + outputs := [][]byte{} + for j := 0; j < numOutputs; j++ { + var output []byte + p.UnpackBytes(consts.MaxInt, false, &output) + outputs = append(outputs, output) + } + totalOutputs = append(totalOutputs, outputs) + } + result.Outputs = totalOutputs + if len(result.Outputs) == 0 { // Enforce object standardization - result.Output = nil + result.Outputs = nil } consumedRaw := make([]byte, fees.DimensionsLen) p.UnpackFixedBytes(fees.DimensionsLen, &consumedRaw) diff --git a/chain/transaction.go b/chain/transaction.go index f0b18d75d5..3252694e0e 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -294,7 +294,7 @@ func (t *Transaction) Execute( r Rules, ts *tstate.TStateView, timestamp int64, -) ([]*Result, error) { +) (*Result, error) { // Always charge fee first (in case [Action] moves funds) maxUnits, err := t.MaxUnits(s, r) if err != nil { @@ -314,25 +314,28 @@ func (t *Transaction) Execute( // We create a temp state checkpoint to ensure we don't commit failed actions to state. actionStart := ts.OpIndex() - handleRevert := func(rerr error) ([]*Result, error) { + handleRevert := func(rerr error) (*Result, error) { // Be warned that the variables captured in this function // are set when this function is defined. If any of them are // modified later, they will not be used here. ts.Rollback(ctx, actionStart) - return []*Result{{false, utils.ErrBytes(rerr), maxUnits, maxFee}}, nil + return &Result{false, [][][]byte{{utils.ErrBytes(rerr)}}, maxUnits, maxFee}, nil } - results := make([]*Result, 0) + resultOutputs := [][][]byte{} + totalUsed := fees.Dimensions{} + var totalFeeRequired uint64 for i, action := range t.Actions { actionID := action.GetActionID(uint8(i), t.id) - success, actionCUs, output, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) + success, actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) if err != nil { return handleRevert(err) } - if len(output) == 0 && output != nil { + if len(outputs) == 0 && outputs != nil { // Enforce object standardization (this is a VM bug and we should fail // fast) return handleRevert(ErrInvalidObject) } + resultOutputs = append(resultOutputs, outputs) if !success { ts.Rollback(ctx, actionStart) } @@ -392,6 +395,11 @@ func (t *Transaction) Execute( return handleRevert(err) } used := fees.Dimensions{uint64(t.Size()), computeUnits, readUnits, allocateUnits, writeUnits} + nused, err := fees.Add(totalUsed, used) + if err != nil { + return handleRevert(err) + } + totalUsed = nused // Check to see if the units consumed are greater than the max units // @@ -408,6 +416,7 @@ func (t *Transaction) Execute( if err != nil { return handleRevert(err) } + totalFeeRequired += feeRequired refund := maxFee - feeRequired if refund > 0 { ts.DisableAllocation() @@ -416,16 +425,14 @@ func (t *Transaction) Execute( return handleRevert(err) } } - - results = append(results, &Result{ - Success: success, - Output: output, - - Consumed: used, - Fee: feeRequired, - }) } - return results, nil + return &Result{ + Success: true, + Outputs: resultOutputs, + + Consumed: totalUsed, + Fee: totalFeeRequired, + }, nil } func (t *Transaction) Marshal(p *codec.Packer) error { diff --git a/examples/morpheusvm/actions/transfer.go b/examples/morpheusvm/actions/transfer.go index 851532a540..389d24acef 100644 --- a/examples/morpheusvm/actions/transfer.go +++ b/examples/morpheusvm/actions/transfer.go @@ -52,17 +52,17 @@ func (t *Transfer) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, []byte, error) { +) (bool, uint64, [][]byte, error) { if t.Value == 0 { - return false, 1, OutputValueZero, nil + return false, 1, [][]byte{OutputValueZero}, nil } if err := storage.SubBalance(ctx, mu, actor, t.Value); err != nil { - return false, 1, utils.ErrBytes(err), nil + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } if err := storage.AddBalance(ctx, mu, t.To, t.Value, true); err != nil { - return false, 1, utils.ErrBytes(err), nil + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } - return true, 1, nil, nil + return true, 1, [][]byte{{}}, nil } func (*Transfer) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/morpheusvm/controller/controller.go b/examples/morpheusvm/controller/controller.go index 7fb305ceee..0f99755c09 100644 --- a/examples/morpheusvm/controller/controller.go +++ b/examples/morpheusvm/controller/controller.go @@ -153,6 +153,7 @@ func (c *Controller) Accepted(ctx context.Context, blk *chain.StatelessBlock) er results := blk.Results() for i, tx := range blk.Txs { result := results[i] + fmt.Printf("result %v | results %v\n", result, results) if c.config.GetStoreTransactions() { err := storage.StoreTransaction( ctx, diff --git a/examples/morpheusvm/tests/integration/integration_test.go b/examples/morpheusvm/tests/integration/integration_test.go index 714bfe8fd0..795d202bc3 100644 --- a/examples/morpheusvm/tests/integration/integration_test.go +++ b/examples/morpheusvm/tests/integration/integration_test.go @@ -423,7 +423,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := blk.(*chain.StatelessBlock).Results() gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - gomega.Ω(results[0].Output).Should(gomega.BeNil()) + gomega.Ω(len(results[0].Outputs[0])).To(gomega.Equal(1)) // Unit explanation // From 9fe30c925a3ccaf9d06ccbf716ca628446a3ac93 Mon Sep 17 00:00:00 2001 From: William Law Date: Sat, 27 Apr 2024 21:07:53 -0400 Subject: [PATCH 38/78] tokenvm integration passes using multiple result.Outputs --- chain/transaction.go | 4 ++- examples/tokenvm/actions/burn_asset.go | 16 ++++----- examples/tokenvm/actions/close_order.go | 16 ++++----- examples/tokenvm/actions/create_asset.go | 16 ++++----- examples/tokenvm/actions/create_order.go | 18 +++++----- examples/tokenvm/actions/fill_order.go | 36 +++++++++---------- examples/tokenvm/actions/mint_asset.go | 20 +++++------ examples/tokenvm/actions/transfer.go | 12 +++---- examples/tokenvm/controller/controller.go | 21 ++++++----- .../tests/integration/integration_test.go | 24 ++++++------- 10 files changed, 94 insertions(+), 89 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 3252694e0e..1dc772def9 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -324,6 +324,7 @@ func (t *Transaction) Execute( resultOutputs := [][][]byte{} totalUsed := fees.Dimensions{} var totalFeeRequired uint64 + txSuccess := true for i, action := range t.Actions { actionID := action.GetActionID(uint8(i), t.id) success, actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) @@ -337,6 +338,7 @@ func (t *Transaction) Execute( } resultOutputs = append(resultOutputs, outputs) if !success { + txSuccess = false ts.Rollback(ctx, actionStart) } @@ -427,7 +429,7 @@ func (t *Transaction) Execute( } } return &Result{ - Success: true, + Success: txSuccess, Outputs: resultOutputs, Consumed: totalUsed, diff --git a/examples/tokenvm/actions/burn_asset.go b/examples/tokenvm/actions/burn_asset.go index f77bafa0a6..d273796767 100644 --- a/examples/tokenvm/actions/burn_asset.go +++ b/examples/tokenvm/actions/burn_asset.go @@ -53,28 +53,28 @@ func (b *BurnAsset) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, []byte, error) { +) (bool, uint64, [][]byte, error) { if b.Value == 0 { - return false, BurnComputeUnits, OutputValueZero, nil + return false, BurnComputeUnits, [][]byte{OutputValueZero}, nil } if err := storage.SubBalance(ctx, mu, actor, b.Asset, b.Value); err != nil { - return false, BurnComputeUnits, utils.ErrBytes(err), nil + return false, BurnComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } exists, symbol, decimals, metadata, supply, owner, err := storage.GetAsset(ctx, mu, b.Asset) if err != nil { - return false, BurnComputeUnits, utils.ErrBytes(err), nil + return false, BurnComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if !exists { - return false, BurnComputeUnits, OutputAssetMissing, nil + return false, BurnComputeUnits, [][]byte{OutputAssetMissing}, nil } newSupply, err := smath.Sub(supply, b.Value) if err != nil { - return false, BurnComputeUnits, utils.ErrBytes(err), nil + return false, BurnComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if err := storage.SetAsset(ctx, mu, b.Asset, symbol, decimals, metadata, newSupply, owner); err != nil { - return false, BurnComputeUnits, utils.ErrBytes(err), nil + return false, BurnComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } - return true, BurnComputeUnits, nil, nil + return true, BurnComputeUnits, [][]byte{{}}, nil } func (*BurnAsset) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/close_order.go b/examples/tokenvm/actions/close_order.go index 6c21f56bcd..508cdc9591 100644 --- a/examples/tokenvm/actions/close_order.go +++ b/examples/tokenvm/actions/close_order.go @@ -52,27 +52,27 @@ func (c *CloseOrder) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, []byte, error) { +) (bool, uint64, [][]byte, error) { exists, _, _, out, _, remaining, owner, err := storage.GetOrder(ctx, mu, c.Order) if err != nil { - return false, CloseOrderComputeUnits, utils.ErrBytes(err), nil + return false, CloseOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if !exists { - return false, CloseOrderComputeUnits, OutputOrderMissing, nil + return false, CloseOrderComputeUnits, [][]byte{OutputOrderMissing}, nil } if owner != actor { - return false, CloseOrderComputeUnits, OutputUnauthorized, nil + return false, CloseOrderComputeUnits, [][]byte{OutputUnauthorized}, nil } if out != c.Out { - return false, CloseOrderComputeUnits, OutputWrongOut, nil + return false, CloseOrderComputeUnits, [][]byte{OutputWrongOut}, nil } if err := storage.DeleteOrder(ctx, mu, c.Order); err != nil { - return false, CloseOrderComputeUnits, utils.ErrBytes(err), nil + return false, CloseOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if err := storage.AddBalance(ctx, mu, actor, c.Out, remaining, true); err != nil { - return false, CloseOrderComputeUnits, utils.ErrBytes(err), nil + return false, CloseOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } - return true, CloseOrderComputeUnits, nil, nil + return true, CloseOrderComputeUnits, [][]byte{{}}, nil } func (*CloseOrder) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/create_asset.go b/examples/tokenvm/actions/create_asset.go index 80a3b4afac..e1e7437bb7 100644 --- a/examples/tokenvm/actions/create_asset.go +++ b/examples/tokenvm/actions/create_asset.go @@ -48,28 +48,28 @@ func (c *CreateAsset) Execute( _ int64, actor codec.Address, actionID codec.LID, -) (bool, uint64, []byte, error) { +) (bool, uint64, [][]byte, error) { if len(c.Symbol) == 0 { - return false, CreateAssetComputeUnits, OutputSymbolEmpty, nil + return false, CreateAssetComputeUnits, [][]byte{OutputSymbolEmpty}, nil } if len(c.Symbol) > MaxSymbolSize { - return false, CreateAssetComputeUnits, OutputSymbolTooLarge, nil + return false, CreateAssetComputeUnits, [][]byte{OutputSymbolTooLarge}, nil } if c.Decimals > MaxDecimals { - return false, CreateAssetComputeUnits, OutputDecimalsTooLarge, nil + return false, CreateAssetComputeUnits, [][]byte{OutputDecimalsTooLarge}, nil } if len(c.Metadata) == 0 { - return false, CreateAssetComputeUnits, OutputMetadataEmpty, nil + return false, CreateAssetComputeUnits, [][]byte{OutputMetadataEmpty}, nil } if len(c.Metadata) > MaxMetadataSize { - return false, CreateAssetComputeUnits, OutputMetadataTooLarge, nil + return false, CreateAssetComputeUnits, [][]byte{OutputMetadataTooLarge}, nil } // It should only be possible to overwrite an existing asset if there is // a hash collision. if err := storage.SetAsset(ctx, mu, actionID, c.Symbol, c.Decimals, c.Metadata, 0, actor); err != nil { - return false, CreateAssetComputeUnits, utils.ErrBytes(err), nil + return false, CreateAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } - return true, CreateAssetComputeUnits, nil, nil + return true, CreateAssetComputeUnits, [][]byte{{}}, nil } func (*CreateAsset) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/create_order.go b/examples/tokenvm/actions/create_order.go index 88ace01edf..8bee353b88 100644 --- a/examples/tokenvm/actions/create_order.go +++ b/examples/tokenvm/actions/create_order.go @@ -73,29 +73,29 @@ func (c *CreateOrder) Execute( _ int64, actor codec.Address, actionID codec.LID, -) (bool, uint64, []byte, error) { +) (bool, uint64, [][]byte, error) { if c.In == c.Out { - return false, CreateOrderComputeUnits, OutputSameInOut, nil + return false, CreateOrderComputeUnits, [][]byte{OutputSameInOut}, nil } if c.InTick == 0 { - return false, CreateOrderComputeUnits, OutputInTickZero, nil + return false, CreateOrderComputeUnits, [][]byte{OutputInTickZero}, nil } if c.OutTick == 0 { - return false, CreateOrderComputeUnits, OutputOutTickZero, nil + return false, CreateOrderComputeUnits, [][]byte{OutputOutTickZero}, nil } if c.Supply == 0 { - return false, CreateOrderComputeUnits, OutputSupplyZero, nil + return false, CreateOrderComputeUnits, [][]byte{OutputSupplyZero}, nil } if c.Supply%c.OutTick != 0 { - return false, CreateOrderComputeUnits, OutputSupplyMisaligned, nil + return false, CreateOrderComputeUnits, [][]byte{OutputSupplyMisaligned}, nil } if err := storage.SubBalance(ctx, mu, actor, c.Out, c.Supply); err != nil { - return false, CreateOrderComputeUnits, utils.ErrBytes(err), nil + return false, CreateOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if err := storage.SetOrder(ctx, mu, actionID, c.In, c.InTick, c.Out, c.OutTick, c.Supply, actor); err != nil { - return false, CreateOrderComputeUnits, utils.ErrBytes(err), nil + return false, CreateOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } - return true, CreateOrderComputeUnits, nil, nil + return true, CreateOrderComputeUnits, [][]byte{{}}, nil } func (*CreateOrder) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/fill_order.go b/examples/tokenvm/actions/fill_order.go index 19e38f6a0b..0251e3ce87 100644 --- a/examples/tokenvm/actions/fill_order.go +++ b/examples/tokenvm/actions/fill_order.go @@ -67,39 +67,39 @@ func (f *FillOrder) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, []byte, error) { +) (bool, uint64, [][]byte, error) { exists, in, inTick, out, outTick, remaining, owner, err := storage.GetOrder(ctx, mu, f.Order) if err != nil { - return false, NoFillOrderComputeUnits, utils.ErrBytes(err), nil + return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if !exists { - return false, NoFillOrderComputeUnits, OutputOrderMissing, nil + return false, NoFillOrderComputeUnits, [][]byte{OutputOrderMissing}, nil } if owner != f.Owner { - return false, NoFillOrderComputeUnits, OutputWrongOwner, nil + return false, NoFillOrderComputeUnits, [][]byte{OutputWrongOwner}, nil } if in != f.In { - return false, NoFillOrderComputeUnits, OutputWrongIn, nil + return false, NoFillOrderComputeUnits, [][]byte{OutputWrongIn}, nil } if out != f.Out { - return false, NoFillOrderComputeUnits, OutputWrongOut, nil + return false, NoFillOrderComputeUnits, [][]byte{OutputWrongOut}, nil } if f.Value == 0 { // This should be guarded via [Unmarshal] but we check anyways. - return false, NoFillOrderComputeUnits, OutputValueZero, nil + return false, NoFillOrderComputeUnits, [][]byte{OutputValueZero}, nil } if f.Value%inTick != 0 { - return false, NoFillOrderComputeUnits, OutputValueMisaligned, nil + return false, NoFillOrderComputeUnits, [][]byte{OutputValueMisaligned}, nil } // Determine amount of [Out] counterparty will receive if the trade is // successful. outputAmount, err := smath.Mul64(outTick, f.Value/inTick) if err != nil { - return false, NoFillOrderComputeUnits, utils.ErrBytes(err), nil + return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if outputAmount == 0 { // This should never happen because [f.Value] > 0 - return false, NoFillOrderComputeUnits, OutputInsufficientOutput, nil + return false, NoFillOrderComputeUnits, [][]byte{OutputInsufficientOutput}, nil } var ( inputAmount = f.Value @@ -125,32 +125,32 @@ func (f *FillOrder) Execute( } if inputAmount == 0 { // Don't allow free trades (can happen due to refund rounding) - return false, NoFillOrderComputeUnits, OutputInsufficientInput, nil + return false, NoFillOrderComputeUnits, [][]byte{OutputInsufficientInput}, nil } if err := storage.SubBalance(ctx, mu, actor, f.In, inputAmount); err != nil { - return false, NoFillOrderComputeUnits, utils.ErrBytes(err), nil + return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if err := storage.AddBalance(ctx, mu, f.Owner, f.In, inputAmount, true); err != nil { - return false, NoFillOrderComputeUnits, utils.ErrBytes(err), nil + return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if err := storage.AddBalance(ctx, mu, actor, f.Out, outputAmount, true); err != nil { - return false, NoFillOrderComputeUnits, utils.ErrBytes(err), nil + return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if shouldDelete { if err := storage.DeleteOrder(ctx, mu, f.Order); err != nil { - return false, NoFillOrderComputeUnits, utils.ErrBytes(err), nil + return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } } else { if err := storage.SetOrder(ctx, mu, f.Order, in, inTick, out, outTick, orderRemaining, owner); err != nil { - return false, NoFillOrderComputeUnits, utils.ErrBytes(err), nil + return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } } or := &OrderResult{In: inputAmount, Out: outputAmount, Remaining: orderRemaining} output, err := or.Marshal() if err != nil { - return false, NoFillOrderComputeUnits, utils.ErrBytes(err), nil + return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } - return true, FillOrderComputeUnits, output, nil + return true, FillOrderComputeUnits, [][]byte{output}, nil } func (*FillOrder) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index 301da7666e..31af030279 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -56,34 +56,34 @@ func (m *MintAsset) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, []byte, error) { +) (bool, uint64, [][]byte, error) { if m.Asset == codec.EmptyAddress { - return false, MintAssetComputeUnits, OutputAssetIsNative, nil + return false, MintAssetComputeUnits, [][]byte{OutputAssetIsNative}, nil } if m.Value == 0 { - return false, MintAssetComputeUnits, OutputValueZero, nil + return false, MintAssetComputeUnits, [][]byte{OutputValueZero}, nil } exists, symbol, decimals, metadata, supply, owner, err := storage.GetAsset(ctx, mu, m.Asset) if err != nil { - return false, MintAssetComputeUnits, utils.ErrBytes(err), nil + return false, MintAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if !exists { - return false, MintAssetComputeUnits, OutputAssetMissing, nil + return false, MintAssetComputeUnits, [][]byte{OutputAssetMissing}, nil } if owner != actor { - return false, MintAssetComputeUnits, OutputWrongOwner, nil + return false, MintAssetComputeUnits, [][]byte{OutputWrongOwner}, nil } newSupply, err := smath.Add64(supply, m.Value) if err != nil { - return false, MintAssetComputeUnits, utils.ErrBytes(err), nil + return false, MintAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if err := storage.SetAsset(ctx, mu, m.Asset, symbol, decimals, metadata, newSupply, actor); err != nil { - return false, MintAssetComputeUnits, utils.ErrBytes(err), nil + return false, MintAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } if err := storage.AddBalance(ctx, mu, m.To, m.Asset, m.Value, true); err != nil { - return false, MintAssetComputeUnits, utils.ErrBytes(err), nil + return false, MintAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } - return true, MintAssetComputeUnits, nil, nil + return true, MintAssetComputeUnits, [][]byte{{}}, nil } func (*MintAsset) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/transfer.go b/examples/tokenvm/actions/transfer.go index 14a27e3b87..969b74f64e 100644 --- a/examples/tokenvm/actions/transfer.go +++ b/examples/tokenvm/actions/transfer.go @@ -57,21 +57,21 @@ func (t *Transfer) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, []byte, error) { +) (bool, uint64, [][]byte, error) { if t.Value == 0 { - return false, TransferComputeUnits, OutputValueZero, nil + return false, TransferComputeUnits, [][]byte{OutputValueZero}, nil } if len(t.Memo) > MaxMemoSize { - return false, CreateAssetComputeUnits, OutputMemoTooLarge, nil + return false, CreateAssetComputeUnits, [][]byte{OutputMemoTooLarge}, nil } if err := storage.SubBalance(ctx, mu, actor, t.Asset, t.Value); err != nil { - return false, TransferComputeUnits, utils.ErrBytes(err), nil + return false, TransferComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } // TODO: allow sender to configure whether they will pay to create if err := storage.AddBalance(ctx, mu, t.To, t.Asset, t.Value, true); err != nil { - return false, TransferComputeUnits, utils.ErrBytes(err), nil + return false, TransferComputeUnits, [][]byte{utils.ErrBytes(err)}, nil } - return true, TransferComputeUnits, nil, nil + return true, TransferComputeUnits, [][]byte{{}}, nil } func (*Transfer) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/controller/controller.go b/examples/tokenvm/controller/controller.go index 53a3cd6508..62ee630764 100644 --- a/examples/tokenvm/controller/controller.go +++ b/examples/tokenvm/controller/controller.go @@ -194,16 +194,19 @@ func (c *Controller) Accepted(ctx context.Context, blk *chain.StatelessBlock) er c.orderBook.Add(action.GetActionID(uint8(i), tx.ID()), tx.Auth.Actor(), action) case *actions.FillOrder: c.metrics.fillOrder.Inc() - orderResult, err := actions.UnmarshalOrderResult(result.Output) - if err != nil { - // This should never happen - return err + outputs := result.Outputs[i] + for _, output := range outputs { + orderResult, err := actions.UnmarshalOrderResult(output) + if err != nil { + // This should never happen + return err + } + if orderResult.Remaining == 0 { + c.orderBook.Remove(action.Order) + continue + } + c.orderBook.UpdateRemaining(action.Order, orderResult.Remaining) } - if orderResult.Remaining == 0 { - c.orderBook.Remove(action.Order) - continue - } - c.orderBook.UpdateRemaining(action.Order, orderResult.Remaining) case *actions.CloseOrder: c.metrics.closeOrder.Inc() c.orderBook.Remove(action.Order) diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index 7f0f2bbee4..004db64b14 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -422,7 +422,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := blk.(*chain.StatelessBlock).Results() gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - gomega.Ω(results[0].Output).Should(gomega.BeNil()) + gomega.Ω(len(results[0].Outputs[0])).To(gomega.Equal(1)) // Unit explanation // @@ -792,7 +792,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Output)). + gomega.Ω(string(result.Outputs[0][0])). Should(gomega.ContainSubstring("asset missing")) exists, _, _, _, _, _, err := instances[0].tcli.Asset(context.TODO(), assetID, false) @@ -984,7 +984,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Output)). + gomega.Ω(string(result.Outputs[0][0])). Should(gomega.ContainSubstring("wrong owner")) exists, symbol, decimals, metadata, supply, owner, err := instances[0].tcli.Asset(context.TODO(), asset1ID, false) @@ -1052,7 +1052,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Output)). + gomega.Ω(string(result.Outputs[0][0])). Should(gomega.ContainSubstring("invalid balance")) exists, symbol, decimals, metadata, supply, owner, err := instances[0].tcli.Asset(context.TODO(), asset1ID, false) @@ -1116,7 +1116,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Output)). + gomega.Ω(string(result.Outputs[0][0])). Should(gomega.ContainSubstring("overflow")) balance, err := instances[0].tcli.Balance(context.TODO(), sender2, asset1ID) @@ -1312,7 +1312,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Output)). + gomega.Ω(string(result.Outputs[0][0])). Should(gomega.ContainSubstring("supply is misaligned")) }) @@ -1375,7 +1375,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Output)). + gomega.Ω(string(result.Outputs[0][0])). Should(gomega.ContainSubstring("invalid balance")) }) @@ -1407,7 +1407,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Output)). + gomega.Ω(string(result.Outputs[0][0])). Should(gomega.ContainSubstring("value is misaligned")) }) @@ -1439,7 +1439,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Output)). + gomega.Ω(string(result.Outputs[0][0])). Should(gomega.ContainSubstring("invalid balance")) }) @@ -1471,7 +1471,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeTrue()) - or, err := actions.UnmarshalOrderResult(result.Output) + or, err := actions.UnmarshalOrderResult(result.Outputs[0][0]) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(or.In).Should(gomega.Equal(uint64(4))) gomega.Ω(or.Out).Should(gomega.Equal(uint64(1))) @@ -1514,7 +1514,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Output)). + gomega.Ω(string(result.Outputs[0][0])). Should(gomega.ContainSubstring("unauthorized")) }) @@ -1625,7 +1625,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeTrue()) - or, err := actions.UnmarshalOrderResult(result.Output) + or, err := actions.UnmarshalOrderResult(result.Outputs[0][0]) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(or.In).Should(gomega.Equal(uint64(2))) gomega.Ω(or.Out).Should(gomega.Equal(uint64(1))) From 3a36024abde8a7c25d101a2450f4ade1a7b54745 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 29 Apr 2024 16:01:55 -0400 Subject: [PATCH 39/78] increase maxActions for tokenvm to 2 --- examples/tokenvm/genesis/genesis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tokenvm/genesis/genesis.go b/examples/tokenvm/genesis/genesis.go index eb95a527bd..bc46bb05a1 100644 --- a/examples/tokenvm/genesis/genesis.go +++ b/examples/tokenvm/genesis/genesis.go @@ -93,7 +93,7 @@ func Default() *Genesis { StorageValueWriteUnits: 3, // Action Per Tx - MaxActionsPerTx: 1, + MaxActionsPerTx: 2, } } From c55ba329056d8a4e6e9414a214fa2141e5e13ffb Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 29 Apr 2024 16:16:50 -0400 Subject: [PATCH 40/78] add multiple transfers in 1 tx --- chain/transaction.go | 5 +- .../tests/integration/integration_test.go | 53 ++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 1dc772def9..e97c55fcb9 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -422,8 +422,9 @@ func (t *Transaction) Execute( refund := maxFee - feeRequired if refund > 0 { ts.DisableAllocation() - defer ts.EnableAllocation() - if err := s.Refund(ctx, t.Auth.Sponsor(), ts, refund); err != nil { + err = s.Refund(ctx, t.Auth.Sponsor(), ts, refund) + ts.EnableAllocation() + if err != nil { return handleRevert(err) } } diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index 004db64b14..b930186a14 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -85,7 +85,7 @@ func init() { flag.IntVar( &vms, "vms", - 3, + 4, "number of VMs to create", ) } @@ -101,6 +101,11 @@ var ( rsender2 codec.Address sender2 string + priv3 ed25519.PrivateKey + factory3 *auth.ED25519Factory + rsender3 codec.Address + sender3 string + asset1 []byte asset1Symbol []byte asset1Decimals uint8 @@ -161,6 +166,17 @@ var _ = ginkgo.BeforeSuite(func() { zap.String("pk", hex.EncodeToString(priv2[:])), ) + priv3, err = ed25519.GeneratePrivateKey() + gomega.Ω(err).Should(gomega.BeNil()) + factory3 = auth.NewED25519Factory(priv3) + rsender3 = auth.NewED25519Address(priv3.PublicKey()) + sender3 = codec.MustAddressBech32(tconsts.HRP, rsender3) + log.Debug( + "generated key", + zap.String("addr", sender3), + zap.String("pk", hex.EncodeToString(priv3[:])), + ) + asset1 = []byte("1") asset1Symbol = []byte("s1") asset1Decimals = uint8(1) @@ -1648,6 +1664,41 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(0)) }) + + ginkgo.It("transfer to multiple accounts in a single tx", func() { + parser, err := instances[3].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) + submit, _, _, err := instances[3].cli.GenerateTransaction( + context.Background(), + parser, + []chain.Action{ + &actions.Transfer{ + To: rsender2, + Value: 101, + }, + &actions.Transfer{ + To: rsender3, + Value: 50, + }, + }, + factory, + ) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) + time.Sleep(2 * time.Second) // for replay test + accept := expectBlk(instances[3]) + results := accept(false) + gomega.Ω(results).Should(gomega.HaveLen(1)) + gomega.Ω(results[0].Success).Should(gomega.BeTrue()) + + balance2, err := instances[3].tcli.Balance(context.Background(), sender2, codec.EmptyAddress) + gomega.Ω(err).To(gomega.BeNil()) + gomega.Ω(balance2).To(gomega.Equal(uint64(101))) + + balance3, err := instances[3].tcli.Balance(context.Background(), sender3, codec.EmptyAddress) + gomega.Ω(err).To(gomega.BeNil()) + gomega.Ω(balance3).To(gomega.Equal(uint64(50))) + }) }) func expectBlk(i instance) func(bool) []*chain.Result { From f66582130d9daaa0b204061cd8f8636511502129 Mon Sep 17 00:00:00 2001 From: William Law Date: Tue, 30 Apr 2024 13:05:55 -0400 Subject: [PATCH 41/78] add create and mint multiple assets --- examples/tokenvm/actions/mint_asset.go | 2 +- examples/tokenvm/genesis/genesis.go | 2 +- .../tests/integration/integration_test.go | 123 ++++++++++++++++++ 3 files changed, 125 insertions(+), 2 deletions(-) diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index 31af030279..a4c7ace767 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -23,7 +23,7 @@ type MintAsset struct { // To is the recipient of the [Value]. To codec.Address `json:"to"` - // Asset is the [TxID] that created the asset. + // Asset is the [ActionID] that created the asset. Asset codec.LID `json:"asset"` // Number of assets to mint to [To]. diff --git a/examples/tokenvm/genesis/genesis.go b/examples/tokenvm/genesis/genesis.go index bc46bb05a1..cc0c849d2e 100644 --- a/examples/tokenvm/genesis/genesis.go +++ b/examples/tokenvm/genesis/genesis.go @@ -93,7 +93,7 @@ func Default() *Genesis { StorageValueWriteUnits: 3, // Action Per Tx - MaxActionsPerTx: 2, + MaxActionsPerTx: 10, } } diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index b930186a14..dadf9fc371 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -118,6 +118,10 @@ var ( asset3Symbol []byte asset3Decimals uint8 asset3ID codec.LID + asset4 []byte + asset4Symbol []byte + asset4Decimals uint8 + asset4ID codec.LID // when used with embedded VMs genesisBytes []byte @@ -186,6 +190,9 @@ var _ = ginkgo.BeforeSuite(func() { asset3 = []byte("3") asset3Symbol = []byte("s3") asset3Decimals = uint8(3) + asset4 = []byte("4") + asset4Symbol = []byte("s4") + asset4Decimals = uint8(4) // create embedded VMs instances = make([]instance, vms) @@ -1699,6 +1706,122 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance3).To(gomega.Equal(uint64(50))) }) + + ginkgo.It("create and mint multiple of assets in a single tx", func() { + // Create asset + parser, err := instances[3].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) + submit, tx, _, err := instances[3].cli.GenerateTransaction( + context.Background(), + parser, + []chain.Action{ + &actions.CreateAsset{ + Symbol: asset1Symbol, + Decimals: asset1Decimals, + Metadata: asset1, + }, + &actions.CreateAsset{ + Symbol: asset2Symbol, + Decimals: asset2Decimals, + Metadata: asset2, + }, + &actions.CreateAsset{ + Symbol: asset3Symbol, + Decimals: asset3Decimals, + Metadata: asset3, + }, + &actions.CreateAsset{ + Symbol: asset4Symbol, + Decimals: asset4Decimals, + Metadata: asset4, + }, + }, + factory, + ) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) + accept := expectBlk(instances[3]) + results := accept(true) + gomega.Ω(results).Should(gomega.HaveLen(1)) + gomega.Ω(results[0].Success).Should(gomega.BeTrue()) + + asset1ID = codec.CreateLID(0, tx.ID()) + asset2ID = codec.CreateLID(1, tx.ID()) + asset3ID = codec.CreateLID(2, tx.ID()) + asset4ID = codec.CreateLID(3, tx.ID()) + + // Mint multiple + submit, _, _, err = instances[3].cli.GenerateTransaction( + context.Background(), + parser, + []chain.Action{ + &actions.MintAsset{ + To: rsender2, + Asset: asset1ID, + Value: 10, + }, + &actions.MintAsset{ + To: rsender2, + Asset: asset2ID, + Value: 10, + }, + &actions.MintAsset{ + To: rsender2, + Asset: asset3ID, + Value: 10, + }, + &actions.MintAsset{ + To: rsender2, + Asset: asset4ID, + Value: 10, + }, + &actions.MintAsset{ + To: rsender3, + Asset: asset1ID, + Value: 10, + }, + &actions.MintAsset{ + To: rsender3, + Asset: asset2ID, + Value: 10, + }, + &actions.MintAsset{ + To: rsender3, + Asset: asset3ID, + Value: 10, + }, + &actions.MintAsset{ + To: rsender3, + Asset: asset4ID, + Value: 10, + }, + }, + factory, + ) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) + accept = expectBlk(instances[3]) + results = accept(true) + gomega.Ω(results).Should(gomega.HaveLen(1)) + gomega.Ω(results[0].Success).Should(gomega.BeTrue()) + + // check sender2 assets + balance1, err := instances[3].tcli.Balance(context.TODO(), sender2, asset1ID) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(balance1).Should(gomega.Equal(uint64(10))) + + balance2, err := instances[3].tcli.Balance(context.TODO(), sender2, asset2ID) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(balance2).Should(gomega.Equal(uint64(10))) + + balance3, err := instances[3].tcli.Balance(context.TODO(), sender2, asset3ID) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(balance3).Should(gomega.Equal(uint64(10))) + + balance4, err := instances[3].tcli.Balance(context.TODO(), sender2, asset4ID) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(balance4).Should(gomega.Equal(uint64(10))) + }) }) func expectBlk(i instance) func(bool) []*chain.Result { From 23feaa230fc9f85ac87a09e7ae7291045eba9816 Mon Sep 17 00:00:00 2001 From: William Law Date: Tue, 30 Apr 2024 14:37:36 -0400 Subject: [PATCH 42/78] add multiple trades --- .../tests/integration/integration_test.go | 242 +++++++++++++++++- 1 file changed, 229 insertions(+), 13 deletions(-) diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index dadf9fc371..db85acb774 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -36,9 +36,9 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/fees" - "github.com/ava-labs/hypersdk/pubsub" + _ "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" - hutils "github.com/ava-labs/hypersdk/utils" + // hutils "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/vm" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" @@ -473,7 +473,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) }) - ginkgo.It("ensure multiple txs work ", func() { + /*ginkgo.It("ensure multiple txs work ", func() { ginkgo.By("transfer funds again", func() { parser, err := instances[1].tcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) @@ -1670,7 +1670,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { orders, err = instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(0)) - }) + })*/ ginkgo.It("transfer to multiple accounts in a single tx", func() { parser, err := instances[3].tcli.Parser(context.Background()) @@ -1681,11 +1681,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { []chain.Action{ &actions.Transfer{ To: rsender2, - Value: 101, + Value: 10000, }, &actions.Transfer{ To: rsender3, - Value: 50, + Value: 5000, }, }, factory, @@ -1694,17 +1694,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) time.Sleep(2 * time.Second) // for replay test accept := expectBlk(instances[3]) - results := accept(false) + results := accept(true) gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) balance2, err := instances[3].tcli.Balance(context.Background(), sender2, codec.EmptyAddress) gomega.Ω(err).To(gomega.BeNil()) - gomega.Ω(balance2).To(gomega.Equal(uint64(101))) + gomega.Ω(balance2).To(gomega.Equal(uint64(10000))) balance3, err := instances[3].tcli.Balance(context.Background(), sender3, codec.EmptyAddress) gomega.Ω(err).To(gomega.BeNil()) - gomega.Ω(balance3).To(gomega.Equal(uint64(50))) + gomega.Ω(balance3).To(gomega.Equal(uint64(5000))) }) ginkgo.It("create and mint multiple of assets in a single tx", func() { @@ -1776,22 +1776,22 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Value: 10, }, &actions.MintAsset{ - To: rsender3, + To: rsender, Asset: asset1ID, Value: 10, }, &actions.MintAsset{ - To: rsender3, + To: rsender, Asset: asset2ID, Value: 10, }, &actions.MintAsset{ - To: rsender3, + To: rsender, Asset: asset3ID, Value: 10, }, &actions.MintAsset{ - To: rsender3, + To: rsender, Asset: asset4ID, Value: 10, }, @@ -1822,6 +1822,222 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance4).Should(gomega.Equal(uint64(10))) }) + + ginkgo.It("create & fill order of multiple of assets in a single tx", func() { + // Create Orders + parser, err := instances[3].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) + submit, tx, _, err := instances[3].cli.GenerateTransaction( + context.Background(), + parser, + []chain.Action{ + &actions.CreateOrder{ + In: asset3ID, + InTick: 1, + Out: asset2ID, + OutTick: 2, + Supply: 2, + }, + &actions.CreateOrder{ + In: asset1ID, + InTick: 1, + Out: asset4ID, + OutTick: 2, + Supply: 2, + }, + }, + factory, + ) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) + accept := expectBlk(instances[3]) + results := accept(false) + gomega.Ω(results).Should(gomega.HaveLen(1)) + gomega.Ω(results[0].Success).Should(gomega.BeTrue()) + + balance, err := instances[3].tcli.Balance(context.TODO(), sender, asset2ID) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(balance).Should(gomega.Equal(uint64(8))) + + balance, err = instances[3].tcli.Balance(context.TODO(), sender, asset4ID) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(balance).Should(gomega.Equal(uint64(8))) + + // Get Orders + orders32, err := instances[3].tcli.Orders(context.TODO(), actions.PairID(asset3ID, asset2ID)) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(orders32).Should(gomega.HaveLen(1)) + + order32 := orders32[0] + gomega.Ω(order32.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) + gomega.Ω(order32.InTick).Should(gomega.Equal(uint64(1))) + gomega.Ω(order32.OutTick).Should(gomega.Equal(uint64(2))) + gomega.Ω(order32.Owner).Should(gomega.Equal(sender)) + gomega.Ω(order32.Remaining).Should(gomega.Equal(uint64(2))) + + orders14, err := instances[3].tcli.Orders(context.TODO(), actions.PairID(asset1ID, asset4ID)) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(orders14).Should(gomega.HaveLen(1)) + + order14 := orders14[0] + gomega.Ω(order14.ID).Should(gomega.Equal(codec.CreateLID(1, tx.ID()))) + gomega.Ω(order14.InTick).Should(gomega.Equal(uint64(1))) + gomega.Ω(order14.OutTick).Should(gomega.Equal(uint64(2))) + gomega.Ω(order14.Owner).Should(gomega.Equal(sender)) + gomega.Ω(order14.Remaining).Should(gomega.Equal(uint64(2))) + + // Fill Orders + submit, _, _, err = instances[3].cli.GenerateTransaction( + context.Background(), + parser, + []chain.Action{ + &actions.FillOrder{ + Order: order32.ID, + Owner: rsender, + In: asset3ID, + Out: asset2ID, + Value: 1, + }, + &actions.FillOrder{ + Order: order14.ID, + Owner: rsender, + In: asset1ID, + Out: asset4ID, + Value: 1, + }, + }, + factory2, + ) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) + accept = expectBlk(instances[3]) + results = accept(false) + gomega.Ω(results).Should(gomega.HaveLen(1)) + result := results[0] + gomega.Ω(result.Success).Should(gomega.BeTrue()) + + // Order for asset3 <> asset2 + or, err := actions.UnmarshalOrderResult(result.Outputs[0][0]) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(or.In).Should(gomega.Equal(uint64(1))) + gomega.Ω(or.Out).Should(gomega.Equal(uint64(2))) + gomega.Ω(or.Remaining).Should(gomega.Equal(uint64(0))) + + // Order for asset1 <> asset4 + or, err = actions.UnmarshalOrderResult(result.Outputs[1][0]) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(or.In).Should(gomega.Equal(uint64(1))) + gomega.Ω(or.Out).Should(gomega.Equal(uint64(2))) + gomega.Ω(or.Remaining).Should(gomega.Equal(uint64(0))) + }) + + ginkgo.It("fail to create & fill order of multiple of assets in a single tx", func() { + // Create Orders + parser, err := instances[3].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) + submit, tx, _, err := instances[3].cli.GenerateTransaction( + context.Background(), + parser, + []chain.Action{ + &actions.CreateOrder{ + In: asset3ID, + InTick: 1, + Out: asset2ID, + OutTick: 2, + Supply: 2, + }, + &actions.CreateOrder{ + In: asset1ID, + InTick: 1, + Out: asset4ID, + OutTick: 2, + Supply: 2, + }, + }, + factory, + ) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) + accept := expectBlk(instances[3]) + results := accept(false) + gomega.Ω(results).Should(gomega.HaveLen(1)) + gomega.Ω(results[0].Success).Should(gomega.BeTrue()) + + balance, err := instances[3].tcli.Balance(context.TODO(), sender, asset2ID) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(balance).Should(gomega.Equal(uint64(8))) + + balance, err = instances[3].tcli.Balance(context.TODO(), sender, asset4ID) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(balance).Should(gomega.Equal(uint64(8))) + + // Get Orders + orders32, err := instances[3].tcli.Orders(context.TODO(), actions.PairID(asset3ID, asset2ID)) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(orders32).Should(gomega.HaveLen(1)) + + order32 := orders32[0] + gomega.Ω(order32.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) + gomega.Ω(order32.InTick).Should(gomega.Equal(uint64(1))) + gomega.Ω(order32.OutTick).Should(gomega.Equal(uint64(2))) + gomega.Ω(order32.Owner).Should(gomega.Equal(sender)) + gomega.Ω(order32.Remaining).Should(gomega.Equal(uint64(2))) + + orders14, err := instances[3].tcli.Orders(context.TODO(), actions.PairID(asset1ID, asset4ID)) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(orders14).Should(gomega.HaveLen(1)) + + order14 := orders14[0] + gomega.Ω(order14.ID).Should(gomega.Equal(codec.CreateLID(1, tx.ID()))) + gomega.Ω(order14.InTick).Should(gomega.Equal(uint64(1))) + gomega.Ω(order14.OutTick).Should(gomega.Equal(uint64(2))) + gomega.Ω(order14.Owner).Should(gomega.Equal(sender)) + gomega.Ω(order14.Remaining).Should(gomega.Equal(uint64(2))) + + // Fill Orders + submit, _, _, err = instances[3].cli.GenerateTransaction( + context.Background(), + parser, + []chain.Action{ + &actions.FillOrder{ + Order: order32.ID, + Owner: rsender, + In: asset3ID, + Out: asset2ID, + Value: 1, + }, + &actions.FillOrder{ + Order: order14.ID, + Owner: rsender, + In: asset1ID, + Out: asset4ID, + Value: 1, + }, + }, + factory2, + ) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) + accept = expectBlk(instances[3]) + results = accept(false) + gomega.Ω(results).Should(gomega.HaveLen(1)) + result := results[0] + gomega.Ω(result.Success).Should(gomega.BeTrue()) + + // Order for asset3 <> asset2 + or, err := actions.UnmarshalOrderResult(result.Outputs[0][0]) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(or.In).Should(gomega.Equal(uint64(1))) + gomega.Ω(or.Out).Should(gomega.Equal(uint64(2))) + gomega.Ω(or.Remaining).Should(gomega.Equal(uint64(0))) + + // Order for asset1 <> asset4 + or, err = actions.UnmarshalOrderResult(result.Outputs[1][0]) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(or.In).Should(gomega.Equal(uint64(1))) + gomega.Ω(or.Out).Should(gomega.Equal(uint64(2))) + gomega.Ω(or.Remaining).Should(gomega.Equal(uint64(0))) + }) }) func expectBlk(i instance) func(bool) []*chain.Result { From 94b77a68e7b367433d188b97c68ad24f12166821 Mon Sep 17 00:00:00 2001 From: William Law Date: Tue, 30 Apr 2024 14:44:03 -0400 Subject: [PATCH 43/78] add failed fill order --- .../tests/integration/integration_test.go | 94 ++++++++----------- 1 file changed, 41 insertions(+), 53 deletions(-) diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index db85acb774..e0363a547b 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -36,9 +36,9 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/fees" - _ "github.com/ava-labs/hypersdk/pubsub" + "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" - // hutils "github.com/ava-labs/hypersdk/utils" + hutils "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/vm" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" @@ -473,7 +473,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) }) - /*ginkgo.It("ensure multiple txs work ", func() { + ginkgo.It("ensure multiple txs work ", func() { ginkgo.By("transfer funds again", func() { parser, err := instances[1].tcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) @@ -1670,7 +1670,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { orders, err = instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(0)) - })*/ + }) ginkgo.It("transfer to multiple accounts in a single tx", func() { parser, err := instances[3].tcli.Parser(context.Background()) @@ -1940,18 +1940,18 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { parser, []chain.Action{ &actions.CreateOrder{ - In: asset3ID, - InTick: 1, - Out: asset2ID, - OutTick: 2, - Supply: 2, + In: asset1ID, + InTick: 2, + Out: asset3ID, + OutTick: 4, + Supply: 4, }, &actions.CreateOrder{ - In: asset1ID, - InTick: 1, + In: asset2ID, + InTick: 2, Out: asset4ID, - OutTick: 2, - Supply: 2, + OutTick: 4, + Supply: 4, }, }, factory, @@ -1963,36 +1963,36 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance, err := instances[3].tcli.Balance(context.TODO(), sender, asset2ID) + balance, err := instances[3].tcli.Balance(context.TODO(), sender, asset3ID) gomega.Ω(err).Should(gomega.BeNil()) - gomega.Ω(balance).Should(gomega.Equal(uint64(8))) + gomega.Ω(balance).Should(gomega.Equal(uint64(7))) balance, err = instances[3].tcli.Balance(context.TODO(), sender, asset4ID) gomega.Ω(err).Should(gomega.BeNil()) - gomega.Ω(balance).Should(gomega.Equal(uint64(8))) + gomega.Ω(balance).Should(gomega.Equal(uint64(4))) // Get Orders - orders32, err := instances[3].tcli.Orders(context.TODO(), actions.PairID(asset3ID, asset2ID)) + orders13, err := instances[3].tcli.Orders(context.TODO(), actions.PairID(asset1ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) - gomega.Ω(orders32).Should(gomega.HaveLen(1)) + gomega.Ω(orders13).Should(gomega.HaveLen(1)) - order32 := orders32[0] - gomega.Ω(order32.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) - gomega.Ω(order32.InTick).Should(gomega.Equal(uint64(1))) - gomega.Ω(order32.OutTick).Should(gomega.Equal(uint64(2))) - gomega.Ω(order32.Owner).Should(gomega.Equal(sender)) - gomega.Ω(order32.Remaining).Should(gomega.Equal(uint64(2))) + order13 := orders13[0] + gomega.Ω(order13.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) + gomega.Ω(order13.InTick).Should(gomega.Equal(uint64(2))) + gomega.Ω(order13.OutTick).Should(gomega.Equal(uint64(4))) + gomega.Ω(order13.Owner).Should(gomega.Equal(sender)) + gomega.Ω(order13.Remaining).Should(gomega.Equal(uint64(4))) - orders14, err := instances[3].tcli.Orders(context.TODO(), actions.PairID(asset1ID, asset4ID)) + orders24, err := instances[3].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset4ID)) gomega.Ω(err).Should(gomega.BeNil()) - gomega.Ω(orders14).Should(gomega.HaveLen(1)) + gomega.Ω(orders24).Should(gomega.HaveLen(1)) - order14 := orders14[0] - gomega.Ω(order14.ID).Should(gomega.Equal(codec.CreateLID(1, tx.ID()))) - gomega.Ω(order14.InTick).Should(gomega.Equal(uint64(1))) - gomega.Ω(order14.OutTick).Should(gomega.Equal(uint64(2))) - gomega.Ω(order14.Owner).Should(gomega.Equal(sender)) - gomega.Ω(order14.Remaining).Should(gomega.Equal(uint64(2))) + order24 := orders24[0] + gomega.Ω(order24.ID).Should(gomega.Equal(codec.CreateLID(1, tx.ID()))) + gomega.Ω(order24.InTick).Should(gomega.Equal(uint64(2))) + gomega.Ω(order24.OutTick).Should(gomega.Equal(uint64(4))) + gomega.Ω(order24.Owner).Should(gomega.Equal(sender)) + gomega.Ω(order24.Remaining).Should(gomega.Equal(uint64(4))) // Fill Orders submit, _, _, err = instances[3].cli.GenerateTransaction( @@ -2000,18 +2000,18 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { parser, []chain.Action{ &actions.FillOrder{ - Order: order32.ID, + Order: order13.ID, Owner: rsender, - In: asset3ID, - Out: asset2ID, + In: asset1ID, + Out: asset3ID, Value: 1, }, &actions.FillOrder{ - Order: order14.ID, + Order: order24.ID, Owner: rsender, - In: asset1ID, + In: asset2ID, Out: asset4ID, - Value: 1, + Value: 3, // misaligned value }, }, factory2, @@ -2022,21 +2022,9 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results = accept(false) gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] - gomega.Ω(result.Success).Should(gomega.BeTrue()) - - // Order for asset3 <> asset2 - or, err := actions.UnmarshalOrderResult(result.Outputs[0][0]) - gomega.Ω(err).Should(gomega.BeNil()) - gomega.Ω(or.In).Should(gomega.Equal(uint64(1))) - gomega.Ω(or.Out).Should(gomega.Equal(uint64(2))) - gomega.Ω(or.Remaining).Should(gomega.Equal(uint64(0))) - - // Order for asset1 <> asset4 - or, err = actions.UnmarshalOrderResult(result.Outputs[1][0]) - gomega.Ω(err).Should(gomega.BeNil()) - gomega.Ω(or.In).Should(gomega.Equal(uint64(1))) - gomega.Ω(or.Out).Should(gomega.Equal(uint64(2))) - gomega.Ω(or.Remaining).Should(gomega.Equal(uint64(0))) + gomega.Ω(result.Success).Should(gomega.BeFalse()) + gomega.Ω(string(result.Outputs[0][0])). + Should(gomega.ContainSubstring("value is misaligned")) }) }) From da5f23571e71b9d8898227e8480eac9c0b602679 Mon Sep 17 00:00:00 2001 From: William Law Date: Tue, 30 Apr 2024 14:58:32 -0400 Subject: [PATCH 44/78] address lints --- chain/mock_action.go | 10 +- cli/spam.go | 10 +- .../morpheusvm/cmd/morpheus-cli/cmd/key.go | 2 +- .../cmd/morpheus-cli/cmd/resolutions.go | 46 +- examples/morpheusvm/tests/load/load_test.go | 6 +- .../tokenvm/cmd/token-cli/cmd/resolutions.go | 160 ++--- .../cmd/token-wallet/backend/backend.go | 597 +++++++++--------- .../cmd/token-wallet/backend/storage.go | 24 +- examples/tokenvm/tests/e2e/e2e_test.go | 19 +- examples/tokenvm/tests/load/load_test.go | 14 +- heap/heap_test.go | 4 +- 11 files changed, 457 insertions(+), 435 deletions(-) diff --git a/chain/mock_action.go b/chain/mock_action.go index 7ccb1a4604..0d28be63e5 100644 --- a/chain/mock_action.go +++ b/chain/mock_action.go @@ -46,12 +46,12 @@ func (m *MockAction) EXPECT() *MockActionMockRecorder { } // Execute mocks base method. -func (m *MockAction) Execute(arg0 context.Context, arg1 Rules, arg2 state.Mutable, arg3 int64, arg4 codec.Address, arg5 ids.ID) (bool, uint64, []byte, error) { +func (m *MockAction) Execute(arg0 context.Context, arg1 Rules, arg2 state.Mutable, arg3 int64, arg4 codec.Address, arg5 codec.LID) (bool, uint64, [][]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Execute", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(uint64) - ret2, _ := ret[2].([]byte) + ret2, _ := ret[2].([][]byte) ret3, _ := ret[3].(error) return ret0, ret1, ret2, ret3 } @@ -63,10 +63,10 @@ func (mr *MockActionMockRecorder) Execute(arg0, arg1, arg2, arg3, arg4, arg5 any } // GetActionID mocks base method. -func (m *MockAction) GetActionID(arg0 byte, arg1 ids.ID) codec.Address { +func (m *MockAction) GetActionID(arg0 byte, arg1 ids.ID) codec.LID { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetActionID", arg0, arg1) - ret0, _ := ret[0].(codec.Address) + ret0, _ := ret[0].(codec.LID) return ret0 } @@ -131,7 +131,7 @@ func (mr *MockActionMockRecorder) Size() *gomock.Call { } // StateKeys mocks base method. -func (m *MockAction) StateKeys(arg0, arg1 codec.Address) state.Keys { +func (m *MockAction) StateKeys(arg0 codec.Address, arg1 codec.LID) state.Keys { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "StateKeys", arg0, arg1) ret0, _ := ret[0].(state.Keys) diff --git a/cli/spam.go b/cli/spam.go index 41a5b3a1b9..be0e29a334 100644 --- a/cli/spam.go +++ b/cli/spam.go @@ -186,7 +186,7 @@ func (h *Handler) Spam( } if !result.Success { // Should never happen - return fmt.Errorf("%w: %s", ErrTxFailed, result.Output) + return fmt.Errorf("%w: %s", ErrTxFailed, result.Outputs) } } var recipientFunc func() (*PrivateKey, error) @@ -446,7 +446,7 @@ func (h *Handler) Spam( } if !result.Success { // Should never happen - return fmt.Errorf("%w: %s", ErrTxFailed, result.Output) + return fmt.Errorf("%w: %s", ErrTxFailed, result.Outputs) } } utils.Outf( @@ -492,7 +492,11 @@ func startIssuer(cctx context.Context, issuer *txIssuer) { if result.Success { confirmedTxs++ } else { - utils.Outf("{{orange}}on-chain tx failure:{{/}} %s %t\n", string(result.Output), result.Success) + for i := 0; i < len(result.Outputs); i++ { + for j := 0; j < len(result.Outputs[i]); j++ { + utils.Outf("{{orange}}on-chain tx failure:{{/}} %s %t\n", string(result.Outputs[i][j]), result.Success) + } + } } } else { // We can't error match here because we receive it over the wire. diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/key.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/key.go index 43f2b99b8d..c876fa707e 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/key.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/key.go @@ -217,7 +217,7 @@ var setKeyCmd = &cobra.Command{ }, } -func lookupKeyBalance(addr codec.Address, uri string, networkID uint32, chainID ids.ID, _ ids.ID) error { +func lookupKeyBalance(addr codec.Address, uri string, networkID uint32, chainID ids.ID, _ codec.LID) error { _, err := handler.GetBalance(context.TODO(), brpc.NewJSONRPCClient(uri, networkID, chainID), addr) return err } diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go index 215a419c99..dba02cee15 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go @@ -57,28 +57,32 @@ func sendAndWait( } func handleTx(tx *chain.Transaction, result *chain.Result) { - summaryStr := string(result.Output) - actor := tx.Auth.Actor() - status := "❌" - if result.Success { - status = "✅" - for _, action := range tx.Actions { - switch act := action.(type) { //nolint:gocritic - case *actions.Transfer: - summaryStr = fmt.Sprintf("%s %s -> %s", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) + for i := 0; i < len(result.Outputs); i++ { + for j := 0; j < len(result.Outputs[i]); j++ { + summaryStr := string(result.Outputs[i][j]) + actor := tx.Auth.Actor() + status := "❌" + if result.Success { + status = "✅" + for _, action := range tx.Actions { + switch act := action.(type) { //nolint:gocritic + case *actions.Transfer: + summaryStr = fmt.Sprintf("%s %s -> %s", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) + } + } } + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + status, + tx.ID(), + codec.MustAddressBech32(consts.HRP, actor), + reflect.TypeOf(tx.Actions), + summaryStr, + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, consts.Decimals), + consts.Symbol, + cli.ParseDimensions(result.Consumed), + ) } } - utils.Outf( - "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", - status, - tx.ID(), - codec.MustAddressBech32(consts.HRP, actor), - reflect.TypeOf(tx.Actions), - summaryStr, - float64(result.Fee)/float64(tx.Base.MaxFee)*100, - utils.FormatBalance(result.Fee, consts.Decimals), - consts.Symbol, - cli.ParseDimensions(result.Consumed), - ) } diff --git a/examples/morpheusvm/tests/load/load_test.go b/examples/morpheusvm/tests/load/load_test.go index 3fef5700cc..629d579b57 100644 --- a/examples/morpheusvm/tests/load/load_test.go +++ b/examples/morpheusvm/tests/load/load_test.go @@ -411,7 +411,11 @@ var _ = ginkgo.Describe("load tests vm", func() { for _, result := range blk.Results() { if !result.Success { unitPrices, _ := instances[0].cli.UnitPrices(context.Background(), false) - fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "output:", string(result.Output)) + for i := 0; i < len(result.Outputs); i++ { + for j := 0; j < len(result.Outputs[i]); j++ { + fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "output:", string(result.Outputs[i][j])) + } + } } gomega.Ω(result.Success).Should(gomega.BeTrue()) } diff --git a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go index b798ad6197..3c36404c34 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go +++ b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go @@ -57,88 +57,92 @@ func sendAndWait( } func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result) { - summaryStr := string(result.Output) - actor := tx.Auth.Actor() - status := "❌" - if result.Success { - status = "✅" - for i, act := range tx.Actions { - switch action := act.(type) { - case *actions.CreateAsset: - assetID := action.GetActionID(uint8(i), tx.ID()) - summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", assetID, action.Symbol, action.Decimals, action.Metadata) - case *actions.MintAsset: - _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - amountStr := utils.FormatBalance(action.Value, decimals) - summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - case *actions.BurnAsset: - summaryStr = fmt.Sprintf("%d %s -> 🔥", action.Value, action.Asset) + for i := 0; i < len(result.Outputs); i++ { + for j := 0; j < len(result.Outputs[i]); j++ { + summaryStr := string(result.Outputs[i][j]) + actor := tx.Auth.Actor() + status := "❌" + if result.Success { + status = "✅" + for i, act := range tx.Actions { + switch action := act.(type) { + case *actions.CreateAsset: + assetID := action.GetActionID(uint8(i), tx.ID()) + summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", assetID, action.Symbol, action.Decimals, action.Metadata) + case *actions.MintAsset: + _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + amountStr := utils.FormatBalance(action.Value, decimals) + summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + case *actions.BurnAsset: + summaryStr = fmt.Sprintf("%d %s -> 🔥", action.Value, action.Asset) - case *actions.Transfer: - _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - amountStr := utils.FormatBalance(action.Value, decimals) - summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - if len(action.Memo) > 0 { - summaryStr += fmt.Sprintf(" (memo: %s)", action.Memo) - } + case *actions.Transfer: + _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + amountStr := utils.FormatBalance(action.Value, decimals) + summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + if len(action.Memo) > 0 { + summaryStr += fmt.Sprintf(" (memo: %s)", action.Memo) + } - case *actions.CreateOrder: - _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - inTickStr := utils.FormatBalance(action.InTick, inDecimals) - _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - outTickStr := utils.FormatBalance(action.OutTick, outDecimals) - supplyStr := utils.FormatBalance(action.Supply, outDecimals) - summaryStr = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", inTickStr, inSymbol, outTickStr, outSymbol, supplyStr, outSymbol) - case *actions.FillOrder: - or, _ := actions.UnmarshalOrderResult(result.Output) - _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - inAmtStr := utils.FormatBalance(or.In, inDecimals) - _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return + case *actions.CreateOrder: + _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + inTickStr := utils.FormatBalance(action.InTick, inDecimals) + _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + outTickStr := utils.FormatBalance(action.OutTick, outDecimals) + supplyStr := utils.FormatBalance(action.Supply, outDecimals) + summaryStr = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", inTickStr, inSymbol, outTickStr, outSymbol, supplyStr, outSymbol) + case *actions.FillOrder: + or, _ := actions.UnmarshalOrderResult(result.Outputs[i][j]) + _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + inAmtStr := utils.FormatBalance(or.In, inDecimals) + _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + outAmtStr := utils.FormatBalance(or.Out, outDecimals) + remainingStr := utils.FormatBalance(or.Remaining, outDecimals) + summaryStr = fmt.Sprintf( + "%s %s -> %s %s (remaining: %s %s)", + inAmtStr, inSymbol, outAmtStr, outSymbol, remainingStr, outSymbol, + ) + case *actions.CloseOrder: + summaryStr = fmt.Sprintf("orderID: %s", action.Order) + } } - outAmtStr := utils.FormatBalance(or.Out, outDecimals) - remainingStr := utils.FormatBalance(or.Remaining, outDecimals) - summaryStr = fmt.Sprintf( - "%s %s -> %s %s (remaining: %s %s)", - inAmtStr, inSymbol, outAmtStr, outSymbol, remainingStr, outSymbol, - ) - case *actions.CloseOrder: - summaryStr = fmt.Sprintf("orderID: %s", action.Order) } + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + status, + tx.ID(), + codec.MustAddressBech32(tconsts.HRP, actor), + reflect.TypeOf(tx.Actions), + summaryStr, + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, tconsts.Decimals), + tconsts.Symbol, + cli.ParseDimensions(result.Consumed), + ) } } - utils.Outf( - "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", - status, - tx.ID(), - codec.MustAddressBech32(tconsts.HRP, actor), - reflect.TypeOf(tx.Actions), - summaryStr, - float64(result.Fee)/float64(tx.Base.MaxFee)*100, - utils.FormatBalance(result.Fee, tconsts.Decimals), - tconsts.Symbol, - cli.ParseDimensions(result.Consumed), - ) } diff --git a/examples/tokenvm/cmd/token-wallet/backend/backend.go b/examples/tokenvm/cmd/token-wallet/backend/backend.go index 61c1fa3608..f80260696a 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/backend.go +++ b/examples/tokenvm/cmd/token-wallet/backend/backend.go @@ -136,7 +136,7 @@ func (b *Backend) Start(ctx context.Context) error { if err := b.AddAddressBook("Me", b.addrStr); err != nil { return err } - if err := b.s.StoreAsset(ids.Empty, false); err != nil { + if err := b.s.StoreAsset(codec.EmptyAddress, false); err != nil { return err } @@ -220,257 +220,286 @@ func (b *Backend) collectBlocks() { } // We should exit action parsing as soon as possible - switch action := tx.Action.(type) { - case *actions.Transfer: - if actor != b.addr && action.To != b.addr { - continue - } - - _, symbol, decimals, _, _, owner, err := b.tcli.Asset(b.ctx, action.Asset, true) - if err != nil { - b.fatal(err) - return - } - txInfo := &TransactionInfo{ - ID: tx.ID().String(), - Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), - Success: result.Success, - Timestamp: blk.Tmstmp, - Actor: codec.MustAddressBech32(tconsts.HRP, actor), - Type: "Transfer", - Units: hcli.ParseDimensions(result.Consumed), - Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), - } - if result.Success { - txInfo.Summary = fmt.Sprintf("%s %s -> %s", hutils.FormatBalance(action.Value, decimals), symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - if len(action.Memo) > 0 { - txInfo.Summary += fmt.Sprintf(" (memo: %s)", action.Memo) + for i, act := range tx.Actions { + actionID := act.GetActionID(uint8(i), tx.ID()) + switch action := act.(type) { + case *actions.Transfer: + if actor != b.addr && action.To != b.addr { + continue } - } else { - txInfo.Summary = string(result.Output) - } - if action.To == b.addr { - if actor != b.addr && result.Success { - b.txAlertLock.Lock() - b.transactionAlerts = append(b.transactionAlerts, &Alert{"info", fmt.Sprintf("Received %s %s from Transfer", hutils.FormatBalance(action.Value, decimals), symbol)}) - b.txAlertLock.Unlock() - } - hasAsset, err := b.s.HasAsset(action.Asset) + + _, symbol, decimals, _, _, owner, err := b.tcli.Asset(b.ctx, action.Asset, true) if err != nil { b.fatal(err) return } - if !hasAsset { - if err := b.s.StoreAsset(action.Asset, b.addrStr == owner); err != nil { + txInfo := &TransactionInfo{ + ID: tx.ID().String(), + Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), + Success: result.Success, + Timestamp: blk.Tmstmp, + Actor: codec.MustAddressBech32(tconsts.HRP, actor), + Type: "Transfer", + Units: hcli.ParseDimensions(result.Consumed), + Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), + } + if result.Success { + txInfo.Summary = fmt.Sprintf("%s %s -> %s", hutils.FormatBalance(action.Value, decimals), symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + if len(action.Memo) > 0 { + txInfo.Summary += fmt.Sprintf(" (memo: %s)", action.Memo) + } + } else { + for j := 0; j < len(result.Outputs); j++ { + for k := 0; k < len(result.Outputs[j]); k++ { + txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) + } + } + } + if action.To == b.addr { + if actor != b.addr && result.Success { + b.txAlertLock.Lock() + b.transactionAlerts = append(b.transactionAlerts, &Alert{"info", fmt.Sprintf("Received %s %s from Transfer", hutils.FormatBalance(action.Value, decimals), symbol)}) + b.txAlertLock.Unlock() + } + hasAsset, err := b.s.HasAsset(action.Asset) + if err != nil { + b.fatal(err) + return + } + if !hasAsset { + if err := b.s.StoreAsset(action.Asset, b.addrStr == owner); err != nil { + b.fatal(err) + return + } + } + if err := b.s.StoreTransaction(txInfo); err != nil { + b.fatal(err) + return + } + } else if actor == b.addr { + if err := b.s.StoreTransaction(txInfo); err != nil { b.fatal(err) return } } - if err := b.s.StoreTransaction(txInfo); err != nil { + case *actions.CreateAsset: + if actor != b.addr { + continue + } + + if err := b.s.StoreAsset(actionID, true); err != nil { b.fatal(err) return } - } else if actor == b.addr { + txInfo := &TransactionInfo{ + ID: tx.ID().String(), + Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), + Success: result.Success, + Timestamp: blk.Tmstmp, + Actor: codec.MustAddressBech32(tconsts.HRP, actor), + Type: "CreateAsset", + Units: hcli.ParseDimensions(result.Consumed), + Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), + } + if result.Success { + txInfo.Summary = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", actionID, action.Symbol, action.Decimals, action.Metadata) + } else { + for j := 0; j < len(result.Outputs); j++ { + for k := 0; k < len(result.Outputs[j]); k++ { + txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) + } + } + } if err := b.s.StoreTransaction(txInfo); err != nil { b.fatal(err) return } - } - case *actions.CreateAsset: - if actor != b.addr { - continue - } - - if err := b.s.StoreAsset(tx.ID(), true); err != nil { - b.fatal(err) - return - } - txInfo := &TransactionInfo{ - ID: tx.ID().String(), - Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), - Success: result.Success, - Timestamp: blk.Tmstmp, - Actor: codec.MustAddressBech32(tconsts.HRP, actor), - Type: "CreateAsset", - Units: hcli.ParseDimensions(result.Consumed), - Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), - } - if result.Success { - txInfo.Summary = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", tx.ID(), action.Symbol, action.Decimals, action.Metadata) - } else { - txInfo.Summary = string(result.Output) - } - if err := b.s.StoreTransaction(txInfo); err != nil { - b.fatal(err) - return - } - case *actions.MintAsset: - if actor != b.addr && action.To != b.addr { - continue - } - - _, symbol, decimals, _, _, owner, err := b.tcli.Asset(b.ctx, action.Asset, true) - if err != nil { - b.fatal(err) - return - } - txInfo := &TransactionInfo{ - ID: tx.ID().String(), - Timestamp: blk.Tmstmp, - Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), - Success: result.Success, - Actor: codec.MustAddressBech32(tconsts.HRP, actor), - Type: "Mint", - Units: hcli.ParseDimensions(result.Consumed), - Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), - } - if result.Success { - txInfo.Summary = fmt.Sprintf("%s %s -> %s", hutils.FormatBalance(action.Value, decimals), symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - } else { - txInfo.Summary = string(result.Output) - } - if action.To == b.addr { - if actor != b.addr && result.Success { - b.txAlertLock.Lock() - b.transactionAlerts = append(b.transactionAlerts, &Alert{"info", fmt.Sprintf("Received %s %s from Mint", hutils.FormatBalance(action.Value, decimals), symbol)}) - b.txAlertLock.Unlock() + case *actions.MintAsset: + if actor != b.addr && action.To != b.addr { + continue } - hasAsset, err := b.s.HasAsset(action.Asset) + + _, symbol, decimals, _, _, owner, err := b.tcli.Asset(b.ctx, action.Asset, true) if err != nil { b.fatal(err) return } - if !hasAsset { - if err := b.s.StoreAsset(action.Asset, b.addrStr == owner); err != nil { + txInfo := &TransactionInfo{ + ID: tx.ID().String(), + Timestamp: blk.Tmstmp, + Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), + Success: result.Success, + Actor: codec.MustAddressBech32(tconsts.HRP, actor), + Type: "Mint", + Units: hcli.ParseDimensions(result.Consumed), + Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), + } + if result.Success { + txInfo.Summary = fmt.Sprintf("%s %s -> %s", hutils.FormatBalance(action.Value, decimals), symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + } else { + for j := 0; j < len(result.Outputs); j++ { + for k := 0; k < len(result.Outputs[j]); k++ { + txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) + } + } + } + if action.To == b.addr { + if actor != b.addr && result.Success { + b.txAlertLock.Lock() + b.transactionAlerts = append(b.transactionAlerts, &Alert{"info", fmt.Sprintf("Received %s %s from Mint", hutils.FormatBalance(action.Value, decimals), symbol)}) + b.txAlertLock.Unlock() + } + hasAsset, err := b.s.HasAsset(action.Asset) + if err != nil { + b.fatal(err) + return + } + if !hasAsset { + if err := b.s.StoreAsset(action.Asset, b.addrStr == owner); err != nil { + b.fatal(err) + return + } + } + if err := b.s.StoreTransaction(txInfo); err != nil { + b.fatal(err) + return + } + } else if actor == b.addr { + if err := b.s.StoreTransaction(txInfo); err != nil { b.fatal(err) return } } - if err := b.s.StoreTransaction(txInfo); err != nil { + case *actions.CreateOrder: + if actor != b.addr { + continue + } + + _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, action.In, true) + if err != nil { + b.fatal(err) + return + } + _, outSymbol, outDecimals, _, _, _, err := b.tcli.Asset(b.ctx, action.Out, true) + if err != nil { b.fatal(err) return } - } else if actor == b.addr { + txInfo := &TransactionInfo{ + ID: tx.ID().String(), + Timestamp: blk.Tmstmp, + Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), + Success: result.Success, + Actor: codec.MustAddressBech32(tconsts.HRP, actor), + Type: "CreateOrder", + Units: hcli.ParseDimensions(result.Consumed), + Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), + } + if result.Success { + txInfo.Summary = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", + hutils.FormatBalance(action.InTick, inDecimals), + inSymbol, + hutils.FormatBalance(action.OutTick, outDecimals), + outSymbol, + hutils.FormatBalance(action.Supply, outDecimals), + outSymbol, + ) + } else { + for j := 0; j < len(result.Outputs); j++ { + for k := 0; k < len(result.Outputs[j]); k++ { + txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) + } + } + } if err := b.s.StoreTransaction(txInfo); err != nil { b.fatal(err) return } - } - case *actions.CreateOrder: - if actor != b.addr { - continue - } + case *actions.FillOrder: + if actor != b.addr && action.Owner != b.addr { + continue + } - _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, action.In, true) - if err != nil { - b.fatal(err) - return - } - _, outSymbol, outDecimals, _, _, _, err := b.tcli.Asset(b.ctx, action.Out, true) - if err != nil { - b.fatal(err) - return - } - txInfo := &TransactionInfo{ - ID: tx.ID().String(), - Timestamp: blk.Tmstmp, - Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), - Success: result.Success, - Actor: codec.MustAddressBech32(tconsts.HRP, actor), - Type: "CreateOrder", - Units: hcli.ParseDimensions(result.Consumed), - Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), - } - if result.Success { - txInfo.Summary = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", - hutils.FormatBalance(action.InTick, inDecimals), - inSymbol, - hutils.FormatBalance(action.OutTick, outDecimals), - outSymbol, - hutils.FormatBalance(action.Supply, outDecimals), - outSymbol, - ) - } else { - txInfo.Summary = string(result.Output) - } - if err := b.s.StoreTransaction(txInfo); err != nil { - b.fatal(err) - return - } - case *actions.FillOrder: - if actor != b.addr && action.Owner != b.addr { - continue - } + _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, action.In, true) + if err != nil { + b.fatal(err) + return + } + _, outSymbol, outDecimals, _, _, _, err := b.tcli.Asset(b.ctx, action.Out, true) + if err != nil { + b.fatal(err) + return + } + txInfo := &TransactionInfo{ + ID: tx.ID().String(), + Timestamp: blk.Tmstmp, + Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), + Success: result.Success, + Actor: codec.MustAddressBech32(tconsts.HRP, actor), + Type: "FillOrder", + Units: hcli.ParseDimensions(result.Consumed), + Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), + } + if result.Success { + for j := 0; j < len(result.Outputs[i]); j++ { + or, _ := actions.UnmarshalOrderResult(result.Outputs[i][j]) + txInfo.Summary = fmt.Sprintf("%s %s -> %s %s (remaining: %s %s)", + hutils.FormatBalance(or.In, inDecimals), + inSymbol, + hutils.FormatBalance(or.Out, outDecimals), + outSymbol, + hutils.FormatBalance(or.Remaining, outDecimals), + outSymbol, + ) + + if action.Owner == b.addr && actor != b.addr { + b.txAlertLock.Lock() + b.transactionAlerts = append(b.transactionAlerts, &Alert{"info", fmt.Sprintf("Received %s %s from FillOrder", hutils.FormatBalance(or.In, inDecimals), inSymbol)}) + b.txAlertLock.Unlock() + } + } + } else { + for j := 0; j < len(result.Outputs); j++ { + for k := 0; k < len(result.Outputs[j]); k++ { + txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) + } + } + } + if actor == b.addr { + if err := b.s.StoreTransaction(txInfo); err != nil { + b.fatal(err) + return + } + } + case *actions.CloseOrder: + if actor != b.addr { + continue + } - _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, action.In, true) - if err != nil { - b.fatal(err) - return - } - _, outSymbol, outDecimals, _, _, _, err := b.tcli.Asset(b.ctx, action.Out, true) - if err != nil { - b.fatal(err) - return - } - txInfo := &TransactionInfo{ - ID: tx.ID().String(), - Timestamp: blk.Tmstmp, - Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), - Success: result.Success, - Actor: codec.MustAddressBech32(tconsts.HRP, actor), - Type: "FillOrder", - Units: hcli.ParseDimensions(result.Consumed), - Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), - } - if result.Success { - or, _ := actions.UnmarshalOrderResult(result.Output) - txInfo.Summary = fmt.Sprintf("%s %s -> %s %s (remaining: %s %s)", - hutils.FormatBalance(or.In, inDecimals), - inSymbol, - hutils.FormatBalance(or.Out, outDecimals), - outSymbol, - hutils.FormatBalance(or.Remaining, outDecimals), - outSymbol, - ) - - if action.Owner == b.addr && actor != b.addr { - b.txAlertLock.Lock() - b.transactionAlerts = append(b.transactionAlerts, &Alert{"info", fmt.Sprintf("Received %s %s from FillOrder", hutils.FormatBalance(or.In, inDecimals), inSymbol)}) - b.txAlertLock.Unlock() + txInfo := &TransactionInfo{ + ID: tx.ID().String(), + Timestamp: blk.Tmstmp, + Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), + Success: result.Success, + Actor: codec.MustAddressBech32(tconsts.HRP, actor), + Type: "CloseOrder", + Units: hcli.ParseDimensions(result.Consumed), + Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), + } + if result.Success { + txInfo.Summary = fmt.Sprintf("OrderID: %s", action.Order) + } else { + for j := 0; j < len(result.Outputs); j++ { + for k := 0; k < len(result.Outputs[j]); k++ { + txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) + } + } } - } else { - txInfo.Summary = string(result.Output) - } - if actor == b.addr { if err := b.s.StoreTransaction(txInfo); err != nil { b.fatal(err) return } } - case *actions.CloseOrder: - if actor != b.addr { - continue - } - - txInfo := &TransactionInfo{ - ID: tx.ID().String(), - Timestamp: blk.Tmstmp, - Size: fmt.Sprintf("%.2fKB", float64(tx.Size())/units.KiB), - Success: result.Success, - Actor: codec.MustAddressBech32(tconsts.HRP, actor), - Type: "CloseOrder", - Units: hcli.ParseDimensions(result.Consumed), - Fee: fmt.Sprintf("%s %s", hutils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol), - } - if result.Success { - txInfo.Summary = fmt.Sprintf("OrderID: %s", action.Order) - } else { - txInfo.Summary = string(result.Output) - } - if err := b.s.StoreTransaction(txInfo); err != nil { - b.fatal(err) - return - } } } now := time.Now() @@ -613,9 +642,9 @@ func (b *Backend) GetMyAssets() []*AssetInfo { b.fatal(err) return nil } - strAsset := asset.String() + strAsset := codec.LIDToString(tconsts.HRP, asset) assets = append(assets, &AssetInfo{ - ID: asset.String(), + ID: codec.LIDToString(tconsts.HRP, asset), Symbol: string(symbol), Decimals: int(decimals), Metadata: string(metadata), @@ -629,7 +658,7 @@ func (b *Backend) GetMyAssets() []*AssetInfo { func (b *Backend) CreateAsset(symbol string, decimals string, metadata string) error { // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) if err != nil { return err } @@ -639,11 +668,11 @@ func (b *Backend) CreateAsset(symbol string, decimals string, metadata string) e if err != nil { return err } - _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, &actions.CreateAsset{ + _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, []chain.Action{&actions.CreateAsset{ Symbol: []byte(symbol), Decimals: uint8(udecimals), Metadata: []byte(metadata), - }, b.factory) + }}, b.factory) if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } @@ -663,17 +692,14 @@ func (b *Backend) CreateAsset(symbol string, decimals string, metadata string) e return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Output) + return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) } return nil } func (b *Backend) MintAsset(asset string, address string, amount string) error { // Input validation - assetID, err := ids.FromString(asset) - if err != nil { - return err - } + assetID := codec.LIDFromString(asset) _, _, decimals, _, _, _, err := b.tcli.Asset(b.ctx, assetID, true) if err != nil { return err @@ -688,17 +714,17 @@ func (b *Backend) MintAsset(asset string, address string, amount string) error { } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) if err != nil { return err } // Generate transaction - _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, &actions.MintAsset{ + _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, []chain.Action{&actions.MintAsset{ To: to, Asset: assetID, Value: value, - }, b.factory) + }}, b.factory) if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } @@ -718,17 +744,14 @@ func (b *Backend) MintAsset(asset string, address string, amount string) error { return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Output) + return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) } return nil } func (b *Backend) Transfer(asset string, address string, amount string, memo string) error { // Input validation - assetID, err := ids.FromString(asset) - if err != nil { - return err - } + assetID := codec.LIDFromString(asset) _, symbol, decimals, _, _, _, err := b.tcli.Asset(b.ctx, assetID, true) if err != nil { return err @@ -752,22 +775,22 @@ func (b *Backend) Transfer(asset string, address string, amount string, memo str } // Ensure have sufficient balance for fees - bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) if err != nil { return err } // Generate transaction - _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, &actions.Transfer{ + _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, []chain.Action{&actions.Transfer{ To: to, Asset: assetID, Value: value, Memo: []byte(memo), - }, b.factory) + }}, b.factory) if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } - if assetID != ids.Empty { + if assetID != codec.EmptyAddress { if maxFee > bal { return fmt.Errorf("insufficient balance (have: %s %s, want: %s %s)", hutils.FormatBalance(bal, tconsts.Decimals), tconsts.Symbol, hutils.FormatBalance(maxFee, tconsts.Decimals), tconsts.Symbol) } @@ -789,7 +812,7 @@ func (b *Backend) Transfer(asset string, address string, amount string, memo str return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Output) + return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) } return nil } @@ -813,11 +836,11 @@ func (b *Backend) GetBalance() ([]*BalanceInfo, error) { if err != nil { return nil, err } - strAsset := asset.String() - if asset == ids.Empty { - balances = append(balances, &BalanceInfo{ID: asset.String(), Str: fmt.Sprintf("%s %s", hutils.FormatBalance(bal, decimals), symbol), Bal: fmt.Sprintf("%s (Balance: %s)", symbol, hutils.FormatBalance(bal, decimals)), Has: bal > 0}) + strAsset := codec.LIDToString(tconsts.HRP, asset) + if asset == codec.EmptyAddress { + balances = append(balances, &BalanceInfo{ID: strAsset, Str: fmt.Sprintf("%s %s", hutils.FormatBalance(bal, decimals), symbol), Bal: fmt.Sprintf("%s (Balance: %s)", symbol, hutils.FormatBalance(bal, decimals)), Has: bal > 0}) } else { - balances = append(balances, &BalanceInfo{ID: asset.String(), Str: fmt.Sprintf("%s %s [%s]", hutils.FormatBalance(bal, decimals), symbol, asset), Bal: fmt.Sprintf("%s [%s..%s] (Balance: %s)", symbol, strAsset[:3], strAsset[len(strAsset)-3:], hutils.FormatBalance(bal, decimals)), Has: bal > 0}) + balances = append(balances, &BalanceInfo{ID: strAsset, Str: fmt.Sprintf("%s %s [%s]", hutils.FormatBalance(bal, decimals), symbol, asset), Bal: fmt.Sprintf("%s [%s..%s] (Balance: %s)", symbol, strAsset[:3], strAsset[len(strAsset)-3:], hutils.FormatBalance(bal, decimals)), Has: bal > 0}) } } return balances, nil @@ -942,9 +965,9 @@ func (b *Backend) GetAllAssets() []*AssetInfo { b.fatal(err) return nil } - strAsset := asset.String() + strAsset := codec.LIDToString(tconsts.HRP, asset) assets = append(assets, &AssetInfo{ - ID: asset.String(), + ID: codec.LIDToString(tconsts.HRP, asset), Symbol: string(symbol), Decimals: int(decimals), Metadata: string(metadata), @@ -957,10 +980,7 @@ func (b *Backend) GetAllAssets() []*AssetInfo { } func (b *Backend) AddAsset(asset string) error { - assetID, err := ids.FromString(asset) - if err != nil { - return err - } + assetID := codec.LIDFromString(asset) hasAsset, err := b.s.HasAsset(assetID) if err != nil { return err @@ -1003,10 +1023,10 @@ func (b *Backend) GetMyOrders() ([]*Order, error) { return nil, err } orders = append(orders, &Order{ - ID: orderID.String(), - InID: inID.String(), + ID: codec.LIDToString(tconsts.HRP, orderID), + InID: codec.LIDToString(tconsts.HRP, inID), InSymbol: string(inSymbol), - OutID: outID.String(), + OutID: codec.LIDToString(tconsts.HRP, outID), OutSymbol: string(outSymbol), Price: fmt.Sprintf("%s %s / %s %s", hutils.FormatBalance(order.InTick, inDecimals), inSymbol, hutils.FormatBalance(order.OutTick, outDecimals), outSymbol), InTick: fmt.Sprintf("%s %s", hutils.FormatBalance(order.InTick, inDecimals), inSymbol), @@ -1031,7 +1051,7 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { } assetIDs := strings.Split(pair, "-") in := assetIDs[0] - inID, err := ids.FromString(in) + inID, err := codec.LIDFromString(in) if err != nil { return nil, err } @@ -1040,7 +1060,7 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { return nil, err } out := assetIDs[1] - outID, err := ids.FromString(out) + outID, err := codec.LIDFromString(out) if err != nil { return nil, err } @@ -1053,7 +1073,7 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { for i := 0; i < len(rawOrders); i++ { order := rawOrders[i] orders[i] = &Order{ - ID: order.ID.String(), + ID: codec.LIDToString(tconsts.HRP, order.ID), InID: in, InSymbol: string(inSymbol), OutID: out, @@ -1072,14 +1092,8 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { } func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, outTick string, supply string) error { - inID, err := ids.FromString(assetIn) - if err != nil { - return err - } - outID, err := ids.FromString(assetOut) - if err != nil { - return err - } + inID := codec.LIDFromString(assetIn) + outID := codec.LIDFromString(assetOut) _, _, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, inID, true) if err != nil { return err @@ -1090,7 +1104,7 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) if err != nil { return err } @@ -1112,17 +1126,17 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou } // Generate transaction - _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, &actions.CreateOrder{ + _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, []chain.Action{&actions.CreateOrder{ In: inID, InTick: iTick, Out: outID, OutTick: oTick, Supply: oSupply, - }, b.factory) + }}, b.factory) if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } - if inID == ids.Empty { + if inID == codec.EmptyAddress { if maxFee+oSupply > bal { return fmt.Errorf("insufficient balance (have: %s %s, want: %s %s)", hutils.FormatBalance(bal, tconsts.Decimals), tconsts.Symbol, hutils.FormatBalance(maxFee+oSupply, tconsts.Decimals), tconsts.Symbol) } @@ -1147,37 +1161,28 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Output) + return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) } // We rely on order checking to clear backlog - return b.s.StoreOrder(tx.ID()) + return b.s.StoreOrder(codec.CreateLID(0, tx.ID())) } func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, inTick string, assetOut string, amount string) error { - oID, err := ids.FromString(orderID) - if err != nil { - return err - } + oID := codec.LIDFromString(orderID) owner, err := codec.ParseAddressBech32(tconsts.HRP, orderOwner) if err != nil { return err } - inID, err := ids.FromString(assetIn) - if err != nil { - return err - } - outID, err := ids.FromString(assetOut) - if err != nil { - return err - } + inID := codec.LIDFromString(assetIn) + outID := codec.LIDFromString(assetOut) _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, inID, true) if err != nil { return err } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) if err != nil { return err } @@ -1198,17 +1203,17 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i } // Generate transaction - _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, &actions.FillOrder{ + _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, []chain.Action{&actions.FillOrder{ Order: oID, Owner: owner, In: inID, Out: outID, Value: inAmount, - }, b.factory) + }}, b.factory) if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } - if inID == ids.Empty { + if inID == codec.EmptyAddress { if maxFee+inAmount > bal { return fmt.Errorf("insufficient balance (have: %s %s, want: %s %s)", hutils.FormatBalance(bal, tconsts.Decimals), tconsts.Symbol, hutils.FormatBalance(maxFee+inAmount, tconsts.Decimals), tconsts.Symbol) } @@ -1233,32 +1238,26 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Output) + return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) } return nil } func (b *Backend) CloseOrder(orderID string, assetOut string) error { - oID, err := ids.FromString(orderID) - if err != nil { - return err - } - outID, err := ids.FromString(assetOut) - if err != nil { - return err - } + oID := codec.LIDFromString(orderID) + outID := codec.LIDFromString(assetOut) // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) if err != nil { return err } // Generate transaction - _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, &actions.CloseOrder{ + _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, []chain.Action{&actions.CloseOrder{ Order: oID, Out: outID, - }, b.factory) + }}, b.factory) if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } @@ -1278,7 +1277,7 @@ func (b *Backend) CloseOrder(orderID string, assetOut string) error { return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Output) + return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) } return nil } @@ -1389,18 +1388,18 @@ func (b *Backend) Message(message string, url string) error { } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) if err != nil { return err } // Generate transaction - _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, &actions.Transfer{ + _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, []chain.Action{&actions.Transfer{ To: recipientAddr, - Asset: ids.Empty, + Asset: codec.EmptyAddress, Value: fee, Memo: data, - }, b.factory) + }}, b.factory) if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } @@ -1420,7 +1419,7 @@ func (b *Backend) Message(message string, url string) error { return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Output) + return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) } return nil } diff --git a/examples/tokenvm/cmd/token-wallet/backend/storage.go b/examples/tokenvm/cmd/token-wallet/backend/storage.go index 9712584369..2dd85cdaaf 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/storage.go +++ b/examples/tokenvm/cmd/token-wallet/backend/storage.go @@ -63,8 +63,8 @@ func (s *Storage) GetKey() (ed25519.PrivateKey, error) { return ed25519.PrivateKey(v), nil } -func (s *Storage) StoreAsset(assetID ids.ID, owned bool) error { - k := make([]byte, 1+ids.IDLen) +func (s *Storage) StoreAsset(assetID codec.LID, owned bool) error { + k := make([]byte, 1+codec.AddressLen) k[0] = assetsPrefix copy(k[1:], assetID[:]) v := []byte{0x0} @@ -74,21 +74,21 @@ func (s *Storage) StoreAsset(assetID ids.ID, owned bool) error { return s.db.Put(k, v) } -func (s *Storage) HasAsset(assetID ids.ID) (bool, error) { - k := make([]byte, 1+ids.IDLen) +func (s *Storage) HasAsset(assetID codec.LID) (bool, error) { + k := make([]byte, 1+codec.AddressLen) k[0] = assetsPrefix copy(k[1:], assetID[:]) return s.db.Has(k) } -func (s *Storage) GetAssets() ([]ids.ID, []bool, error) { +func (s *Storage) GetAssets() ([]codec.LID, []bool, error) { iter := s.db.NewIteratorWithPrefix([]byte{assetsPrefix}) defer iter.Release() - assets := []ids.ID{} + assets := []codec.LID{} owned := []bool{} for iter.Next() { - assets = append(assets, ids.ID(iter.Key()[1:])) + assets = append(assets, codec.LID(iter.Key()[1:])) owned = append(owned, iter.Value()[0] == 0x1) } return assets, owned, iter.Error() @@ -179,24 +179,24 @@ func (s *Storage) GetSolutions() ([]*FaucetSearchInfo, error) { return fs, iter.Error() } -func (s *Storage) StoreOrder(orderID ids.ID) error { +func (s *Storage) StoreOrder(orderID codec.LID) error { inverseTime := consts.MaxUint64 - uint64(time.Now().UnixMilli()) - k := make([]byte, 1+consts.Uint64Len+ids.IDLen) + k := make([]byte, 1+consts.Uint64Len+codec.AddressLen) k[0] = orderPrefix binary.BigEndian.PutUint64(k[1:], inverseTime) copy(k[1+consts.Uint64Len:], orderID[:]) return s.db.Put(k, nil) } -func (s *Storage) GetOrders() ([]ids.ID, [][]byte, error) { +func (s *Storage) GetOrders() ([]codec.LID, [][]byte, error) { iter := s.db.NewIteratorWithPrefix([]byte{orderPrefix}) defer iter.Release() - orders := []ids.ID{} + orders := []codec.LID{} keys := [][]byte{} for iter.Next() { k := iter.Key() - orders = append(orders, ids.ID(k[1+consts.Uint64Len:])) + orders = append(orders, codec.LID(k[1+consts.Uint64Len:])) keys = append(keys, k) } return orders, keys, iter.Error() diff --git a/examples/tokenvm/tests/e2e/e2e_test.go b/examples/tokenvm/tests/e2e/e2e_test.go index 60532dea6d..236e020ccc 100644 --- a/examples/tokenvm/tests/e2e/e2e_test.go +++ b/examples/tokenvm/tests/e2e/e2e_test.go @@ -16,6 +16,7 @@ import ( "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" @@ -511,7 +512,7 @@ var _ = ginkgo.Describe("[Test]", func() { } ginkgo.It("transfer in a single node (raw)", func() { - nativeBalance, err := instancesA[0].tcli.Balance(context.TODO(), sender, ids.Empty) + nativeBalance, err := instancesA[0].tcli.Balance(context.TODO(), sender, codec.EmptyAddress) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(nativeBalance).Should(gomega.Equal(startAmount)) @@ -526,10 +527,10 @@ var _ = ginkgo.Describe("[Test]", func() { submit, tx, maxFee, err := instancesA[0].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: aother, Value: sendAmount, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -546,7 +547,7 @@ var _ = ginkgo.Describe("[Test]", func() { hutils.Outf("{{yellow}}found transaction{{/}}\n") // Check sender balance - balance, err := instancesA[0].tcli.Balance(context.Background(), sender, ids.Empty) + balance, err := instancesA[0].tcli.Balance(context.Background(), sender, codec.EmptyAddress) gomega.Ω(err).Should(gomega.BeNil()) hutils.Outf( "{{yellow}}start=%d fee=%d send=%d balance=%d{{/}}\n", @@ -574,7 +575,7 @@ var _ = ginkgo.Describe("[Test]", func() { } // Check balance of recipient - balance, err := inst.tcli.Balance(context.Background(), codec.MustAddressBech32(consts.HRP, aother), ids.Empty) + balance, err := inst.tcli.Balance(context.Background(), codec.MustAddressBech32(consts.HRP, aother), codec.EmptyAddress) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(sendAmount)) } @@ -791,10 +792,10 @@ func generateBlocks( submit, _, _, err := instances[cumulativeTxs%len(instances)].cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: auth.NewED25519Address(other.PublicKey()), Value: 1, - }, + }}, factory, ) if failOnError { @@ -858,10 +859,10 @@ func acceptTransaction(cli *rpc.JSONRPCClient, tcli *trpc.JSONRPCClient) { submit, tx, maxFee, err := cli.GenerateTransaction( context.Background(), parser, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: auth.NewED25519Address(other.PublicKey()), Value: sendAmount, - }, + }}, factory, ) gomega.Ω(err).Should(gomega.BeNil()) diff --git a/examples/tokenvm/tests/load/load_test.go b/examples/tokenvm/tests/load/load_test.go index c4278ef280..2d300f1696 100644 --- a/examples/tokenvm/tests/load/load_test.go +++ b/examples/tokenvm/tests/load/load_test.go @@ -303,7 +303,7 @@ var _ = ginkgo.BeforeSuite(func() { gomega.Ω(err).Should(gomega.BeNil()) for _, alloc := range g.CustomAllocation { - bal, err := cli.Balance(context.Background(), alloc.Address, ids.Empty) + bal, err := cli.Balance(context.Background(), alloc.Address, codec.EmptyAddress) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(bal).Should(gomega.Equal(alloc.Balance)) } @@ -403,7 +403,13 @@ var _ = ginkgo.Describe("load tests vm", func() { for _, result := range blk.Results() { if !result.Success { unitPrices, _ := instances[0].cli.UnitPrices(context.Background(), false) - fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "output:", string(result.Output)) + var resultOutputs string + for i := 0; i < len(result.Outputs); i++ { + for j := 0; j < len(result.Outputs[i]); j++ { + resultOutputs += fmt.Sprintf(" %s", result.Outputs[i][j]) + } + } + fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "output:", resultOutputs) } gomega.Ω(result.Success).Should(gomega.BeTrue()) } @@ -533,10 +539,10 @@ func issueSimpleTx( ChainID: i.chainID, MaxFee: maxFee, }, - &actions.Transfer{ + []chain.Action{&actions.Transfer{ To: to, Value: amount, - }, + }}, ) tx, err := tx.Sign(factory, consts.ActionRegistry, consts.AuthRegistry) gomega.Ω(err).To(gomega.BeNil()) diff --git a/heap/heap_test.go b/heap/heap_test.go index 59380fd860..f18f08f20a 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -6,10 +6,10 @@ package heap import ( "testing" - "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/avalanchego/ids" "github.com/stretchr/testify/require" + + "github.com/ava-labs/hypersdk/codec" ) type testItem[T comparable] struct { From 38a470289d8ec1cdd4bcbcc0e8a362bfd15f46a5 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 1 May 2024 10:21:44 -0400 Subject: [PATCH 45/78] add hrp param to LIDFromString --- cli/prompt.go | 8 ++--- codec/address.go | 4 +-- .../cmd/token-wallet/backend/backend.go | 30 ++++++++----------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/cli/prompt.go b/cli/prompt.go index e3cbbfcb01..106bbf1fe7 100644 --- a/cli/prompt.go +++ b/cli/prompt.go @@ -71,7 +71,7 @@ func (h *Handler) PromptAsset(label string, allowNative bool) (codec.LID, error) if allowNative && input == symbol { return nil } - _ = codec.LIDFromString(input) + _ = codec.LIDFromString(label, input) return nil }, } @@ -82,7 +82,7 @@ func (h *Handler) PromptAsset(label string, allowNative bool) (codec.LID, error) asset = strings.TrimSpace(asset) var assetID codec.LID if asset != symbol { - assetID = codec.LIDFromString(asset) + assetID = codec.LIDFromString(label, asset) } if !allowNative && assetID == codec.EmptyAddress { return codec.EmptyAddress, ErrInvalidChoice @@ -281,7 +281,7 @@ func (*Handler) PromptLID(label string) (codec.LID, error) { if len(input) == 0 { return ErrInputEmpty } - _ = codec.LIDFromString(input) + _ = codec.LIDFromString(label, input) return nil }, } @@ -290,7 +290,7 @@ func (*Handler) PromptLID(label string) (codec.LID, error) { return codec.EmptyAddress, err } rawID = strings.TrimSpace(rawID) - return codec.LIDFromString(rawID), nil + return codec.LIDFromString(label, rawID), nil } func (h *Handler) PromptChain(label string, excluded set.Set[ids.ID]) (ids.ID, []string, error) { diff --git a/codec/address.go b/codec/address.go index cb0c3e625b..5b95154f55 100644 --- a/codec/address.go +++ b/codec/address.go @@ -97,8 +97,8 @@ func LIDToString(hrp string, lid LID) string { return addr } -func LIDFromString(lid string) LID { - addr, err := ParseAddressBech32("token", lid) +func LIDFromString(hrp string, lid string) LID { + addr, err := ParseAddressBech32(hrp, lid) if err != nil { panic(err) } diff --git a/examples/tokenvm/cmd/token-wallet/backend/backend.go b/examples/tokenvm/cmd/token-wallet/backend/backend.go index f80260696a..ff04e792c3 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/backend.go +++ b/examples/tokenvm/cmd/token-wallet/backend/backend.go @@ -699,7 +699,7 @@ func (b *Backend) CreateAsset(symbol string, decimals string, metadata string) e func (b *Backend) MintAsset(asset string, address string, amount string) error { // Input validation - assetID := codec.LIDFromString(asset) + assetID := codec.LIDFromString(tconsts.HRP, asset) _, _, decimals, _, _, _, err := b.tcli.Asset(b.ctx, assetID, true) if err != nil { return err @@ -751,7 +751,7 @@ func (b *Backend) MintAsset(asset string, address string, amount string) error { func (b *Backend) Transfer(asset string, address string, amount string, memo string) error { // Input validation - assetID := codec.LIDFromString(asset) + assetID := codec.LIDFromString(tconsts.HRP, asset) _, symbol, decimals, _, _, _, err := b.tcli.Asset(b.ctx, assetID, true) if err != nil { return err @@ -980,7 +980,7 @@ func (b *Backend) GetAllAssets() []*AssetInfo { } func (b *Backend) AddAsset(asset string) error { - assetID := codec.LIDFromString(asset) + assetID := codec.LIDFromString(tconsts.HRP, asset) hasAsset, err := b.s.HasAsset(assetID) if err != nil { return err @@ -1051,19 +1051,13 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { } assetIDs := strings.Split(pair, "-") in := assetIDs[0] - inID, err := codec.LIDFromString(in) - if err != nil { - return nil, err - } + inID := codec.LIDFromString(tconsts.HRP, in) _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, inID, true) if err != nil { return nil, err } out := assetIDs[1] - outID, err := codec.LIDFromString(out) - if err != nil { - return nil, err - } + outID := codec.LIDFromString(tconsts.HRP, out) _, outSymbol, outDecimals, _, _, _, err := b.tcli.Asset(b.ctx, outID, true) if err != nil { return nil, err @@ -1092,8 +1086,8 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { } func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, outTick string, supply string) error { - inID := codec.LIDFromString(assetIn) - outID := codec.LIDFromString(assetOut) + inID := codec.LIDFromString(tconsts.HRP, assetIn) + outID := codec.LIDFromString(tconsts.HRP, assetOut) _, _, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, inID, true) if err != nil { return err @@ -1169,13 +1163,13 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou } func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, inTick string, assetOut string, amount string) error { - oID := codec.LIDFromString(orderID) + oID := codec.LIDFromString(tconsts.HRP, orderID) owner, err := codec.ParseAddressBech32(tconsts.HRP, orderOwner) if err != nil { return err } - inID := codec.LIDFromString(assetIn) - outID := codec.LIDFromString(assetOut) + inID := codec.LIDFromString(tconsts.HRP, assetIn) + outID := codec.LIDFromString(tconsts.HRP, assetOut) _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, inID, true) if err != nil { return err @@ -1244,8 +1238,8 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i } func (b *Backend) CloseOrder(orderID string, assetOut string) error { - oID := codec.LIDFromString(orderID) - outID := codec.LIDFromString(assetOut) + oID := codec.LIDFromString(tconsts.HRP, orderID) + outID := codec.LIDFromString(tconsts.HRP, assetOut) // Ensure have sufficient balance bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) From 94b7449aded52bba0bfd276f04ff1643d15addf6 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 1 May 2024 13:56:29 -0400 Subject: [PATCH 46/78] fix x/programs lint --- x/programs/cmd/simulator/cmd/plan.go | 6 +- x/programs/cmd/simulator/cmd/program.go | 60 ++++++++++++------- .../simulator/vm/actions/program_create.go | 18 +++--- .../simulator/vm/actions/program_execute.go | 45 ++++++-------- .../cmd/simulator/vm/genesis/genesis.go | 6 ++ x/programs/cmd/simulator/vm/genesis/rules.go | 4 ++ .../cmd/simulator/vm/storage/storage.go | 9 +-- .../examples/imports/program/program.go | 6 +- x/programs/examples/storage/storage.go | 15 +++-- x/programs/examples/token.go | 14 ++--- x/programs/program/context.go | 4 +- x/programs/runtime/runtime_test.go | 19 +++--- 12 files changed, 118 insertions(+), 88 deletions(-) diff --git a/x/programs/cmd/simulator/cmd/plan.go b/x/programs/cmd/simulator/cmd/plan.go index ad40ac92ac..ca76ea332f 100644 --- a/x/programs/cmd/simulator/cmd/plan.go +++ b/x/programs/cmd/simulator/cmd/plan.go @@ -25,7 +25,9 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/state" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/utils" ) @@ -235,7 +237,7 @@ func runStepFunc( if err != nil { return err } - resp.setTxID(id.String()) + resp.setTxID(codec.LIDToString(consts.HRP, id)) resp.setTimestamp(time.Now().Unix()) return nil @@ -252,7 +254,7 @@ func runStepFunc( if err != nil { return err } - resp.setTxID(id.String()) + resp.setTxID(codec.LIDToString(consts.HRP, id)) resp.setBalance(balance) return nil diff --git a/x/programs/cmd/simulator/cmd/program.go b/x/programs/cmd/simulator/cmd/program.go index 7a2b33012b..050bcced26 100644 --- a/x/programs/cmd/simulator/cmd/program.go +++ b/x/programs/cmd/simulator/cmd/program.go @@ -10,14 +10,15 @@ import ( "github.com/spf13/cobra" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/state" hutils "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/actions" + xconsts "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" ) func newProgramCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { @@ -38,7 +39,7 @@ type programCreate struct { db *state.SimpleMutable keyName string path string - id ids.ID + id codec.LID } func newProgramCreateCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { @@ -63,7 +64,7 @@ func newProgramCreateCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Com return err } - hutils.Outf("{{green}}create program transaction successful: {{/}}%s\n", p.id.String()) + hutils.Outf("{{green}}create program transaction successful: {{/}}%s\n", codec.LIDToString(xconsts.HRP, p.id)) return nil }, } @@ -97,38 +98,45 @@ func (p *programCreate) Run(ctx context.Context) (err error) { } // createProgram simulates a create program transaction and stores the program to disk. -func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string) (ids.ID, error) { +func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string) (codec.LID, error) { programBytes, err := os.ReadFile(path) if err != nil { - return ids.Empty, err + return codec.EmptyAddress, err } // simulate create program transaction - programID, err := generateRandomID() + id, err := generateRandomID() if err != nil { - return ids.Empty, err + return codec.EmptyAddress, err } + programID := codec.CreateLID(0, id) programCreateAction := actions.ProgramCreate{ Program: programBytes, } // execute the action - success, _, output, err := programCreateAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programID) - if output != nil { - fmt.Println(string(output)) + success, _, outputs, err := programCreateAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programID) + var resultOutputs string + for i := 0; i < len(outputs); i++ { + for j := 0; j < len(outputs[i]); j++ { + resultOutputs += fmt.Sprintf(" %s", string(outputs[i][j])) + } + } + if len(resultOutputs) > 0 { + fmt.Println(resultOutputs) } if !success { - return ids.Empty, fmt.Errorf("program creation failed: %s", err) + return codec.EmptyAddress, fmt.Errorf("program creation failed: %s", err) } if err != nil { - return ids.Empty, err + return codec.EmptyAddress, err } // store program to disk only on success err = db.Commit(ctx) if err != nil { - return ids.Empty, err + return codec.EmptyAddress, err } return programID, nil @@ -141,12 +149,13 @@ func programExecuteFunc( callParams []actions.CallParam, function string, maxUnits uint64, -) (ids.ID, []int64, uint64, error) { +) (codec.LID, []int64, uint64, error) { // simulate create program transaction programTxID, err := generateRandomID() if err != nil { - return ids.Empty, nil, 0, err + return codec.EmptyAddress, nil, 0, err } + programActionID := codec.CreateLID(0, programTxID) programExecuteAction := actions.ProgramExecute{ Function: function, @@ -156,16 +165,25 @@ func programExecuteFunc( } // execute the action - success, _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programTxID) + success, _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programActionID) if !success { - return ids.Empty, nil, 0, fmt.Errorf("program execution failed: %s", string(resp)) + var respOutput string + for i := 0; i < len(resp); i++ { + respOutput += fmt.Sprintf(" %s", string(resp[i])) + } + return codec.EmptyAddress, nil, 0, fmt.Errorf("program execution failed: %s", respOutput) } if err != nil { - return ids.Empty, nil, 0, err + return codec.EmptyAddress, nil, 0, err } - p := codec.NewReader(resp, len(resp)) + // TODO: I don't think this is right + size := 0 + for i := 1; i < len(resp); i++ { + size += consts.IntLen + codec.BytesLen(resp[i]) + } + p := codec.NewWriter(size, consts.MaxInt) var result []int64 for !p.Empty() { v := p.UnpackInt64(true) @@ -175,11 +193,11 @@ func programExecuteFunc( // store program to disk only on success err = db.Commit(ctx) if err != nil { - return ids.Empty, nil, 0, err + return codec.EmptyAddress, nil, 0, err } // get remaining balance from runtime meter balance, err := programExecuteAction.GetBalance() - return programTxID, result, balance, err + return programActionID, result, balance, err } diff --git a/x/programs/cmd/simulator/vm/actions/program_create.go b/x/programs/cmd/simulator/vm/actions/program_create.go index f010f1cb31..29ac87d6e0 100644 --- a/x/programs/cmd/simulator/vm/actions/program_create.go +++ b/x/programs/cmd/simulator/vm/actions/program_create.go @@ -23,7 +23,7 @@ type ProgramCreate struct { Program []byte `json:"program"` } -func (t *ProgramCreate) StateKeys(_ codec.Address, _ ids.ID) state.Keys { +func (t *ProgramCreate) StateKeys(_ codec.Address, _ codec.LID) state.Keys { return state.Keys{} } @@ -31,6 +31,10 @@ func (*ProgramCreate) GetTypeID() uint8 { return programCreateID } +func (*ProgramCreate) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) +} + func (*ProgramCreate) StateKeysMaxChunks() []uint16 { return []uint16{storage.ProgramChunks} } @@ -41,17 +45,17 @@ func (t *ProgramCreate) Execute( mu state.Mutable, _ int64, _ codec.Address, - id ids.ID, -) (bool, uint64, []byte, error) { + actionID codec.LID, +) (bool, uint64, [][]byte, error) { if len(t.Program) == 0 { - return false, 1, OutputValueZero, nil + return false, 1, [][]byte{OutputValueZero}, nil } - if err := storage.SetProgram(ctx, mu, id, t.Program); err != nil { - return false, 1, utils.ErrBytes(err), nil + if err := storage.SetProgram(ctx, mu, actionID, t.Program); err != nil { + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } - return true, 1, nil, nil + return true, 1, [][]byte{{}}, nil } func (*ProgramCreate) MaxComputeUnits(chain.Rules) uint64 { diff --git a/x/programs/cmd/simulator/vm/actions/program_execute.go b/x/programs/cmd/simulator/vm/actions/program_execute.go index 280f08f0e1..27ce892005 100644 --- a/x/programs/cmd/simulator/vm/actions/program_execute.go +++ b/x/programs/cmd/simulator/vm/actions/program_execute.go @@ -5,8 +5,6 @@ package actions import ( "context" - "errors" - "fmt" "github.com/near/borsh-go" @@ -45,7 +43,11 @@ func (*ProgramExecute) GetTypeID() uint8 { return programExecuteID } -func (t *ProgramExecute) StateKeys(actor codec.Address, txID ids.ID) state.Keys { +func (*ProgramExecute) GetActionID(i uint8, txID ids.ID) codec.LID { + return codec.CreateLID(i, txID) +} + +func (t *ProgramExecute) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{} } @@ -59,40 +61,31 @@ func (t *ProgramExecute) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ ids.ID, -) (success bool, computeUnits uint64, output []byte, err error) { + actionID codec.LID, +) (success bool, computeUnits uint64, output [][]byte, err error) { if len(t.Function) == 0 { - return false, 1, OutputValueZero, nil + return false, 1, [][]byte{OutputValueZero}, nil } if len(t.Params) == 0 { - return false, 1, OutputValueZero, nil + return false, 1, [][]byte{OutputValueZero}, nil } - programID, ok := t.Params[0].Value.(ids.ID) - if !ok { - return false, 1, utils.ErrBytes(fmt.Errorf("invalid call param: must be ID")), nil - } - - // TODO: take fee out of balance? - programBytes, exists, err := storage.GetProgram(context.Background(), mu, programID) - if !exists { - err = errors.New("unknown program") - } + programBytes, err := storage.GetProgram(ctx, mu, actionID) if err != nil { - return false, 1, utils.ErrBytes(err), nil + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } // TODO: get cfg from genesis cfg := runtime.NewConfig() if err != nil { - return false, 1, utils.ErrBytes(err), nil + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } ecfg, err := engine.NewConfigBuilder(). WithDefaultCache(true). Build() if err != nil { - return false, 1, utils.ErrBytes(err), nil + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } eng := engine.New(ecfg) @@ -102,7 +95,7 @@ func (t *ProgramExecute) Execute( return pstate.New(logging.NoLog{}, mu) }) callContext := program.Context{ - ProgramID: programID, + ProgramID: actionID, // Actor: [32]byte(actor[1:]), // OriginatingActor: [32]byte(actor[1:]) } @@ -115,22 +108,22 @@ func (t *ProgramExecute) Execute( t.rt = runtime.New(logging.NoLog{}, eng, imports, cfg) err = t.rt.Initialize(ctx, callContext, programBytes, t.MaxUnits) if err != nil { - return false, 1, utils.ErrBytes(err), nil + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } defer t.rt.Stop() mem, err := t.rt.Memory() if err != nil { - return false, 1, utils.ErrBytes(err), nil + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } params, err := WriteParams(mem, t.Params) if err != nil { - return false, 1, utils.ErrBytes(err), nil + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } resp, err := t.rt.Call(ctx, t.Function, callContext, params[1:]...) if err != nil { - return false, 1, utils.ErrBytes(err), nil + return false, 1, [][]byte{utils.ErrBytes(err)}, nil } // TODO: remove this is to support readonly response for now. @@ -139,7 +132,7 @@ func (t *ProgramExecute) Execute( p.PackInt64(r) } - return true, 1, p.Bytes(), nil + return true, 1, [][]byte{p.Bytes()}, nil } func (*ProgramExecute) MaxComputeUnits(chain.Rules) uint64 { diff --git a/x/programs/cmd/simulator/vm/genesis/genesis.go b/x/programs/cmd/simulator/vm/genesis/genesis.go index 958ca59817..d9aca6f32b 100644 --- a/x/programs/cmd/simulator/vm/genesis/genesis.go +++ b/x/programs/cmd/simulator/vm/genesis/genesis.go @@ -59,6 +59,9 @@ type Genesis struct { EnableDebugMode bool `json:"enableDebugMode"` EnableBulkMemory bool `json:"enableBulkMemory"` + // Action Per Tx + MaxActionsPerTx int `json:"maxActionsPerTx"` + // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` } @@ -100,6 +103,9 @@ func Default() *Genesis { EnableDebugMode: true, // Enabled to only enable Wasi for testing mode EnableBulkMemory: true, + + // Action Per Tx + MaxActionsPerTx: 1, } } diff --git a/x/programs/cmd/simulator/vm/genesis/rules.go b/x/programs/cmd/simulator/vm/genesis/rules.go index f9b1c10d87..68c4d14336 100644 --- a/x/programs/cmd/simulator/vm/genesis/rules.go +++ b/x/programs/cmd/simulator/vm/genesis/rules.go @@ -94,3 +94,7 @@ func (r *Rules) ChainID() ids.ID { func (*Rules) FetchCustom(string) (any, bool) { return nil, false } + +func (r *Rules) GetMaxActionsPerTx() int { + return r.g.MaxActionsPerTx +} diff --git a/x/programs/cmd/simulator/vm/storage/storage.go b/x/programs/cmd/simulator/vm/storage/storage.go index f8de82fd93..f6138ffbfe 100644 --- a/x/programs/cmd/simulator/vm/storage/storage.go +++ b/x/programs/cmd/simulator/vm/storage/storage.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/fees" @@ -46,8 +47,8 @@ const ProgramChunks uint16 = 1 // Program // -func ProgramKey(id ids.ID) (k []byte) { - k = make([]byte, 1+consts.IDLen) +func ProgramKey(id codec.LID) (k []byte) { + k = make([]byte, 1+codec.AddressLen) k[0] = programPrefix copy(k[1:], id[:]) return @@ -57,7 +58,7 @@ func ProgramKey(id ids.ID) (k []byte) { func GetProgram( ctx context.Context, db state.Immutable, - programID ids.ID, + programID codec.LID, ) ( []byte, // program bytes bool, // exists @@ -78,7 +79,7 @@ func GetProgram( func SetProgram( ctx context.Context, mu state.Mutable, - programID ids.ID, + programID codec.LID, program []byte, ) error { return storage.SetProgram(ctx, mu, programID, program) diff --git a/x/programs/examples/imports/program/program.go b/x/programs/examples/imports/program/program.go index 85b07941a3..a64929dfd8 100644 --- a/x/programs/examples/imports/program/program.go +++ b/x/programs/examples/imports/program/program.go @@ -14,6 +14,7 @@ import ( "github.com/near/borsh-go" "go.uber.org/zap" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/x/programs/engine" @@ -168,7 +169,7 @@ func (i *Import) callProgramFn(callContext program.Context) func(*wasmtime.Calle functionName := string(args.Function) res, err := rt.Call(ctx, functionName, program.Context{ - ProgramID: ids.ID(args.ProgramID), + ProgramID: codec.LID(args.ProgramID), // Actor: callContext.ProgramID, // OriginatingActor: callContext.OriginatingActor, }, params...) @@ -212,8 +213,9 @@ func getProgramWasmBytes(log logging.Logger, db state.Immutable, idBytes []byte) return nil, err } + programID := codec.CreateLID(0, id) // get the program bytes from storage - bytes, exists, err := storage.GetProgram(context.Background(), db, id) + bytes, exists, err := storage.GetProgram(context.Background(), db, programID) if !exists { log.Debug("key does not exist", zap.String("id", id.String())) return nil, errors.New("unknown program") diff --git a/x/programs/examples/storage/storage.go b/x/programs/examples/storage/storage.go index c42759879a..ecc270431b 100644 --- a/x/programs/examples/storage/storage.go +++ b/x/programs/examples/storage/storage.go @@ -8,9 +8,8 @@ import ( "errors" "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/consts" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/state" ) @@ -20,10 +19,10 @@ const ( ) func ProgramPrefixKey(id []byte, key []byte) (k []byte) { - k = make([]byte, consts.IDLen+1+len(key)) + k = make([]byte, codec.AddressLen+1+len(key)) k[0] = programPrefix copy(k, id) - copy(k[consts.IDLen:], (key)) + copy(k[codec.AddressLen:], (key)) return } @@ -31,8 +30,8 @@ func ProgramPrefixKey(id []byte, key []byte) (k []byte) { // Program // -func ProgramKey(id ids.ID) (k []byte) { - k = make([]byte, 1+consts.IDLen) +func ProgramKey(id codec.LID) (k []byte) { + k = make([]byte, 1+codec.AddressLen) copy(k[1:], id[:]) return } @@ -41,7 +40,7 @@ func ProgramKey(id ids.ID) (k []byte) { func GetProgram( ctx context.Context, db state.Immutable, - programID ids.ID, + programID codec.LID, ) ( []byte, // program bytes bool, // exists @@ -62,7 +61,7 @@ func GetProgram( func SetProgram( ctx context.Context, mu state.Mutable, - programID ids.ID, + programID codec.LID, program []byte, ) error { k := ProgramKey(programID) diff --git a/x/programs/examples/token.go b/x/programs/examples/token.go index 6617e6d54f..ca0fa39c35 100644 --- a/x/programs/examples/token.go +++ b/x/programs/examples/token.go @@ -7,11 +7,11 @@ import ( "context" "fmt" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/near/borsh-go" "go.uber.org/zap" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/x/programs/engine" @@ -34,7 +34,7 @@ const ( Balance ) -func NewToken(programID ids.ID, log logging.Logger, engine *engine.Engine, programBytes []byte, db state.Mutable, cfg *runtime.Config, imports host.SupportedImports, maxUnits uint64) *Token { +func NewToken(programID codec.LID, log logging.Logger, engine *engine.Engine, programBytes []byte, db state.Mutable, cfg *runtime.Config, imports host.SupportedImports, maxUnits uint64) *Token { return &Token{ programID: programID, log: log, @@ -55,7 +55,7 @@ type minter struct { } type Token struct { - programID ids.ID + programID codec.LID log logging.Logger programBytes []byte cfg *runtime.Config @@ -71,7 +71,7 @@ func (t *Token) Context() program.Context { } } -func (t *Token) ProgramID() ids.ID { +func (t *Token) ProgramID() codec.LID { return t.programID } @@ -106,7 +106,7 @@ func (t *Token) Run(ctx context.Context) error { } t.log.Debug("new token program created", - zap.String("id", programID.String()), + zap.String("id", codec.LIDToString("matrix", programID)), ) // initialize program @@ -371,7 +371,7 @@ func (t *Token) RunShort(ctx context.Context) error { } t.log.Debug("new token program created", - zap.String("id", programID.String()), + zap.String("id", codec.LIDToString("matrix", programID)), ) // initialize program @@ -386,7 +386,7 @@ func (t *Token) RunShort(ctx context.Context) error { return nil } -func (t *Token) GetUserBalanceFromState(ctx context.Context, programID ids.ID, userPublicKey ed25519.PublicKey) (res uint32, err error) { +func (t *Token) GetUserBalanceFromState(ctx context.Context, programID codec.LID, userPublicKey ed25519.PublicKey) (res int64, err error) { key := storage.ProgramPrefixKey(programID[:], append([]byte{uint8(Balance)}, userPublicKey[:]...)) b, err := t.db.GetValue(ctx, key) if err != nil { diff --git a/x/programs/program/context.go b/x/programs/program/context.go index b2e67cd6ba..d72de09df1 100644 --- a/x/programs/program/context.go +++ b/x/programs/program/context.go @@ -4,11 +4,11 @@ package program import ( - "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/codec" ) type Context struct { - ProgramID ids.ID `json:"program"` + ProgramID codec.LID `json:"program"` // Actor [32]byte `json:"actor"` // OriginatingActor [32]byte `json:"originating_actor"` } diff --git a/x/programs/runtime/runtime_test.go b/x/programs/runtime/runtime_test.go index e8f16e4e18..60fb36a906 100644 --- a/x/programs/runtime/runtime_test.go +++ b/x/programs/runtime/runtime_test.go @@ -13,6 +13,7 @@ import ( "github.com/bytecodealliance/wasmtime-go/v14" "github.com/stretchr/testify/require" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/x/programs/engine" "github.com/ava-labs/hypersdk/x/programs/host" "github.com/ava-labs/hypersdk/x/programs/program" @@ -44,7 +45,7 @@ func TestStop(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := ids.GenerateTestID() + id := codec.CreateLID(0, ids.GenerateTestID()) programContext := program.Context{ ProgramID: id, } @@ -90,7 +91,7 @@ func TestCallParams(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := ids.GenerateTestID() + id := codec.CreateLID(0, ids.GenerateTestID()) programContext := program.Context{ ProgramID: id, } @@ -137,7 +138,7 @@ func TestInfiniteLoop(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := ids.GenerateTestID() + id := codec.CreateLID(0, ids.GenerateTestID()) programContext := program.Context{ ProgramID: id, } @@ -176,7 +177,7 @@ func TestMetering(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := ids.GenerateTestID() + id := codec.CreateLID(0, ids.GenerateTestID()) programContext := program.Context{ ProgramID: id, } @@ -224,7 +225,7 @@ func TestMeterAfterStop(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := ids.GenerateTestID() + id := codec.CreateLID(0, ids.GenerateTestID()) programContext := program.Context{ ProgramID: id, } @@ -264,7 +265,7 @@ func TestLimitMaxMemory(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := ids.GenerateTestID() + id := codec.CreateLID(0, ids.GenerateTestID()) programContext := program.Context{ ProgramID: id, } @@ -295,7 +296,7 @@ func TestLimitMaxMemoryGrow(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := ids.GenerateTestID() + id := codec.CreateLID(0, ids.GenerateTestID()) programContext := program.Context{ ProgramID: id, } @@ -335,7 +336,7 @@ func TestWriteExceedsLimitMaxMemory(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := ids.GenerateTestID() + id := codec.CreateLID(0, ids.GenerateTestID()) programContext := program.Context{ ProgramID: id, } @@ -379,7 +380,7 @@ func TestWithMaxWasmStack(t *testing.T) { cfg := NewConfig() runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := ids.GenerateTestID() + id := codec.CreateLID(0, ids.GenerateTestID()) programContext := program.Context{ ProgramID: id, } From 35f51ec846ec488b317d057a06c9a26f61324ca5 Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 3 May 2024 11:51:34 -0400 Subject: [PATCH 47/78] getmaxactionspertx should be uint8 --- chain/dependencies.go | 2 +- chain/mock_rules.go | 4 ++-- chain/transaction.go | 2 +- examples/morpheusvm/genesis/genesis.go | 2 +- examples/morpheusvm/genesis/rules.go | 2 +- examples/tokenvm/genesis/genesis.go | 2 +- examples/tokenvm/genesis/rules.go | 2 +- x/programs/cmd/simulator/vm/genesis/genesis.go | 2 +- x/programs/cmd/simulator/vm/genesis/rules.go | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/chain/dependencies.go b/chain/dependencies.go index d10cb1a409..af296d3b85 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -153,7 +153,7 @@ type Rules interface { FetchCustom(string) (any, bool) - GetMaxActionsPerTx() int + GetMaxActionsPerTx() uint8 } type MetadataManager interface { diff --git a/chain/mock_rules.go b/chain/mock_rules.go index 003a4ac970..292f719345 100644 --- a/chain/mock_rules.go +++ b/chain/mock_rules.go @@ -87,10 +87,10 @@ func (mr *MockRulesMockRecorder) GetBaseComputeUnits() *gomock.Call { } // GetMaxActionsPerTx mocks base method. -func (m *MockRules) GetMaxActionsPerTx() int { +func (m *MockRules) GetMaxActionsPerTx() byte { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMaxActionsPerTx") - ret0, _ := ret[0].(int) + ret0, _ := ret[0].(byte) return ret0 } diff --git a/chain/transaction.go b/chain/transaction.go index e97c55fcb9..be86cd14f4 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -249,7 +249,7 @@ func (t *Transaction) PreExecute( if err := t.Base.Execute(r.ChainID(), r, timestamp); err != nil { return err } - if len(t.Actions) > r.GetMaxActionsPerTx() { + if len(t.Actions) > int(r.GetMaxActionsPerTx()) { return ErrTooManyActions } for _, action := range t.Actions { diff --git a/examples/morpheusvm/genesis/genesis.go b/examples/morpheusvm/genesis/genesis.go index 58aaa5bfc1..adeedf5696 100644 --- a/examples/morpheusvm/genesis/genesis.go +++ b/examples/morpheusvm/genesis/genesis.go @@ -55,7 +55,7 @@ type Genesis struct { StorageValueWriteUnits uint64 `json:"storageValueWriteUnits"` // per chunk // Action Per Tx - MaxActionsPerTx int `json:"maxActionsPerTx"` + MaxActionsPerTx uint8 `json:"maxActionsPerTx"` // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` diff --git a/examples/morpheusvm/genesis/rules.go b/examples/morpheusvm/genesis/rules.go index f6eefb8398..aca4ff6d64 100644 --- a/examples/morpheusvm/genesis/rules.go +++ b/examples/morpheusvm/genesis/rules.go @@ -96,6 +96,6 @@ func (*Rules) FetchCustom(string) (any, bool) { return nil, false } -func (r *Rules) GetMaxActionsPerTx() int { +func (r *Rules) GetMaxActionsPerTx() uint8 { return r.g.MaxActionsPerTx } diff --git a/examples/tokenvm/genesis/genesis.go b/examples/tokenvm/genesis/genesis.go index cc0c849d2e..4d342e6df8 100644 --- a/examples/tokenvm/genesis/genesis.go +++ b/examples/tokenvm/genesis/genesis.go @@ -55,7 +55,7 @@ type Genesis struct { StorageValueWriteUnits uint64 `json:"storageValueWriteUnits"` // per chunk // Action Per Tx - MaxActionsPerTx int `json:"maxActionsPerTx"` + MaxActionsPerTx uint8 `json:"maxActionsPerTx"` // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` diff --git a/examples/tokenvm/genesis/rules.go b/examples/tokenvm/genesis/rules.go index d2681650e1..03bbf9386f 100644 --- a/examples/tokenvm/genesis/rules.go +++ b/examples/tokenvm/genesis/rules.go @@ -96,6 +96,6 @@ func (*Rules) FetchCustom(string) (any, bool) { return nil, false } -func (r *Rules) GetMaxActionsPerTx() int { +func (r *Rules) GetMaxActionsPerTx() uint8 { return r.g.MaxActionsPerTx } diff --git a/x/programs/cmd/simulator/vm/genesis/genesis.go b/x/programs/cmd/simulator/vm/genesis/genesis.go index d9aca6f32b..729d42c2a2 100644 --- a/x/programs/cmd/simulator/vm/genesis/genesis.go +++ b/x/programs/cmd/simulator/vm/genesis/genesis.go @@ -60,7 +60,7 @@ type Genesis struct { EnableBulkMemory bool `json:"enableBulkMemory"` // Action Per Tx - MaxActionsPerTx int `json:"maxActionsPerTx"` + MaxActionsPerTx uint8 `json:"maxActionsPerTx"` // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` diff --git a/x/programs/cmd/simulator/vm/genesis/rules.go b/x/programs/cmd/simulator/vm/genesis/rules.go index 68c4d14336..8676653e25 100644 --- a/x/programs/cmd/simulator/vm/genesis/rules.go +++ b/x/programs/cmd/simulator/vm/genesis/rules.go @@ -95,6 +95,6 @@ func (*Rules) FetchCustom(string) (any, bool) { return nil, false } -func (r *Rules) GetMaxActionsPerTx() int { +func (r *Rules) GetMaxActionsPerTx() uint8 { return r.g.MaxActionsPerTx } From ded349404ce15b67c92c121b2d02d659968a9f51 Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 3 May 2024 11:54:00 -0400 Subject: [PATCH 48/78] update readme --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b4f614cf5a..b4da92bd82 100644 --- a/README.md +++ b/README.md @@ -627,6 +627,10 @@ You can view what this looks like in the `tokenvm` by clicking this type Action interface { Object + // GetActionID returns the LID for an [Action] in a [Transaction]. There may be + // multiple [Action]s, so we pass its index in the [Action] array along with the txID. + GetActionID(i uint8, txID ids.ID) codec.LID + // MaxComputeUnits is the maximum amount of compute a given [Action] could use. This is // used to determine whether the [Action] can be included in a given block and to compute // the required fee to execute. @@ -648,7 +652,7 @@ type Action interface { // key (formatted as a big-endian uint16). This is used to automatically calculate storage usage. // // If any key is removed and then re-created, this will count as a creation instead of a modification. - StateKeys(actor codec.Address, txID ids.ID) state.Keys + StateKeys(actor codec.Address, actionID codec.LID) state.Keys // Execute actually runs the [Action]. Any state changes that the [Action] performs should // be done here. @@ -664,8 +668,8 @@ type Action interface { mu state.Mutable, timestamp int64, actor codec.Address, - txID ids.ID, - ) (success bool, computeUnits uint64, output []byte, err error) + actionID codec.LID, + ) (success bool, computeUnits uint64, outputs [][]byte, err error) } ``` @@ -772,6 +776,8 @@ type Rules interface { GetStorageValueWriteUnits() uint64 // per chunk FetchCustom(string) (any, bool) + + GetMaxActionsPerTx() uint8 } ``` From 0a1dc9dfc9eb5d8699227914d3fad11052b4fbb6 Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 3 May 2024 13:44:49 -0400 Subject: [PATCH 49/78] self review --- chain/transaction.go | 59 +++++++++++-------- codec/address.go | 24 ++++---- .../cmd/morpheus-cli/cmd/resolutions.go | 38 ++++++------ examples/morpheusvm/controller/controller.go | 1 - examples/morpheusvm/tests/load/load_test.go | 4 +- examples/tokenvm/actions/burn_asset.go | 2 +- .../tokenvm/cmd/token-cli/cmd/resolutions.go | 36 +++++------ heap/heap_test.go | 2 +- 8 files changed, 90 insertions(+), 76 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index be86cd14f4..3a0320877d 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -321,10 +321,14 @@ func (t *Transaction) Execute( ts.Rollback(ctx, actionStart) return &Result{false, [][][]byte{{utils.ErrBytes(rerr)}}, maxUnits, maxFee}, nil } - resultOutputs := [][][]byte{} - totalUsed := fees.Dimensions{} - var totalFeeRequired uint64 - txSuccess := true + + var ( + totalFeeRequired uint64 + + resultOutputs = [][][]byte{} + totalUsed = fees.Dimensions{} + txSuccess = true + ) for i, action := range t.Actions { actionID := action.GetActionID(uint8(i), t.id) success, actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) @@ -337,6 +341,7 @@ func (t *Transaction) Execute( return handleRevert(ErrInvalidObject) } resultOutputs = append(resultOutputs, outputs) + if !success { txSuccess = false ts.Rollback(ctx, actionStart) @@ -419,6 +424,7 @@ func (t *Transaction) Execute( return handleRevert(err) } totalFeeRequired += feeRequired + refund := maxFee - feeRequired if refund > 0 { ts.DisableAllocation() @@ -461,28 +467,6 @@ func (t *Transaction) marshalActions(p *codec.Packer) error { return p.Err() } -// todo: move below UnmarshalTx -func unmarshalActions( - p *codec.Packer, - actionRegistry *codec.TypeParser[Action, bool], -) ([]Action, error) { - actionCount := p.UnpackInt(true) - actions := make([]Action, 0) - for i := 0; i < actionCount; i++ { - actionType := p.UnpackByte() - unmarshalAction, ok := actionRegistry.LookupIndex(actionType) - if !ok { - return nil, fmt.Errorf("%w: %d is unknown action type", ErrInvalidObject, actionType) - } - action, err := unmarshalAction(p) - if err != nil { - return nil, fmt.Errorf("%w: could not unmarshal action", err) - } - actions = append(actions, action) - } - return actions, nil -} - func MarshalTxs(txs []*Transaction) ([]byte, error) { if len(txs) == 0 { return nil, ErrNoTxs @@ -568,3 +552,26 @@ func UnmarshalTx( tx.id = utils.ToID(tx.bytes) return &tx, nil } + +func unmarshalActions( + p *codec.Packer, + actionRegistry *codec.TypeParser[Action, bool], +) ([]Action, error) { + actionCount := p.UnpackInt(true) + actions := make([]Action, 0) + + for i := 0; i < actionCount; i++ { + actionType := p.UnpackByte() + unmarshalAction, ok := actionRegistry.LookupIndex(actionType) + if !ok { + return nil, fmt.Errorf("%w: %d is unknown action type", ErrInvalidObject, actionType) + } + + action, err := unmarshalAction(p) + if err != nil { + return nil, fmt.Errorf("%w: could not unmarshal action", err) + } + actions = append(actions, action) + } + return actions, nil +} diff --git a/codec/address.go b/codec/address.go index 5b95154f55..5d056c3453 100644 --- a/codec/address.go +++ b/codec/address.go @@ -21,13 +21,22 @@ const ( maxBech32Size = 90 ) -type Address LID - -// Long ID -type LID [AddressLen]byte +type ( + LID [AddressLen]byte // Long ID + Address LID +) var EmptyAddress = [AddressLen]byte{} +// CreateLID returns [LID] made from concatenating +// some [i] with an [id] +func CreateLID(i uint8, id ids.ID) LID { + a := make([]byte, AddressLen) + a[0] = i + copy(a[1:], id[:]) + return LID(a) +} + // CreateAddress returns [Address] made from concatenating // [typeID] with [id]. func CreateAddress(typeID uint8, id ids.ID) Address { @@ -82,13 +91,6 @@ func ParseAddressBech32(hrp, saddr string) (Address, error) { return Address(p[:AddressLen]), nil } -func CreateLID(idx uint8, txID ids.ID) LID { - a := make([]byte, AddressLen) - a[0] = idx - copy(a[1:], txID[:]) - return LID(a) -} - func LIDToString(hrp string, lid LID) string { addr, err := AddressBech32(hrp, Address(lid)) if err != nil { diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go index dba02cee15..ca95bfb04d 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go @@ -57,32 +57,34 @@ func sendAndWait( } func handleTx(tx *chain.Transaction, result *chain.Result) { + status := "❌" + if result.Success { + status = "✅" + } for i := 0; i < len(result.Outputs); i++ { for j := 0; j < len(result.Outputs[i]); j++ { - summaryStr := string(result.Outputs[i][j]) actor := tx.Auth.Actor() - status := "❌" - if result.Success { - status = "✅" - for _, action := range tx.Actions { + for _, action := range tx.Actions { + summaryStr := string(result.Outputs[i][j]) + if result.Success { switch act := action.(type) { //nolint:gocritic case *actions.Transfer: - summaryStr = fmt.Sprintf("%s %s -> %s", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) + summaryStr = fmt.Sprintf("%s %s -> %s\n", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) } } + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + status, + tx.ID(), + codec.MustAddressBech32(consts.HRP, actor), + reflect.TypeOf(action), + summaryStr, + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, consts.Decimals), + consts.Symbol, + cli.ParseDimensions(result.Consumed), + ) } - utils.Outf( - "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", - status, - tx.ID(), - codec.MustAddressBech32(consts.HRP, actor), - reflect.TypeOf(tx.Actions), - summaryStr, - float64(result.Fee)/float64(tx.Base.MaxFee)*100, - utils.FormatBalance(result.Fee, consts.Decimals), - consts.Symbol, - cli.ParseDimensions(result.Consumed), - ) } } } diff --git a/examples/morpheusvm/controller/controller.go b/examples/morpheusvm/controller/controller.go index 0f99755c09..7fb305ceee 100644 --- a/examples/morpheusvm/controller/controller.go +++ b/examples/morpheusvm/controller/controller.go @@ -153,7 +153,6 @@ func (c *Controller) Accepted(ctx context.Context, blk *chain.StatelessBlock) er results := blk.Results() for i, tx := range blk.Txs { result := results[i] - fmt.Printf("result %v | results %v\n", result, results) if c.config.GetStoreTransactions() { err := storage.StoreTransaction( ctx, diff --git a/examples/morpheusvm/tests/load/load_test.go b/examples/morpheusvm/tests/load/load_test.go index 629d579b57..85ecf8eab3 100644 --- a/examples/morpheusvm/tests/load/load_test.go +++ b/examples/morpheusvm/tests/load/load_test.go @@ -411,11 +411,13 @@ var _ = ginkgo.Describe("load tests vm", func() { for _, result := range blk.Results() { if !result.Success { unitPrices, _ := instances[0].cli.UnitPrices(context.Background(), false) + var resultOutputs string for i := 0; i < len(result.Outputs); i++ { for j := 0; j < len(result.Outputs[i]); j++ { - fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "output:", string(result.Outputs[i][j])) + resultOutputs += fmt.Sprintf(" %s", result.Outputs[i][j]) } } + fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "output:", resultOutputs) } gomega.Ω(result.Success).Should(gomega.BeTrue()) } diff --git a/examples/tokenvm/actions/burn_asset.go b/examples/tokenvm/actions/burn_asset.go index d273796767..34dc4a37d4 100644 --- a/examples/tokenvm/actions/burn_asset.go +++ b/examples/tokenvm/actions/burn_asset.go @@ -20,7 +20,7 @@ import ( var _ chain.Action = (*BurnAsset)(nil) type BurnAsset struct { - // Asset is the [TxID] that created the asset. + // Asset is the [ActionID] that created the asset. Asset codec.LID `json:"asset"` // Number of assets to mint to [To]. diff --git a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go index 3c36404c34..d0a07c9c0e 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go +++ b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go @@ -57,14 +57,16 @@ func sendAndWait( } func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result) { + status := "❌" + if result.Success { + status = "✅" + } for i := 0; i < len(result.Outputs); i++ { for j := 0; j < len(result.Outputs[i]); j++ { - summaryStr := string(result.Outputs[i][j]) actor := tx.Auth.Actor() - status := "❌" - if result.Success { - status = "✅" - for i, act := range tx.Actions { + for i, act := range tx.Actions { + summaryStr := string(result.Outputs[i][j]) + if result.Success { switch action := act.(type) { case *actions.CreateAsset: assetID := action.GetActionID(uint8(i), tx.ID()) @@ -130,19 +132,19 @@ func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result summaryStr = fmt.Sprintf("orderID: %s", action.Order) } } + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + status, + tx.ID(), + codec.MustAddressBech32(tconsts.HRP, actor), + reflect.TypeOf(act), + summaryStr, + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, tconsts.Decimals), + tconsts.Symbol, + cli.ParseDimensions(result.Consumed), + ) } - utils.Outf( - "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", - status, - tx.ID(), - codec.MustAddressBech32(tconsts.HRP, actor), - reflect.TypeOf(tx.Actions), - summaryStr, - float64(result.Fee)/float64(tx.Base.MaxFee)*100, - utils.FormatBalance(result.Fee, tconsts.Decimals), - tconsts.Symbol, - cli.ParseDimensions(result.Consumed), - ) } } } diff --git a/heap/heap_test.go b/heap/heap_test.go index f18f08f20a..5728dc94f4 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -184,7 +184,7 @@ func TestUnit64HeapHasID(t *testing.T) { require.True(ok, "Entry was not found in heap.") } -func TestUnit64HeapPushPopMinForActionID(t *testing.T) { +func TestUnit64HeapPushPopMinForLID(t *testing.T) { require := require.New(t) minHeap := New[codec.LID, *testItem[codec.LID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") From d37e43a1c6997cf532b8623a224f84e525ea9629 Mon Sep 17 00:00:00 2001 From: William Law Date: Fri, 3 May 2024 15:40:36 -0400 Subject: [PATCH 50/78] fix rust ci --- x/programs/cmd/simulator/vm/actions/program_execute.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/programs/cmd/simulator/vm/actions/program_execute.go b/x/programs/cmd/simulator/vm/actions/program_execute.go index 27ce892005..e17d2a21f5 100644 --- a/x/programs/cmd/simulator/vm/actions/program_execute.go +++ b/x/programs/cmd/simulator/vm/actions/program_execute.go @@ -70,7 +70,7 @@ func (t *ProgramExecute) Execute( return false, 1, [][]byte{OutputValueZero}, nil } - programBytes, err := storage.GetProgram(ctx, mu, actionID) + programBytes, _, err := storage.GetProgram(ctx, mu, actionID) if err != nil { return false, 1, [][]byte{utils.ErrBytes(err)}, nil } From 84fdb11fe220f783fefbe165cadab7f80bad2ec3 Mon Sep 17 00:00:00 2001 From: William Law Date: Sat, 4 May 2024 20:33:40 -0400 Subject: [PATCH 51/78] make get action id a helper --- README.md | 4 ---- chain/dependencies.go | 4 ---- chain/mock_action.go | 15 --------------- chain/transaction.go | 4 ++-- codec/address.go | 8 +++++++- examples/morpheusvm/actions/transfer.go | 5 ----- examples/tokenvm/actions/burn_asset.go | 5 ----- examples/tokenvm/actions/close_order.go | 5 ----- examples/tokenvm/actions/create_asset.go | 5 ----- examples/tokenvm/actions/create_order.go | 5 ----- examples/tokenvm/actions/fill_order.go | 5 ----- examples/tokenvm/actions/mint_asset.go | 5 ----- examples/tokenvm/actions/transfer.go | 5 ----- examples/tokenvm/cmd/token-cli/cmd/resolutions.go | 2 +- .../tokenvm/cmd/token-wallet/backend/backend.go | 2 +- examples/tokenvm/controller/controller.go | 3 ++- .../cmd/simulator/vm/actions/program_create.go | 4 ---- .../cmd/simulator/vm/actions/program_execute.go | 4 ---- 18 files changed, 13 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index b4da92bd82..6018f657c2 100644 --- a/README.md +++ b/README.md @@ -627,10 +627,6 @@ You can view what this looks like in the `tokenvm` by clicking this type Action interface { Object - // GetActionID returns the LID for an [Action] in a [Transaction]. There may be - // multiple [Action]s, so we pass its index in the [Action] array along with the txID. - GetActionID(i uint8, txID ids.ID) codec.LID - // MaxComputeUnits is the maximum amount of compute a given [Action] could use. This is // used to determine whether the [Action] can be included in a given block and to compute // the required fee to execute. diff --git a/chain/dependencies.go b/chain/dependencies.go index af296d3b85..d2f4b9a92e 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -219,10 +219,6 @@ type Object interface { type Action interface { Object - // GetActionID returns the LID for an [Action] in a [Transaction]. There may be - // multiple [Action]s, so we pass its index in the [Action] array along with the txID. - GetActionID(i uint8, txID ids.ID) codec.LID - // MaxComputeUnits is the maximum amount of compute a given [Action] could use. This is // used to determine whether the [Action] can be included in a given block and to compute // the required fee to execute. diff --git a/chain/mock_action.go b/chain/mock_action.go index 0d28be63e5..0c8da8fcbf 100644 --- a/chain/mock_action.go +++ b/chain/mock_action.go @@ -16,7 +16,6 @@ import ( context "context" reflect "reflect" - ids "github.com/ava-labs/avalanchego/ids" codec "github.com/ava-labs/hypersdk/codec" state "github.com/ava-labs/hypersdk/state" gomock "go.uber.org/mock/gomock" @@ -62,20 +61,6 @@ func (mr *MockActionMockRecorder) Execute(arg0, arg1, arg2, arg3, arg4, arg5 any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockAction)(nil).Execute), arg0, arg1, arg2, arg3, arg4, arg5) } -// GetActionID mocks base method. -func (m *MockAction) GetActionID(arg0 byte, arg1 ids.ID) codec.LID { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetActionID", arg0, arg1) - ret0, _ := ret[0].(codec.LID) - return ret0 -} - -// GetActionID indicates an expected call of GetActionID. -func (mr *MockActionMockRecorder) GetActionID(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActionID", reflect.TypeOf((*MockAction)(nil).GetActionID), arg0, arg1) -} - // GetTypeID mocks base method. func (m *MockAction) GetTypeID() byte { m.ctrl.T.Helper() diff --git a/chain/transaction.go b/chain/transaction.go index 3a0320877d..c705a6e7eb 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -111,7 +111,7 @@ func (t *Transaction) StateKeys(sm StateManager) (state.Keys, error) { // Verify the formatting of state keys passed by the controller for i, action := range t.Actions { - actionKeys := action.StateKeys(t.Auth.Actor(), action.GetActionID(uint8(i), t.id)) + actionKeys := action.StateKeys(t.Auth.Actor(), codec.CreateLID(uint8(i), t.id)) sponsorKeys := sm.SponsorStateKeys(t.Auth.Sponsor()) for _, m := range []state.Keys{actionKeys, sponsorKeys} { for k, v := range m { @@ -330,7 +330,7 @@ func (t *Transaction) Execute( txSuccess = true ) for i, action := range t.Actions { - actionID := action.GetActionID(uint8(i), t.id) + actionID := codec.CreateLID(uint8(i), t.id) success, actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) if err != nil { return handleRevert(err) diff --git a/codec/address.go b/codec/address.go index 5d056c3453..4032f597c8 100644 --- a/codec/address.go +++ b/codec/address.go @@ -29,7 +29,13 @@ type ( var EmptyAddress = [AddressLen]byte{} // CreateLID returns [LID] made from concatenating -// some [i] with an [id] +// some [i] with an [id]. +// +// This is commonly used for creating an ActionID. We +// keep the index|txID format to keep consistency with +// how address construction works. Index is the index +// in the [Action] array of a transaction. txID is the +// ID of that transaction. func CreateLID(i uint8, id ids.ID) LID { a := make([]byte, AddressLen) a[0] = i diff --git a/examples/morpheusvm/actions/transfer.go b/examples/morpheusvm/actions/transfer.go index 389d24acef..44055afabd 100644 --- a/examples/morpheusvm/actions/transfer.go +++ b/examples/morpheusvm/actions/transfer.go @@ -6,7 +6,6 @@ package actions import ( "context" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" @@ -30,10 +29,6 @@ func (*Transfer) GetTypeID() uint8 { return mconsts.TransferID } -func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (t *Transfer) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor)): state.Read | state.Write, diff --git a/examples/tokenvm/actions/burn_asset.go b/examples/tokenvm/actions/burn_asset.go index 34dc4a37d4..45763e20c9 100644 --- a/examples/tokenvm/actions/burn_asset.go +++ b/examples/tokenvm/actions/burn_asset.go @@ -6,7 +6,6 @@ package actions import ( "context" - "github.com/ava-labs/avalanchego/ids" smath "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/hypersdk/chain" @@ -31,10 +30,6 @@ func (*BurnAsset) GetTypeID() uint8 { return burnAssetID } -func (*BurnAsset) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (b *BurnAsset) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.AssetKey(b.Asset)): state.Read | state.Write, diff --git a/examples/tokenvm/actions/close_order.go b/examples/tokenvm/actions/close_order.go index 508cdc9591..4ca867f187 100644 --- a/examples/tokenvm/actions/close_order.go +++ b/examples/tokenvm/actions/close_order.go @@ -6,7 +6,6 @@ package actions import ( "context" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" @@ -30,10 +29,6 @@ func (*CloseOrder) GetTypeID() uint8 { return closeOrderID } -func (*CloseOrder) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (c *CloseOrder) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.OrderKey(c.Order)): state.Read | state.Write, diff --git a/examples/tokenvm/actions/create_asset.go b/examples/tokenvm/actions/create_asset.go index e1e7437bb7..91d6aa373f 100644 --- a/examples/tokenvm/actions/create_asset.go +++ b/examples/tokenvm/actions/create_asset.go @@ -6,7 +6,6 @@ package actions import ( "context" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" @@ -27,10 +26,6 @@ func (*CreateAsset) GetTypeID() uint8 { return createAssetID } -func (*CreateAsset) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (*CreateAsset) StateKeys(_ codec.Address, actionID codec.LID) state.Keys { return state.Keys{ string(storage.AssetKey(actionID)): state.Allocate | state.Write, diff --git a/examples/tokenvm/actions/create_order.go b/examples/tokenvm/actions/create_order.go index 8bee353b88..4b4d26458f 100644 --- a/examples/tokenvm/actions/create_order.go +++ b/examples/tokenvm/actions/create_order.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" @@ -51,10 +50,6 @@ func (*CreateOrder) GetTypeID() uint8 { return createOrderID } -func (*CreateOrder) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (c *CreateOrder) StateKeys(actor codec.Address, actionID codec.LID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor, c.Out)): state.Read | state.Write, diff --git a/examples/tokenvm/actions/fill_order.go b/examples/tokenvm/actions/fill_order.go index 0251e3ce87..5d6ec4634f 100644 --- a/examples/tokenvm/actions/fill_order.go +++ b/examples/tokenvm/actions/fill_order.go @@ -6,7 +6,6 @@ package actions import ( "context" - "github.com/ava-labs/avalanchego/ids" smath "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/hypersdk/chain" @@ -43,10 +42,6 @@ func (*FillOrder) GetTypeID() uint8 { return fillOrderID } -func (*FillOrder) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (f *FillOrder) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.OrderKey(f.Order)): state.Read | state.Write, diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index a4c7ace767..71e52c1ca8 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -6,7 +6,6 @@ package actions import ( "context" - "github.com/ava-labs/avalanchego/ids" smath "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/hypersdk/chain" @@ -34,10 +33,6 @@ func (*MintAsset) GetTypeID() uint8 { return mintAssetID } -func (*MintAsset) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (m *MintAsset) StateKeys(codec.Address, codec.LID) state.Keys { return state.Keys{ string(storage.AssetKey(m.Asset)): state.Read | state.Write, diff --git a/examples/tokenvm/actions/transfer.go b/examples/tokenvm/actions/transfer.go index 969b74f64e..c998b422fb 100644 --- a/examples/tokenvm/actions/transfer.go +++ b/examples/tokenvm/actions/transfer.go @@ -6,7 +6,6 @@ package actions import ( "context" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" @@ -35,10 +34,6 @@ func (*Transfer) GetTypeID() uint8 { return transferID } -func (*Transfer) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (t *Transfer) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor, t.Asset)): state.Read | state.Write, diff --git a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go index d0a07c9c0e..796ca25de8 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go +++ b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go @@ -69,7 +69,7 @@ func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result if result.Success { switch action := act.(type) { case *actions.CreateAsset: - assetID := action.GetActionID(uint8(i), tx.ID()) + assetID := codec.CreateLID(uint8(i), tx.ID()) summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", assetID, action.Symbol, action.Decimals, action.Metadata) case *actions.MintAsset: _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) diff --git a/examples/tokenvm/cmd/token-wallet/backend/backend.go b/examples/tokenvm/cmd/token-wallet/backend/backend.go index ff04e792c3..b5ddf064a3 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/backend.go +++ b/examples/tokenvm/cmd/token-wallet/backend/backend.go @@ -221,7 +221,7 @@ func (b *Backend) collectBlocks() { // We should exit action parsing as soon as possible for i, act := range tx.Actions { - actionID := act.GetActionID(uint8(i), tx.ID()) + actionID := codec.CreateLID(uint8(i), tx.ID()) switch action := act.(type) { case *actions.Transfer: if actor != b.addr && action.To != b.addr { diff --git a/examples/tokenvm/controller/controller.go b/examples/tokenvm/controller/controller.go index 62ee630764..5ef557814d 100644 --- a/examples/tokenvm/controller/controller.go +++ b/examples/tokenvm/controller/controller.go @@ -13,6 +13,7 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/hypersdk/builder" "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/gossiper" hrpc "github.com/ava-labs/hypersdk/rpc" hstorage "github.com/ava-labs/hypersdk/storage" @@ -191,7 +192,7 @@ func (c *Controller) Accepted(ctx context.Context, blk *chain.StatelessBlock) er c.metrics.transfer.Inc() case *actions.CreateOrder: c.metrics.createOrder.Inc() - c.orderBook.Add(action.GetActionID(uint8(i), tx.ID()), tx.Auth.Actor(), action) + c.orderBook.Add(codec.CreateLID(uint8(i), tx.ID()), tx.Auth.Actor(), action) case *actions.FillOrder: c.metrics.fillOrder.Inc() outputs := result.Outputs[i] diff --git a/x/programs/cmd/simulator/vm/actions/program_create.go b/x/programs/cmd/simulator/vm/actions/program_create.go index 29ac87d6e0..54fe3ad961 100644 --- a/x/programs/cmd/simulator/vm/actions/program_create.go +++ b/x/programs/cmd/simulator/vm/actions/program_create.go @@ -31,10 +31,6 @@ func (*ProgramCreate) GetTypeID() uint8 { return programCreateID } -func (*ProgramCreate) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (*ProgramCreate) StateKeysMaxChunks() []uint16 { return []uint16{storage.ProgramChunks} } diff --git a/x/programs/cmd/simulator/vm/actions/program_execute.go b/x/programs/cmd/simulator/vm/actions/program_execute.go index e17d2a21f5..2378bdd07f 100644 --- a/x/programs/cmd/simulator/vm/actions/program_execute.go +++ b/x/programs/cmd/simulator/vm/actions/program_execute.go @@ -43,10 +43,6 @@ func (*ProgramExecute) GetTypeID() uint8 { return programExecuteID } -func (*ProgramExecute) GetActionID(i uint8, txID ids.ID) codec.LID { - return codec.CreateLID(i, txID) -} - func (t *ProgramExecute) StateKeys(actor codec.Address, _ codec.LID) state.Keys { return state.Keys{} } From cd5fbdb662b991607e78c7ea58ee230a2f51b369 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 11:04:56 -0400 Subject: [PATCH 52/78] move GetMaxActionsPerTx --- README.md | 5 ++--- chain/dependencies.go | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6018f657c2..e60b9d5aeb 100644 --- a/README.md +++ b/README.md @@ -743,6 +743,7 @@ type Rules interface { GetMinBlockGap() int64 // in milliseconds GetMinEmptyBlockGap() int64 // in milliseconds GetValidityWindow() int64 // in milliseconds + GetMaxActionsPerTx() uint8 GetMinUnitPrice() Dimensions GetUnitPriceChangeDenominator() Dimensions @@ -771,9 +772,7 @@ type Rules interface { GetStorageKeyWriteUnits() uint64 GetStorageValueWriteUnits() uint64 // per chunk - FetchCustom(string) (any, bool) - - GetMaxActionsPerTx() uint8 + FetchCustom(string) (any, bool) } ``` diff --git a/chain/dependencies.go b/chain/dependencies.go index d2f4b9a92e..40dcd2cb9c 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -123,6 +123,7 @@ type Rules interface { GetMinBlockGap() int64 // in milliseconds GetMinEmptyBlockGap() int64 // in milliseconds GetValidityWindow() int64 // in milliseconds + GetMaxActionsPerTx() uint8 GetMinUnitPrice() fees.Dimensions GetUnitPriceChangeDenominator() fees.Dimensions @@ -152,8 +153,6 @@ type Rules interface { GetStorageValueWriteUnits() uint64 // per chunk FetchCustom(string) (any, bool) - - GetMaxActionsPerTx() uint8 } type MetadataManager interface { From ce2a89442da4e20647334ab410dd5f05f94e830c Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 11:22:43 -0400 Subject: [PATCH 53/78] add MaxOutputsPerAction --- README.md | 2 ++ chain/dependencies.go | 4 ++-- chain/errors.go | 1 + chain/transaction.go | 3 +++ examples/morpheusvm/genesis/genesis.go | 14 ++++++-------- examples/morpheusvm/genesis/rules.go | 12 ++++++++---- examples/tokenvm/genesis/genesis.go | 14 ++++++-------- examples/tokenvm/genesis/rules.go | 12 ++++++++---- x/programs/cmd/simulator/vm/genesis/genesis.go | 14 ++++++-------- x/programs/cmd/simulator/vm/genesis/rules.go | 12 ++++++++---- 10 files changed, 50 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index e60b9d5aeb..48c9ce1e9e 100644 --- a/README.md +++ b/README.md @@ -743,7 +743,9 @@ type Rules interface { GetMinBlockGap() int64 // in milliseconds GetMinEmptyBlockGap() int64 // in milliseconds GetValidityWindow() int64 // in milliseconds + GetMaxActionsPerTx() uint8 + GetMaxOutputsPerAction() uint8 GetMinUnitPrice() Dimensions GetUnitPriceChangeDenominator() Dimensions diff --git a/chain/dependencies.go b/chain/dependencies.go index 40dcd2cb9c..e46cf46d73 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -123,7 +123,9 @@ type Rules interface { GetMinBlockGap() int64 // in milliseconds GetMinEmptyBlockGap() int64 // in milliseconds GetValidityWindow() int64 // in milliseconds + GetMaxActionsPerTx() uint8 + GetMaxOutputsPerAction() uint8 GetMinUnitPrice() fees.Dimensions GetUnitPriceChangeDenominator() fees.Dimensions @@ -249,8 +251,6 @@ type Action interface { // // An error should only be returned if a fatal error was encountered, otherwise [success] should // be marked as false and fees will still be charged. - // - // TODO: Consider limiting number of outputs an [Action] can have Execute( ctx context.Context, r Rules, diff --git a/chain/errors.go b/chain/errors.go index e131f4cc86..5a3121cf22 100644 --- a/chain/errors.go +++ b/chain/errors.go @@ -51,6 +51,7 @@ var ( ErrInvalidActor = errors.New("invalid actor") ErrInvalidSponsor = errors.New("invalid sponsor") ErrTooManyActions = errors.New("too many actions") + ErrTooManyOutputs = errors.New("too many outputs") // Execution Correctness ErrInvalidBalance = errors.New("invalid balance") diff --git a/chain/transaction.go b/chain/transaction.go index c705a6e7eb..f0ac406a68 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -340,6 +340,9 @@ func (t *Transaction) Execute( // fast) return handleRevert(ErrInvalidObject) } + if len(outputs) > int(r.GetMaxOutputsPerAction()) { + return handleRevert(ErrTooManyOutputs) + } resultOutputs = append(resultOutputs, outputs) if !success { diff --git a/examples/morpheusvm/genesis/genesis.go b/examples/morpheusvm/genesis/genesis.go index adeedf5696..c14a4e47e0 100644 --- a/examples/morpheusvm/genesis/genesis.go +++ b/examples/morpheusvm/genesis/genesis.go @@ -43,7 +43,9 @@ type Genesis struct { MaxBlockUnits fees.Dimensions `json:"maxBlockUnits"` // must be possible to reach before block too large // Tx Parameters - ValidityWindow int64 `json:"validityWindow"` // ms + ValidityWindow int64 `json:"validityWindow"` // ms + MaxActionsPerTx uint8 `json:"maxActionsPerTx"` + MaxOutputsPerAction uint8 `json:"maxOutputsPerAction"` // Tx Fee Parameters BaseComputeUnits uint64 `json:"baseUnits"` @@ -54,9 +56,6 @@ type Genesis struct { StorageKeyWriteUnits uint64 `json:"storageKeyWriteUnits"` StorageValueWriteUnits uint64 `json:"storageValueWriteUnits"` // per chunk - // Action Per Tx - MaxActionsPerTx uint8 `json:"maxActionsPerTx"` - // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` } @@ -77,7 +76,9 @@ func Default() *Genesis { MaxBlockUnits: fees.Dimensions{1_800_000, 2_000, 2_000, 2_000, 2_000}, // Tx Parameters - ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms + ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms + MaxActionsPerTx: 1, + MaxOutputsPerAction: 1, // Tx Fee Compute Parameters BaseComputeUnits: 1, @@ -91,9 +92,6 @@ func Default() *Genesis { StorageValueAllocateUnits: 5, StorageKeyWriteUnits: 10, StorageValueWriteUnits: 3, - - // Action Per Tx - MaxActionsPerTx: 1, } } diff --git a/examples/morpheusvm/genesis/rules.go b/examples/morpheusvm/genesis/rules.go index aca4ff6d64..46c45ef2e3 100644 --- a/examples/morpheusvm/genesis/rules.go +++ b/examples/morpheusvm/genesis/rules.go @@ -44,6 +44,14 @@ func (r *Rules) GetValidityWindow() int64 { return r.g.ValidityWindow } +func (r *Rules) GetMaxActionsPerTx() uint8 { + return r.g.MaxActionsPerTx +} + +func (r *Rules) GetMaxOutputsPerAction() uint8 { + return r.g.MaxOutputsPerAction +} + func (r *Rules) GetMaxBlockUnits() fees.Dimensions { return r.g.MaxBlockUnits } @@ -95,7 +103,3 @@ func (r *Rules) GetWindowTargetUnits() fees.Dimensions { func (*Rules) FetchCustom(string) (any, bool) { return nil, false } - -func (r *Rules) GetMaxActionsPerTx() uint8 { - return r.g.MaxActionsPerTx -} diff --git a/examples/tokenvm/genesis/genesis.go b/examples/tokenvm/genesis/genesis.go index 4d342e6df8..cea202b2e9 100644 --- a/examples/tokenvm/genesis/genesis.go +++ b/examples/tokenvm/genesis/genesis.go @@ -43,7 +43,9 @@ type Genesis struct { MaxBlockUnits fees.Dimensions `json:"maxBlockUnits"` // must be possible to reach before block too large // Tx Parameters - ValidityWindow int64 `json:"validityWindow"` // ms + ValidityWindow int64 `json:"validityWindow"` // ms + MaxActionsPerTx uint8 `json:"maxActionsPerTx"` + MaxOutputsPerAction uint8 `json:"maxOutputsPerAction"` // Tx Fee Parameters BaseComputeUnits uint64 `json:"baseUnits"` @@ -54,9 +56,6 @@ type Genesis struct { StorageKeyWriteUnits uint64 `json:"storageKeyWriteUnits"` StorageValueWriteUnits uint64 `json:"storageValueWriteUnits"` // per chunk - // Action Per Tx - MaxActionsPerTx uint8 `json:"maxActionsPerTx"` - // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` } @@ -77,7 +76,9 @@ func Default() *Genesis { MaxBlockUnits: fees.Dimensions{1_800_000, 2_000, 2_000, 2_000, 2_000}, // Tx Parameters - ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms + ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms + MaxActionsPerTx: 10, + MaxOutputsPerAction: 1, // Tx Fee Compute Parameters BaseComputeUnits: 1, @@ -91,9 +92,6 @@ func Default() *Genesis { StorageValueAllocateUnits: 5, StorageKeyWriteUnits: 10, StorageValueWriteUnits: 3, - - // Action Per Tx - MaxActionsPerTx: 10, } } diff --git a/examples/tokenvm/genesis/rules.go b/examples/tokenvm/genesis/rules.go index 03bbf9386f..dcbfe39642 100644 --- a/examples/tokenvm/genesis/rules.go +++ b/examples/tokenvm/genesis/rules.go @@ -44,6 +44,14 @@ func (r *Rules) GetValidityWindow() int64 { return r.g.ValidityWindow } +func (r *Rules) GetMaxActionsPerTx() uint8 { + return r.g.MaxActionsPerTx +} + +func (r *Rules) GetMaxOutputsPerAction() uint8 { + return r.g.MaxOutputsPerAction +} + func (r *Rules) GetMaxBlockUnits() fees.Dimensions { return r.g.MaxBlockUnits } @@ -95,7 +103,3 @@ func (r *Rules) GetWindowTargetUnits() fees.Dimensions { func (*Rules) FetchCustom(string) (any, bool) { return nil, false } - -func (r *Rules) GetMaxActionsPerTx() uint8 { - return r.g.MaxActionsPerTx -} diff --git a/x/programs/cmd/simulator/vm/genesis/genesis.go b/x/programs/cmd/simulator/vm/genesis/genesis.go index 729d42c2a2..e730c1b526 100644 --- a/x/programs/cmd/simulator/vm/genesis/genesis.go +++ b/x/programs/cmd/simulator/vm/genesis/genesis.go @@ -44,7 +44,9 @@ type Genesis struct { MaxBlockUnits fees.Dimensions `json:"maxBlockUnits"` // must be possible to reach before block too large // Tx Parameters - ValidityWindow int64 `json:"validityWindow"` // ms + ValidityWindow int64 `json:"validityWindow"` // ms + MaxActionsPerTx uint8 `json:"maxActionsPerTx"` + MaxOutputsPerAction uint8 `json:"maxOutputsPerAction"` // Tx Fee Parameters BaseComputeUnits uint64 `json:"baseUnits"` @@ -59,9 +61,6 @@ type Genesis struct { EnableDebugMode bool `json:"enableDebugMode"` EnableBulkMemory bool `json:"enableBulkMemory"` - // Action Per Tx - MaxActionsPerTx uint8 `json:"maxActionsPerTx"` - // Allocates CustomAllocation []*CustomAllocation `json:"customAllocation"` } @@ -97,15 +96,14 @@ func Default() *Genesis { StorageValueWriteUnits: 3, // Tx Parameters - ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms + ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms + MaxActionsPerTx: 1, + MaxOutputsPerAction: 1, // program Runtime Parameters EnableDebugMode: true, // Enabled to only enable Wasi for testing mode EnableBulkMemory: true, - - // Action Per Tx - MaxActionsPerTx: 1, } } diff --git a/x/programs/cmd/simulator/vm/genesis/rules.go b/x/programs/cmd/simulator/vm/genesis/rules.go index 8676653e25..1952a2e81e 100644 --- a/x/programs/cmd/simulator/vm/genesis/rules.go +++ b/x/programs/cmd/simulator/vm/genesis/rules.go @@ -39,6 +39,14 @@ func (r *Rules) GetValidityWindow() int64 { return r.g.ValidityWindow } +func (r *Rules) GetMaxActionsPerTx() uint8 { + return r.g.MaxActionsPerTx +} + +func (r *Rules) GetMaxOutputsPerAction() uint8 { + return r.g.MaxOutputsPerAction +} + func (r *Rules) GetMinUnitPrice() fees.Dimensions { return r.g.MinUnitPrice } @@ -94,7 +102,3 @@ func (r *Rules) ChainID() ids.ID { func (*Rules) FetchCustom(string) (any, bool) { return nil, false } - -func (r *Rules) GetMaxActionsPerTx() uint8 { - return r.g.MaxActionsPerTx -} From 63c7d3bba47b81a3421238654a0f0581db372d3c Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 11:27:16 -0400 Subject: [PATCH 54/78] actions instead of action --- chain/transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/transaction.go b/chain/transaction.go index f0ac406a68..87547f1c2f 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -29,7 +29,7 @@ var ( type Transaction struct { Base *Base `json:"base"` - Actions []Action `json:"action"` + Actions []Action `json:"actions"` Auth Auth `json:"auth"` digest []byte From c95fa4ad24bfe6f19384be4727fd31d1f2ae0d48 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 11:36:58 -0400 Subject: [PATCH 55/78] do sponsorkeys after action iteration --- chain/transaction.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 87547f1c2f..02823c8a34 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -112,16 +112,20 @@ func (t *Transaction) StateKeys(sm StateManager) (state.Keys, error) { // Verify the formatting of state keys passed by the controller for i, action := range t.Actions { actionKeys := action.StateKeys(t.Auth.Actor(), codec.CreateLID(uint8(i), t.id)) - sponsorKeys := sm.SponsorStateKeys(t.Auth.Sponsor()) - for _, m := range []state.Keys{actionKeys, sponsorKeys} { - for k, v := range m { - if !keys.Valid(k) { - return nil, ErrInvalidKeyValue - } - stateKeys.Add(k, v) + for k, v := range actionKeys { + if !keys.Valid(k) { + return nil, ErrInvalidKeyValue } + stateKeys.Add(k, v) } } + sponsorKeys := sm.SponsorStateKeys(t.Auth.Sponsor()) + for k, v := range sponsorKeys { + if !keys.Valid(k) { + return nil, ErrInvalidKeyValue + } + stateKeys.Add(k, v) + } // Cache keys if called again t.stateKeys = stateKeys From a1a20a5f88773d80dabb2839990a277a1e5fa3d9 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 11:50:02 -0400 Subject: [PATCH 56/78] add action idx and type to ErrActionNotActivated --- chain/transaction.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 02823c8a34..f1fd4614f7 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -256,13 +256,13 @@ func (t *Transaction) PreExecute( if len(t.Actions) > int(r.GetMaxActionsPerTx()) { return ErrTooManyActions } - for _, action := range t.Actions { + for i, action := range t.Actions { start, end := action.ValidRange(r) if start >= 0 && timestamp < start { - return ErrActionNotActivated + return fmt.Errorf("%w: action type %d at index %d", ErrActionNotActivated, action.GetTypeID(), i) } if end >= 0 && timestamp > end { - return ErrActionNotActivated + return fmt.Errorf("%w: action type %d at index %d", ErrActionNotActivated, action.GetTypeID(), i) } } start, end := t.Auth.ValidRange(r) From 27e34b94f0377238bf086004b55e8895ff01a0b3 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 12:04:35 -0400 Subject: [PATCH 57/78] dont override prev outputs in handleRevert --- chain/transaction.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index f1fd4614f7..19a981c9a9 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -318,12 +318,19 @@ func (t *Transaction) Execute( // We create a temp state checkpoint to ensure we don't commit failed actions to state. actionStart := ts.OpIndex() - handleRevert := func(rerr error) (*Result, error) { + handleRevert := func(outputs [][]byte, rerr error) (*Result, error) { // Be warned that the variables captured in this function // are set when this function is defined. If any of them are // modified later, they will not be used here. + // + // Note: Revert will not return an error per action. ts.Rollback(ctx, actionStart) - return &Result{false, [][][]byte{{utils.ErrBytes(rerr)}}, maxUnits, maxFee}, nil + resultOutputs := [][][]byte{} + if outputs != nil { + resultOutputs = append(resultOutputs, outputs) + } + resultOutputs = append(resultOutputs, [][]byte{utils.ErrBytes(rerr)}) + return &Result{false, resultOutputs, maxUnits, maxFee}, nil } var ( @@ -337,15 +344,15 @@ func (t *Transaction) Execute( actionID := codec.CreateLID(uint8(i), t.id) success, actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) if err != nil { - return handleRevert(err) + return handleRevert(nil, err) } if len(outputs) == 0 && outputs != nil { // Enforce object standardization (this is a VM bug and we should fail // fast) - return handleRevert(ErrInvalidObject) + return handleRevert(nil, ErrInvalidObject) } if len(outputs) > int(r.GetMaxOutputsPerAction()) { - return handleRevert(ErrTooManyOutputs) + return handleRevert(outputs, ErrTooManyOutputs) } resultOutputs = append(resultOutputs, outputs) @@ -360,7 +367,7 @@ func (t *Transaction) Execute( computeUnitsOp.Add(actionCUs) computeUnits, err := computeUnitsOp.Value() if err != nil { - return handleRevert(err) + return handleRevert(outputs, err) } // Because the key database is abstracted from [Auth]/[Actions], we can compute @@ -374,7 +381,7 @@ func (t *Transaction) Execute( // so we don't need to check for pre-existing values. maxChunks, ok := keys.MaxChunks([]byte(key)) if !ok { - return handleRevert(ErrInvalidKeyValue) + return handleRevert(outputs, ErrInvalidKeyValue) } writes[key] = maxChunks } @@ -388,7 +395,7 @@ func (t *Transaction) Execute( } readUnits, err := readsOp.Value() if err != nil { - return handleRevert(err) + return handleRevert(outputs, err) } allocatesOp := math.NewUint64Operator(0) for _, chunksStored := range allocates { @@ -397,7 +404,7 @@ func (t *Transaction) Execute( } allocateUnits, err := allocatesOp.Value() if err != nil { - return handleRevert(err) + return handleRevert(outputs, err) } writesOp := math.NewUint64Operator(0) for _, chunksModified := range writes { @@ -406,12 +413,12 @@ func (t *Transaction) Execute( } writeUnits, err := writesOp.Value() if err != nil { - return handleRevert(err) + return handleRevert(outputs, err) } used := fees.Dimensions{uint64(t.Size()), computeUnits, readUnits, allocateUnits, writeUnits} nused, err := fees.Add(totalUsed, used) if err != nil { - return handleRevert(err) + return handleRevert(outputs, err) } totalUsed = nused @@ -428,7 +435,7 @@ func (t *Transaction) Execute( // To avoid storage abuse of [Auth.Refund], we precharge for possible usage. feeRequired, err := feeManager.MaxFee(used) if err != nil { - return handleRevert(err) + return handleRevert(outputs, err) } totalFeeRequired += feeRequired @@ -438,7 +445,7 @@ func (t *Transaction) Execute( err = s.Refund(ctx, t.Auth.Sponsor(), ts, refund) ts.EnableAllocation() if err != nil { - return handleRevert(err) + return handleRevert(outputs, err) } } } From bc8a81436636c8e2be75088858d39a488bd434dc Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 12:58:38 -0400 Subject: [PATCH 58/78] skip all if one action is failed, do fee comp at the end, refund once --- chain/transaction.go | 166 +++++++++++++++++++++---------------------- 1 file changed, 81 insertions(+), 85 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 19a981c9a9..8523691776 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -334,11 +334,9 @@ func (t *Transaction) Execute( } var ( - totalFeeRequired uint64 - - resultOutputs = [][][]byte{} - totalUsed = fees.Dimensions{} - txSuccess = true + computeUnitsOp = math.NewUint64Operator(r.GetBaseComputeUnits()) + resultOutputs = [][][]byte{} + txSuccess = true ) for i, action := range t.Actions { actionID := codec.CreateLID(uint8(i), t.id) @@ -356,105 +354,103 @@ func (t *Transaction) Execute( } resultOutputs = append(resultOutputs, outputs) + if !txSuccess { + break + } if !success { txSuccess = false ts.Rollback(ctx, actionStart) } // Calculate units used - computeUnitsOp := math.NewUint64Operator(r.GetBaseComputeUnits()) - computeUnitsOp.Add(t.Auth.ComputeUnits(r)) computeUnitsOp.Add(actionCUs) - computeUnits, err := computeUnitsOp.Value() - if err != nil { - return handleRevert(outputs, err) - } + } + computeUnitsOp.Add(t.Auth.ComputeUnits(r)) + computeUnits, err := computeUnitsOp.Value() + if err != nil { + return handleRevert(nil, err) + } - // Because the key database is abstracted from [Auth]/[Actions], we can compute - // all storage use in the background. KeyOperations is unique to a view. - allocates, writes := ts.KeyOperations() - - // Because we compute the fee before [Auth.Refund] is called, we need - // to pessimistically precompute the storage it will change. - for key := range s.SponsorStateKeys(t.Auth.Sponsor()) { - // maxChunks will be greater than the chunks read in any of these keys, - // so we don't need to check for pre-existing values. - maxChunks, ok := keys.MaxChunks([]byte(key)) - if !ok { - return handleRevert(outputs, ErrInvalidKeyValue) - } - writes[key] = maxChunks - } + // Because the key database is abstracted from [Auth]/[Actions], we can compute + // all storage use in the background. KeyOperations is unique to a view. + allocates, writes := ts.KeyOperations() - // We only charge for the chunks read from disk instead of charging for the max chunks - // specified by the key. - readsOp := math.NewUint64Operator(0) - for _, chunksRead := range reads { - readsOp.Add(r.GetStorageKeyReadUnits()) - readsOp.MulAdd(uint64(chunksRead), r.GetStorageValueReadUnits()) - } - readUnits, err := readsOp.Value() - if err != nil { - return handleRevert(outputs, err) - } - allocatesOp := math.NewUint64Operator(0) - for _, chunksStored := range allocates { - allocatesOp.Add(r.GetStorageKeyAllocateUnits()) - allocatesOp.MulAdd(uint64(chunksStored), r.GetStorageValueAllocateUnits()) - } - allocateUnits, err := allocatesOp.Value() - if err != nil { - return handleRevert(outputs, err) - } - writesOp := math.NewUint64Operator(0) - for _, chunksModified := range writes { - writesOp.Add(r.GetStorageKeyWriteUnits()) - writesOp.MulAdd(uint64(chunksModified), r.GetStorageValueWriteUnits()) - } - writeUnits, err := writesOp.Value() - if err != nil { - return handleRevert(outputs, err) - } - used := fees.Dimensions{uint64(t.Size()), computeUnits, readUnits, allocateUnits, writeUnits} - nused, err := fees.Add(totalUsed, used) - if err != nil { - return handleRevert(outputs, err) + // Because we compute the fee before [Auth.Refund] is called, we need + // to pessimistically precompute the storage it will change. + for key := range s.SponsorStateKeys(t.Auth.Sponsor()) { + // maxChunks will be greater than the chunks read in any of these keys, + // so we don't need to check for pre-existing values. + maxChunks, ok := keys.MaxChunks([]byte(key)) + if !ok { + return handleRevert(nil, ErrInvalidKeyValue) } - totalUsed = nused + writes[key] = maxChunks + } - // Check to see if the units consumed are greater than the max units - // - // This should never be the case but erroring here is better than - // underflowing the refund. - if !maxUnits.Greater(used) { - return nil, fmt.Errorf("%w: max=%+v consumed=%+v", ErrInvalidUnitsConsumed, maxUnits, used) - } + // We only charge for the chunks read from disk instead of charging for the max chunks + // specified by the key. + readsOp := math.NewUint64Operator(0) + for _, chunksRead := range reads { + readsOp.Add(r.GetStorageKeyReadUnits()) + readsOp.MulAdd(uint64(chunksRead), r.GetStorageValueReadUnits()) + } + readUnits, err := readsOp.Value() + if err != nil { + return handleRevert(nil, err) + } + allocatesOp := math.NewUint64Operator(0) + for _, chunksStored := range allocates { + allocatesOp.Add(r.GetStorageKeyAllocateUnits()) + allocatesOp.MulAdd(uint64(chunksStored), r.GetStorageValueAllocateUnits()) + } + allocateUnits, err := allocatesOp.Value() + if err != nil { + return handleRevert(nil, err) + } + writesOp := math.NewUint64Operator(0) + for _, chunksModified := range writes { + writesOp.Add(r.GetStorageKeyWriteUnits()) + writesOp.MulAdd(uint64(chunksModified), r.GetStorageValueWriteUnits()) + } + writeUnits, err := writesOp.Value() + if err != nil { + return handleRevert(nil, err) + } + used := fees.Dimensions{uint64(t.Size()), computeUnits, readUnits, allocateUnits, writeUnits} + if err != nil { + return handleRevert(nil, err) + } - // Return any funds from unused units - // - // To avoid storage abuse of [Auth.Refund], we precharge for possible usage. - feeRequired, err := feeManager.MaxFee(used) + // Check to see if the units consumed are greater than the max units + // + // This should never be the case but erroring here is better than + // underflowing the refund. + if !maxUnits.Greater(used) { + return nil, fmt.Errorf("%w: max=%+v consumed=%+v", ErrInvalidUnitsConsumed, maxUnits, used) + } + + // Return any funds from unused units + // + // To avoid storage abuse of [Auth.Refund], we precharge for possible usage. + feeRequired, err := feeManager.MaxFee(used) + if err != nil { + return handleRevert(nil, err) + } + refund := maxFee - feeRequired + if refund > 0 { + ts.DisableAllocation() + err = s.Refund(ctx, t.Auth.Sponsor(), ts, refund) + ts.EnableAllocation() if err != nil { - return handleRevert(outputs, err) - } - totalFeeRequired += feeRequired - - refund := maxFee - feeRequired - if refund > 0 { - ts.DisableAllocation() - err = s.Refund(ctx, t.Auth.Sponsor(), ts, refund) - ts.EnableAllocation() - if err != nil { - return handleRevert(outputs, err) - } + return handleRevert(nil, err) } } return &Result{ Success: txSuccess, Outputs: resultOutputs, - Consumed: totalUsed, - Fee: totalFeeRequired, + Consumed: used, + Fee: feeRequired, }, nil } From 06682428f81caba170269fa42143fba9ff33fa0e Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 13:09:42 -0400 Subject: [PATCH 59/78] introduce LIDLen --- codec/address.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codec/address.go b/codec/address.go index 4032f597c8..0f32ce574b 100644 --- a/codec/address.go +++ b/codec/address.go @@ -12,6 +12,7 @@ import ( const ( AddressLen = 33 + LIDLen = 33 // These consts are pulled from BIP-173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki fromBits = 8 @@ -22,7 +23,7 @@ const ( ) type ( - LID [AddressLen]byte // Long ID + LID [LIDLen]byte // Long ID Address LID ) @@ -37,7 +38,7 @@ var EmptyAddress = [AddressLen]byte{} // in the [Action] array of a transaction. txID is the // ID of that transaction. func CreateLID(i uint8, id ids.ID) LID { - a := make([]byte, AddressLen) + a := make([]byte, LIDLen) a[0] = i copy(a[1:], id[:]) return LID(a) From 8010dd77cca424648fe9452fb2fe9ac8956cee70 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 13:47:25 -0400 Subject: [PATCH 60/78] fix type alias --- codec/address.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codec/address.go b/codec/address.go index 0f32ce574b..de1017b60b 100644 --- a/codec/address.go +++ b/codec/address.go @@ -24,10 +24,10 @@ const ( type ( LID [LIDLen]byte // Long ID - Address LID + Address = LID ) -var EmptyAddress = [AddressLen]byte{} +var EmptyAddress = LID{} // CreateLID returns [LID] made from concatenating // some [i] with an [id]. From 929c742a6e93d990347e99476eae1e708540def6 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 13:55:42 -0400 Subject: [PATCH 61/78] use PackLID --- codec/optional_packer.go | 4 ++-- codec/packer.go | 17 +++-------------- codec/packer_test.go | 4 ++-- examples/morpheusvm/actions/transfer.go | 4 ++-- examples/tokenvm/actions/burn_asset.go | 4 ++-- examples/tokenvm/actions/close_order.go | 8 ++++---- examples/tokenvm/actions/create_order.go | 8 ++++---- examples/tokenvm/actions/fill_order.go | 16 ++++++++-------- examples/tokenvm/actions/mint_asset.go | 8 ++++---- examples/tokenvm/actions/transfer.go | 8 ++++---- 10 files changed, 35 insertions(+), 46 deletions(-) diff --git a/codec/optional_packer.go b/codec/optional_packer.go index d90736d3bf..b9104b7e8b 100644 --- a/codec/optional_packer.go +++ b/codec/optional_packer.go @@ -141,7 +141,7 @@ func (o *OptionalPacker) PackAddress(addr Address) { o.skipBit() return } - o.ip.PackAddress(addr) + o.ip.PackLID(addr) o.setBit() } @@ -149,7 +149,7 @@ func (o *OptionalPacker) PackAddress(addr Address) { // the current offset. Increments offset regardless. func (o *OptionalPacker) UnpackAddress(dest *Address) { if o.checkBit() { - o.ip.UnpackAddress(dest) + o.ip.UnpackLID(true, dest) } else { *dest = EmptyAddress } diff --git a/codec/packer.go b/codec/packer.go index 0120568b5d..e965bf5afb 100644 --- a/codec/packer.go +++ b/codec/packer.go @@ -70,23 +70,12 @@ func (p *Packer) PackFixedBytes(b []byte) { p.p.PackFixedBytes(b) } -func (p *Packer) PackAddress(a Address) { +func (p *Packer) PackLID(a LID) { p.p.PackFixedBytes(a[:]) } -func (p *Packer) UnpackAddress(dest *Address) { - copy((*dest)[:], p.p.UnpackFixedBytes(AddressLen)) - if *dest == EmptyAddress { - p.addErr(fmt.Errorf("%w: Address field is not populated", ErrFieldNotPopulated)) - } -} - -func (p *Packer) PackActionID(a LID) { - p.p.PackFixedBytes(a[:]) -} - -func (p *Packer) UnpackActionID(required bool, dest *LID) { - copy((*dest)[:], p.p.UnpackFixedBytes(AddressLen)) +func (p *Packer) UnpackLID(required bool, dest *LID) { + copy((*dest)[:], p.p.UnpackFixedBytes(LIDLen)) if required && *dest == EmptyAddress { p.addErr(fmt.Errorf("%w: LID field is not populated", ErrFieldNotPopulated)) } diff --git a/codec/packer_test.go b/codec/packer_test.go index 001e902604..0655d44385 100644 --- a/codec/packer_test.go +++ b/codec/packer_test.go @@ -99,7 +99,7 @@ func TestPackerAddress(t *testing.T) { t.Run("Pack", func(t *testing.T) { require := require.New(t) - wp.PackAddress(addr) + wp.PackLID(addr) b := wp.Bytes() require.NoError(wp.Err()) require.Len(b, AddressLen) @@ -112,7 +112,7 @@ func TestPackerAddress(t *testing.T) { rp := NewReader(wp.Bytes(), AddressLen) require.Equal(wp.Bytes(), rp.Bytes()) var unpackedAddr Address - rp.UnpackAddress(&unpackedAddr) + rp.UnpackLID(true, &unpackedAddr) require.Equal(addr[:], unpackedAddr[:]) require.NoError(rp.Err()) }) diff --git a/examples/morpheusvm/actions/transfer.go b/examples/morpheusvm/actions/transfer.go index 44055afabd..c5e06cecd9 100644 --- a/examples/morpheusvm/actions/transfer.go +++ b/examples/morpheusvm/actions/transfer.go @@ -69,13 +69,13 @@ func (*Transfer) Size() int { } func (t *Transfer) Marshal(p *codec.Packer) { - p.PackAddress(t.To) + p.PackLID(t.To) p.PackUint64(t.Value) } func UnmarshalTransfer(p *codec.Packer) (chain.Action, error) { var transfer Transfer - p.UnpackAddress(&transfer.To) // we do not verify the typeID is valid + p.UnpackLID(true, &transfer.To) // we do not verify the typeID is valid transfer.Value = p.UnpackUint64(true) if err := p.Err(); err != nil { return nil, err diff --git a/examples/tokenvm/actions/burn_asset.go b/examples/tokenvm/actions/burn_asset.go index 45763e20c9..3237899b61 100644 --- a/examples/tokenvm/actions/burn_asset.go +++ b/examples/tokenvm/actions/burn_asset.go @@ -81,13 +81,13 @@ func (*BurnAsset) Size() int { } func (b *BurnAsset) Marshal(p *codec.Packer) { - p.PackActionID(b.Asset) + p.PackLID(b.Asset) p.PackUint64(b.Value) } func UnmarshalBurnAsset(p *codec.Packer) (chain.Action, error) { var burn BurnAsset - p.UnpackActionID(false, &burn.Asset) // can burn native asset + p.UnpackLID(false, &burn.Asset) // can burn native asset burn.Value = p.UnpackUint64(true) return &burn, p.Err() } diff --git a/examples/tokenvm/actions/close_order.go b/examples/tokenvm/actions/close_order.go index 4ca867f187..67cbbcd015 100644 --- a/examples/tokenvm/actions/close_order.go +++ b/examples/tokenvm/actions/close_order.go @@ -79,14 +79,14 @@ func (*CloseOrder) Size() int { } func (c *CloseOrder) Marshal(p *codec.Packer) { - p.PackActionID(c.Order) - p.PackActionID(c.Out) + p.PackLID(c.Order) + p.PackLID(c.Out) } func UnmarshalCloseOrder(p *codec.Packer) (chain.Action, error) { var cl CloseOrder - p.UnpackActionID(true, &cl.Order) - p.UnpackActionID(false, &cl.Out) // empty ID is the native asset + p.UnpackLID(true, &cl.Order) + p.UnpackLID(false, &cl.Out) // empty ID is the native asset return &cl, p.Err() } diff --git a/examples/tokenvm/actions/create_order.go b/examples/tokenvm/actions/create_order.go index 4b4d26458f..c55bfa3e62 100644 --- a/examples/tokenvm/actions/create_order.go +++ b/examples/tokenvm/actions/create_order.go @@ -102,18 +102,18 @@ func (*CreateOrder) Size() int { } func (c *CreateOrder) Marshal(p *codec.Packer) { - p.PackActionID(c.In) + p.PackLID(c.In) p.PackUint64(c.InTick) - p.PackActionID(c.Out) + p.PackLID(c.Out) p.PackUint64(c.OutTick) p.PackUint64(c.Supply) } func UnmarshalCreateOrder(p *codec.Packer) (chain.Action, error) { var create CreateOrder - p.UnpackActionID(false, &create.In) // empty ID is the native asset + p.UnpackLID(false, &create.In) // empty ID is the native asset create.InTick = p.UnpackUint64(true) - p.UnpackActionID(false, &create.Out) // empty ID is the native asset + p.UnpackLID(false, &create.Out) // empty ID is the native asset create.OutTick = p.UnpackUint64(true) create.Supply = p.UnpackUint64(true) return &create, p.Err() diff --git a/examples/tokenvm/actions/fill_order.go b/examples/tokenvm/actions/fill_order.go index 5d6ec4634f..1bd9e1fcec 100644 --- a/examples/tokenvm/actions/fill_order.go +++ b/examples/tokenvm/actions/fill_order.go @@ -157,19 +157,19 @@ func (*FillOrder) Size() int { } func (f *FillOrder) Marshal(p *codec.Packer) { - p.PackActionID(f.Order) - p.PackAddress(f.Owner) - p.PackActionID(f.In) - p.PackActionID(f.Out) + p.PackLID(f.Order) + p.PackLID(f.Owner) + p.PackLID(f.In) + p.PackLID(f.Out) p.PackUint64(f.Value) } func UnmarshalFillOrder(p *codec.Packer) (chain.Action, error) { var fill FillOrder - p.UnpackActionID(true, &fill.Order) - p.UnpackAddress(&fill.Owner) - p.UnpackActionID(false, &fill.In) // empty ID is the native asset - p.UnpackActionID(false, &fill.Out) // empty ID is the native asset + p.UnpackLID(true, &fill.Order) + p.UnpackLID(true, &fill.Owner) + p.UnpackLID(false, &fill.In) // empty ID is the native asset + p.UnpackLID(false, &fill.Out) // empty ID is the native asset fill.Value = p.UnpackUint64(true) return &fill, p.Err() } diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index 71e52c1ca8..489cf4dfc7 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -90,15 +90,15 @@ func (*MintAsset) Size() int { } func (m *MintAsset) Marshal(p *codec.Packer) { - p.PackAddress(m.To) - p.PackActionID(m.Asset) + p.PackLID(m.To) + p.PackLID(m.Asset) p.PackUint64(m.Value) } func UnmarshalMintAsset(p *codec.Packer) (chain.Action, error) { var mint MintAsset - p.UnpackAddress(&mint.To) - p.UnpackActionID(true, &mint.Asset) // empty ID is the native asset + p.UnpackLID(true, &mint.To) + p.UnpackLID(true, &mint.Asset) // empty ID is the native asset mint.Value = p.UnpackUint64(true) return &mint, p.Err() } diff --git a/examples/tokenvm/actions/transfer.go b/examples/tokenvm/actions/transfer.go index c998b422fb..499d477a0a 100644 --- a/examples/tokenvm/actions/transfer.go +++ b/examples/tokenvm/actions/transfer.go @@ -78,16 +78,16 @@ func (t *Transfer) Size() int { } func (t *Transfer) Marshal(p *codec.Packer) { - p.PackAddress(t.To) - p.PackActionID(t.Asset) + p.PackLID(t.To) + p.PackLID(t.Asset) p.PackUint64(t.Value) p.PackBytes(t.Memo) } func UnmarshalTransfer(p *codec.Packer) (chain.Action, error) { var transfer Transfer - p.UnpackAddress(&transfer.To) - p.UnpackActionID(false, &transfer.Asset) // empty ID is the native asset + p.UnpackLID(true, &transfer.To) + p.UnpackLID(false, &transfer.Asset) // empty ID is the native asset transfer.Value = p.UnpackUint64(true) p.UnpackBytes(MaxMemoSize, false, &transfer.Memo) return &transfer, p.Err() From 77579680cce7b93c8f9ba4c701278d7288951d0b Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 14:02:13 -0400 Subject: [PATCH 62/78] use codec.LIDLen --- .../cmd/token-wallet/backend/storage.go | 6 +-- examples/tokenvm/storage/storage.go | 44 +++++++++---------- .../cmd/simulator/vm/storage/storage.go | 2 +- x/programs/examples/storage/storage.go | 6 +-- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/examples/tokenvm/cmd/token-wallet/backend/storage.go b/examples/tokenvm/cmd/token-wallet/backend/storage.go index 2dd85cdaaf..c68d5d5aac 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/storage.go +++ b/examples/tokenvm/cmd/token-wallet/backend/storage.go @@ -64,7 +64,7 @@ func (s *Storage) GetKey() (ed25519.PrivateKey, error) { } func (s *Storage) StoreAsset(assetID codec.LID, owned bool) error { - k := make([]byte, 1+codec.AddressLen) + k := make([]byte, 1+codec.LIDLen) k[0] = assetsPrefix copy(k[1:], assetID[:]) v := []byte{0x0} @@ -75,7 +75,7 @@ func (s *Storage) StoreAsset(assetID codec.LID, owned bool) error { } func (s *Storage) HasAsset(assetID codec.LID) (bool, error) { - k := make([]byte, 1+codec.AddressLen) + k := make([]byte, 1+codec.LIDLen) k[0] = assetsPrefix copy(k[1:], assetID[:]) return s.db.Has(k) @@ -181,7 +181,7 @@ func (s *Storage) GetSolutions() ([]*FaucetSearchInfo, error) { func (s *Storage) StoreOrder(orderID codec.LID) error { inverseTime := consts.MaxUint64 - uint64(time.Now().UnixMilli()) - k := make([]byte, 1+consts.Uint64Len+codec.AddressLen) + k := make([]byte, 1+consts.Uint64Len+codec.LIDLen) k[0] = orderPrefix binary.BigEndian.PutUint64(k[1:], inverseTime) copy(k[1+consts.Uint64Len:], orderID[:]) diff --git a/examples/tokenvm/storage/storage.go b/examples/tokenvm/storage/storage.go index 94d6753169..84596a4923 100644 --- a/examples/tokenvm/storage/storage.go +++ b/examples/tokenvm/storage/storage.go @@ -69,7 +69,7 @@ var ( balanceKeyPool = sync.Pool{ New: func() any { - return make([]byte, 1+codec.AddressLen*2+consts.Uint16Len) + return make([]byte, 1+codec.LIDLen*2+consts.Uint16Len) }, } ) @@ -136,7 +136,7 @@ func BalanceKey(addr codec.Address, asset codec.LID) (k []byte) { k[0] = balancePrefix copy(k[1:], addr[:]) copy(k[1+codec.AddressLen:], asset[:]) - binary.BigEndian.PutUint16(k[1+codec.AddressLen*2:], BalanceChunks) + binary.BigEndian.PutUint16(k[1+codec.AddressLen+codec.LIDLen:], BalanceChunks) return } @@ -282,10 +282,10 @@ func SubBalance( // [assetPrefix] + [address] func AssetKey(asset codec.LID) (k []byte) { - k = make([]byte, 1+codec.AddressLen+consts.Uint16Len) + k = make([]byte, 1+codec.LIDLen+consts.Uint16Len) k[0] = assetPrefix copy(k[1:], asset[:]) - binary.BigEndian.PutUint16(k[1+codec.AddressLen:], AssetChunks) + binary.BigEndian.PutUint16(k[1+codec.LIDLen:], AssetChunks) return } @@ -342,7 +342,7 @@ func SetAsset( k := AssetKey(asset) symbolLen := len(symbol) metadataLen := len(metadata) - v := make([]byte, consts.Uint16Len+symbolLen+consts.Uint8Len+consts.Uint16Len+metadataLen+consts.Uint64Len+codec.AddressLen) + v := make([]byte, consts.Uint16Len+symbolLen+consts.Uint8Len+consts.Uint16Len+metadataLen+consts.Uint64Len+codec.LIDLen) binary.BigEndian.PutUint16(v, uint16(symbolLen)) copy(v[consts.Uint16Len:], symbol) v[consts.Uint16Len+symbolLen] = decimals @@ -360,10 +360,10 @@ func DeleteAsset(ctx context.Context, mu state.Mutable, asset codec.LID) error { // [orderPrefix] + [actionID] func OrderKey(actionID codec.LID) (k []byte) { - k = make([]byte, 1+codec.AddressLen+consts.Uint16Len) + k = make([]byte, 1+codec.LIDLen+consts.Uint16Len) k[0] = orderPrefix copy(k[1:], actionID[:]) - binary.BigEndian.PutUint16(k[1+codec.AddressLen:], OrderChunks) + binary.BigEndian.PutUint16(k[1+codec.LIDLen:], OrderChunks) return } @@ -379,13 +379,13 @@ func SetOrder( owner codec.Address, ) error { k := OrderKey(actionID) - v := make([]byte, codec.AddressLen*2+consts.Uint64Len*3+codec.AddressLen) + v := make([]byte, codec.LIDLen*2+consts.Uint64Len*3+codec.AddressLen) copy(v, in[:]) - binary.BigEndian.PutUint64(v[codec.AddressLen:], inTick) - copy(v[codec.AddressLen+consts.Uint64Len:], out[:]) - binary.BigEndian.PutUint64(v[codec.AddressLen*2+consts.Uint64Len:], outTick) - binary.BigEndian.PutUint64(v[codec.AddressLen*2+consts.Uint64Len*2:], supply) - copy(v[codec.AddressLen*2+consts.Uint64Len*3:], owner[:]) + binary.BigEndian.PutUint64(v[codec.LIDLen:], inTick) + copy(v[codec.LIDLen+consts.Uint64Len:], out[:]) + binary.BigEndian.PutUint64(v[codec.LIDLen*2+consts.Uint64Len:], outTick) + binary.BigEndian.PutUint64(v[codec.LIDLen*2+consts.Uint64Len*2:], supply) + copy(v[codec.LIDLen*2+consts.Uint64Len*3:], owner[:]) return mu.Insert(ctx, k, v) } @@ -444,14 +444,14 @@ func innerGetOrder(v []byte, err error) ( return false, codec.EmptyAddress, 0, codec.EmptyAddress, 0, 0, codec.EmptyAddress, err } var in codec.LID - copy(in[:], v[:codec.AddressLen]) - inTick := binary.BigEndian.Uint64(v[codec.AddressLen:]) + copy(in[:], v[:codec.LIDLen]) + inTick := binary.BigEndian.Uint64(v[codec.LIDLen:]) var out codec.LID - copy(out[:], v[codec.AddressLen+consts.Uint64Len:codec.AddressLen*2+consts.Uint64Len]) - outTick := binary.BigEndian.Uint64(v[codec.AddressLen*2+consts.Uint64Len:]) - supply := binary.BigEndian.Uint64(v[codec.AddressLen*2+consts.Uint64Len*2:]) + copy(out[:], v[codec.LIDLen+consts.Uint64Len:codec.LIDLen*2+consts.Uint64Len]) + outTick := binary.BigEndian.Uint64(v[codec.LIDLen*2+consts.Uint64Len:]) + supply := binary.BigEndian.Uint64(v[codec.LIDLen*2+consts.Uint64Len*2:]) var owner codec.Address - copy(owner[:], v[codec.AddressLen*2+consts.Uint64Len*3:]) + copy(owner[:], v[codec.LIDLen*2+consts.Uint64Len*3:]) return true, in, inTick, out, outTick, supply, owner, nil } @@ -462,11 +462,11 @@ func DeleteOrder(ctx context.Context, mu state.Mutable, order codec.LID) error { // [loanPrefix] + [asset] + [destination] func LoanKey(asset codec.LID, destination ids.ID) (k []byte) { - k = make([]byte, 1+codec.AddressLen+consts.IDLen+consts.Uint16Len) + k = make([]byte, 1+codec.LIDLen+consts.IDLen+consts.Uint16Len) k[0] = loanPrefix copy(k[1:], asset[:]) - copy(k[1+codec.AddressLen:], destination[:]) - binary.BigEndian.PutUint16(k[1+codec.AddressLen+consts.IDLen:], LoanChunks) + copy(k[1+codec.LIDLen:], destination[:]) + binary.BigEndian.PutUint16(k[1+codec.LIDLen+consts.IDLen:], LoanChunks) return } diff --git a/x/programs/cmd/simulator/vm/storage/storage.go b/x/programs/cmd/simulator/vm/storage/storage.go index f6138ffbfe..c973bf43d5 100644 --- a/x/programs/cmd/simulator/vm/storage/storage.go +++ b/x/programs/cmd/simulator/vm/storage/storage.go @@ -48,7 +48,7 @@ const ProgramChunks uint16 = 1 // func ProgramKey(id codec.LID) (k []byte) { - k = make([]byte, 1+codec.AddressLen) + k = make([]byte, 1+codec.LIDLen) k[0] = programPrefix copy(k[1:], id[:]) return diff --git a/x/programs/examples/storage/storage.go b/x/programs/examples/storage/storage.go index ecc270431b..110065aa52 100644 --- a/x/programs/examples/storage/storage.go +++ b/x/programs/examples/storage/storage.go @@ -19,10 +19,10 @@ const ( ) func ProgramPrefixKey(id []byte, key []byte) (k []byte) { - k = make([]byte, codec.AddressLen+1+len(key)) + k = make([]byte, codec.LIDLen+1+len(key)) k[0] = programPrefix copy(k, id) - copy(k[codec.AddressLen:], (key)) + copy(k[codec.LIDLen:], (key)) return } @@ -31,7 +31,7 @@ func ProgramPrefixKey(id []byte, key []byte) (k []byte) { // func ProgramKey(id codec.LID) (k []byte) { - k = make([]byte, 1+codec.AddressLen) + k = make([]byte, 1+codec.LIDLen) copy(k[1:], id[:]) return } From 59da0dc3a53d8fc30d24a65199c127afc10cc300 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 14:40:15 -0400 Subject: [PATCH 63/78] EstimateMaxUnits over all actions --- chain/transaction.go | 21 +++++++++++++-------- cli/spam.go | 2 +- rpc/jsonrpc_client.go | 16 ++++------------ 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index 8523691776..abfcfef1ea 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -192,19 +192,24 @@ func (t *Transaction) MaxUnits(sm StateManager, r Rules) (fees.Dimensions, error // EstimateMaxUnits provides a pessimistic estimate of the cost to execute a transaction. This is // typically used during transaction construction. -func EstimateMaxUnits(r Rules, action Action, authFactory AuthFactory) (fees.Dimensions, error) { +func EstimateMaxUnits(r Rules, actions []Action, authFactory AuthFactory) (fees.Dimensions, error) { authBandwidth, authCompute := authFactory.MaxUnits() - bandwidth := BaseSize + consts.ByteLen + uint64(action.Size()) + consts.ByteLen + authBandwidth - actionStateKeysMaxChunks := action.StateKeysMaxChunks() + bandwidth := BaseSize + consts.ByteLen + consts.ByteLen + authBandwidth + computeUnitsOp := math.NewUint64Operator(r.GetBaseComputeUnits()) + computeUnitsOp.Add(authCompute) + + stateKeysMaxChunks := []uint16{} sponsorStateKeyMaxChunks := r.GetSponsorStateKeysMaxChunks() - stateKeysMaxChunks := make([]uint16, 0, len(sponsorStateKeyMaxChunks)+len(actionStateKeysMaxChunks)) stateKeysMaxChunks = append(stateKeysMaxChunks, sponsorStateKeyMaxChunks...) - stateKeysMaxChunks = append(stateKeysMaxChunks, actionStateKeysMaxChunks...) + for _, action := range actions { + bandwidth += uint64(action.Size()) + actionStateKeysMaxChunks := action.StateKeysMaxChunks() + stateKeysMaxChunks = append(stateKeysMaxChunks, actionStateKeysMaxChunks...) + + computeUnitsOp.Add(action.MaxComputeUnits(r)) + } // Estimate compute costs - computeUnitsOp := math.NewUint64Operator(r.GetBaseComputeUnits()) - computeUnitsOp.Add(authCompute) - computeUnitsOp.Add(action.MaxComputeUnits(r)) computeUnits, err := computeUnitsOp.Value() if err != nil { return fees.Dimensions{}, err diff --git a/cli/spam.go b/cli/spam.go index be0e29a334..a9fd68570e 100644 --- a/cli/spam.go +++ b/cli/spam.go @@ -111,7 +111,7 @@ func (h *Handler) Spam( return err } actions := getTransfer(keys[0].Address, 0) - maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), actions[0], factory) + maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), actions, factory) if err != nil { return err } diff --git a/rpc/jsonrpc_client.go b/rpc/jsonrpc_client.go index 41fb384449..43815026cb 100644 --- a/rpc/jsonrpc_client.go +++ b/rpc/jsonrpc_client.go @@ -132,19 +132,11 @@ func (cli *JSONRPCClient) GenerateTransaction( return nil, nil, 0, err } - totalUnits := fees.Dimensions{} - for _, action := range actions { - maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), action, authFactory) - if err != nil { - return nil, nil, 0, err - } - nTotalUnits, err := fees.Add(totalUnits, maxUnits) - if err != nil { - return nil, nil, 0, err - } - totalUnits = nTotalUnits + maxUnits, err := chain.EstimateMaxUnits(parser.Rules(time.Now().UnixMilli()), actions, authFactory) + if err != nil { + return nil, nil, 0, err } - maxFee, err := fees.MulSum(unitPrices, totalUnits) + maxFee, err := fees.MulSum(unitPrices, maxUnits) if err != nil { return nil, nil, 0, err } From 51dc7142a85190b62973fbc7259f810afb04f257 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 15:35:08 -0400 Subject: [PATCH 64/78] revert is populated in last output of last action --- chain/transaction.go | 36 +++++++++++++++++------------------- cli/spam.go | 8 +++----- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index abfcfef1ea..f62a7e8a2f 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -323,41 +323,39 @@ func (t *Transaction) Execute( // We create a temp state checkpoint to ensure we don't commit failed actions to state. actionStart := ts.OpIndex() - handleRevert := func(outputs [][]byte, rerr error) (*Result, error) { + resultOutputs := [][][]byte{} + handleRevert := func(rerr error) (*Result, error) { // Be warned that the variables captured in this function // are set when this function is defined. If any of them are // modified later, they will not be used here. // // Note: Revert will not return an error per action. ts.Rollback(ctx, actionStart) - resultOutputs := [][][]byte{} - if outputs != nil { - resultOutputs = append(resultOutputs, outputs) - } - resultOutputs = append(resultOutputs, [][]byte{utils.ErrBytes(rerr)}) + + // include error in the last action + resultOutputs[len(resultOutputs)-1] = append(resultOutputs[len(resultOutputs)-1], utils.ErrBytes(rerr)) return &Result{false, resultOutputs, maxUnits, maxFee}, nil } var ( computeUnitsOp = math.NewUint64Operator(r.GetBaseComputeUnits()) - resultOutputs = [][][]byte{} txSuccess = true ) for i, action := range t.Actions { actionID := codec.CreateLID(uint8(i), t.id) success, actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) if err != nil { - return handleRevert(nil, err) + return handleRevert(err) } if len(outputs) == 0 && outputs != nil { // Enforce object standardization (this is a VM bug and we should fail // fast) - return handleRevert(nil, ErrInvalidObject) + return handleRevert(ErrInvalidObject) } + resultOutputs = append(resultOutputs, outputs) if len(outputs) > int(r.GetMaxOutputsPerAction()) { - return handleRevert(outputs, ErrTooManyOutputs) + return handleRevert(ErrTooManyOutputs) } - resultOutputs = append(resultOutputs, outputs) if !txSuccess { break @@ -373,7 +371,7 @@ func (t *Transaction) Execute( computeUnitsOp.Add(t.Auth.ComputeUnits(r)) computeUnits, err := computeUnitsOp.Value() if err != nil { - return handleRevert(nil, err) + return handleRevert(err) } // Because the key database is abstracted from [Auth]/[Actions], we can compute @@ -387,7 +385,7 @@ func (t *Transaction) Execute( // so we don't need to check for pre-existing values. maxChunks, ok := keys.MaxChunks([]byte(key)) if !ok { - return handleRevert(nil, ErrInvalidKeyValue) + return handleRevert(ErrInvalidKeyValue) } writes[key] = maxChunks } @@ -401,7 +399,7 @@ func (t *Transaction) Execute( } readUnits, err := readsOp.Value() if err != nil { - return handleRevert(nil, err) + return handleRevert(err) } allocatesOp := math.NewUint64Operator(0) for _, chunksStored := range allocates { @@ -410,7 +408,7 @@ func (t *Transaction) Execute( } allocateUnits, err := allocatesOp.Value() if err != nil { - return handleRevert(nil, err) + return handleRevert(err) } writesOp := math.NewUint64Operator(0) for _, chunksModified := range writes { @@ -419,11 +417,11 @@ func (t *Transaction) Execute( } writeUnits, err := writesOp.Value() if err != nil { - return handleRevert(nil, err) + return handleRevert(err) } used := fees.Dimensions{uint64(t.Size()), computeUnits, readUnits, allocateUnits, writeUnits} if err != nil { - return handleRevert(nil, err) + return handleRevert(err) } // Check to see if the units consumed are greater than the max units @@ -439,7 +437,7 @@ func (t *Transaction) Execute( // To avoid storage abuse of [Auth.Refund], we precharge for possible usage. feeRequired, err := feeManager.MaxFee(used) if err != nil { - return handleRevert(nil, err) + return handleRevert(err) } refund := maxFee - feeRequired if refund > 0 { @@ -447,7 +445,7 @@ func (t *Transaction) Execute( err = s.Refund(ctx, t.Auth.Sponsor(), ts, refund) ts.EnableAllocation() if err != nil { - return handleRevert(nil, err) + return handleRevert(err) } } return &Result{ diff --git a/cli/spam.go b/cli/spam.go index a9fd68570e..5928f4f1a1 100644 --- a/cli/spam.go +++ b/cli/spam.go @@ -492,11 +492,9 @@ func startIssuer(cctx context.Context, issuer *txIssuer) { if result.Success { confirmedTxs++ } else { - for i := 0; i < len(result.Outputs); i++ { - for j := 0; j < len(result.Outputs[i]); j++ { - utils.Outf("{{orange}}on-chain tx failure:{{/}} %s %t\n", string(result.Outputs[i][j]), result.Success) - } - } + // revert error is populated in the last output of the last action + output := result.Outputs[len(result.Outputs)-1][len(result.Outputs[len(result.Outputs)-1])-1] + utils.Outf("{{orange}}on-chain tx failure:{{/}} %s %t\n", string(output), result.Success) } } else { // We can't error match here because we receive it over the wire. From bbd1bd2be4b9420e157e0b54268533d1dd8e9332 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 15:44:23 -0400 Subject: [PATCH 65/78] fix action Size --- examples/tokenvm/actions/burn_asset.go | 2 +- examples/tokenvm/actions/close_order.go | 3 +-- examples/tokenvm/actions/create_order.go | 2 +- examples/tokenvm/actions/fill_order.go | 2 +- examples/tokenvm/actions/mint_asset.go | 2 +- examples/tokenvm/actions/transfer.go | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/tokenvm/actions/burn_asset.go b/examples/tokenvm/actions/burn_asset.go index 3237899b61..7602477052 100644 --- a/examples/tokenvm/actions/burn_asset.go +++ b/examples/tokenvm/actions/burn_asset.go @@ -77,7 +77,7 @@ func (*BurnAsset) MaxComputeUnits(chain.Rules) uint64 { } func (*BurnAsset) Size() int { - return consts.IDLen + consts.Uint64Len + return codec.LIDLen + consts.Uint64Len } func (b *BurnAsset) Marshal(p *codec.Packer) { diff --git a/examples/tokenvm/actions/close_order.go b/examples/tokenvm/actions/close_order.go index 67cbbcd015..d3f0a435f4 100644 --- a/examples/tokenvm/actions/close_order.go +++ b/examples/tokenvm/actions/close_order.go @@ -8,7 +8,6 @@ import ( "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/utils" @@ -75,7 +74,7 @@ func (*CloseOrder) MaxComputeUnits(chain.Rules) uint64 { } func (*CloseOrder) Size() int { - return consts.IDLen * 2 + return codec.LIDLen * 2 } func (c *CloseOrder) Marshal(p *codec.Packer) { diff --git a/examples/tokenvm/actions/create_order.go b/examples/tokenvm/actions/create_order.go index c55bfa3e62..8e2eed1beb 100644 --- a/examples/tokenvm/actions/create_order.go +++ b/examples/tokenvm/actions/create_order.go @@ -98,7 +98,7 @@ func (*CreateOrder) MaxComputeUnits(chain.Rules) uint64 { } func (*CreateOrder) Size() int { - return consts.IDLen*2 + consts.Uint64Len*3 + return codec.LIDLen*2 + consts.Uint64Len*3 } func (c *CreateOrder) Marshal(p *codec.Packer) { diff --git a/examples/tokenvm/actions/fill_order.go b/examples/tokenvm/actions/fill_order.go index 1bd9e1fcec..9a3505dd8c 100644 --- a/examples/tokenvm/actions/fill_order.go +++ b/examples/tokenvm/actions/fill_order.go @@ -153,7 +153,7 @@ func (*FillOrder) MaxComputeUnits(chain.Rules) uint64 { } func (*FillOrder) Size() int { - return consts.IDLen*3 + codec.AddressLen + consts.Uint64Len + return codec.LIDLen*3 + codec.AddressLen + consts.Uint64Len } func (f *FillOrder) Marshal(p *codec.Packer) { diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index 489cf4dfc7..6296da7b3b 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -86,7 +86,7 @@ func (*MintAsset) MaxComputeUnits(chain.Rules) uint64 { } func (*MintAsset) Size() int { - return codec.AddressLen + consts.IDLen + consts.Uint64Len + return codec.AddressLen + codec.LIDLen + consts.Uint64Len } func (m *MintAsset) Marshal(p *codec.Packer) { diff --git a/examples/tokenvm/actions/transfer.go b/examples/tokenvm/actions/transfer.go index 499d477a0a..96099bd399 100644 --- a/examples/tokenvm/actions/transfer.go +++ b/examples/tokenvm/actions/transfer.go @@ -74,7 +74,7 @@ func (*Transfer) MaxComputeUnits(chain.Rules) uint64 { } func (t *Transfer) Size() int { - return codec.AddressLen + consts.IDLen + consts.Uint64Len + codec.BytesLen(t.Memo) + return codec.AddressLen + codec.LIDLen + consts.Uint64Len + codec.BytesLen(t.Memo) } func (t *Transfer) Marshal(p *codec.Packer) { From 385eb7de69071cbeafd73f63c0ab20faa9230b3d Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 6 May 2024 16:40:51 -0400 Subject: [PATCH 66/78] fix LID to and from String --- cli/prompt.go | 15 ++-- codec/address.go | 19 +++-- examples/tokenvm/actions/create_order.go | 3 +- .../cmd/token-wallet/backend/backend.go | 78 ++++++++++++++----- x/programs/cmd/simulator/cmd/plan.go | 4 +- x/programs/cmd/simulator/cmd/program.go | 2 +- x/programs/examples/token.go | 4 +- 7 files changed, 81 insertions(+), 44 deletions(-) diff --git a/cli/prompt.go b/cli/prompt.go index 106bbf1fe7..6ba442aead 100644 --- a/cli/prompt.go +++ b/cli/prompt.go @@ -71,8 +71,8 @@ func (h *Handler) PromptAsset(label string, allowNative bool) (codec.LID, error) if allowNative && input == symbol { return nil } - _ = codec.LIDFromString(label, input) - return nil + _, err := codec.FromString(input) + return err }, } asset, err := promptText.Run() @@ -82,7 +82,10 @@ func (h *Handler) PromptAsset(label string, allowNative bool) (codec.LID, error) asset = strings.TrimSpace(asset) var assetID codec.LID if asset != symbol { - assetID = codec.LIDFromString(label, asset) + assetID, err = codec.FromString(asset) + if err != nil { + return codec.EmptyAddress, err + } } if !allowNative && assetID == codec.EmptyAddress { return codec.EmptyAddress, ErrInvalidChoice @@ -281,8 +284,8 @@ func (*Handler) PromptLID(label string) (codec.LID, error) { if len(input) == 0 { return ErrInputEmpty } - _ = codec.LIDFromString(label, input) - return nil + _, err := codec.FromString(input) + return err }, } rawID, err := promptText.Run() @@ -290,7 +293,7 @@ func (*Handler) PromptLID(label string) (codec.LID, error) { return codec.EmptyAddress, err } rawID = strings.TrimSpace(rawID) - return codec.LIDFromString(label, rawID), nil + return codec.FromString(rawID) } func (h *Handler) PromptChain(label string, excluded set.Set[ids.ID]) (ids.ID, []string, error) { diff --git a/codec/address.go b/codec/address.go index de1017b60b..b8b436cbdf 100644 --- a/codec/address.go +++ b/codec/address.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/cb58" "github.com/ava-labs/avalanchego/utils/formatting/address" ) @@ -98,18 +99,16 @@ func ParseAddressBech32(hrp, saddr string) (Address, error) { return Address(p[:AddressLen]), nil } -func LIDToString(hrp string, lid LID) string { - addr, err := AddressBech32(hrp, Address(lid)) - if err != nil { - panic(err) - } - return addr +func (l LID) String() string { + s, _ := cb58.Encode(l[:]) + return s } -func LIDFromString(hrp string, lid string) LID { - addr, err := ParseAddressBech32(hrp, lid) +// FromString is the inverse of LID.String() +func FromString(lidStr string) (LID, error) { + bytes, err := cb58.Decode(lidStr) if err != nil { - panic(err) + return LID{}, err } - return LID(addr) + return LID(bytes), nil } diff --git a/examples/tokenvm/actions/create_order.go b/examples/tokenvm/actions/create_order.go index 8e2eed1beb..68c7dbb2be 100644 --- a/examples/tokenvm/actions/create_order.go +++ b/examples/tokenvm/actions/create_order.go @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" - tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/utils" @@ -125,5 +124,5 @@ func (*CreateOrder) ValidRange(chain.Rules) (int64, int64) { } func PairID(in codec.LID, out codec.LID) string { - return fmt.Sprintf("%s-%s", codec.LIDToString(tconsts.HRP, in), codec.LIDToString(tconsts.HRP, out)) + return fmt.Sprintf("%s-%s", in.String(), out.String()) } diff --git a/examples/tokenvm/cmd/token-wallet/backend/backend.go b/examples/tokenvm/cmd/token-wallet/backend/backend.go index b5ddf064a3..0c88b232e3 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/backend.go +++ b/examples/tokenvm/cmd/token-wallet/backend/backend.go @@ -642,9 +642,9 @@ func (b *Backend) GetMyAssets() []*AssetInfo { b.fatal(err) return nil } - strAsset := codec.LIDToString(tconsts.HRP, asset) + strAsset := asset.String() assets = append(assets, &AssetInfo{ - ID: codec.LIDToString(tconsts.HRP, asset), + ID: asset.String(), Symbol: string(symbol), Decimals: int(decimals), Metadata: string(metadata), @@ -699,7 +699,10 @@ func (b *Backend) CreateAsset(symbol string, decimals string, metadata string) e func (b *Backend) MintAsset(asset string, address string, amount string) error { // Input validation - assetID := codec.LIDFromString(tconsts.HRP, asset) + assetID, err := codec.FromString(asset) + if err != nil { + return err + } _, _, decimals, _, _, _, err := b.tcli.Asset(b.ctx, assetID, true) if err != nil { return err @@ -751,7 +754,10 @@ func (b *Backend) MintAsset(asset string, address string, amount string) error { func (b *Backend) Transfer(asset string, address string, amount string, memo string) error { // Input validation - assetID := codec.LIDFromString(tconsts.HRP, asset) + assetID, err := codec.FromString(asset) + if err != nil { + return err + } _, symbol, decimals, _, _, _, err := b.tcli.Asset(b.ctx, assetID, true) if err != nil { return err @@ -836,7 +842,7 @@ func (b *Backend) GetBalance() ([]*BalanceInfo, error) { if err != nil { return nil, err } - strAsset := codec.LIDToString(tconsts.HRP, asset) + strAsset := asset.String() if asset == codec.EmptyAddress { balances = append(balances, &BalanceInfo{ID: strAsset, Str: fmt.Sprintf("%s %s", hutils.FormatBalance(bal, decimals), symbol), Bal: fmt.Sprintf("%s (Balance: %s)", symbol, hutils.FormatBalance(bal, decimals)), Has: bal > 0}) } else { @@ -965,9 +971,9 @@ func (b *Backend) GetAllAssets() []*AssetInfo { b.fatal(err) return nil } - strAsset := codec.LIDToString(tconsts.HRP, asset) + strAsset := asset.String() assets = append(assets, &AssetInfo{ - ID: codec.LIDToString(tconsts.HRP, asset), + ID: asset.String(), Symbol: string(symbol), Decimals: int(decimals), Metadata: string(metadata), @@ -980,7 +986,10 @@ func (b *Backend) GetAllAssets() []*AssetInfo { } func (b *Backend) AddAsset(asset string) error { - assetID := codec.LIDFromString(tconsts.HRP, asset) + assetID, err := codec.FromString(asset) + if err != nil { + return err + } hasAsset, err := b.s.HasAsset(assetID) if err != nil { return err @@ -1023,10 +1032,10 @@ func (b *Backend) GetMyOrders() ([]*Order, error) { return nil, err } orders = append(orders, &Order{ - ID: codec.LIDToString(tconsts.HRP, orderID), - InID: codec.LIDToString(tconsts.HRP, inID), + ID: orderID.String(), + InID: inID.String(), InSymbol: string(inSymbol), - OutID: codec.LIDToString(tconsts.HRP, outID), + OutID: outID.String(), OutSymbol: string(outSymbol), Price: fmt.Sprintf("%s %s / %s %s", hutils.FormatBalance(order.InTick, inDecimals), inSymbol, hutils.FormatBalance(order.OutTick, outDecimals), outSymbol), InTick: fmt.Sprintf("%s %s", hutils.FormatBalance(order.InTick, inDecimals), inSymbol), @@ -1051,13 +1060,19 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { } assetIDs := strings.Split(pair, "-") in := assetIDs[0] - inID := codec.LIDFromString(tconsts.HRP, in) + inID, err := codec.FromString(in) + if err != nil { + return err + } _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, inID, true) if err != nil { return nil, err } out := assetIDs[1] - outID := codec.LIDFromString(tconsts.HRP, out) + outID, err := codec.FromString(out) + if err != nil { + return err + } _, outSymbol, outDecimals, _, _, _, err := b.tcli.Asset(b.ctx, outID, true) if err != nil { return nil, err @@ -1067,7 +1082,7 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { for i := 0; i < len(rawOrders); i++ { order := rawOrders[i] orders[i] = &Order{ - ID: codec.LIDToString(tconsts.HRP, order.ID), + ID: order.ID.String(), InID: in, InSymbol: string(inSymbol), OutID: out, @@ -1086,8 +1101,14 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { } func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, outTick string, supply string) error { - inID := codec.LIDFromString(tconsts.HRP, assetIn) - outID := codec.LIDFromString(tconsts.HRP, assetOut) + inID, err := codec.FromString(assetIn) + if err != nil { + return err + } + outID, err := codec.FromString(assetOut) + if err != nil { + return err + } _, _, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, inID, true) if err != nil { return err @@ -1163,13 +1184,22 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou } func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, inTick string, assetOut string, amount string) error { - oID := codec.LIDFromString(tconsts.HRP, orderID) + oID, err := codec.FromString(orderID) + if err != nil { + return err + } owner, err := codec.ParseAddressBech32(tconsts.HRP, orderOwner) if err != nil { return err } - inID := codec.LIDFromString(tconsts.HRP, assetIn) - outID := codec.LIDFromString(tconsts.HRP, assetOut) + inID, err := codec.FromString(assetIn) + if err != nil { + return err + } + outID, err := codec.FromString(tconsts.HRP, assetOut) + if err != nil { + return err + } _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, inID, true) if err != nil { return err @@ -1238,8 +1268,14 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i } func (b *Backend) CloseOrder(orderID string, assetOut string) error { - oID := codec.LIDFromString(tconsts.HRP, orderID) - outID := codec.LIDFromString(tconsts.HRP, assetOut) + oID, err := codec.FromString(orderID) + if err != nil { + return err + } + outID, err := codec.FromString(assetOut) + if err != nil { + return err + } // Ensure have sufficient balance bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) diff --git a/x/programs/cmd/simulator/cmd/plan.go b/x/programs/cmd/simulator/cmd/plan.go index ca76ea332f..773bcb0c03 100644 --- a/x/programs/cmd/simulator/cmd/plan.go +++ b/x/programs/cmd/simulator/cmd/plan.go @@ -237,7 +237,7 @@ func runStepFunc( if err != nil { return err } - resp.setTxID(codec.LIDToString(consts.HRP, id)) + resp.setTxID(id.String()) resp.setTimestamp(time.Now().Unix()) return nil @@ -254,7 +254,7 @@ func runStepFunc( if err != nil { return err } - resp.setTxID(codec.LIDToString(consts.HRP, id)) + resp.setTxID(id.String()) resp.setBalance(balance) return nil diff --git a/x/programs/cmd/simulator/cmd/program.go b/x/programs/cmd/simulator/cmd/program.go index 050bcced26..c8e86db76d 100644 --- a/x/programs/cmd/simulator/cmd/program.go +++ b/x/programs/cmd/simulator/cmd/program.go @@ -64,7 +64,7 @@ func newProgramCreateCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Com return err } - hutils.Outf("{{green}}create program transaction successful: {{/}}%s\n", codec.LIDToString(xconsts.HRP, p.id)) + hutils.Outf("{{green}}create program transaction successful: {{/}}%s\n", p.id.String()) return nil }, } diff --git a/x/programs/examples/token.go b/x/programs/examples/token.go index ca0fa39c35..48a1c3095f 100644 --- a/x/programs/examples/token.go +++ b/x/programs/examples/token.go @@ -106,7 +106,7 @@ func (t *Token) Run(ctx context.Context) error { } t.log.Debug("new token program created", - zap.String("id", codec.LIDToString("matrix", programID)), + zap.String("id", programID.String()), ) // initialize program @@ -371,7 +371,7 @@ func (t *Token) RunShort(ctx context.Context) error { } t.log.Debug("new token program created", - zap.String("id", codec.LIDToString("matrix", programID)), + zap.String("id", programID.String()), ) // initialize program From c4a270af9e90a9273b3ecf20e73845b314b14ce4 Mon Sep 17 00:00:00 2001 From: William Law Date: Tue, 7 May 2024 11:16:37 -0400 Subject: [PATCH 67/78] make EmptyAddress const more generic --- cli/prompt.go | 12 ++++----- cli/spam.go | 2 +- cli/storage.go | 6 ++--- codec/address.go | 9 ++++--- codec/optional_packer.go | 4 +-- codec/optional_packer_test.go | 4 +-- codec/packer.go | 2 +- examples/morpheusvm/auth/bls.go | 2 +- examples/morpheusvm/auth/ed25519.go | 2 +- examples/morpheusvm/auth/secp256r1.go | 2 +- examples/tokenvm/actions/mint_asset.go | 2 +- examples/tokenvm/auth/ed25519.go | 2 +- examples/tokenvm/cmd/token-cli/cmd/action.go | 6 ++--- examples/tokenvm/cmd/token-cli/cmd/handler.go | 2 +- examples/tokenvm/cmd/token-cli/cmd/key.go | 2 +- examples/tokenvm/cmd/token-cli/cmd/spam.go | 4 +-- .../cmd/token-faucet/manager/manager.go | 6 ++--- .../tokenvm/cmd/token-feed/config/config.go | 2 +- .../cmd/token-wallet/backend/backend.go | 26 +++++++++---------- examples/tokenvm/controller/state_manager.go | 8 +++--- examples/tokenvm/genesis/genesis.go | 6 ++--- examples/tokenvm/storage/storage.go | 8 +++--- examples/tokenvm/tests/e2e/e2e_test.go | 6 ++--- .../tests/integration/integration_test.go | 20 +++++++------- examples/tokenvm/tests/load/load_test.go | 2 +- x/programs/cmd/simulator/cmd/program.go | 22 ++++++++-------- 26 files changed, 85 insertions(+), 84 deletions(-) diff --git a/cli/prompt.go b/cli/prompt.go index 6ba442aead..52621300c9 100644 --- a/cli/prompt.go +++ b/cli/prompt.go @@ -30,7 +30,7 @@ func (h *Handler) PromptAddress(label string) (codec.Address, error) { } recipient, err := promptText.Run() if err != nil { - return codec.EmptyAddress, err + return codec.Empty, err } recipient = strings.TrimSpace(recipient) return h.c.ParseAddress(recipient) @@ -77,18 +77,18 @@ func (h *Handler) PromptAsset(label string, allowNative bool) (codec.LID, error) } asset, err := promptText.Run() if err != nil { - return codec.EmptyAddress, err + return codec.Empty, err } asset = strings.TrimSpace(asset) var assetID codec.LID if asset != symbol { assetID, err = codec.FromString(asset) if err != nil { - return codec.EmptyAddress, err + return codec.Empty, err } } - if !allowNative && assetID == codec.EmptyAddress { - return codec.EmptyAddress, ErrInvalidChoice + if !allowNative && assetID == codec.Empty { + return codec.Empty, ErrInvalidChoice } return assetID, nil } @@ -290,7 +290,7 @@ func (*Handler) PromptLID(label string) (codec.LID, error) { } rawID, err := promptText.Run() if err != nil { - return codec.EmptyAddress, err + return codec.Empty, err } rawID = strings.TrimSpace(rawID) return codec.FromString(rawID) diff --git a/cli/spam.go b/cli/spam.go index 5928f4f1a1..c37188f6b2 100644 --- a/cli/spam.go +++ b/cli/spam.go @@ -535,7 +535,7 @@ func getNextRecipient(self int, createAccount func() (*PrivateKey, error), keys if createAccount != nil { priv, err := createAccount() if err != nil { - return codec.EmptyAddress, err + return codec.Empty, err } return priv.Address, nil } diff --git a/cli/storage.go b/cli/storage.go index 49878ff52d..520857cad3 100644 --- a/cli/storage.go +++ b/cli/storage.go @@ -125,15 +125,15 @@ func (h *Handler) StoreDefaultKey(addr codec.Address) error { func (h *Handler) GetDefaultKey(log bool) (codec.Address, []byte, error) { raddr, err := h.GetDefault(defaultKeyKey) if err != nil { - return codec.EmptyAddress, nil, err + return codec.Empty, nil, err } if len(raddr) == 0 { - return codec.EmptyAddress, nil, ErrNoKeys + return codec.Empty, nil, ErrNoKeys } addr := codec.Address(raddr) priv, err := h.GetKey(addr) if err != nil { - return codec.EmptyAddress, nil, err + return codec.Empty, nil, err } if log { utils.Outf("{{yellow}}address:{{/}} %s\n", h.c.Address(addr)) diff --git a/codec/address.go b/codec/address.go index b8b436cbdf..eefdfe8c9e 100644 --- a/codec/address.go +++ b/codec/address.go @@ -28,7 +28,8 @@ type ( Address = LID ) -var EmptyAddress = LID{} +// Empty is a useful all zero value +var Empty = LID{} // CreateLID returns [LID] made from concatenating // some [i] with an [id]. @@ -84,17 +85,17 @@ func MustAddressBech32(hrp string, p Address) string { func ParseAddressBech32(hrp, saddr string) (Address, error) { phrp, p, err := address.ParseBech32(saddr) if err != nil { - return EmptyAddress, err + return Empty, err } if phrp != hrp { - return EmptyAddress, ErrIncorrectHRP + return Empty, ErrIncorrectHRP } // The parsed value may be greater than [minLength] because the // underlying Bech32 implementation requires bytes to each encode 5 bits // instead of 8 (and we must pad the input to ensure we fill all bytes): // https://github.com/btcsuite/btcd/blob/902f797b0c4b3af3f7196d2f5d2343931d1b2bdf/btcutil/bech32/bech32.go#L325-L331 if len(p) < AddressLen { - return EmptyAddress, ErrInsufficientLength + return Empty, ErrInsufficientLength } return Address(p[:AddressLen]), nil } diff --git a/codec/optional_packer.go b/codec/optional_packer.go index b9104b7e8b..e2e798d76c 100644 --- a/codec/optional_packer.go +++ b/codec/optional_packer.go @@ -137,7 +137,7 @@ func (o *OptionalPacker) UnpackInt64() int64 { // PackAddress packs [addr] into OptionalPacker if [addr] is not empty. // Updates the bitset and offset accordingly. func (o *OptionalPacker) PackAddress(addr Address) { - if addr == EmptyAddress { + if addr == Empty { o.skipBit() return } @@ -151,7 +151,7 @@ func (o *OptionalPacker) UnpackAddress(dest *Address) { if o.checkBit() { o.ip.UnpackLID(true, dest) } else { - *dest = EmptyAddress + *dest = Empty } } diff --git a/codec/optional_packer_test.go b/codec/optional_packer_test.go index cfe2b9b7f9..6d12d7ba4a 100644 --- a/codec/optional_packer_test.go +++ b/codec/optional_packer_test.go @@ -94,7 +94,7 @@ func TestOptionalPackerAddress(t *testing.T) { require := require.New(t) // Pack empty - opw.PackAddress(EmptyAddress) + opw.PackAddress(Empty) require.Empty(opw.ip.Bytes(), "PackAddress packed an empty Address.") // Pack address @@ -107,7 +107,7 @@ func TestOptionalPackerAddress(t *testing.T) { opr := opw.toReader() var unpackedAddr Address opr.UnpackAddress(&unpackedAddr) - require.True(bytes.Equal(EmptyAddress[:], unpackedAddr[:]), "AddressBytes unpacked correctly") + require.True(bytes.Equal(Empty[:], unpackedAddr[:]), "AddressBytes unpacked correctly") opr.UnpackAddress(&unpackedAddr) require.Equal(addr, unpackedAddr, "PublicKey unpacked correctly") opr.Done() diff --git a/codec/packer.go b/codec/packer.go index e965bf5afb..ca776bbc37 100644 --- a/codec/packer.go +++ b/codec/packer.go @@ -76,7 +76,7 @@ func (p *Packer) PackLID(a LID) { func (p *Packer) UnpackLID(required bool, dest *LID) { copy((*dest)[:], p.p.UnpackFixedBytes(LIDLen)) - if required && *dest == EmptyAddress { + if required && *dest == Empty { p.addErr(fmt.Errorf("%w: LID field is not populated", ErrFieldNotPopulated)) } } diff --git a/examples/morpheusvm/auth/bls.go b/examples/morpheusvm/auth/bls.go index f56f59bf8b..54def0b881 100644 --- a/examples/morpheusvm/auth/bls.go +++ b/examples/morpheusvm/auth/bls.go @@ -29,7 +29,7 @@ type BLS struct { } func (b *BLS) address() codec.Address { - if b.addr == codec.EmptyAddress { + if b.addr == codec.Empty { b.addr = NewBLSAddress(b.Signer) } return b.addr diff --git a/examples/morpheusvm/auth/ed25519.go b/examples/morpheusvm/auth/ed25519.go index e8dd4ac7fe..36af6261d6 100644 --- a/examples/morpheusvm/auth/ed25519.go +++ b/examples/morpheusvm/auth/ed25519.go @@ -29,7 +29,7 @@ type ED25519 struct { } func (d *ED25519) address() codec.Address { - if d.addr == codec.EmptyAddress { + if d.addr == codec.Empty { d.addr = NewED25519Address(d.Signer) } return d.addr diff --git a/examples/morpheusvm/auth/secp256r1.go b/examples/morpheusvm/auth/secp256r1.go index 41f9adde6c..805f947a40 100644 --- a/examples/morpheusvm/auth/secp256r1.go +++ b/examples/morpheusvm/auth/secp256r1.go @@ -29,7 +29,7 @@ type SECP256R1 struct { } func (d *SECP256R1) address() codec.Address { - if d.addr == codec.EmptyAddress { + if d.addr == codec.Empty { d.addr = NewSECP256R1Address(d.Signer) } return d.addr diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index 6296da7b3b..c73a44bd0c 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -52,7 +52,7 @@ func (m *MintAsset) Execute( actor codec.Address, _ codec.LID, ) (bool, uint64, [][]byte, error) { - if m.Asset == codec.EmptyAddress { + if m.Asset == codec.Empty { return false, MintAssetComputeUnits, [][]byte{OutputAssetIsNative}, nil } if m.Value == 0 { diff --git a/examples/tokenvm/auth/ed25519.go b/examples/tokenvm/auth/ed25519.go index ee78b9a0bc..61673a18e2 100644 --- a/examples/tokenvm/auth/ed25519.go +++ b/examples/tokenvm/auth/ed25519.go @@ -28,7 +28,7 @@ type ED25519 struct { } func (d *ED25519) address() codec.Address { - if d.addr == codec.EmptyAddress { + if d.addr == codec.Empty { d.addr = NewED25519Address(d.Signer) } return d.addr diff --git a/examples/tokenvm/cmd/token-cli/cmd/action.go b/examples/tokenvm/cmd/token-cli/cmd/action.go index 4d36fb0145..f9e394c308 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/action.go +++ b/examples/tokenvm/cmd/token-cli/cmd/action.go @@ -47,7 +47,7 @@ var fundFaucetCmd = &cobra.Command{ } // Get balance - _, decimals, balance, _, err := handler.GetAssetInfo(ctx, tcli, priv.Address, codec.EmptyAddress, true) + _, decimals, balance, _, err := handler.GetAssetInfo(ctx, tcli, priv.Address, codec.Empty, true) if balance == 0 || err != nil { return err } @@ -71,7 +71,7 @@ var fundFaucetCmd = &cobra.Command{ } if err = sendAndWait(ctx, []chain.Action{&actions.Transfer{ To: addr, - Asset: codec.EmptyAddress, + Asset: codec.Empty, Value: amount, }}, cli, scli, tcli, factory, true); err != nil { return err @@ -289,7 +289,7 @@ var createOrderCmd = &cobra.Command{ if err != nil { return err } - if inAssetID != codec.EmptyAddress { + if inAssetID != codec.Empty { if !exists { hutils.Outf("{{red}}%s does not exist{{/}}\n", inAssetID) hutils.Outf("{{red}}exiting...{{/}}\n") diff --git a/examples/tokenvm/cmd/token-cli/cmd/handler.go b/examples/tokenvm/cmd/token-cli/cmd/handler.go index da055b37c3..9b934d237f 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/handler.go +++ b/examples/tokenvm/cmd/token-cli/cmd/handler.go @@ -45,7 +45,7 @@ func (*Handler) GetAssetInfo( if err != nil { return nil, 0, 0, ids.Empty, err } - if assetID != codec.EmptyAddress { + if assetID != codec.Empty { if !exists { hutils.Outf("{{red}}%s does not exist{{/}}\n", assetID) hutils.Outf("{{red}}exiting...{{/}}\n") diff --git a/examples/tokenvm/cmd/token-cli/cmd/key.go b/examples/tokenvm/cmd/token-cli/cmd/key.go index 76d84ee63e..acdb423f77 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/key.go +++ b/examples/tokenvm/cmd/token-cli/cmd/key.go @@ -89,7 +89,7 @@ var importKeyCmd = &cobra.Command{ func lookupSetKeyBalance(choice int, address string, uri string, networkID uint32, chainID ids.ID) error { // TODO: just load once cli := trpc.NewJSONRPCClient(uri, networkID, chainID) - balance, err := cli.Balance(context.TODO(), address, codec.EmptyAddress) + balance, err := cli.Balance(context.TODO(), address, codec.Empty) if err != nil { return err } diff --git a/examples/tokenvm/cmd/token-cli/cmd/spam.go b/examples/tokenvm/cmd/token-cli/cmd/spam.go index 77616d4cdb..8986f89db1 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/spam.go +++ b/examples/tokenvm/cmd/token-cli/cmd/spam.go @@ -62,7 +62,7 @@ var runSpamCmd = &cobra.Command{ }, nil }, func(choice int, address string) (uint64, error) { // lookupBalance - balance, err := tclient.Balance(context.TODO(), address, codec.EmptyAddress) + balance, err := tclient.Balance(context.TODO(), address, codec.Empty) if err != nil { return 0, err } @@ -81,7 +81,7 @@ var runSpamCmd = &cobra.Command{ func(addr codec.Address, amount uint64) []chain.Action { // getTransfer return []chain.Action{&actions.Transfer{ To: addr, - Asset: codec.EmptyAddress, + Asset: codec.Empty, Value: amount, }} }, diff --git a/examples/tokenvm/cmd/token-faucet/manager/manager.go b/examples/tokenvm/cmd/token-faucet/manager/manager.go index 775b7b00be..acbc4ba4cd 100644 --- a/examples/tokenvm/cmd/token-faucet/manager/manager.go +++ b/examples/tokenvm/cmd/token-faucet/manager/manager.go @@ -61,7 +61,7 @@ func New(logger logging.Logger, config *config.Config) (*Manager, error) { if err != nil { return nil, err } - bal, err := tcli.Balance(ctx, m.config.AddressBech32(), codec.EmptyAddress) + bal, err := tcli.Balance(ctx, m.config.AddressBech32(), codec.Empty) if err != nil { return nil, err } @@ -126,7 +126,7 @@ func (m *Manager) sendFunds(ctx context.Context, destination codec.Address, amou } submit, tx, maxFee, err := m.cli.GenerateTransaction(ctx, parser, []chain.Action{&actions.Transfer{ To: destination, - Asset: codec.EmptyAddress, + Asset: codec.Empty, Value: amount, }}, m.factory) if err != nil { @@ -136,7 +136,7 @@ func (m *Manager) sendFunds(ctx context.Context, destination codec.Address, amou m.log.Warn("abandoning airdrop because network fee is greater than amount", zap.String("maxFee", utils.FormatBalance(maxFee, consts.Decimals))) return ids.Empty, 0, errors.New("network fee too high") } - bal, err := m.tcli.Balance(ctx, m.config.AddressBech32(), codec.EmptyAddress) + bal, err := m.tcli.Balance(ctx, m.config.AddressBech32(), codec.Empty) if err != nil { return ids.Empty, 0, err } diff --git a/examples/tokenvm/cmd/token-feed/config/config.go b/examples/tokenvm/cmd/token-feed/config/config.go index 7ff17ce4e5..314e95ebd9 100644 --- a/examples/tokenvm/cmd/token-feed/config/config.go +++ b/examples/tokenvm/cmd/token-feed/config/config.go @@ -25,7 +25,7 @@ type Config struct { } func (c *Config) RecipientAddress() (codec.Address, error) { - if c.recipientAddr != codec.EmptyAddress { + if c.recipientAddr != codec.Empty { return c.recipientAddr, nil } addr, err := codec.ParseAddressBech32(consts.HRP, c.Recipient) diff --git a/examples/tokenvm/cmd/token-wallet/backend/backend.go b/examples/tokenvm/cmd/token-wallet/backend/backend.go index 0c88b232e3..ff811c4a5f 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/backend.go +++ b/examples/tokenvm/cmd/token-wallet/backend/backend.go @@ -136,7 +136,7 @@ func (b *Backend) Start(ctx context.Context) error { if err := b.AddAddressBook("Me", b.addrStr); err != nil { return err } - if err := b.s.StoreAsset(codec.EmptyAddress, false); err != nil { + if err := b.s.StoreAsset(codec.Empty, false); err != nil { return err } @@ -658,7 +658,7 @@ func (b *Backend) GetMyAssets() []*AssetInfo { func (b *Backend) CreateAsset(symbol string, decimals string, metadata string) error { // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) if err != nil { return err } @@ -717,7 +717,7 @@ func (b *Backend) MintAsset(asset string, address string, amount string) error { } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) if err != nil { return err } @@ -781,7 +781,7 @@ func (b *Backend) Transfer(asset string, address string, amount string, memo str } // Ensure have sufficient balance for fees - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) if err != nil { return err } @@ -796,7 +796,7 @@ func (b *Backend) Transfer(asset string, address string, amount string, memo str if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } - if assetID != codec.EmptyAddress { + if assetID != codec.Empty { if maxFee > bal { return fmt.Errorf("insufficient balance (have: %s %s, want: %s %s)", hutils.FormatBalance(bal, tconsts.Decimals), tconsts.Symbol, hutils.FormatBalance(maxFee, tconsts.Decimals), tconsts.Symbol) } @@ -843,7 +843,7 @@ func (b *Backend) GetBalance() ([]*BalanceInfo, error) { return nil, err } strAsset := asset.String() - if asset == codec.EmptyAddress { + if asset == codec.Empty { balances = append(balances, &BalanceInfo{ID: strAsset, Str: fmt.Sprintf("%s %s", hutils.FormatBalance(bal, decimals), symbol), Bal: fmt.Sprintf("%s (Balance: %s)", symbol, hutils.FormatBalance(bal, decimals)), Has: bal > 0}) } else { balances = append(balances, &BalanceInfo{ID: strAsset, Str: fmt.Sprintf("%s %s [%s]", hutils.FormatBalance(bal, decimals), symbol, asset), Bal: fmt.Sprintf("%s [%s..%s] (Balance: %s)", symbol, strAsset[:3], strAsset[len(strAsset)-3:], hutils.FormatBalance(bal, decimals)), Has: bal > 0}) @@ -1119,7 +1119,7 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) if err != nil { return err } @@ -1151,7 +1151,7 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } - if inID == codec.EmptyAddress { + if inID == codec.Empty { if maxFee+oSupply > bal { return fmt.Errorf("insufficient balance (have: %s %s, want: %s %s)", hutils.FormatBalance(bal, tconsts.Decimals), tconsts.Symbol, hutils.FormatBalance(maxFee+oSupply, tconsts.Decimals), tconsts.Symbol) } @@ -1206,7 +1206,7 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) if err != nil { return err } @@ -1237,7 +1237,7 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } - if inID == codec.EmptyAddress { + if inID == codec.Empty { if maxFee+inAmount > bal { return fmt.Errorf("insufficient balance (have: %s %s, want: %s %s)", hutils.FormatBalance(bal, tconsts.Decimals), tconsts.Symbol, hutils.FormatBalance(maxFee+inAmount, tconsts.Decimals), tconsts.Symbol) } @@ -1278,7 +1278,7 @@ func (b *Backend) CloseOrder(orderID string, assetOut string) error { } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) if err != nil { return err } @@ -1418,7 +1418,7 @@ func (b *Backend) Message(message string, url string) error { } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.EmptyAddress) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) if err != nil { return err } @@ -1426,7 +1426,7 @@ func (b *Backend) Message(message string, url string) error { // Generate transaction _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, []chain.Action{&actions.Transfer{ To: recipientAddr, - Asset: codec.EmptyAddress, + Asset: codec.Empty, Value: fee, Memo: data, }}, b.factory) diff --git a/examples/tokenvm/controller/state_manager.go b/examples/tokenvm/controller/state_manager.go index 27b859ac11..2d0c055103 100644 --- a/examples/tokenvm/controller/state_manager.go +++ b/examples/tokenvm/controller/state_manager.go @@ -30,7 +30,7 @@ func (*StateManager) FeeKey() []byte { func (*StateManager) SponsorStateKeys(addr codec.Address) state.Keys { return state.Keys{ - string(storage.BalanceKey(addr, codec.EmptyAddress)): state.Read | state.Write, + string(storage.BalanceKey(addr, codec.Empty)): state.Read | state.Write, } } @@ -40,7 +40,7 @@ func (*StateManager) CanDeduct( im state.Immutable, amount uint64, ) error { - bal, err := storage.GetBalance(ctx, im, addr, codec.EmptyAddress) + bal, err := storage.GetBalance(ctx, im, addr, codec.Empty) if err != nil { return err } @@ -56,7 +56,7 @@ func (*StateManager) Deduct( mu state.Mutable, amount uint64, ) error { - return storage.SubBalance(ctx, mu, addr, codec.EmptyAddress, amount) + return storage.SubBalance(ctx, mu, addr, codec.Empty, amount) } func (*StateManager) Refund( @@ -66,5 +66,5 @@ func (*StateManager) Refund( amount uint64, ) error { // Don't create account if it doesn't exist (may have sent all funds). - return storage.AddBalance(ctx, mu, addr, codec.EmptyAddress, amount, false) + return storage.AddBalance(ctx, mu, addr, codec.Empty, amount, false) } diff --git a/examples/tokenvm/genesis/genesis.go b/examples/tokenvm/genesis/genesis.go index cea202b2e9..6fe86a5fdd 100644 --- a/examples/tokenvm/genesis/genesis.go +++ b/examples/tokenvm/genesis/genesis.go @@ -123,19 +123,19 @@ func (g *Genesis) Load(ctx context.Context, tracer trace.Tracer, mu state.Mutabl if err != nil { return err } - if err := storage.SetBalance(ctx, mu, pk, codec.EmptyAddress, alloc.Balance); err != nil { + if err := storage.SetBalance(ctx, mu, pk, codec.Empty, alloc.Balance); err != nil { return fmt.Errorf("%w: addr=%s, bal=%d", err, alloc.Address, alloc.Balance) } } return storage.SetAsset( ctx, mu, - codec.EmptyAddress, + codec.Empty, []byte(consts.Symbol), consts.Decimals, []byte(consts.Name), supply, - codec.EmptyAddress, + codec.Empty, ) } diff --git a/examples/tokenvm/storage/storage.go b/examples/tokenvm/storage/storage.go index 84596a4923..56273dcd7d 100644 --- a/examples/tokenvm/storage/storage.go +++ b/examples/tokenvm/storage/storage.go @@ -313,10 +313,10 @@ func innerGetAsset( err error, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { if errors.Is(err, database.ErrNotFound) { - return false, nil, 0, nil, 0, codec.EmptyAddress, nil + return false, nil, 0, nil, 0, codec.Empty, nil } if err != nil { - return false, nil, 0, nil, 0, codec.EmptyAddress, err + return false, nil, 0, nil, 0, codec.Empty, err } symbolLen := binary.BigEndian.Uint16(v) symbol := v[consts.Uint16Len : consts.Uint16Len+symbolLen] @@ -438,10 +438,10 @@ func innerGetOrder(v []byte, err error) ( error, ) { if errors.Is(err, database.ErrNotFound) { - return false, codec.EmptyAddress, 0, codec.EmptyAddress, 0, 0, codec.EmptyAddress, nil + return false, codec.Empty, 0, codec.Empty, 0, 0, codec.Empty, nil } if err != nil { - return false, codec.EmptyAddress, 0, codec.EmptyAddress, 0, 0, codec.EmptyAddress, err + return false, codec.Empty, 0, codec.Empty, 0, 0, codec.Empty, err } var in codec.LID copy(in[:], v[:codec.LIDLen]) diff --git a/examples/tokenvm/tests/e2e/e2e_test.go b/examples/tokenvm/tests/e2e/e2e_test.go index 236e020ccc..7243ee5b12 100644 --- a/examples/tokenvm/tests/e2e/e2e_test.go +++ b/examples/tokenvm/tests/e2e/e2e_test.go @@ -512,7 +512,7 @@ var _ = ginkgo.Describe("[Test]", func() { } ginkgo.It("transfer in a single node (raw)", func() { - nativeBalance, err := instancesA[0].tcli.Balance(context.TODO(), sender, codec.EmptyAddress) + nativeBalance, err := instancesA[0].tcli.Balance(context.TODO(), sender, codec.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(nativeBalance).Should(gomega.Equal(startAmount)) @@ -547,7 +547,7 @@ var _ = ginkgo.Describe("[Test]", func() { hutils.Outf("{{yellow}}found transaction{{/}}\n") // Check sender balance - balance, err := instancesA[0].tcli.Balance(context.Background(), sender, codec.EmptyAddress) + balance, err := instancesA[0].tcli.Balance(context.Background(), sender, codec.Empty) gomega.Ω(err).Should(gomega.BeNil()) hutils.Outf( "{{yellow}}start=%d fee=%d send=%d balance=%d{{/}}\n", @@ -575,7 +575,7 @@ var _ = ginkgo.Describe("[Test]", func() { } // Check balance of recipient - balance, err := inst.tcli.Balance(context.Background(), codec.MustAddressBech32(consts.HRP, aother), codec.EmptyAddress) + balance, err := inst.tcli.Balance(context.Background(), codec.MustAddressBech32(consts.HRP, aother), codec.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(sendAmount)) } diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index e0363a547b..50bff465cd 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -285,19 +285,19 @@ var _ = ginkgo.BeforeSuite(func() { csupply := uint64(0) for _, alloc := range g.CustomAllocation { - balance, err := cli.Balance(context.Background(), alloc.Address, codec.EmptyAddress) + balance, err := cli.Balance(context.Background(), alloc.Address, codec.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(alloc.Balance)) csupply += alloc.Balance } - exists, symbol, decimals, metadata, supply, owner, err := cli.Asset(context.Background(), codec.EmptyAddress, false) + exists, symbol, decimals, metadata, supply, owner, err := cli.Asset(context.Background(), codec.Empty, false) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(exists).Should(gomega.BeTrue()) gomega.Ω(string(symbol)).Should(gomega.Equal(tconsts.Symbol)) gomega.Ω(decimals).Should(gomega.Equal(uint8(tconsts.Decimals))) gomega.Ω(string(metadata)).Should(gomega.Equal(tconsts.Name)) gomega.Ω(supply).Should(gomega.Equal(csupply)) - gomega.Ω(owner).Should(gomega.Equal(codec.MustAddressBech32(tconsts.HRP, codec.EmptyAddress))) + gomega.Ω(owner).Should(gomega.Equal(codec.MustAddressBech32(tconsts.HRP, codec.Empty))) } blocks = []snowman.Block{} @@ -464,10 +464,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.By("ensure balance is updated", func() { - balance, err := instances[1].tcli.Balance(context.Background(), sender, codec.EmptyAddress) + balance, err := instances[1].tcli.Balance(context.Background(), sender, codec.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance).To(gomega.Equal(uint64(9899702))) - balance2, err := instances[1].tcli.Balance(context.Background(), sender2, codec.EmptyAddress) + balance2, err := instances[1].tcli.Balance(context.Background(), sender2, codec.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100000))) }) @@ -494,7 +494,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance2, err := instances[1].tcli.Balance(context.Background(), sender2, codec.EmptyAddress) + balance2, err := instances[1].tcli.Balance(context.Background(), sender2, codec.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100101))) }) @@ -637,7 +637,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { time.Sleep(2 * pubsub.MaxMessageWait) // Fetch balances - balance, err := instances[0].tcli.Balance(context.TODO(), sender, codec.EmptyAddress) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, codec.Empty) gomega.Ω(err).Should(gomega.BeNil()) // Send tx @@ -676,7 +676,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(prices).Should(gomega.Equal(fees.Dimensions{1, 1, 1, 1, 1})) // Check balance modifications are correct - balancea, err := instances[0].tcli.Balance(context.TODO(), sender, codec.EmptyAddress) + balancea, err := instances[0].tcli.Balance(context.TODO(), sender, codec.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(balancea + lresults[0].Fee + 1)) @@ -1698,11 +1698,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance2, err := instances[3].tcli.Balance(context.Background(), sender2, codec.EmptyAddress) + balance2, err := instances[3].tcli.Balance(context.Background(), sender2, codec.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(10000))) - balance3, err := instances[3].tcli.Balance(context.Background(), sender3, codec.EmptyAddress) + balance3, err := instances[3].tcli.Balance(context.Background(), sender3, codec.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance3).To(gomega.Equal(uint64(5000))) }) diff --git a/examples/tokenvm/tests/load/load_test.go b/examples/tokenvm/tests/load/load_test.go index 2d300f1696..0591b346d4 100644 --- a/examples/tokenvm/tests/load/load_test.go +++ b/examples/tokenvm/tests/load/load_test.go @@ -303,7 +303,7 @@ var _ = ginkgo.BeforeSuite(func() { gomega.Ω(err).Should(gomega.BeNil()) for _, alloc := range g.CustomAllocation { - bal, err := cli.Balance(context.Background(), alloc.Address, codec.EmptyAddress) + bal, err := cli.Balance(context.Background(), alloc.Address, codec.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(bal).Should(gomega.Equal(alloc.Balance)) } diff --git a/x/programs/cmd/simulator/cmd/program.go b/x/programs/cmd/simulator/cmd/program.go index c8e86db76d..c96517f893 100644 --- a/x/programs/cmd/simulator/cmd/program.go +++ b/x/programs/cmd/simulator/cmd/program.go @@ -101,13 +101,13 @@ func (p *programCreate) Run(ctx context.Context) (err error) { func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string) (codec.LID, error) { programBytes, err := os.ReadFile(path) if err != nil { - return codec.EmptyAddress, err + return codec.Empty, err } // simulate create program transaction id, err := generateRandomID() if err != nil { - return codec.EmptyAddress, err + return codec.Empty, err } programID := codec.CreateLID(0, id) @@ -116,7 +116,7 @@ func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string } // execute the action - success, _, outputs, err := programCreateAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programID) + success, _, outputs, err := programCreateAction.Execute(ctx, nil, db, 0, codec.Empty, programID) var resultOutputs string for i := 0; i < len(outputs); i++ { for j := 0; j < len(outputs[i]); j++ { @@ -127,16 +127,16 @@ func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string fmt.Println(resultOutputs) } if !success { - return codec.EmptyAddress, fmt.Errorf("program creation failed: %s", err) + return codec.Empty, fmt.Errorf("program creation failed: %s", err) } if err != nil { - return codec.EmptyAddress, err + return codec.Empty, err } // store program to disk only on success err = db.Commit(ctx) if err != nil { - return codec.EmptyAddress, err + return codec.Empty, err } return programID, nil @@ -153,7 +153,7 @@ func programExecuteFunc( // simulate create program transaction programTxID, err := generateRandomID() if err != nil { - return codec.EmptyAddress, nil, 0, err + return codec.Empty, nil, 0, err } programActionID := codec.CreateLID(0, programTxID) @@ -165,17 +165,17 @@ func programExecuteFunc( } // execute the action - success, _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programActionID) + success, _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.Empty, programActionID) if !success { var respOutput string for i := 0; i < len(resp); i++ { respOutput += fmt.Sprintf(" %s", string(resp[i])) } - return codec.EmptyAddress, nil, 0, fmt.Errorf("program execution failed: %s", respOutput) + return codec.Empty, nil, 0, fmt.Errorf("program execution failed: %s", respOutput) } if err != nil { - return codec.EmptyAddress, nil, 0, err + return codec.Empty, nil, 0, err } // TODO: I don't think this is right @@ -193,7 +193,7 @@ func programExecuteFunc( // store program to disk only on success err = db.Commit(ctx) if err != nil { - return codec.EmptyAddress, nil, 0, err + return codec.Empty, nil, 0, err } // get remaining balance from runtime meter From 7721b0089758499185a00b8da1817cb10bb93627 Mon Sep 17 00:00:00 2001 From: William Law Date: Tue, 7 May 2024 11:54:12 -0400 Subject: [PATCH 68/78] if not successful error is last output in last action --- .../cmd/morpheus-cli/cmd/resolutions.go | 31 ++-- .../tokenvm/cmd/token-cli/cmd/resolutions.go | 145 ++++++++++-------- 2 files changed, 97 insertions(+), 79 deletions(-) diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go index ca95bfb04d..39e4390060 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go @@ -57,24 +57,33 @@ func sendAndWait( } func handleTx(tx *chain.Transaction, result *chain.Result) { - status := "❌" - if result.Success { - status = "✅" + actor := tx.Auth.Actor() + if !result.Success { + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary:{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + "❌", + tx.ID(), + codec.MustAddressBech32(consts.HRP, actor), + string(result.Outputs[len(result.Outputs)-1][len(result.Outputs[len(result.Outputs)-1])-1]), // revert error + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, consts.Decimals), + consts.Symbol, + cli.ParseDimensions(result.Consumed), + ) + return } + for i := 0; i < len(result.Outputs); i++ { for j := 0; j < len(result.Outputs[i]); j++ { - actor := tx.Auth.Actor() for _, action := range tx.Actions { - summaryStr := string(result.Outputs[i][j]) - if result.Success { - switch act := action.(type) { //nolint:gocritic - case *actions.Transfer: - summaryStr = fmt.Sprintf("%s %s -> %s\n", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) - } + var summaryStr string + switch act := action.(type) { //nolint:gocritic + case *actions.Transfer: + summaryStr = fmt.Sprintf("%s %s -> %s\n", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) } utils.Outf( "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", - status, + "✅", tx.ID(), codec.MustAddressBech32(consts.HRP, actor), reflect.TypeOf(action), diff --git a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go index 796ca25de8..641f5218d9 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go +++ b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go @@ -57,84 +57,93 @@ func sendAndWait( } func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result) { - status := "❌" - if result.Success { - status = "✅" + actor := tx.Auth.Actor() + if !result.Success { + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary:{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + "❌", + tx.ID(), + codec.MustAddressBech32(tconsts.HRP, actor), + string(result.Outputs[len(result.Outputs)-1][len(result.Outputs[len(result.Outputs)-1])-1]), // revert error, + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, tconsts.Decimals), + tconsts.Symbol, + cli.ParseDimensions(result.Consumed), + ) + return } + for i := 0; i < len(result.Outputs); i++ { for j := 0; j < len(result.Outputs[i]); j++ { - actor := tx.Auth.Actor() for i, act := range tx.Actions { - summaryStr := string(result.Outputs[i][j]) - if result.Success { - switch action := act.(type) { - case *actions.CreateAsset: - assetID := codec.CreateLID(uint8(i), tx.ID()) - summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", assetID, action.Symbol, action.Decimals, action.Metadata) - case *actions.MintAsset: - _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - amountStr := utils.FormatBalance(action.Value, decimals) - summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - case *actions.BurnAsset: - summaryStr = fmt.Sprintf("%d %s -> 🔥", action.Value, action.Asset) + var summaryStr string + switch action := act.(type) { + case *actions.CreateAsset: + assetID := codec.CreateLID(uint8(i), tx.ID()) + summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", assetID, action.Symbol, action.Decimals, action.Metadata) + case *actions.MintAsset: + _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + amountStr := utils.FormatBalance(action.Value, decimals) + summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + case *actions.BurnAsset: + summaryStr = fmt.Sprintf("%d %s -> 🔥", action.Value, action.Asset) - case *actions.Transfer: - _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - amountStr := utils.FormatBalance(action.Value, decimals) - summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - if len(action.Memo) > 0 { - summaryStr += fmt.Sprintf(" (memo: %s)", action.Memo) - } + case *actions.Transfer: + _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + amountStr := utils.FormatBalance(action.Value, decimals) + summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + if len(action.Memo) > 0 { + summaryStr += fmt.Sprintf(" (memo: %s)", action.Memo) + } - case *actions.CreateOrder: - _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - inTickStr := utils.FormatBalance(action.InTick, inDecimals) - _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - outTickStr := utils.FormatBalance(action.OutTick, outDecimals) - supplyStr := utils.FormatBalance(action.Supply, outDecimals) - summaryStr = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", inTickStr, inSymbol, outTickStr, outSymbol, supplyStr, outSymbol) - case *actions.FillOrder: - or, _ := actions.UnmarshalOrderResult(result.Outputs[i][j]) - _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - inAmtStr := utils.FormatBalance(or.In, inDecimals) - _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - outAmtStr := utils.FormatBalance(or.Out, outDecimals) - remainingStr := utils.FormatBalance(or.Remaining, outDecimals) - summaryStr = fmt.Sprintf( - "%s %s -> %s %s (remaining: %s %s)", - inAmtStr, inSymbol, outAmtStr, outSymbol, remainingStr, outSymbol, - ) - case *actions.CloseOrder: - summaryStr = fmt.Sprintf("orderID: %s", action.Order) + case *actions.CreateOrder: + _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + inTickStr := utils.FormatBalance(action.InTick, inDecimals) + _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + outTickStr := utils.FormatBalance(action.OutTick, outDecimals) + supplyStr := utils.FormatBalance(action.Supply, outDecimals) + summaryStr = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", inTickStr, inSymbol, outTickStr, outSymbol, supplyStr, outSymbol) + case *actions.FillOrder: + or, _ := actions.UnmarshalOrderResult(result.Outputs[i][j]) + _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + inAmtStr := utils.FormatBalance(or.In, inDecimals) + _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return } + outAmtStr := utils.FormatBalance(or.Out, outDecimals) + remainingStr := utils.FormatBalance(or.Remaining, outDecimals) + summaryStr = fmt.Sprintf( + "%s %s -> %s %s (remaining: %s %s)", + inAmtStr, inSymbol, outAmtStr, outSymbol, remainingStr, outSymbol, + ) + case *actions.CloseOrder: + summaryStr = fmt.Sprintf("orderID: %s", action.Order) } utils.Outf( "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", - status, + "✅", tx.ID(), codec.MustAddressBech32(tconsts.HRP, actor), reflect.TypeOf(act), From a22a37c7c52fb94ece99bfa02b76dd321fcd31c8 Mon Sep 17 00:00:00 2001 From: William Law Date: Tue, 7 May 2024 13:01:26 -0400 Subject: [PATCH 69/78] add comments in transaction --- chain/result.go | 2 ++ chain/transaction.go | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/chain/result.go b/chain/result.go index 9047f153d8..897e808b05 100644 --- a/chain/result.go +++ b/chain/result.go @@ -11,6 +11,8 @@ import ( type Result struct { Success bool + // An error will always be in the last output + // of the last action Outputs [][][]byte Consumed fees.Dimensions diff --git a/chain/transaction.go b/chain/transaction.go index f62a7e8a2f..b7edbcbe28 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -116,6 +116,7 @@ func (t *Transaction) StateKeys(sm StateManager) (state.Keys, error) { if !keys.Valid(k) { return nil, ErrInvalidKeyValue } + // [Add] will take the union of key permissions stateKeys.Add(k, v) } } @@ -342,6 +343,10 @@ func (t *Transaction) Execute( txSuccess = true ) for i, action := range t.Actions { + // skip all following actions if one is unsuccessful + if !txSuccess { + break + } actionID := codec.CreateLID(uint8(i), t.id) success, actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) if err != nil { @@ -357,9 +362,6 @@ func (t *Transaction) Execute( return handleRevert(ErrTooManyOutputs) } - if !txSuccess { - break - } if !success { txSuccess = false ts.Rollback(ctx, actionStart) From 2f487efd82f577fbd5f517a282b9eb13be3b55eb Mon Sep 17 00:00:00 2001 From: William Law Date: Tue, 7 May 2024 13:47:53 -0400 Subject: [PATCH 70/78] fix mock gen --- chain/mock_action.go | 4 ++-- chain/mock_auth.go | 8 ++++---- chain/mock_rules.go | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/chain/mock_action.go b/chain/mock_action.go index 0c8da8fcbf..fc395242b5 100644 --- a/chain/mock_action.go +++ b/chain/mock_action.go @@ -45,7 +45,7 @@ func (m *MockAction) EXPECT() *MockActionMockRecorder { } // Execute mocks base method. -func (m *MockAction) Execute(arg0 context.Context, arg1 Rules, arg2 state.Mutable, arg3 int64, arg4 codec.Address, arg5 codec.LID) (bool, uint64, [][]byte, error) { +func (m *MockAction) Execute(arg0 context.Context, arg1 Rules, arg2 state.Mutable, arg3 int64, arg4, arg5 codec.LID) (bool, uint64, [][]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Execute", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(bool) @@ -116,7 +116,7 @@ func (mr *MockActionMockRecorder) Size() *gomock.Call { } // StateKeys mocks base method. -func (m *MockAction) StateKeys(arg0 codec.Address, arg1 codec.LID) state.Keys { +func (m *MockAction) StateKeys(arg0, arg1 codec.LID) state.Keys { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "StateKeys", arg0, arg1) ret0, _ := ret[0].(state.Keys) diff --git a/chain/mock_auth.go b/chain/mock_auth.go index 6643ec2ac0..c593acb64b 100644 --- a/chain/mock_auth.go +++ b/chain/mock_auth.go @@ -44,10 +44,10 @@ func (m *MockAuth) EXPECT() *MockAuthMockRecorder { } // Actor mocks base method. -func (m *MockAuth) Actor() codec.Address { +func (m *MockAuth) Actor() codec.LID { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Actor") - ret0, _ := ret[0].(codec.Address) + ret0, _ := ret[0].(codec.LID) return ret0 } @@ -112,10 +112,10 @@ func (mr *MockAuthMockRecorder) Size() *gomock.Call { } // Sponsor mocks base method. -func (m *MockAuth) Sponsor() codec.Address { +func (m *MockAuth) Sponsor() codec.LID { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Sponsor") - ret0, _ := ret[0].(codec.Address) + ret0, _ := ret[0].(codec.LID) return ret0 } diff --git a/chain/mock_rules.go b/chain/mock_rules.go index 292f719345..0389c46648 100644 --- a/chain/mock_rules.go +++ b/chain/mock_rules.go @@ -114,6 +114,20 @@ func (mr *MockRulesMockRecorder) GetMaxBlockUnits() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMaxBlockUnits", reflect.TypeOf((*MockRules)(nil).GetMaxBlockUnits)) } +// GetMaxOutputsPerAction mocks base method. +func (m *MockRules) GetMaxOutputsPerAction() byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMaxOutputsPerAction") + ret0, _ := ret[0].(byte) + return ret0 +} + +// GetMaxOutputsPerAction indicates an expected call of GetMaxOutputsPerAction. +func (mr *MockRulesMockRecorder) GetMaxOutputsPerAction() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMaxOutputsPerAction", reflect.TypeOf((*MockRules)(nil).GetMaxOutputsPerAction)) +} + // GetMinBlockGap mocks base method. func (m *MockRules) GetMinBlockGap() int64 { m.ctrl.T.Helper() From 43d77e2b3fb93574b503b6fe22cd1d8391f62a89 Mon Sep 17 00:00:00 2001 From: William Law Date: Tue, 7 May 2024 13:58:29 -0400 Subject: [PATCH 71/78] fix rust ci --- x/programs/cmd/simulator/cmd/plan.go | 2 -- x/programs/cmd/simulator/cmd/program.go | 1 - x/programs/cmd/simulator/vm/actions/program_create.go | 1 - 3 files changed, 4 deletions(-) diff --git a/x/programs/cmd/simulator/cmd/plan.go b/x/programs/cmd/simulator/cmd/plan.go index 773bcb0c03..ad40ac92ac 100644 --- a/x/programs/cmd/simulator/cmd/plan.go +++ b/x/programs/cmd/simulator/cmd/plan.go @@ -25,9 +25,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/utils" ) diff --git a/x/programs/cmd/simulator/cmd/program.go b/x/programs/cmd/simulator/cmd/program.go index c96517f893..8a0cf4eea1 100644 --- a/x/programs/cmd/simulator/cmd/program.go +++ b/x/programs/cmd/simulator/cmd/program.go @@ -18,7 +18,6 @@ import ( hutils "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/actions" - xconsts "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" ) func newProgramCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { diff --git a/x/programs/cmd/simulator/vm/actions/program_create.go b/x/programs/cmd/simulator/vm/actions/program_create.go index 54fe3ad961..b4fdf4388b 100644 --- a/x/programs/cmd/simulator/vm/actions/program_create.go +++ b/x/programs/cmd/simulator/vm/actions/program_create.go @@ -6,7 +6,6 @@ package actions import ( "context" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" From 4d7e3dca990259ac7b3ae578f0a518b897632265 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 20 May 2024 15:47:02 -0400 Subject: [PATCH 72/78] [chain] fix result size (#889) * include r.Outputs size in calculation above * fix packing of Outputs for each Action --- chain/result.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/chain/result.go b/chain/result.go index 897e808b05..f7efaeb743 100644 --- a/chain/result.go +++ b/chain/result.go @@ -20,10 +20,10 @@ type Result struct { } func (r *Result) Size() int { - outputSize := consts.IntLen + outputSize := len(r.Outputs) for _, action := range r.Outputs { for _, output := range action { - outputSize += codec.BytesLen(output) + outputSize += codec.BytesLen(output) + 1 // for each output } } return consts.BoolLen + outputSize + fees.DimensionsLen + consts.Uint64Len @@ -31,19 +31,13 @@ func (r *Result) Size() int { func (r *Result) Marshal(p *codec.Packer) error { p.PackBool(r.Success) - - numOutputs := 0 - for _, action := range r.Outputs { - numOutputs += len(action) - } p.PackInt(len(r.Outputs)) - p.PackInt(numOutputs) for _, action := range r.Outputs { + p.PackInt(len(action)) for _, output := range action { p.PackBytes(output) } } - p.PackFixedBytes(r.Consumed.Bytes()) p.PackUint64(r.Fee) return nil @@ -65,11 +59,10 @@ func UnmarshalResult(p *codec.Packer) (*Result, error) { result := &Result{ Success: p.UnpackBool(), } - totalOutputs := [][][]byte{} numActions := p.UnpackInt(false) - numOutputs := p.UnpackInt(false) for i := 0; i < numActions; i++ { + numOutputs := p.UnpackInt(false) outputs := [][]byte{} for j := 0; j < numOutputs; j++ { var output []byte From b67c5f07936fc758fc78b90f702449e3e7f29185 Mon Sep 17 00:00:00 2001 From: William Law Date: Mon, 20 May 2024 19:07:29 -0400 Subject: [PATCH 73/78] [chain] Remove execute success (#894) * remove err from Action * mock gen * add more TODOs * add context on why revert is better * add back error * cleanup interface * cleanup tx loop * update error marshaling * update transfer op * cleaning up access * finish tokenvm * update mocks * fix programs * update create order * cleanup cmd program * backend.go compiles * more cleanup * morpheusvm integration passing * fix tokenvm integration * add nolint * require lint --------- Co-authored-by: Patrick O'Grady --- .github/workflows/hypersdk-ci.yml | 8 +-- README.md | 7 +-- chain/dependencies.go | 5 +- chain/mock_action.go | 11 ++-- chain/result.go | 47 +++++++------- chain/transaction.go | 63 +++++++++---------- examples/morpheusvm/actions/outputs.go | 4 +- examples/morpheusvm/actions/transfer.go | 11 ++-- .../tests/integration/integration_test.go | 2 +- examples/tokenvm/actions/burn_asset.go | 17 +++-- examples/tokenvm/actions/close_order.go | 17 +++-- examples/tokenvm/actions/create_asset.go | 17 +++-- examples/tokenvm/actions/create_order.go | 21 +++---- examples/tokenvm/actions/fill_order.go | 37 ++++++----- examples/tokenvm/actions/mint_asset.go | 21 +++---- examples/tokenvm/actions/outputs.go | 58 ++++++++--------- examples/tokenvm/actions/transfer.go | 13 ++-- .../cmd/token-wallet/backend/backend.go | 6 +- .../tests/integration/integration_test.go | 21 +++---- x/programs/cmd/simulator/cmd/program.go | 52 +++++---------- .../cmd/simulator/vm/actions/outputs.go | 4 +- .../simulator/vm/actions/program_create.go | 9 ++- .../simulator/vm/actions/program_execute.go | 23 ++++--- 23 files changed, 220 insertions(+), 254 deletions(-) diff --git a/.github/workflows/hypersdk-ci.yml b/.github/workflows/hypersdk-ci.yml index 143be18d79..0f30864582 100644 --- a/.github/workflows/hypersdk-ci.yml +++ b/.github/workflows/hypersdk-ci.yml @@ -133,7 +133,7 @@ jobs: MODE: 'test' tokenvm-load-tests: - needs: [tokenvm-unit-tests] + needs: [tokenvm-lint, tokenvm-unit-tests] strategy: matrix: level: [v1, v2, v3] # v4 is not supported @@ -154,7 +154,7 @@ jobs: run: GOAMD64=${{ matrix.level }} scripts/tests.load.sh tokenvm-sync-tests: - needs: [tokenvm-unit-tests] + needs: [tokenvm-lint, tokenvm-unit-tests] runs-on: ubuntu-20.04-32 timeout-minutes: 25 steps: @@ -255,7 +255,7 @@ jobs: MODE: 'test' morpheusvm-load-tests: - needs: [morpheusvm-unit-tests] + needs: [morpheusvm-lint, morpheusvm-unit-tests] strategy: matrix: level: [v1, v2, v3] # v4 is not supported @@ -276,7 +276,7 @@ jobs: run: GOAMD64=${{ matrix.level }} scripts/tests.load.sh morpheusvm-sync-tests: - needs: [morpheusvm-unit-tests] + needs: [morpheusvm-lint, morpheusvm-unit-tests] runs-on: ubuntu-20.04-32 timeout-minutes: 25 steps: diff --git a/README.md b/README.md index 48c9ce1e9e..add9648582 100644 --- a/README.md +++ b/README.md @@ -655,9 +655,8 @@ type Action interface { // // If any keys are touched during [Execute] that are not specified in [StateKeys], the transaction // will revert and the max fee will be charged. - // - // An error should only be returned if a fatal error was encountered, otherwise [success] should - // be marked as false and fees will still be charged. + // + // If [Execute] returns an error, execution will halt and any state changes will revert. Execute( ctx context.Context, r Rules, @@ -665,7 +664,7 @@ type Action interface { timestamp int64, actor codec.Address, actionID codec.LID, - ) (success bool, computeUnits uint64, outputs [][]byte, err error) + ) (computeUnits uint64, outputs [][]byte, err error) } ``` diff --git a/chain/dependencies.go b/chain/dependencies.go index e46cf46d73..3f4b56b3d9 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -249,8 +249,7 @@ type Action interface { // If any keys are touched during [Execute] that are not specified in [StateKeys], the transaction // will revert and the max fee will be charged. // - // An error should only be returned if a fatal error was encountered, otherwise [success] should - // be marked as false and fees will still be charged. + // If [Execute] returns an error, execution will halt and any state changes will revert. Execute( ctx context.Context, r Rules, @@ -258,7 +257,7 @@ type Action interface { timestamp int64, actor codec.Address, actionID codec.LID, - ) (success bool, computeUnits uint64, outputs [][]byte, err error) + ) (computeUnits uint64, outputs [][]byte, err error) } type Auth interface { diff --git a/chain/mock_action.go b/chain/mock_action.go index fc395242b5..5b16346794 100644 --- a/chain/mock_action.go +++ b/chain/mock_action.go @@ -45,14 +45,13 @@ func (m *MockAction) EXPECT() *MockActionMockRecorder { } // Execute mocks base method. -func (m *MockAction) Execute(arg0 context.Context, arg1 Rules, arg2 state.Mutable, arg3 int64, arg4, arg5 codec.LID) (bool, uint64, [][]byte, error) { +func (m *MockAction) Execute(arg0 context.Context, arg1 Rules, arg2 state.Mutable, arg3 int64, arg4, arg5 codec.LID) (uint64, [][]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Execute", arg0, arg1, arg2, arg3, arg4, arg5) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(uint64) - ret2, _ := ret[2].([][]byte) - ret3, _ := ret[3].(error) - return ret0, ret1, ret2, ret3 + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].([][]byte) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // Execute indicates an expected call of Execute. diff --git a/chain/result.go b/chain/result.go index f7efaeb743..c2cc768e74 100644 --- a/chain/result.go +++ b/chain/result.go @@ -11,8 +11,8 @@ import ( type Result struct { Success bool - // An error will always be in the last output - // of the last action + Error []byte + Outputs [][][]byte Consumed fees.Dimensions @@ -20,21 +20,23 @@ type Result struct { } func (r *Result) Size() int { - outputSize := len(r.Outputs) + outputSize := consts.Uint8Len // actions for _, action := range r.Outputs { + outputSize += consts.Uint8Len for _, output := range action { - outputSize += codec.BytesLen(output) + 1 // for each output + outputSize += codec.BytesLen(output) } } - return consts.BoolLen + outputSize + fees.DimensionsLen + consts.Uint64Len + return consts.BoolLen + codec.BytesLen(r.Error) + outputSize + fees.DimensionsLen + consts.Uint64Len } func (r *Result) Marshal(p *codec.Packer) error { p.PackBool(r.Success) - p.PackInt(len(r.Outputs)) - for _, action := range r.Outputs { - p.PackInt(len(action)) - for _, output := range action { + p.PackBytes(r.Error) + p.PackByte(uint8(len(r.Outputs))) + for _, outputs := range r.Outputs { + p.PackByte(uint8(len(outputs))) + for _, output := range outputs { p.PackBytes(output) } } @@ -59,23 +61,20 @@ func UnmarshalResult(p *codec.Packer) (*Result, error) { result := &Result{ Success: p.UnpackBool(), } - totalOutputs := [][][]byte{} - numActions := p.UnpackInt(false) - for i := 0; i < numActions; i++ { - numOutputs := p.UnpackInt(false) - outputs := [][]byte{} - for j := 0; j < numOutputs; j++ { + p.UnpackBytes(consts.MaxInt, false, &result.Error) + outputs := [][][]byte{} + numActions := p.UnpackByte() + for i := uint8(0); i < numActions; i++ { + numOutputs := p.UnpackByte() + actionOutputs := [][]byte{} + for j := uint8(0); j < numOutputs; j++ { var output []byte p.UnpackBytes(consts.MaxInt, false, &output) - outputs = append(outputs, output) + actionOutputs = append(actionOutputs, output) } - totalOutputs = append(totalOutputs, outputs) - } - result.Outputs = totalOutputs - if len(result.Outputs) == 0 { - // Enforce object standardization - result.Outputs = nil + outputs = append(outputs, actionOutputs) } + result.Outputs = outputs consumedRaw := make([]byte, fees.DimensionsLen) p.UnpackFixedBytes(fees.DimensionsLen, &consumedRaw) consumed, err := fees.UnpackDimensions(consumedRaw) @@ -84,9 +83,7 @@ func UnmarshalResult(p *codec.Packer) (*Result, error) { } result.Consumed = consumed result.Fee = p.UnpackUint64(false) - if !p.Empty() { - return nil, p.Err() - } + // Wait to check if empty until after all results are unpacked. return result, p.Err() } diff --git a/chain/transaction.go b/chain/transaction.go index b7edbcbe28..9c737eae1e 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -305,7 +305,7 @@ func (t *Transaction) Execute( ts *tstate.TStateView, timestamp int64, ) (*Result, error) { - // Always charge fee first (in case [Action] moves funds) + // Always charge fee first maxUnits, err := t.MaxUnits(s, r) if err != nil { // Should never happen @@ -323,51 +323,41 @@ func (t *Transaction) Execute( } // We create a temp state checkpoint to ensure we don't commit failed actions to state. - actionStart := ts.OpIndex() - resultOutputs := [][][]byte{} - handleRevert := func(rerr error) (*Result, error) { - // Be warned that the variables captured in this function - // are set when this function is defined. If any of them are - // modified later, they will not be used here. - // - // Note: Revert will not return an error per action. - ts.Rollback(ctx, actionStart) - - // include error in the last action - resultOutputs[len(resultOutputs)-1] = append(resultOutputs[len(resultOutputs)-1], utils.ErrBytes(rerr)) - return &Result{false, resultOutputs, maxUnits, maxFee}, nil - } - + // + // We should favor reverting over returning an error because the caller won't be charged + // for a transaction that returns an error. var ( + actionStart = ts.OpIndex() + resultOutputs = [][][]byte{} + handleRevert = func(err error) (*Result, error) { + ts.Rollback(ctx, actionStart) + return &Result{false, utils.ErrBytes(err), resultOutputs, maxUnits, maxFee}, nil + } computeUnitsOp = math.NewUint64Operator(r.GetBaseComputeUnits()) - txSuccess = true ) for i, action := range t.Actions { - // skip all following actions if one is unsuccessful - if !txSuccess { - break - } actionID := codec.CreateLID(uint8(i), t.id) - success, actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) + + // TODO: remove actionCUs return (VRYX) + actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) if err != nil { return handleRevert(err) } - if len(outputs) == 0 && outputs != nil { - // Enforce object standardization (this is a VM bug and we should fail - // fast) - return handleRevert(ErrInvalidObject) + if outputs == nil { + // Ensure output standardization (match form we will + // unmarshal) + outputs = [][]byte{} } - resultOutputs = append(resultOutputs, outputs) + + // Wait to append outputs until after we check that there aren't too many + // + // TODO: consider removing max here if len(outputs) > int(r.GetMaxOutputsPerAction()) { return handleRevert(ErrTooManyOutputs) } + resultOutputs = append(resultOutputs, outputs) - if !success { - txSuccess = false - ts.Rollback(ctx, actionStart) - } - - // Calculate units used + // Add units used computeUnitsOp.Add(actionCUs) } computeUnitsOp.Add(t.Auth.ComputeUnits(r)) @@ -387,6 +377,7 @@ func (t *Transaction) Execute( // so we don't need to check for pre-existing values. maxChunks, ok := keys.MaxChunks([]byte(key)) if !ok { + // TODO: is this already checked in parse? return handleRevert(ErrInvalidKeyValue) } writes[key] = maxChunks @@ -394,6 +385,8 @@ func (t *Transaction) Execute( // We only charge for the chunks read from disk instead of charging for the max chunks // specified by the key. + // + // TODO: charge max (VRYX) readsOp := math.NewUint64Operator(0) for _, chunksRead := range reads { readsOp.Add(r.GetStorageKeyReadUnits()) @@ -451,7 +444,9 @@ func (t *Transaction) Execute( } } return &Result{ - Success: txSuccess, + Success: true, + Error: []byte{}, + Outputs: resultOutputs, Consumed: used, diff --git a/examples/morpheusvm/actions/outputs.go b/examples/morpheusvm/actions/outputs.go index 231f36c44c..6b9fcf79de 100644 --- a/examples/morpheusvm/actions/outputs.go +++ b/examples/morpheusvm/actions/outputs.go @@ -3,4 +3,6 @@ package actions -var OutputValueZero = []byte("value is zero") +import "errors" + +var ErrOutputValueZero = errors.New("value is zero") diff --git a/examples/morpheusvm/actions/transfer.go b/examples/morpheusvm/actions/transfer.go index c5e06cecd9..464b097450 100644 --- a/examples/morpheusvm/actions/transfer.go +++ b/examples/morpheusvm/actions/transfer.go @@ -12,7 +12,6 @@ import ( mconsts "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" "github.com/ava-labs/hypersdk/examples/morpheusvm/storage" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" ) var _ chain.Action = (*Transfer)(nil) @@ -47,17 +46,17 @@ func (t *Transfer) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, [][]byte, error) { +) (uint64, [][]byte, error) { if t.Value == 0 { - return false, 1, [][]byte{OutputValueZero}, nil + return 1, nil, ErrOutputValueZero } if err := storage.SubBalance(ctx, mu, actor, t.Value); err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } if err := storage.AddBalance(ctx, mu, t.To, t.Value, true); err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } - return true, 1, [][]byte{{}}, nil + return 1, nil, nil } func (*Transfer) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/morpheusvm/tests/integration/integration_test.go b/examples/morpheusvm/tests/integration/integration_test.go index 795d202bc3..20c1ef2ccb 100644 --- a/examples/morpheusvm/tests/integration/integration_test.go +++ b/examples/morpheusvm/tests/integration/integration_test.go @@ -423,7 +423,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := blk.(*chain.StatelessBlock).Results() gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - gomega.Ω(len(results[0].Outputs[0])).To(gomega.Equal(1)) + gomega.Ω(len(results[0].Outputs[0])).To(gomega.Equal(0)) // Unit explanation // diff --git a/examples/tokenvm/actions/burn_asset.go b/examples/tokenvm/actions/burn_asset.go index 7602477052..9cdf47239e 100644 --- a/examples/tokenvm/actions/burn_asset.go +++ b/examples/tokenvm/actions/burn_asset.go @@ -13,7 +13,6 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" ) var _ chain.Action = (*BurnAsset)(nil) @@ -48,28 +47,28 @@ func (b *BurnAsset) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, [][]byte, error) { +) (uint64, [][]byte, error) { if b.Value == 0 { - return false, BurnComputeUnits, [][]byte{OutputValueZero}, nil + return BurnComputeUnits, nil, ErrOutputValueZero } if err := storage.SubBalance(ctx, mu, actor, b.Asset, b.Value); err != nil { - return false, BurnComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return BurnComputeUnits, nil, err } exists, symbol, decimals, metadata, supply, owner, err := storage.GetAsset(ctx, mu, b.Asset) if err != nil { - return false, BurnComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return BurnComputeUnits, nil, err } if !exists { - return false, BurnComputeUnits, [][]byte{OutputAssetMissing}, nil + return BurnComputeUnits, nil, ErrOutputAssetMissing } newSupply, err := smath.Sub(supply, b.Value) if err != nil { - return false, BurnComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return BurnComputeUnits, nil, err } if err := storage.SetAsset(ctx, mu, b.Asset, symbol, decimals, metadata, newSupply, owner); err != nil { - return false, BurnComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return BurnComputeUnits, nil, err } - return true, BurnComputeUnits, [][]byte{{}}, nil + return BurnComputeUnits, nil, nil } func (*BurnAsset) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/close_order.go b/examples/tokenvm/actions/close_order.go index d3f0a435f4..95981ec328 100644 --- a/examples/tokenvm/actions/close_order.go +++ b/examples/tokenvm/actions/close_order.go @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" ) var _ chain.Action = (*CloseOrder)(nil) @@ -46,27 +45,27 @@ func (c *CloseOrder) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, [][]byte, error) { +) (uint64, [][]byte, error) { exists, _, _, out, _, remaining, owner, err := storage.GetOrder(ctx, mu, c.Order) if err != nil { - return false, CloseOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return CloseOrderComputeUnits, nil, err } if !exists { - return false, CloseOrderComputeUnits, [][]byte{OutputOrderMissing}, nil + return CloseOrderComputeUnits, nil, ErrOutputOrderMissing } if owner != actor { - return false, CloseOrderComputeUnits, [][]byte{OutputUnauthorized}, nil + return CloseOrderComputeUnits, nil, ErrOutputUnauthorized } if out != c.Out { - return false, CloseOrderComputeUnits, [][]byte{OutputWrongOut}, nil + return CloseOrderComputeUnits, nil, ErrOutputWrongOut } if err := storage.DeleteOrder(ctx, mu, c.Order); err != nil { - return false, CloseOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return CloseOrderComputeUnits, nil, err } if err := storage.AddBalance(ctx, mu, actor, c.Out, remaining, true); err != nil { - return false, CloseOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return CloseOrderComputeUnits, nil, err } - return true, CloseOrderComputeUnits, [][]byte{{}}, nil + return CloseOrderComputeUnits, nil, nil } func (*CloseOrder) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/create_asset.go b/examples/tokenvm/actions/create_asset.go index 91d6aa373f..55b26fc8bd 100644 --- a/examples/tokenvm/actions/create_asset.go +++ b/examples/tokenvm/actions/create_asset.go @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" ) var _ chain.Action = (*CreateAsset)(nil) @@ -43,28 +42,28 @@ func (c *CreateAsset) Execute( _ int64, actor codec.Address, actionID codec.LID, -) (bool, uint64, [][]byte, error) { +) (uint64, [][]byte, error) { if len(c.Symbol) == 0 { - return false, CreateAssetComputeUnits, [][]byte{OutputSymbolEmpty}, nil + return CreateAssetComputeUnits, nil, ErrOutputSymbolEmpty } if len(c.Symbol) > MaxSymbolSize { - return false, CreateAssetComputeUnits, [][]byte{OutputSymbolTooLarge}, nil + return CreateAssetComputeUnits, nil, ErrOutputSymbolTooLarge } if c.Decimals > MaxDecimals { - return false, CreateAssetComputeUnits, [][]byte{OutputDecimalsTooLarge}, nil + return CreateAssetComputeUnits, nil, ErrOutputDecimalsTooLarge } if len(c.Metadata) == 0 { - return false, CreateAssetComputeUnits, [][]byte{OutputMetadataEmpty}, nil + return CreateAssetComputeUnits, nil, ErrOutputMetadataEmpty } if len(c.Metadata) > MaxMetadataSize { - return false, CreateAssetComputeUnits, [][]byte{OutputMetadataTooLarge}, nil + return CreateAssetComputeUnits, nil, ErrOutputMetadataTooLarge } // It should only be possible to overwrite an existing asset if there is // a hash collision. if err := storage.SetAsset(ctx, mu, actionID, c.Symbol, c.Decimals, c.Metadata, 0, actor); err != nil { - return false, CreateAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return CreateAssetComputeUnits, nil, err } - return true, CreateAssetComputeUnits, [][]byte{{}}, nil + return CreateAssetComputeUnits, nil, nil } func (*CreateAsset) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/create_order.go b/examples/tokenvm/actions/create_order.go index 68c7dbb2be..505a29b84f 100644 --- a/examples/tokenvm/actions/create_order.go +++ b/examples/tokenvm/actions/create_order.go @@ -12,7 +12,6 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" ) var _ chain.Action = (*CreateOrder)(nil) @@ -67,29 +66,29 @@ func (c *CreateOrder) Execute( _ int64, actor codec.Address, actionID codec.LID, -) (bool, uint64, [][]byte, error) { +) (uint64, [][]byte, error) { if c.In == c.Out { - return false, CreateOrderComputeUnits, [][]byte{OutputSameInOut}, nil + return CreateOrderComputeUnits, nil, ErrOutputSameInOut } if c.InTick == 0 { - return false, CreateOrderComputeUnits, [][]byte{OutputInTickZero}, nil + return CreateOrderComputeUnits, nil, ErrOutputInTickZero } if c.OutTick == 0 { - return false, CreateOrderComputeUnits, [][]byte{OutputOutTickZero}, nil + return CreateOrderComputeUnits, nil, ErrOutputOutTickZero } if c.Supply == 0 { - return false, CreateOrderComputeUnits, [][]byte{OutputSupplyZero}, nil + return CreateOrderComputeUnits, nil, ErrOutputSupplyZero } if c.Supply%c.OutTick != 0 { - return false, CreateOrderComputeUnits, [][]byte{OutputSupplyMisaligned}, nil + return CreateOrderComputeUnits, nil, ErrOutputSupplyMisaligned } if err := storage.SubBalance(ctx, mu, actor, c.Out, c.Supply); err != nil { - return false, CreateOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return CreateOrderComputeUnits, nil, err } if err := storage.SetOrder(ctx, mu, actionID, c.In, c.InTick, c.Out, c.OutTick, c.Supply, actor); err != nil { - return false, CreateOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return CreateOrderComputeUnits, nil, err } - return true, CreateOrderComputeUnits, [][]byte{{}}, nil + return CreateOrderComputeUnits, nil, nil } func (*CreateOrder) MaxComputeUnits(chain.Rules) uint64 { @@ -123,6 +122,6 @@ func (*CreateOrder) ValidRange(chain.Rules) (int64, int64) { return -1, -1 } -func PairID(in codec.LID, out codec.LID) string { +func PairID(in codec.LID, out codec.LID) string { //nolint:interfacer return fmt.Sprintf("%s-%s", in.String(), out.String()) } diff --git a/examples/tokenvm/actions/fill_order.go b/examples/tokenvm/actions/fill_order.go index 9a3505dd8c..5883e61bdb 100644 --- a/examples/tokenvm/actions/fill_order.go +++ b/examples/tokenvm/actions/fill_order.go @@ -13,7 +13,6 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" ) var _ chain.Action = (*FillOrder)(nil) @@ -62,39 +61,39 @@ func (f *FillOrder) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, [][]byte, error) { +) (uint64, [][]byte, error) { exists, in, inTick, out, outTick, remaining, owner, err := storage.GetOrder(ctx, mu, f.Order) if err != nil { - return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return NoFillOrderComputeUnits, nil, err } if !exists { - return false, NoFillOrderComputeUnits, [][]byte{OutputOrderMissing}, nil + return NoFillOrderComputeUnits, nil, ErrOutputOrderMissing } if owner != f.Owner { - return false, NoFillOrderComputeUnits, [][]byte{OutputWrongOwner}, nil + return NoFillOrderComputeUnits, nil, ErrOutputWrongOwner } if in != f.In { - return false, NoFillOrderComputeUnits, [][]byte{OutputWrongIn}, nil + return NoFillOrderComputeUnits, nil, ErrOutputWrongIn } if out != f.Out { - return false, NoFillOrderComputeUnits, [][]byte{OutputWrongOut}, nil + return NoFillOrderComputeUnits, nil, ErrOutputWrongOut } if f.Value == 0 { // This should be guarded via [Unmarshal] but we check anyways. - return false, NoFillOrderComputeUnits, [][]byte{OutputValueZero}, nil + return NoFillOrderComputeUnits, nil, ErrOutputValueZero } if f.Value%inTick != 0 { - return false, NoFillOrderComputeUnits, [][]byte{OutputValueMisaligned}, nil + return NoFillOrderComputeUnits, nil, ErrOutputValueMisaligned } // Determine amount of [Out] counterparty will receive if the trade is // successful. outputAmount, err := smath.Mul64(outTick, f.Value/inTick) if err != nil { - return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return NoFillOrderComputeUnits, nil, err } if outputAmount == 0 { // This should never happen because [f.Value] > 0 - return false, NoFillOrderComputeUnits, [][]byte{OutputInsufficientOutput}, nil + return NoFillOrderComputeUnits, nil, ErrOutputInsufficientOutput } var ( inputAmount = f.Value @@ -120,32 +119,32 @@ func (f *FillOrder) Execute( } if inputAmount == 0 { // Don't allow free trades (can happen due to refund rounding) - return false, NoFillOrderComputeUnits, [][]byte{OutputInsufficientInput}, nil + return NoFillOrderComputeUnits, nil, err } if err := storage.SubBalance(ctx, mu, actor, f.In, inputAmount); err != nil { - return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return NoFillOrderComputeUnits, nil, err } if err := storage.AddBalance(ctx, mu, f.Owner, f.In, inputAmount, true); err != nil { - return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return NoFillOrderComputeUnits, nil, err } if err := storage.AddBalance(ctx, mu, actor, f.Out, outputAmount, true); err != nil { - return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return NoFillOrderComputeUnits, nil, err } if shouldDelete { if err := storage.DeleteOrder(ctx, mu, f.Order); err != nil { - return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return NoFillOrderComputeUnits, nil, err } } else { if err := storage.SetOrder(ctx, mu, f.Order, in, inTick, out, outTick, orderRemaining, owner); err != nil { - return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return NoFillOrderComputeUnits, nil, err } } or := &OrderResult{In: inputAmount, Out: outputAmount, Remaining: orderRemaining} output, err := or.Marshal() if err != nil { - return false, NoFillOrderComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return NoFillOrderComputeUnits, nil, err } - return true, FillOrderComputeUnits, [][]byte{output}, nil + return FillOrderComputeUnits, [][]byte{output}, nil } func (*FillOrder) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index c73a44bd0c..68cdc05e51 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -13,7 +13,6 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" ) var _ chain.Action = (*MintAsset)(nil) @@ -51,34 +50,34 @@ func (m *MintAsset) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, [][]byte, error) { +) (uint64, [][]byte, error) { if m.Asset == codec.Empty { - return false, MintAssetComputeUnits, [][]byte{OutputAssetIsNative}, nil + return MintAssetComputeUnits, nil, ErrOutputAssetIsNative } if m.Value == 0 { - return false, MintAssetComputeUnits, [][]byte{OutputValueZero}, nil + return MintAssetComputeUnits, nil, ErrOutputValueZero } exists, symbol, decimals, metadata, supply, owner, err := storage.GetAsset(ctx, mu, m.Asset) if err != nil { - return false, MintAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return MintAssetComputeUnits, nil, err } if !exists { - return false, MintAssetComputeUnits, [][]byte{OutputAssetMissing}, nil + return MintAssetComputeUnits, nil, ErrOutputAssetMissing } if owner != actor { - return false, MintAssetComputeUnits, [][]byte{OutputWrongOwner}, nil + return MintAssetComputeUnits, nil, ErrOutputWrongOwner } newSupply, err := smath.Add64(supply, m.Value) if err != nil { - return false, MintAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return MintAssetComputeUnits, nil, err } if err := storage.SetAsset(ctx, mu, m.Asset, symbol, decimals, metadata, newSupply, actor); err != nil { - return false, MintAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return MintAssetComputeUnits, nil, err } if err := storage.AddBalance(ctx, mu, m.To, m.Asset, m.Value, true); err != nil { - return false, MintAssetComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return MintAssetComputeUnits, nil, err } - return true, MintAssetComputeUnits, [][]byte{{}}, nil + return MintAssetComputeUnits, nil, nil } func (*MintAsset) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/actions/outputs.go b/examples/tokenvm/actions/outputs.go index c9535fc315..10be2706c7 100644 --- a/examples/tokenvm/actions/outputs.go +++ b/examples/tokenvm/actions/outputs.go @@ -3,33 +3,35 @@ package actions +import "errors" + var ( - OutputValueZero = []byte("value is zero") - OutputMemoTooLarge = []byte("memo is too large") - OutputAssetIsNative = []byte("cannot mint native asset") - OutputAssetAlreadyExists = []byte("asset already exists") - OutputAssetMissing = []byte("asset missing") - OutputInTickZero = []byte("in rate is zero") - OutputOutTickZero = []byte("out rate is zero") - OutputSupplyZero = []byte("supply is zero") - OutputSupplyMisaligned = []byte("supply is misaligned") - OutputOrderMissing = []byte("order is missing") - OutputUnauthorized = []byte("unauthorized") - OutputWrongIn = []byte("wrong in asset") - OutputWrongOut = []byte("wrong out asset") - OutputWrongOwner = []byte("wrong owner") - OutputInsufficientInput = []byte("insufficient input") - OutputInsufficientOutput = []byte("insufficient output") - OutputValueMisaligned = []byte("value is misaligned") - OutputSymbolEmpty = []byte("symbol is empty") - OutputSymbolIncorrect = []byte("symbol is incorrect") - OutputSymbolTooLarge = []byte("symbol is too large") - OutputDecimalsIncorrect = []byte("decimal is incorrect") - OutputDecimalsTooLarge = []byte("decimal is too large") - OutputMetadataEmpty = []byte("metadata is empty") - OutputMetadataTooLarge = []byte("metadata is too large") - OutputSameInOut = []byte("same asset used for in and out") - OutputWrongDestination = []byte("wrong destination") - OutputMustFill = []byte("must fill request") - OutputInvalidDestination = []byte("invalid destination") + ErrOutputValueZero = errors.New("value is zero") + ErrOutputMemoTooLarge = errors.New("memo is too large") + ErrOutputAssetIsNative = errors.New("cannot mint native asset") + ErrOutputAssetAlreadyExists = errors.New("asset already exists") + ErrOutputAssetMissing = errors.New("asset missing") + ErrOutputInTickZero = errors.New("in rate is zero") + ErrOutputOutTickZero = errors.New("out rate is zero") + ErrOutputSupplyZero = errors.New("supply is zero") + ErrOutputSupplyMisaligned = errors.New("supply is misaligned") + ErrOutputOrderMissing = errors.New("order is missing") + ErrOutputUnauthorized = errors.New("unauthorized") + ErrOutputWrongIn = errors.New("wrong in asset") + ErrOutputWrongOut = errors.New("wrong out asset") + ErrOutputWrongOwner = errors.New("wrong owner") + ErrOutputInsufficientInput = errors.New("insufficient input") + ErrOutputInsufficientOutput = errors.New("insufficient output") + ErrOutputValueMisaligned = errors.New("value is misaligned") + ErrOutputSymbolEmpty = errors.New("symbol is empty") + ErrOutputSymbolIncorrect = errors.New("symbol is incorrect") + ErrOutputSymbolTooLarge = errors.New("symbol is too large") + ErrOutputDecimalsIncorrect = errors.New("decimal is incorrect") + ErrOutputDecimalsTooLarge = errors.New("decimal is too large") + ErrOutputMetadataEmpty = errors.New("metadata is empty") + ErrOutputMetadataTooLarge = errors.New("metadata is too large") + ErrOutputSameInOut = errors.New("same asset used for in and out") + ErrOutputWrongDestination = errors.New("wrong destination") + ErrOutputMustFill = errors.New("must fill request") + ErrOutputInvalidDestination = errors.New("invalid destination") ) diff --git a/examples/tokenvm/actions/transfer.go b/examples/tokenvm/actions/transfer.go index 96099bd399..f8359f0b6a 100644 --- a/examples/tokenvm/actions/transfer.go +++ b/examples/tokenvm/actions/transfer.go @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" ) var _ chain.Action = (*Transfer)(nil) @@ -52,21 +51,21 @@ func (t *Transfer) Execute( _ int64, actor codec.Address, _ codec.LID, -) (bool, uint64, [][]byte, error) { +) (uint64, [][]byte, error) { if t.Value == 0 { - return false, TransferComputeUnits, [][]byte{OutputValueZero}, nil + return TransferComputeUnits, nil, ErrOutputValueZero } if len(t.Memo) > MaxMemoSize { - return false, CreateAssetComputeUnits, [][]byte{OutputMemoTooLarge}, nil + return CreateAssetComputeUnits, nil, ErrOutputMemoTooLarge } if err := storage.SubBalance(ctx, mu, actor, t.Asset, t.Value); err != nil { - return false, TransferComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return TransferComputeUnits, nil, err } // TODO: allow sender to configure whether they will pay to create if err := storage.AddBalance(ctx, mu, t.To, t.Asset, t.Value, true); err != nil { - return false, TransferComputeUnits, [][]byte{utils.ErrBytes(err)}, nil + return TransferComputeUnits, nil, err } - return true, TransferComputeUnits, [][]byte{{}}, nil + return TransferComputeUnits, nil, nil } func (*Transfer) MaxComputeUnits(chain.Rules) uint64 { diff --git a/examples/tokenvm/cmd/token-wallet/backend/backend.go b/examples/tokenvm/cmd/token-wallet/backend/backend.go index ff811c4a5f..53407758b8 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/backend.go +++ b/examples/tokenvm/cmd/token-wallet/backend/backend.go @@ -1062,7 +1062,7 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { in := assetIDs[0] inID, err := codec.FromString(in) if err != nil { - return err + return nil, err } _, inSymbol, inDecimals, _, _, _, err := b.tcli.Asset(b.ctx, inID, true) if err != nil { @@ -1071,7 +1071,7 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { out := assetIDs[1] outID, err := codec.FromString(out) if err != nil { - return err + return nil, err } _, outSymbol, outDecimals, _, _, _, err := b.tcli.Asset(b.ctx, outID, true) if err != nil { @@ -1196,7 +1196,7 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i if err != nil { return err } - outID, err := codec.FromString(tconsts.HRP, assetOut) + outID, err := codec.FromString(assetOut) if err != nil { return err } diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index 50bff465cd..63db0b88bc 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -445,7 +445,6 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := blk.(*chain.StatelessBlock).Results() gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - gomega.Ω(len(results[0].Outputs[0])).To(gomega.Equal(1)) // Unit explanation // @@ -815,7 +814,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("asset missing")) exists, _, _, _, _, _, err := instances[0].tcli.Asset(context.TODO(), assetID, false) @@ -1007,7 +1006,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("wrong owner")) exists, symbol, decimals, metadata, supply, owner, err := instances[0].tcli.Asset(context.TODO(), asset1ID, false) @@ -1075,7 +1074,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("invalid balance")) exists, symbol, decimals, metadata, supply, owner, err := instances[0].tcli.Asset(context.TODO(), asset1ID, false) @@ -1139,7 +1138,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("overflow")) balance, err := instances[0].tcli.Balance(context.TODO(), sender2, asset1ID) @@ -1335,7 +1334,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("supply is misaligned")) }) @@ -1398,7 +1397,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("invalid balance")) }) @@ -1430,7 +1429,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("value is misaligned")) }) @@ -1462,7 +1461,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("invalid balance")) }) @@ -1537,7 +1536,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("unauthorized")) }) @@ -2023,7 +2022,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) result := results[0] gomega.Ω(result.Success).Should(gomega.BeFalse()) - gomega.Ω(string(result.Outputs[0][0])). + gomega.Ω(string(result.Error)). Should(gomega.ContainSubstring("value is misaligned")) }) }) diff --git a/x/programs/cmd/simulator/cmd/program.go b/x/programs/cmd/simulator/cmd/program.go index 8a0cf4eea1..3bb91804d7 100644 --- a/x/programs/cmd/simulator/cmd/program.go +++ b/x/programs/cmd/simulator/cmd/program.go @@ -13,7 +13,6 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/state" hutils "github.com/ava-labs/hypersdk/utils" @@ -115,21 +114,16 @@ func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string } // execute the action - success, _, outputs, err := programCreateAction.Execute(ctx, nil, db, 0, codec.Empty, programID) - var resultOutputs string - for i := 0; i < len(outputs); i++ { - for j := 0; j < len(outputs[i]); j++ { - resultOutputs += fmt.Sprintf(" %s", string(outputs[i][j])) - } - } - if len(resultOutputs) > 0 { - fmt.Println(resultOutputs) - } - if !success { - return codec.Empty, fmt.Errorf("program creation failed: %s", err) - } + _, outputs, err := programCreateAction.Execute(ctx, nil, db, 0, codec.Empty, programID) if err != nil { - return codec.Empty, err + return codec.Empty, fmt.Errorf("program creation failed: %w", err) + } + if len(outputs) > 0 { + var results []string + for _, output := range outputs { + results = append(results, string(output)) + } + fmt.Println(results) } // store program to disk only on success @@ -164,29 +158,18 @@ func programExecuteFunc( } // execute the action - success, _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.Empty, programActionID) - - if !success { - var respOutput string - for i := 0; i < len(resp); i++ { - respOutput += fmt.Sprintf(" %s", string(resp[i])) - } - return codec.Empty, nil, 0, fmt.Errorf("program execution failed: %s", respOutput) - } + _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.Empty, programActionID) if err != nil { - return codec.Empty, nil, 0, err + return codec.Empty, nil, 0, fmt.Errorf("program execution failed: %w", err) } - // TODO: I don't think this is right - size := 0 - for i := 1; i < len(resp); i++ { - size += consts.IntLen + codec.BytesLen(resp[i]) - } - p := codec.NewWriter(size, consts.MaxInt) var result []int64 - for !p.Empty() { - v := p.UnpackInt64(true) - result = append(result, v) + for _, r := range resp { + p := codec.NewReader(r, len(r)) + for !p.Empty() { + v := p.UnpackInt64(true) + result = append(result, v) + } } // store program to disk only on success @@ -197,6 +180,5 @@ func programExecuteFunc( // get remaining balance from runtime meter balance, err := programExecuteAction.GetBalance() - return programActionID, result, balance, err } diff --git a/x/programs/cmd/simulator/vm/actions/outputs.go b/x/programs/cmd/simulator/vm/actions/outputs.go index 231f36c44c..6b9fcf79de 100644 --- a/x/programs/cmd/simulator/vm/actions/outputs.go +++ b/x/programs/cmd/simulator/vm/actions/outputs.go @@ -3,4 +3,6 @@ package actions -var OutputValueZero = []byte("value is zero") +import "errors" + +var ErrOutputValueZero = errors.New("value is zero") diff --git a/x/programs/cmd/simulator/vm/actions/program_create.go b/x/programs/cmd/simulator/vm/actions/program_create.go index b4fdf4388b..4caae4e0dc 100644 --- a/x/programs/cmd/simulator/vm/actions/program_create.go +++ b/x/programs/cmd/simulator/vm/actions/program_create.go @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" ) @@ -41,16 +40,16 @@ func (t *ProgramCreate) Execute( _ int64, _ codec.Address, actionID codec.LID, -) (bool, uint64, [][]byte, error) { +) (uint64, [][]byte, error) { if len(t.Program) == 0 { - return false, 1, [][]byte{OutputValueZero}, nil + return 1, nil, ErrOutputValueZero } if err := storage.SetProgram(ctx, mu, actionID, t.Program); err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } - return true, 1, [][]byte{{}}, nil + return 1, nil, nil } func (*ProgramCreate) MaxComputeUnits(chain.Rules) uint64 { diff --git a/x/programs/cmd/simulator/vm/actions/program_execute.go b/x/programs/cmd/simulator/vm/actions/program_execute.go index 2378bdd07f..92cb43da7a 100644 --- a/x/programs/cmd/simulator/vm/actions/program_execute.go +++ b/x/programs/cmd/simulator/vm/actions/program_execute.go @@ -19,7 +19,6 @@ import ( "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/utils" importProgram "github.com/ava-labs/hypersdk/x/programs/examples/imports/program" "github.com/ava-labs/hypersdk/x/programs/examples/imports/pstate" @@ -58,30 +57,30 @@ func (t *ProgramExecute) Execute( _ int64, actor codec.Address, actionID codec.LID, -) (success bool, computeUnits uint64, output [][]byte, err error) { +) (computeUnits uint64, output [][]byte, err error) { if len(t.Function) == 0 { - return false, 1, [][]byte{OutputValueZero}, nil + return 1, nil, ErrOutputValueZero } if len(t.Params) == 0 { - return false, 1, [][]byte{OutputValueZero}, nil + return 1, nil, ErrOutputValueZero } programBytes, _, err := storage.GetProgram(ctx, mu, actionID) if err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } // TODO: get cfg from genesis cfg := runtime.NewConfig() if err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } ecfg, err := engine.NewConfigBuilder(). WithDefaultCache(true). Build() if err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } eng := engine.New(ecfg) @@ -104,22 +103,22 @@ func (t *ProgramExecute) Execute( t.rt = runtime.New(logging.NoLog{}, eng, imports, cfg) err = t.rt.Initialize(ctx, callContext, programBytes, t.MaxUnits) if err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } defer t.rt.Stop() mem, err := t.rt.Memory() if err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } params, err := WriteParams(mem, t.Params) if err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } resp, err := t.rt.Call(ctx, t.Function, callContext, params[1:]...) if err != nil { - return false, 1, [][]byte{utils.ErrBytes(err)}, nil + return 1, nil, err } // TODO: remove this is to support readonly response for now. @@ -128,7 +127,7 @@ func (t *ProgramExecute) Execute( p.PackInt64(r) } - return true, 1, [][]byte{p.Bytes()}, nil + return 1, [][]byte{p.Bytes()}, nil } func (*ProgramExecute) MaxComputeUnits(chain.Rules) uint64 { From 0ec49198b7c4b0cd359971b063f08e2e1079a891 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Mon, 20 May 2024 19:09:04 -0400 Subject: [PATCH 74/78] remove trailing enter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index add9648582..710308bc15 100644 --- a/README.md +++ b/README.md @@ -773,7 +773,7 @@ type Rules interface { GetStorageKeyWriteUnits() uint64 GetStorageValueWriteUnits() uint64 // per chunk - FetchCustom(string) (any, bool) + FetchCustom(string) (any, bool) } ``` From e9c2ff43945e8d939f06f867c6e7e1ec5b22a5fd Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Tue, 21 May 2024 13:06:16 -0400 Subject: [PATCH 75/78] revert `codec.LID` (#920) * remove LID definition * remove optional packer * update cli * update heap * eheap + emap * making progress on morpheusvm * cleanup load test error * tokenvm actions * update token-cli * update token-feed + token-faucet * update token-wallet * more progress in tokenvm * update token storage * use id const * update programs * unit tests passing * remove consts form simulator * fix lint * pass integration tests * integration test passing * reduce size of digest * fix load test * e2e lint * reuse nodes * reuse nodes * fix lint * run gci --- README.md | 4 +- chain/base.go | 2 +- chain/block.go | 4 +- chain/dependencies.go | 4 +- chain/mock_action.go | 5 +- chain/mock_auth.go | 8 +- chain/transaction.go | 65 ++--- chain/utils.go | 18 ++ cli/key.go | 4 +- cli/prompt.go | 37 +-- cli/spam.go | 6 +- cli/storage.go | 19 +- codec/address.go | 45 +--- codec/optional_packer.go | 184 -------------- codec/optional_packer_test.go | 144 ----------- codec/packer.go | 13 +- codec/packer_test.go | 9 +- consts/consts.go | 2 - eheap/eheap.go | 6 +- emap/emap.go | 6 +- emap/emap_test.go | 2 +- examples/morpheusvm/actions/transfer.go | 13 +- examples/morpheusvm/auth/bls.go | 2 +- examples/morpheusvm/auth/ed25519.go | 2 +- examples/morpheusvm/auth/secp256r1.go | 2 +- .../morpheusvm/cmd/morpheus-cli/cmd/action.go | 3 +- .../morpheusvm/cmd/morpheus-cli/cmd/chain.go | 3 +- .../cmd/morpheus-cli/cmd/handler.go | 4 +- .../morpheusvm/cmd/morpheus-cli/cmd/key.go | 8 +- .../cmd/morpheus-cli/cmd/prometheus.go | 3 +- .../cmd/morpheus-cli/cmd/resolutions.go | 44 ++-- .../morpheusvm/cmd/morpheus-cli/cmd/root.go | 3 +- .../morpheusvm/cmd/morpheus-cli/cmd/spam.go | 6 +- examples/morpheusvm/cmd/morpheusvm/main.go | 3 +- examples/morpheusvm/config/config.go | 6 +- examples/morpheusvm/consts/consts.go | 4 +- examples/morpheusvm/controller/controller.go | 15 +- examples/morpheusvm/controller/metrics.go | 6 +- examples/morpheusvm/controller/resolutions.go | 1 + examples/morpheusvm/genesis/genesis.go | 5 +- examples/morpheusvm/genesis/rules.go | 1 + examples/morpheusvm/registry/registry.go | 2 +- examples/morpheusvm/rpc/dependencies.go | 1 + examples/morpheusvm/rpc/jsonrpc_client.go | 3 +- examples/morpheusvm/storage/storage.go | 5 +- examples/morpheusvm/tests/e2e/e2e_test.go | 14 +- .../tests/integration/integration_test.go | 40 +-- examples/morpheusvm/tests/load/load_test.go | 26 +- examples/tokenvm/README.md | 36 --- examples/tokenvm/actions/burn_asset.go | 16 +- examples/tokenvm/actions/close_order.go | 20 +- examples/tokenvm/actions/create_asset.go | 6 +- examples/tokenvm/actions/create_order.go | 22 +- examples/tokenvm/actions/fill_order.go | 32 +-- examples/tokenvm/actions/mint_asset.go | 22 +- examples/tokenvm/actions/transfer.go | 18 +- examples/tokenvm/auth/ed25519.go | 2 +- examples/tokenvm/cmd/token-cli/cmd/action.go | 13 +- examples/tokenvm/cmd/token-cli/cmd/chain.go | 3 +- examples/tokenvm/cmd/token-cli/cmd/handler.go | 8 +- examples/tokenvm/cmd/token-cli/cmd/key.go | 11 +- .../tokenvm/cmd/token-cli/cmd/prometheus.go | 3 +- .../tokenvm/cmd/token-cli/cmd/resolutions.go | 161 ++++++------ examples/tokenvm/cmd/token-cli/cmd/root.go | 3 +- examples/tokenvm/cmd/token-cli/cmd/spam.go | 10 +- examples/tokenvm/cmd/token-faucet/main.go | 6 +- .../cmd/token-faucet/manager/manager.go | 11 +- .../cmd/token-faucet/rpc/dependencies.go | 1 + .../tokenvm/cmd/token-feed/config/config.go | 2 +- examples/tokenvm/cmd/token-feed/main.go | 6 +- .../tokenvm/cmd/token-feed/manager/manager.go | 6 +- examples/tokenvm/cmd/token-wallet/app.go | 4 +- .../cmd/token-wallet/backend/backend.go | 118 ++++----- .../cmd/token-wallet/backend/models.go | 1 + .../cmd/token-wallet/backend/storage.go | 28 ++- examples/tokenvm/cmd/tokenvm/main.go | 3 +- examples/tokenvm/config/config.go | 6 +- examples/tokenvm/consts/consts.go | 4 +- examples/tokenvm/controller/controller.go | 18 +- examples/tokenvm/controller/metrics.go | 6 +- examples/tokenvm/controller/resolutions.go | 19 +- examples/tokenvm/controller/state_manager.go | 10 +- examples/tokenvm/genesis/genesis.go | 12 +- examples/tokenvm/genesis/rules.go | 1 + examples/tokenvm/orderbook/orderbook.go | 38 +-- examples/tokenvm/registry/registry.go | 2 +- examples/tokenvm/rpc/dependencies.go | 12 +- examples/tokenvm/rpc/jsonrpc_client.go | 34 +-- examples/tokenvm/rpc/jsonrpc_server.go | 29 +-- examples/tokenvm/scripts/run.sh | 4 +- examples/tokenvm/storage/storage.go | 232 +++++------------- examples/tokenvm/tests/e2e/e2e_test.go | 223 +++++------------ .../tests/integration/integration_test.go | 78 +++--- examples/tokenvm/tests/load/load_test.go | 20 +- heap/heap.go | 30 +-- heap/heap_test.go | 81 ++---- heap/inner_heap.go | 44 ++-- rpc/websocket_packer.go | 4 +- scripts/fix.lint.sh | 6 +- vm/storage.go | 2 +- x/programs/cmd/simulator/cmd/key.go | 3 +- x/programs/cmd/simulator/cmd/logger.go | 2 - x/programs/cmd/simulator/cmd/plan.go | 11 +- x/programs/cmd/simulator/cmd/program.go | 38 ++- x/programs/cmd/simulator/cmd/root.go | 6 +- .../simulator/vm/actions/program_create.go | 7 +- .../simulator/vm/actions/program_execute.go | 21 +- x/programs/cmd/simulator/vm/config/config.go | 2 +- x/programs/cmd/simulator/vm/consts/consts.go | 4 +- .../cmd/simulator/vm/controller/controller.go | 11 +- .../cmd/simulator/vm/controller/metrics.go | 3 +- .../simulator/vm/controller/resolutions.go | 2 +- .../cmd/simulator/vm/genesis/genesis.go | 4 +- x/programs/cmd/simulator/vm/genesis/rules.go | 1 + .../cmd/simulator/vm/registry/registry.go | 2 +- .../cmd/simulator/vm/rpc/dependencies.go | 2 +- .../cmd/simulator/vm/rpc/jsonrpc_client.go | 4 +- .../cmd/simulator/vm/rpc/jsonrpc_server.go | 1 - .../cmd/simulator/vm/storage/storage.go | 12 +- x/programs/cmd/simulator/vm/utils/utils.go | 2 +- .../examples/imports/program/program.go | 8 +- x/programs/examples/storage/storage.go | 14 +- x/programs/examples/token.go | 10 +- x/programs/program/context.go | 6 +- x/programs/runtime/runtime_test.go | 19 +- 125 files changed, 905 insertions(+), 1589 deletions(-) create mode 100644 chain/utils.go delete mode 100644 codec/optional_packer.go delete mode 100644 codec/optional_packer_test.go diff --git a/README.md b/README.md index 710308bc15..c889591874 100644 --- a/README.md +++ b/README.md @@ -648,7 +648,7 @@ type Action interface { // key (formatted as a big-endian uint16). This is used to automatically calculate storage usage. // // If any key is removed and then re-created, this will count as a creation instead of a modification. - StateKeys(actor codec.Address, actionID codec.LID) state.Keys + StateKeys(actor codec.Address, actionID ids.ID) state.Keys // Execute actually runs the [Action]. Any state changes that the [Action] performs should // be done here. @@ -663,7 +663,7 @@ type Action interface { mu state.Mutable, timestamp int64, actor codec.Address, - actionID codec.LID, + actionID ids.ID, ) (computeUnits uint64, outputs [][]byte, err error) } ``` diff --git a/chain/base.go b/chain/base.go index b400a51995..8c4ab47a78 100644 --- a/chain/base.go +++ b/chain/base.go @@ -12,7 +12,7 @@ import ( "github.com/ava-labs/hypersdk/consts" ) -const BaseSize = consts.Uint64Len*2 + consts.IDLen +const BaseSize = consts.Uint64Len*2 + ids.IDLen type Base struct { // Timestamp is the expiry of the transaction (inclusive). Once this time passes and the diff --git a/chain/block.go b/chain/block.go index fd97d86f1d..bdee8fa59d 100644 --- a/chain/block.go +++ b/chain/block.go @@ -799,10 +799,10 @@ func (b *StatelessBlock) FeeManager() *fees.Manager { } func (b *StatefulBlock) Marshal() ([]byte, error) { - size := consts.IDLen + consts.Uint64Len + consts.Uint64Len + + size := ids.IDLen + consts.Uint64Len + consts.Uint64Len + consts.Uint64Len + window.WindowSliceSize + consts.IntLen + codec.CummSize(b.Txs) + - consts.IDLen + consts.Uint64Len + consts.Uint64Len + ids.IDLen + consts.Uint64Len + consts.Uint64Len p := codec.NewWriter(size, consts.NetworkSizeLimit) diff --git a/chain/dependencies.go b/chain/dependencies.go index 3f4b56b3d9..76a719864e 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -241,7 +241,7 @@ type Action interface { // key (formatted as a big-endian uint16). This is used to automatically calculate storage usage. // // If any key is removed and then re-created, this will count as a creation instead of a modification. - StateKeys(actor codec.Address, actionID codec.LID) state.Keys + StateKeys(actor codec.Address, actionID ids.ID) state.Keys // Execute actually runs the [Action]. Any state changes that the [Action] performs should // be done here. @@ -256,7 +256,7 @@ type Action interface { mu state.Mutable, timestamp int64, actor codec.Address, - actionID codec.LID, + actionID ids.ID, ) (computeUnits uint64, outputs [][]byte, err error) } diff --git a/chain/mock_action.go b/chain/mock_action.go index 5b16346794..1b0d244be2 100644 --- a/chain/mock_action.go +++ b/chain/mock_action.go @@ -16,6 +16,7 @@ import ( context "context" reflect "reflect" + ids "github.com/ava-labs/avalanchego/ids" codec "github.com/ava-labs/hypersdk/codec" state "github.com/ava-labs/hypersdk/state" gomock "go.uber.org/mock/gomock" @@ -45,7 +46,7 @@ func (m *MockAction) EXPECT() *MockActionMockRecorder { } // Execute mocks base method. -func (m *MockAction) Execute(arg0 context.Context, arg1 Rules, arg2 state.Mutable, arg3 int64, arg4, arg5 codec.LID) (uint64, [][]byte, error) { +func (m *MockAction) Execute(arg0 context.Context, arg1 Rules, arg2 state.Mutable, arg3 int64, arg4 codec.Address, arg5 ids.ID) (uint64, [][]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Execute", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(uint64) @@ -115,7 +116,7 @@ func (mr *MockActionMockRecorder) Size() *gomock.Call { } // StateKeys mocks base method. -func (m *MockAction) StateKeys(arg0, arg1 codec.LID) state.Keys { +func (m *MockAction) StateKeys(arg0 codec.Address, arg1 ids.ID) state.Keys { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "StateKeys", arg0, arg1) ret0, _ := ret[0].(state.Keys) diff --git a/chain/mock_auth.go b/chain/mock_auth.go index c593acb64b..6643ec2ac0 100644 --- a/chain/mock_auth.go +++ b/chain/mock_auth.go @@ -44,10 +44,10 @@ func (m *MockAuth) EXPECT() *MockAuthMockRecorder { } // Actor mocks base method. -func (m *MockAuth) Actor() codec.LID { +func (m *MockAuth) Actor() codec.Address { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Actor") - ret0, _ := ret[0].(codec.LID) + ret0, _ := ret[0].(codec.Address) return ret0 } @@ -112,10 +112,10 @@ func (mr *MockAuthMockRecorder) Size() *gomock.Call { } // Sponsor mocks base method. -func (m *MockAuth) Sponsor() codec.LID { +func (m *MockAuth) Sponsor() codec.Address { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Sponsor") - ret0, _ := ret[0].(codec.LID) + ret0, _ := ret[0].(codec.Address) return ret0 } diff --git a/chain/transaction.go b/chain/transaction.go index 9c737eae1e..b71101f6fb 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -50,13 +50,13 @@ func (t *Transaction) Digest() ([]byte, error) { if len(t.digest) > 0 { return t.digest, nil } - size := t.Base.Size() + size := t.Base.Size() + consts.Uint8Len for _, action := range t.Actions { size += consts.ByteLen + action.Size() } p := codec.NewWriter(size, consts.NetworkSizeLimit) t.Base.Marshal(p) - p.PackInt(len(t.Actions)) + p.PackByte(uint8(len(t.Actions))) for _, action := range t.Actions { p.PackByte(action.GetTypeID()) action.Marshal(p) @@ -111,7 +111,7 @@ func (t *Transaction) StateKeys(sm StateManager) (state.Keys, error) { // Verify the formatting of state keys passed by the controller for i, action := range t.Actions { - actionKeys := action.StateKeys(t.Auth.Actor(), codec.CreateLID(uint8(i), t.id)) + actionKeys := action.StateKeys(t.Auth.Actor(), CreateActionID(t.ID(), uint8(i))) for k, v := range actionKeys { if !keys.Valid(k) { return nil, ErrInvalidKeyValue @@ -125,6 +125,7 @@ func (t *Transaction) StateKeys(sm StateManager) (state.Keys, error) { if !keys.Valid(k) { return nil, ErrInvalidKeyValue } + // [Add] will take the union of key permissions stateKeys.Add(k, v) } @@ -152,8 +153,6 @@ func (t *Transaction) MaxUnits(sm StateManager, r Rules) (fees.Dimensions, error // Calculate the max storage cost we could incur by processing all // state keys. - // - // TODO: make this a tighter bound (allow for granular storage controls) stateKeys, err := t.StateKeys(sm) if err != nil { return fees.Dimensions{}, err @@ -194,24 +193,31 @@ func (t *Transaction) MaxUnits(sm StateManager, r Rules) (fees.Dimensions, error // EstimateMaxUnits provides a pessimistic estimate of the cost to execute a transaction. This is // typically used during transaction construction. func EstimateMaxUnits(r Rules, actions []Action, authFactory AuthFactory) (fees.Dimensions, error) { - authBandwidth, authCompute := authFactory.MaxUnits() - bandwidth := BaseSize + consts.ByteLen + consts.ByteLen + authBandwidth - computeUnitsOp := math.NewUint64Operator(r.GetBaseComputeUnits()) - computeUnitsOp.Add(authCompute) + var ( + bandwidth = uint64(BaseSize) + stateKeysMaxChunks = []uint16{} // TODO: preallocate + computeOp = math.NewUint64Operator(r.GetBaseComputeUnits()) + readsOp = math.NewUint64Operator(0) + allocatesOp = math.NewUint64Operator(0) + writesOp = math.NewUint64Operator(0) + ) - stateKeysMaxChunks := []uint16{} - sponsorStateKeyMaxChunks := r.GetSponsorStateKeysMaxChunks() - stateKeysMaxChunks = append(stateKeysMaxChunks, sponsorStateKeyMaxChunks...) + // Calculate over action/auth + bandwidth += consts.Uint8Len for _, action := range actions { - bandwidth += uint64(action.Size()) + bandwidth += consts.ByteLen + uint64(action.Size()) actionStateKeysMaxChunks := action.StateKeysMaxChunks() stateKeysMaxChunks = append(stateKeysMaxChunks, actionStateKeysMaxChunks...) - - computeUnitsOp.Add(action.MaxComputeUnits(r)) + computeOp.Add(action.MaxComputeUnits(r)) } + authBandwidth, authCompute := authFactory.MaxUnits() + bandwidth += consts.ByteLen + authBandwidth + sponsorStateKeyMaxChunks := r.GetSponsorStateKeysMaxChunks() + stateKeysMaxChunks = append(stateKeysMaxChunks, sponsorStateKeyMaxChunks...) + computeOp.Add(authCompute) // Estimate compute costs - computeUnits, err := computeUnitsOp.Value() + compute, err := computeOp.Value() if err != nil { return fees.Dimensions{}, err } @@ -219,9 +225,6 @@ func EstimateMaxUnits(r Rules, actions []Action, authFactory AuthFactory) (fees. // Estimate storage costs // // TODO: unify this with [MaxUnits] handling - readsOp := math.NewUint64Operator(0) - allocatesOp := math.NewUint64Operator(0) - writesOp := math.NewUint64Operator(0) for maxChunks := range stateKeysMaxChunks { // Compute key costs readsOp.Add(r.GetStorageKeyReadUnits()) @@ -245,7 +248,7 @@ func EstimateMaxUnits(r Rules, actions []Action, authFactory AuthFactory) (fees. if err != nil { return fees.Dimensions{}, err } - return fees.Dimensions{bandwidth, computeUnits, reads, allocates, writes}, nil + return fees.Dimensions{bandwidth, compute, reads, allocates, writes}, nil } func (t *Transaction) PreExecute( @@ -336,10 +339,8 @@ func (t *Transaction) Execute( computeUnitsOp = math.NewUint64Operator(r.GetBaseComputeUnits()) ) for i, action := range t.Actions { - actionID := codec.CreateLID(uint8(i), t.id) - // TODO: remove actionCUs return (VRYX) - actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), actionID) + actionCUs, outputs, err := action.Execute(ctx, r, ts, timestamp, t.Auth.Actor(), CreateActionID(t.ID(), uint8(i))) if err != nil { return handleRevert(err) } @@ -437,9 +438,8 @@ func (t *Transaction) Execute( refund := maxFee - feeRequired if refund > 0 { ts.DisableAllocation() - err = s.Refund(ctx, t.Auth.Sponsor(), ts, refund) - ts.EnableAllocation() - if err != nil { + defer ts.EnableAllocation() + if err := s.Refund(ctx, t.Auth.Sponsor(), ts, refund); err != nil { return handleRevert(err) } } @@ -465,7 +465,7 @@ func (t *Transaction) Marshal(p *codec.Packer) error { func (t *Transaction) marshalActions(p *codec.Packer) error { t.Base.Marshal(p) - p.PackInt(len(t.Actions)) + p.PackByte(uint8(len(t.Actions))) for _, action := range t.Actions { actionID := action.GetTypeID() p.PackByte(actionID) @@ -567,16 +567,17 @@ func unmarshalActions( p *codec.Packer, actionRegistry *codec.TypeParser[Action, bool], ) ([]Action, error) { - actionCount := p.UnpackInt(true) - actions := make([]Action, 0) - - for i := 0; i < actionCount; i++ { + actionCount := p.UnpackByte() + if actionCount == 0 { + return nil, fmt.Errorf("%w: no actions", ErrInvalidObject) + } + actions := []Action{} + for i := uint8(0); i < actionCount; i++ { actionType := p.UnpackByte() unmarshalAction, ok := actionRegistry.LookupIndex(actionType) if !ok { return nil, fmt.Errorf("%w: %d is unknown action type", ErrInvalidObject, actionType) } - action, err := unmarshalAction(p) if err != nil { return nil, fmt.Errorf("%w: could not unmarshal action", err) diff --git a/chain/utils.go b/chain/utils.go new file mode 100644 index 0000000000..b985d7f905 --- /dev/null +++ b/chain/utils.go @@ -0,0 +1,18 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package chain + +import ( + "github.com/ava-labs/avalanchego/ids" + + "github.com/ava-labs/hypersdk/consts" + "github.com/ava-labs/hypersdk/utils" +) + +func CreateActionID(txID ids.ID, i uint8) ids.ID { + actionBytes := make([]byte, ids.IDLen+consts.Uint8Len) + copy(actionBytes, txID[:]) + actionBytes[ids.IDLen] = i + return utils.ToID(actionBytes) +} diff --git a/cli/key.go b/cli/key.go index 8f26c2dba8..f70fddb6bd 100644 --- a/cli/key.go +++ b/cli/key.go @@ -51,7 +51,7 @@ func (h *Handler) SetKey(lookupBalance func(int, string, string, uint32, ids.ID) return h.StoreDefaultKey(key.Address) } -func (h *Handler) Balance(checkAllChains bool, promptAsset bool, printBalance func(codec.Address, string, uint32, ids.ID, codec.LID) error) error { +func (h *Handler) Balance(checkAllChains bool, promptAsset bool, printBalance func(codec.Address, string, uint32, ids.ID, ids.ID) error) error { addr, _, err := h.GetDefaultKey(true) if err != nil { return err @@ -60,7 +60,7 @@ func (h *Handler) Balance(checkAllChains bool, promptAsset bool, printBalance fu if err != nil { return err } - var assetID codec.LID + var assetID ids.ID if promptAsset { assetID, err = h.PromptAsset("assetID", true) if err != nil { diff --git a/cli/prompt.go b/cli/prompt.go index 52621300c9..127da4c73e 100644 --- a/cli/prompt.go +++ b/cli/prompt.go @@ -30,7 +30,7 @@ func (h *Handler) PromptAddress(label string) (codec.Address, error) { } recipient, err := promptText.Run() if err != nil { - return codec.Empty, err + return codec.EmptyAddress, err } recipient = strings.TrimSpace(recipient) return h.c.ParseAddress(recipient) @@ -56,7 +56,7 @@ func (*Handler) PromptString(label string, min int, max int) (string, error) { return strings.TrimSpace(text), err } -func (h *Handler) PromptAsset(label string, allowNative bool) (codec.LID, error) { +func (h *Handler) PromptAsset(label string, allowNative bool) (ids.ID, error) { symbol := h.c.Symbol() text := fmt.Sprintf("%s (use %s for native token)", label, symbol) if !allowNative { @@ -71,24 +71,24 @@ func (h *Handler) PromptAsset(label string, allowNative bool) (codec.LID, error) if allowNative && input == symbol { return nil } - _, err := codec.FromString(input) + _, err := ids.FromString(input) return err }, } asset, err := promptText.Run() if err != nil { - return codec.Empty, err + return ids.Empty, err } asset = strings.TrimSpace(asset) - var assetID codec.LID + var assetID ids.ID if asset != symbol { - assetID, err = codec.FromString(asset) + assetID, err = ids.FromString(asset) if err != nil { - return codec.Empty, err + return ids.Empty, err } } - if !allowNative && assetID == codec.Empty { - return codec.Empty, ErrInvalidChoice + if !allowNative && assetID == ids.Empty { + return ids.Empty, ErrInvalidChoice } return assetID, nil } @@ -277,25 +277,6 @@ func (*Handler) PromptID(label string) (ids.ID, error) { return id, nil } -func (*Handler) PromptLID(label string) (codec.LID, error) { - promptText := promptui.Prompt{ - Label: label, - Validate: func(input string) error { - if len(input) == 0 { - return ErrInputEmpty - } - _, err := codec.FromString(input) - return err - }, - } - rawID, err := promptText.Run() - if err != nil { - return codec.Empty, err - } - rawID = strings.TrimSpace(rawID) - return codec.FromString(rawID) -} - func (h *Handler) PromptChain(label string, excluded set.Set[ids.ID]) (ids.ID, []string, error) { chains, err := h.GetChains() if err != nil { diff --git a/cli/spam.go b/cli/spam.go index c37188f6b2..ae6fcc9924 100644 --- a/cli/spam.go +++ b/cli/spam.go @@ -492,9 +492,7 @@ func startIssuer(cctx context.Context, issuer *txIssuer) { if result.Success { confirmedTxs++ } else { - // revert error is populated in the last output of the last action - output := result.Outputs[len(result.Outputs)-1][len(result.Outputs[len(result.Outputs)-1])-1] - utils.Outf("{{orange}}on-chain tx failure:{{/}} %s %t\n", string(output), result.Success) + utils.Outf("{{orange}}on-chain tx failure:{{/}} %s %t\n", string(result.Error), result.Success) } } else { // We can't error match here because we receive it over the wire. @@ -535,7 +533,7 @@ func getNextRecipient(self int, createAccount func() (*PrivateKey, error), keys if createAccount != nil { priv, err := createAccount() if err != nil { - return codec.Empty, err + return codec.EmptyAddress, err } return priv.Address, nil } diff --git a/cli/storage.go b/cli/storage.go index 520857cad3..f5d553d69b 100644 --- a/cli/storage.go +++ b/cli/storage.go @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/utils" ) @@ -125,15 +124,15 @@ func (h *Handler) StoreDefaultKey(addr codec.Address) error { func (h *Handler) GetDefaultKey(log bool) (codec.Address, []byte, error) { raddr, err := h.GetDefault(defaultKeyKey) if err != nil { - return codec.Empty, nil, err + return codec.EmptyAddress, nil, err } if len(raddr) == 0 { - return codec.Empty, nil, ErrNoKeys + return codec.EmptyAddress, nil, ErrNoKeys } addr := codec.Address(raddr) priv, err := h.GetKey(addr) if err != nil { - return codec.Empty, nil, err + return codec.EmptyAddress, nil, err } if log { utils.Outf("{{yellow}}address:{{/}} %s\n", h.c.Address(addr)) @@ -142,12 +141,12 @@ func (h *Handler) GetDefaultKey(log bool) (codec.Address, []byte, error) { } func (h *Handler) StoreChain(chainID ids.ID, rpc string) error { - k := make([]byte, 1+consts.IDLen*2) + k := make([]byte, 1+ids.IDLen*2) k[0] = chainPrefix copy(k[1:], chainID[:]) brpc := []byte(rpc) rpcID := utils.ToID(brpc) - copy(k[1+consts.IDLen:], rpcID[:]) + copy(k[1+ids.IDLen:], rpcID[:]) has, err := h.db.Has(k) if err != nil { return err @@ -159,7 +158,7 @@ func (h *Handler) StoreChain(chainID ids.ID, rpc string) error { } func (h *Handler) GetChain(chainID ids.ID) ([]string, error) { - k := make([]byte, 1+consts.IDLen) + k := make([]byte, 1+ids.IDLen) k[0] = chainPrefix copy(k[1:], chainID[:]) @@ -183,7 +182,7 @@ func (h *Handler) GetChains() (map[ids.ID][]string, error) { // It is safe to use these bytes directly because the database copies the // iterator value for us. k := iter.Key() - chainID := ids.ID(k[1 : 1+consts.IDLen]) + chainID := ids.ID(k[1 : 1+ids.IDLen]) rpcs, ok := chains[chainID] if !ok { rpcs = []string{} @@ -202,12 +201,12 @@ func (h *Handler) DeleteChains() ([]ids.ID, error) { chainIDs := make([]ids.ID, 0, len(chains)) for chainID, rpcs := range chains { for _, rpc := range rpcs { - k := make([]byte, 1+consts.IDLen*2) + k := make([]byte, 1+ids.IDLen*2) k[0] = chainPrefix copy(k[1:], chainID[:]) brpc := []byte(rpc) rpcID := utils.ToID(brpc) - copy(k[1+consts.IDLen:], rpcID[:]) + copy(k[1+ids.IDLen:], rpcID[:]) if err := h.db.Delete(k); err != nil { return nil, err } diff --git a/codec/address.go b/codec/address.go index eefdfe8c9e..26eb3575ac 100644 --- a/codec/address.go +++ b/codec/address.go @@ -7,13 +7,11 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/cb58" "github.com/ava-labs/avalanchego/utils/formatting/address" ) const ( AddressLen = 33 - LIDLen = 33 // These consts are pulled from BIP-173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki fromBits = 8 @@ -23,28 +21,9 @@ const ( maxBech32Size = 90 ) -type ( - LID [LIDLen]byte // Long ID - Address = LID -) +type Address [AddressLen]byte -// Empty is a useful all zero value -var Empty = LID{} - -// CreateLID returns [LID] made from concatenating -// some [i] with an [id]. -// -// This is commonly used for creating an ActionID. We -// keep the index|txID format to keep consistency with -// how address construction works. Index is the index -// in the [Action] array of a transaction. txID is the -// ID of that transaction. -func CreateLID(i uint8, id ids.ID) LID { - a := make([]byte, LIDLen) - a[0] = i - copy(a[1:], id[:]) - return LID(a) -} +var EmptyAddress = Address{} // CreateAddress returns [Address] made from concatenating // [typeID] with [id]. @@ -85,31 +64,17 @@ func MustAddressBech32(hrp string, p Address) string { func ParseAddressBech32(hrp, saddr string) (Address, error) { phrp, p, err := address.ParseBech32(saddr) if err != nil { - return Empty, err + return EmptyAddress, err } if phrp != hrp { - return Empty, ErrIncorrectHRP + return EmptyAddress, ErrIncorrectHRP } // The parsed value may be greater than [minLength] because the // underlying Bech32 implementation requires bytes to each encode 5 bits // instead of 8 (and we must pad the input to ensure we fill all bytes): // https://github.com/btcsuite/btcd/blob/902f797b0c4b3af3f7196d2f5d2343931d1b2bdf/btcutil/bech32/bech32.go#L325-L331 if len(p) < AddressLen { - return Empty, ErrInsufficientLength + return EmptyAddress, ErrInsufficientLength } return Address(p[:AddressLen]), nil } - -func (l LID) String() string { - s, _ := cb58.Encode(l[:]) - return s -} - -// FromString is the inverse of LID.String() -func FromString(lidStr string) (LID, error) { - bytes, err := cb58.Decode(lidStr) - if err != nil { - return LID{}, err - } - return LID(bytes), nil -} diff --git a/codec/optional_packer.go b/codec/optional_packer.go deleted file mode 100644 index e2e798d76c..0000000000 --- a/codec/optional_packer.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package codec - -import ( - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/set" - - "github.com/ava-labs/hypersdk/consts" -) - -// OptionalPacker defines a struct that includes a Packer [ip], a bitset -// [b] and an offset [offset]. [b] indicates which fields in the OptionalPacker -// are present and which are not. -type OptionalPacker struct { - b set.Bits64 - offset uint8 - ip *Packer -} - -// NewOptionalWriter returns an instance of OptionalPacker that includes -// a new Packer instance with MaxSize set to the maximum size. The maximum items -// OptionalPacker can hold is set to [size]. If [size] > MaxItems sets -// OptionalPackers MaxItems to MaxItems -func NewOptionalWriter(initial int) *OptionalPacker { - return &OptionalPacker{ - ip: NewWriter(initial, consts.MaxInt), - } -} - -// NewOptionalReader returns an instance of OptionalPacker that includes -// a packer instance set to [p]. It sets the packers bits b to the value of the packers -// UnpackByte and the MaxItems to [size]. If [size] > MaxItems sets -// OptionalPackers MaxItems to MaxItems -// -// used when decoding -func (p *Packer) NewOptionalReader() *OptionalPacker { - o := &OptionalPacker{ - ip: p, - } - o.b = set.Bits64(o.ip.UnpackUint64(false)) - return o -} - -// setBit sets the OptionalPacker's bitset at o.offset and increments -// the offset. If offset exceeds the maximum offset, setBit returns without -// updating the bitset and adds an error to the Packer. -func (o *OptionalPacker) setBit() { - if o.offset > consts.MaxUint64Offset { - o.ip.addErr(ErrTooManyItems) - return - } - o.b.Add(uint(o.offset)) - o.offset++ -} - -// skipBit increments the offset. If offset already exceeds the maximum -// offset, setBit returns and adds an error to the Packer. -func (o *OptionalPacker) skipBit() { - if o.offset > consts.MaxUint64Offset { - o.ip.addErr(ErrTooManyItems) - return - } - o.offset++ -} - -// checkBit returns whether the OptionalPacker's bitset is true at the current -// offset. Increments the offset. -func (o *OptionalPacker) checkBit() bool { - result := o.b.Contains(uint(o.offset)) - o.offset++ - return result -} - -// PackID packs [id] into OptionalPacker if it is not empty. -// Updates the bitset and offset accordingly. -func (o *OptionalPacker) PackID(id ids.ID) { - if id == ids.Empty { - o.skipBit() - return - } - o.ip.PackID(id) - o.setBit() -} - -// UnpackID unpacks an id into [dest] if the bitset is set at the current offset. -// Increments offset regardless. -func (o *OptionalPacker) UnpackID(dest *ids.ID) { - if o.checkBit() { - o.ip.UnpackID(true, dest) - } else { - *dest = ids.Empty - } -} - -// PackUint64 packs [l] into OptionalPacker if [l] is not an 0. -// Updates the bitset and offset accordingly. -func (o *OptionalPacker) PackUint64(l uint64) { - if l == 0 { - o.skipBit() - return - } - o.ip.PackUint64(l) - o.setBit() -} - -// UnpackUint64 unpacks a Uint64 from o and returns the value. -// Increments offset regardless. -func (o *OptionalPacker) UnpackUint64() uint64 { - if o.checkBit() { - return o.ip.UnpackUint64(true) - } - return 0 -} - -// PackInt64 packs [l] into OptionalPacker if [l] is not an 0. -// Updates the bitset and offset accordingly. -func (o *OptionalPacker) PackInt64(l int64) { - if l == 0 { - o.skipBit() - return - } - o.ip.PackInt64(l) - o.setBit() -} - -// UnpackInt64 unpacks a Int64 from o and returns the value. -// Increments offset regardless. -func (o *OptionalPacker) UnpackInt64() int64 { - if o.checkBit() { - return o.ip.UnpackInt64(true) - } - return 0 -} - -// PackAddress packs [addr] into OptionalPacker if [addr] is not empty. -// Updates the bitset and offset accordingly. -func (o *OptionalPacker) PackAddress(addr Address) { - if addr == Empty { - o.skipBit() - return - } - o.ip.PackLID(addr) - o.setBit() -} - -// UnpackAddress unpacks Address into [dest] if the bitset is set at -// the current offset. Increments offset regardless. -func (o *OptionalPacker) UnpackAddress(dest *Address) { - if o.checkBit() { - o.ip.UnpackLID(true, dest) - } else { - *dest = Empty - } -} - -// PackOptional packs an OptionalPacker in a Packer. First packs the bitset [o.b] -// followed by the bytes in the OptionalPacker. -func (p *Packer) PackOptional(o *OptionalPacker) { - p.PackUint64(uint64(o.b)) - p.PackFixedBytes(o.ip.Bytes()) -} - -// Done is called when done reading items from an OptionalPacker. It asserts -// that no bits are populated above the largest read offset. -func (o *OptionalPacker) Done() { - if o.offset == consts.MaxUint64Offset+1 { - // When checking the MaxUint64Offset item, we increment the offset once - // more. - return - } - var maxSet set.Bits64 - maxSet.Add(uint(o.offset)) - if o.b < maxSet { - return - } - o.ip.addErr(ErrInvalidBitset) -} - -// Err returns any error associated with the inner Packer. -func (o *OptionalPacker) Err() error { - return o.ip.Err() -} diff --git a/codec/optional_packer_test.go b/codec/optional_packer_test.go deleted file mode 100644 index 6d12d7ba4a..0000000000 --- a/codec/optional_packer_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package codec - -import ( - "bytes" - "encoding/binary" - "testing" - - "github.com/ava-labs/avalanchego/ids" - "github.com/stretchr/testify/require" - - "github.com/ava-labs/hypersdk/consts" -) - -// toReader returns an OptionalPacker that is a reader of [o]. -// Initializes the OptionalPacker size and bytes from [o]. The returned -// bitmask is equal to the bitmask of [o]. -func (o *OptionalPacker) toReader() *OptionalPacker { - // Add one for o.b byte - size := len(o.ip.Bytes()) + consts.Uint64Len - p := NewWriter(10_000, size) - p.PackOptional(o) - pr := NewReader(p.Bytes(), size) - return pr.NewOptionalReader() -} - -func TestOptionalPackerID(t *testing.T) { - opw := NewOptionalWriter(10_000) - id := ids.GenerateTestID() - t.Run("Pack", func(t *testing.T) { - require := require.New(t) - - // Pack empty - opw.PackID(ids.Empty) - require.Empty(opw.ip.Bytes(), "PackID packed an empty ID.") - - // Pack ID - opw.PackID(id) - bytes, err := ids.ToID(opw.ip.Bytes()) - require.NoError(err, "Error retrieving bytes.") - require.Equal(id, bytes, "PackID did not set bytes correctly.") - }) - t.Run("Unpack", func(t *testing.T) { - require := require.New(t) - - opr := opw.toReader() - var unpackedID ids.ID - // Unpack - opr.UnpackID(&unpackedID) - require.Equal(ids.Empty, unpackedID, "ID unpacked correctly") - opr.UnpackID(&unpackedID) - require.Equal(id, unpackedID, "ID unpacked correctly") - opr.Done() - require.NoError(opr.Err()) - }) -} - -func TestOptionalPackerUint64(t *testing.T) { - opw := NewOptionalWriter(10_000) - val := uint64(900) - t.Run("Pack", func(t *testing.T) { - require := require.New(t) - - // Pack empty - opw.PackUint64(0) - require.Empty(opw.ip.Bytes(), "PackUint64 packed a zero uint.") - - // Pack ID - opw.PackUint64(val) - require.Equal( - val, - binary.BigEndian.Uint64(opw.ip.Bytes()), - "PackUint64 did not set bytes correctly.", - ) - }) - t.Run("Unpack", func(t *testing.T) { - require := require.New(t) - - opr := opw.toReader() - require.Equal(uint64(0), opr.UnpackUint64(), "Uint64 unpacked correctly") - require.Equal(val, opr.UnpackUint64(), "Uint64 unpacked correctly") - opr.Done() - require.NoError(opr.Err()) - }) -} - -func TestOptionalPackerAddress(t *testing.T) { - opw := NewOptionalWriter(10_000) - id := ids.GenerateTestID() - addr := CreateAddress(1, id) - t.Run("Pack", func(t *testing.T) { - require := require.New(t) - - // Pack empty - opw.PackAddress(Empty) - require.Empty(opw.ip.Bytes(), "PackAddress packed an empty Address.") - - // Pack address - opw.PackAddress(addr) - require.True(bytes.Equal(addr[:], opw.ip.Bytes()), "PackPublickey did not set bytes correctly.") - }) - t.Run("Unpack", func(t *testing.T) { - require := require.New(t) - - opr := opw.toReader() - var unpackedAddr Address - opr.UnpackAddress(&unpackedAddr) - require.True(bytes.Equal(Empty[:], unpackedAddr[:]), "AddressBytes unpacked correctly") - opr.UnpackAddress(&unpackedAddr) - require.Equal(addr, unpackedAddr, "PublicKey unpacked correctly") - opr.Done() - require.NoError(opr.Err()) - }) -} - -func TestOptionalPackerInvalidSet(t *testing.T) { - opw := NewOptionalWriter(10_000) - val := uint64(900) - t.Run("Pack", func(t *testing.T) { - require := require.New(t) - - // Pack empty - opw.PackUint64(0) - require.Empty(opw.ip.Bytes(), "PackUint64 packed a zero uint.") - - // Pack ID - opw.PackUint64(val) - require.Equal( - val, - binary.BigEndian.Uint64(opw.ip.Bytes()), - "PackUint64 did not set bytes correctly.", - ) - }) - t.Run("Unpack", func(t *testing.T) { - require := require.New(t) - - // Setup optional reader (expects no entries) - opr := opw.toReader() - opr.Done() - require.ErrorIs(opr.Err(), ErrInvalidBitset) - }) -} diff --git a/codec/packer.go b/codec/packer.go index ca776bbc37..782b741b6e 100644 --- a/codec/packer.go +++ b/codec/packer.go @@ -9,7 +9,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/window" ) @@ -52,7 +51,7 @@ func (p *Packer) PackID(src ids.ID) { // UnpackID unpacks an avalanchego ID into [dest]. If [required] is true, // and the unpacked bytes are empty, Packer will add an ErrFieldNotPopulated error. func (p *Packer) UnpackID(required bool, dest *ids.ID) { - copy((*dest)[:], p.p.UnpackFixedBytes(consts.IDLen)) + copy((*dest)[:], p.p.UnpackFixedBytes(ids.IDLen)) if required && *dest == ids.Empty { p.addErr(fmt.Errorf("%w: ID field is not populated", ErrFieldNotPopulated)) } @@ -70,14 +69,14 @@ func (p *Packer) PackFixedBytes(b []byte) { p.p.PackFixedBytes(b) } -func (p *Packer) PackLID(a LID) { +func (p *Packer) PackAddress(a Address) { p.p.PackFixedBytes(a[:]) } -func (p *Packer) UnpackLID(required bool, dest *LID) { - copy((*dest)[:], p.p.UnpackFixedBytes(LIDLen)) - if required && *dest == Empty { - p.addErr(fmt.Errorf("%w: LID field is not populated", ErrFieldNotPopulated)) +func (p *Packer) UnpackAddress(dest *Address) { + copy((*dest)[:], p.p.UnpackFixedBytes(AddressLen)) + if *dest == EmptyAddress { + p.addErr(fmt.Errorf("%w: Address field is not populated", ErrFieldNotPopulated)) } } diff --git a/codec/packer_test.go b/codec/packer_test.go index 0655d44385..f24ce5c379 100644 --- a/codec/packer_test.go +++ b/codec/packer_test.go @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/stretchr/testify/require" - "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/window" ) @@ -35,7 +34,7 @@ func TestNewWriter(t *testing.T) { } func TestPackerID(t *testing.T) { - wp := NewWriter(consts.IDLen, consts.IDLen) + wp := NewWriter(ids.IDLen, ids.IDLen) id := ids.GenerateTestID() t.Run("Pack", func(t *testing.T) { require := require.New(t) @@ -49,7 +48,7 @@ func TestPackerID(t *testing.T) { t.Run("Unpack", func(t *testing.T) { require := require.New(t) - rp := NewReader(wp.Bytes(), consts.IDLen) + rp := NewReader(wp.Bytes(), ids.IDLen) require.Equal(wp.Bytes(), rp.Bytes(), "Reader not initialized correctly.") unpackedID := ids.Empty rp.UnpackID(true, &unpackedID) @@ -99,7 +98,7 @@ func TestPackerAddress(t *testing.T) { t.Run("Pack", func(t *testing.T) { require := require.New(t) - wp.PackLID(addr) + wp.PackAddress(addr) b := wp.Bytes() require.NoError(wp.Err()) require.Len(b, AddressLen) @@ -112,7 +111,7 @@ func TestPackerAddress(t *testing.T) { rp := NewReader(wp.Bytes(), AddressLen) require.Equal(wp.Bytes(), rp.Bytes()) var unpackedAddr Address - rp.UnpackLID(true, &unpackedAddr) + rp.UnpackAddress(&unpackedAddr) require.Equal(addr[:], unpackedAddr[:]) require.NoError(rp.Err()) }) diff --git a/consts/consts.go b/consts/consts.go index 37fd62e3f5..89a083e8e2 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -7,8 +7,6 @@ const ( // These `codec` consts are defined here to avoid a circular dependency BoolLen = 1 ByteLen = 1 - IDLen = 32 - NodeIDLen = 20 IntLen = 4 Uint8Len = 1 Uint16Len = 2 diff --git a/eheap/eheap.go b/eheap/eheap.go index 6e1d823a6c..453ee316da 100644 --- a/eheap/eheap.go +++ b/eheap/eheap.go @@ -22,14 +22,14 @@ type Item interface { // instead of grouping by expiry to support this feature, which makes it // less efficient). type ExpiryHeap[T Item] struct { - minHeap *heap.Heap[ids.ID, T, int64] + minHeap *heap.Heap[T, int64] } // New returns an instance of ExpiryHeap with minHeap and maxHeap // containing [items]. func New[T Item](items int) *ExpiryHeap[T] { return &ExpiryHeap[T]{ - minHeap: heap.New[ids.ID, T, int64](items, true), + minHeap: heap.New[T, int64](items, true), } } @@ -37,7 +37,7 @@ func New[T Item](items int) *ExpiryHeap[T] { func (eh *ExpiryHeap[T]) Add(item T) { itemID := item.ID() poolLen := eh.minHeap.Len() - eh.minHeap.Push(&heap.Entry[ids.ID, T, int64]{ + eh.minHeap.Push(&heap.Entry[T, int64]{ ID: itemID, Val: item.Expiry(), Item: item, diff --git a/emap/emap.go b/emap/emap.go index 68f6f642a2..a5d1d3e083 100644 --- a/emap/emap.go +++ b/emap/emap.go @@ -29,7 +29,7 @@ type Item interface { type EMap[T Item] struct { mu sync.RWMutex - bh *heap.Heap[ids.ID, *bucket, int64] + bh *heap.Heap[*bucket, int64] seen set.Set[ids.ID] // Stores a set of unique tx ids times map[int64]*bucket // Uses timestamp as keys to map to buckets of ids. } @@ -39,7 +39,7 @@ func NewEMap[T Item]() *EMap[T] { return &EMap[T]{ seen: set.Set[ids.ID]{}, times: make(map[int64]*bucket), - bh: heap.New[ids.ID, *bucket, int64](120, true), + bh: heap.New[*bucket, int64](120, true), } } @@ -81,7 +81,7 @@ func (e *EMap[T]) add(id ids.ID, t int64) { items: []ids.ID{id}, } e.times[t] = b - e.bh.Push(&heap.Entry[ids.ID, *bucket, int64]{ + e.bh.Push(&heap.Entry[*bucket, int64]{ ID: id, Val: t, Item: b, diff --git a/emap/emap_test.go b/emap/emap_test.go index 09dc2a177d..bb04963a87 100644 --- a/emap/emap_test.go +++ b/emap/emap_test.go @@ -27,7 +27,7 @@ func TestEmapNew(t *testing.T) { emptyE := &EMap[*TestTx]{ seen: set.Set[ids.ID]{}, times: make(map[int64]*bucket), - bh: heap.New[ids.ID, *bucket, int64](0, true), + bh: heap.New[*bucket, int64](0, true), } require.Equal(emptyE.seen, e.seen, "Emap did not return an empty emap struct.") require.Equal(emptyE.times, e.times, "Emap did not return an empty emap struct.") diff --git a/examples/morpheusvm/actions/transfer.go b/examples/morpheusvm/actions/transfer.go index 464b097450..d0ba054a6d 100644 --- a/examples/morpheusvm/actions/transfer.go +++ b/examples/morpheusvm/actions/transfer.go @@ -6,12 +6,15 @@ package actions import ( "context" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" - mconsts "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" "github.com/ava-labs/hypersdk/examples/morpheusvm/storage" "github.com/ava-labs/hypersdk/state" + + mconsts "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" ) var _ chain.Action = (*Transfer)(nil) @@ -28,7 +31,7 @@ func (*Transfer) GetTypeID() uint8 { return mconsts.TransferID } -func (t *Transfer) StateKeys(actor codec.Address, _ codec.LID) state.Keys { +func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor)): state.Read | state.Write, string(storage.BalanceKey(t.To)): state.All, @@ -45,7 +48,7 @@ func (t *Transfer) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.LID, + _ ids.ID, ) (uint64, [][]byte, error) { if t.Value == 0 { return 1, nil, ErrOutputValueZero @@ -68,13 +71,13 @@ func (*Transfer) Size() int { } func (t *Transfer) Marshal(p *codec.Packer) { - p.PackLID(t.To) + p.PackAddress(t.To) p.PackUint64(t.Value) } func UnmarshalTransfer(p *codec.Packer) (chain.Action, error) { var transfer Transfer - p.UnpackLID(true, &transfer.To) // we do not verify the typeID is valid + p.UnpackAddress(&transfer.To) // we do not verify the typeID is valid transfer.Value = p.UnpackUint64(true) if err := p.Err(); err != nil { return nil, err diff --git a/examples/morpheusvm/auth/bls.go b/examples/morpheusvm/auth/bls.go index 54def0b881..f56f59bf8b 100644 --- a/examples/morpheusvm/auth/bls.go +++ b/examples/morpheusvm/auth/bls.go @@ -29,7 +29,7 @@ type BLS struct { } func (b *BLS) address() codec.Address { - if b.addr == codec.Empty { + if b.addr == codec.EmptyAddress { b.addr = NewBLSAddress(b.Signer) } return b.addr diff --git a/examples/morpheusvm/auth/ed25519.go b/examples/morpheusvm/auth/ed25519.go index 36af6261d6..e8dd4ac7fe 100644 --- a/examples/morpheusvm/auth/ed25519.go +++ b/examples/morpheusvm/auth/ed25519.go @@ -29,7 +29,7 @@ type ED25519 struct { } func (d *ED25519) address() codec.Address { - if d.addr == codec.Empty { + if d.addr == codec.EmptyAddress { d.addr = NewED25519Address(d.Signer) } return d.addr diff --git a/examples/morpheusvm/auth/secp256r1.go b/examples/morpheusvm/auth/secp256r1.go index 805f947a40..41f9adde6c 100644 --- a/examples/morpheusvm/auth/secp256r1.go +++ b/examples/morpheusvm/auth/secp256r1.go @@ -29,7 +29,7 @@ type SECP256R1 struct { } func (d *SECP256R1) address() codec.Address { - if d.addr == codec.Empty { + if d.addr == codec.EmptyAddress { d.addr = NewSECP256R1Address(d.Signer) } return d.addr diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go index a3bcd5b9f4..84246f0ac5 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/action.go @@ -6,10 +6,11 @@ package cmd import ( "context" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" - "github.com/spf13/cobra" ) var actionCmd = &cobra.Command{ diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/chain.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/chain.go index 9d29c2f5ac..b45982b647 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/chain.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/chain.go @@ -7,9 +7,10 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/chain" "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/chain" + brpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" ) diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/handler.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/handler.go index 40bf5967e0..786f55b25d 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/handler.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/handler.go @@ -7,6 +7,7 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/codec" @@ -15,10 +16,11 @@ import ( "github.com/ava-labs/hypersdk/crypto/secp256r1" "github.com/ava-labs/hypersdk/examples/morpheusvm/auth" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" - brpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" + + brpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" ) var _ cli.Controller = (*Controller)(nil) diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/key.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/key.go index c876fa707e..a07d79d694 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/key.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/key.go @@ -8,6 +8,8 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto/bls" @@ -15,9 +17,9 @@ import ( "github.com/ava-labs/hypersdk/crypto/secp256r1" "github.com/ava-labs/hypersdk/examples/morpheusvm/auth" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" - brpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" "github.com/ava-labs/hypersdk/utils" - "github.com/spf13/cobra" + + brpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" ) const ( @@ -217,7 +219,7 @@ var setKeyCmd = &cobra.Command{ }, } -func lookupKeyBalance(addr codec.Address, uri string, networkID uint32, chainID ids.ID, _ codec.LID) error { +func lookupKeyBalance(addr codec.Address, uri string, networkID uint32, chainID ids.ID, _ ids.ID) error { _, err := handler.GetBalance(context.TODO(), brpc.NewJSONRPCClient(uri, networkID, chainID), addr) return err } diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/prometheus.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/prometheus.go index 40850af663..00ec2ffdd9 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/prometheus.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/prometheus.go @@ -8,9 +8,10 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/utils" - "github.com/spf13/cobra" ) var prometheusCmd = &cobra.Command{ diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go index 39e4390060..0465ca2849 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go @@ -9,14 +9,16 @@ import ( "reflect" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" - brpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" + + brpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" ) // sendAndWait may not be used concurrently @@ -64,7 +66,7 @@ func handleTx(tx *chain.Transaction, result *chain.Result) { "❌", tx.ID(), codec.MustAddressBech32(consts.HRP, actor), - string(result.Outputs[len(result.Outputs)-1][len(result.Outputs[len(result.Outputs)-1])-1]), // revert error + string(result.Error), // revert error float64(result.Fee)/float64(tx.Base.MaxFee)*100, utils.FormatBalance(result.Fee, consts.Decimals), consts.Symbol, @@ -73,27 +75,23 @@ func handleTx(tx *chain.Transaction, result *chain.Result) { return } - for i := 0; i < len(result.Outputs); i++ { - for j := 0; j < len(result.Outputs[i]); j++ { - for _, action := range tx.Actions { - var summaryStr string - switch act := action.(type) { //nolint:gocritic - case *actions.Transfer: - summaryStr = fmt.Sprintf("%s %s -> %s\n", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) - } - utils.Outf( - "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", - "✅", - tx.ID(), - codec.MustAddressBech32(consts.HRP, actor), - reflect.TypeOf(action), - summaryStr, - float64(result.Fee)/float64(tx.Base.MaxFee)*100, - utils.FormatBalance(result.Fee, consts.Decimals), - consts.Symbol, - cli.ParseDimensions(result.Consumed), - ) - } + for _, action := range tx.Actions { + var summaryStr string + switch act := action.(type) { //nolint:gocritic + case *actions.Transfer: + summaryStr = fmt.Sprintf("%s %s -> %s\n", utils.FormatBalance(act.Value, consts.Decimals), consts.Symbol, codec.MustAddressBech32(consts.HRP, act.To)) } + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + "✅", + tx.ID(), + codec.MustAddressBech32(consts.HRP, actor), + reflect.TypeOf(action), + summaryStr, + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, consts.Decimals), + consts.Symbol, + cli.ParseDimensions(result.Consumed), + ) } } diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/root.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/root.go index c0989c7e3d..015d617c04 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/root.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/root.go @@ -7,9 +7,10 @@ import ( "fmt" "time" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/utils" - "github.com/spf13/cobra" ) const ( diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/spam.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/spam.go index b1feacd422..0bc3bb8f87 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/spam.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/spam.go @@ -7,6 +7,8 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/codec" @@ -16,11 +18,11 @@ import ( "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" "github.com/ava-labs/hypersdk/examples/morpheusvm/auth" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" - brpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" - "github.com/spf13/cobra" + + brpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" ) func getFactory(priv *cli.PrivateKey) (chain.AuthFactory, error) { diff --git a/examples/morpheusvm/cmd/morpheusvm/main.go b/examples/morpheusvm/cmd/morpheusvm/main.go index 9cb97fe7bc..32b7157a4c 100644 --- a/examples/morpheusvm/cmd/morpheusvm/main.go +++ b/examples/morpheusvm/cmd/morpheusvm/main.go @@ -11,9 +11,10 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/ulimit" "github.com/ava-labs/avalanchego/vms/rpcchainvm" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/examples/morpheusvm/cmd/morpheusvm/version" "github.com/ava-labs/hypersdk/examples/morpheusvm/controller" - "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ diff --git a/examples/morpheusvm/config/config.go b/examples/morpheusvm/config/config.go index 1f4bbf9640..e07c7b2fc2 100644 --- a/examples/morpheusvm/config/config.go +++ b/examples/morpheusvm/config/config.go @@ -12,13 +12,13 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/profiler" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/config" - "github.com/ava-labs/hypersdk/trace" - "github.com/ava-labs/hypersdk/vm" - "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" "github.com/ava-labs/hypersdk/examples/morpheusvm/version" + "github.com/ava-labs/hypersdk/trace" + "github.com/ava-labs/hypersdk/vm" ) var _ vm.Config = (*Config)(nil) diff --git a/examples/morpheusvm/consts/consts.go b/examples/morpheusvm/consts/consts.go index 4223730a4d..a1b509f631 100644 --- a/examples/morpheusvm/consts/consts.go +++ b/examples/morpheusvm/consts/consts.go @@ -5,9 +5,9 @@ package consts import ( "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/consts" ) const ( @@ -20,7 +20,7 @@ const ( var ID ids.ID func init() { - b := make([]byte, consts.IDLen) + b := make([]byte, ids.IDLen) copy(b, []byte(Name)) vmID, err := ids.ToID(b) if err != nil { diff --git a/examples/morpheusvm/controller/controller.go b/examples/morpheusvm/controller/controller.go index 7fb305ceee..3ef916a5e5 100644 --- a/examples/morpheusvm/controller/controller.go +++ b/examples/morpheusvm/controller/controller.go @@ -8,17 +8,12 @@ import ( "fmt" "net/http" - ametrics "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/hypersdk/builder" - "github.com/ava-labs/hypersdk/chain" - "github.com/ava-labs/hypersdk/gossiper" - hrpc "github.com/ava-labs/hypersdk/rpc" - hstorage "github.com/ava-labs/hypersdk/storage" - "github.com/ava-labs/hypersdk/vm" "go.uber.org/zap" + "github.com/ava-labs/hypersdk/builder" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" "github.com/ava-labs/hypersdk/examples/morpheusvm/auth" "github.com/ava-labs/hypersdk/examples/morpheusvm/config" @@ -27,6 +22,12 @@ import ( "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" "github.com/ava-labs/hypersdk/examples/morpheusvm/storage" "github.com/ava-labs/hypersdk/examples/morpheusvm/version" + "github.com/ava-labs/hypersdk/gossiper" + "github.com/ava-labs/hypersdk/vm" + + ametrics "github.com/ava-labs/avalanchego/api/metrics" + hrpc "github.com/ava-labs/hypersdk/rpc" + hstorage "github.com/ava-labs/hypersdk/storage" ) var _ vm.Controller = (*Controller)(nil) diff --git a/examples/morpheusvm/controller/metrics.go b/examples/morpheusvm/controller/metrics.go index 0708cb636c..4c2a96fd49 100644 --- a/examples/morpheusvm/controller/metrics.go +++ b/examples/morpheusvm/controller/metrics.go @@ -4,10 +4,12 @@ package controller import ( - ametrics "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" "github.com/prometheus/client_golang/prometheus" + + "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" + + ametrics "github.com/ava-labs/avalanchego/api/metrics" ) type metrics struct { diff --git a/examples/morpheusvm/controller/resolutions.go b/examples/morpheusvm/controller/resolutions.go index 7fae0e1089..011725684c 100644 --- a/examples/morpheusvm/controller/resolutions.go +++ b/examples/morpheusvm/controller/resolutions.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/morpheusvm/genesis" "github.com/ava-labs/hypersdk/examples/morpheusvm/storage" diff --git a/examples/morpheusvm/genesis/genesis.go b/examples/morpheusvm/genesis/genesis.go index c14a4e47e0..a2d6cf9edb 100644 --- a/examples/morpheusvm/genesis/genesis.go +++ b/examples/morpheusvm/genesis/genesis.go @@ -9,16 +9,17 @@ import ( "fmt" "github.com/ava-labs/avalanchego/trace" - smath "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/x/merkledb" "github.com/ava-labs/hypersdk/codec" - hconsts "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" "github.com/ava-labs/hypersdk/examples/morpheusvm/storage" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/vm" + + smath "github.com/ava-labs/avalanchego/utils/math" + hconsts "github.com/ava-labs/hypersdk/consts" ) var _ vm.Genesis = (*Genesis)(nil) diff --git a/examples/morpheusvm/genesis/rules.go b/examples/morpheusvm/genesis/rules.go index 46c45ef2e3..0a6516a966 100644 --- a/examples/morpheusvm/genesis/rules.go +++ b/examples/morpheusvm/genesis/rules.go @@ -5,6 +5,7 @@ package genesis import ( "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/examples/morpheusvm/storage" "github.com/ava-labs/hypersdk/fees" diff --git a/examples/morpheusvm/registry/registry.go b/examples/morpheusvm/registry/registry.go index 1a1cf13e93..2854e06477 100644 --- a/examples/morpheusvm/registry/registry.go +++ b/examples/morpheusvm/registry/registry.go @@ -5,9 +5,9 @@ package registry import ( "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" "github.com/ava-labs/hypersdk/examples/morpheusvm/auth" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" diff --git a/examples/morpheusvm/rpc/dependencies.go b/examples/morpheusvm/rpc/dependencies.go index a941f54bfe..3cb31c1730 100644 --- a/examples/morpheusvm/rpc/dependencies.go +++ b/examples/morpheusvm/rpc/dependencies.go @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/morpheusvm/genesis" "github.com/ava-labs/hypersdk/fees" diff --git a/examples/morpheusvm/rpc/jsonrpc_client.go b/examples/morpheusvm/rpc/jsonrpc_client.go index c976888fb0..654baa0fb6 100644 --- a/examples/morpheusvm/rpc/jsonrpc_client.go +++ b/examples/morpheusvm/rpc/jsonrpc_client.go @@ -9,10 +9,11 @@ import ( "github.com/ava-labs/avalanchego/ids" + _ "github.com/ava-labs/hypersdk/examples/morpheusvm/registry" // ensure registry populated + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" "github.com/ava-labs/hypersdk/examples/morpheusvm/genesis" - _ "github.com/ava-labs/hypersdk/examples/morpheusvm/registry" // ensure registry populated "github.com/ava-labs/hypersdk/examples/morpheusvm/storage" "github.com/ava-labs/hypersdk/requester" "github.com/ava-labs/hypersdk/rpc" diff --git a/examples/morpheusvm/storage/storage.go b/examples/morpheusvm/storage/storage.go index 7db1a7eee6..8838b5b652 100644 --- a/examples/morpheusvm/storage/storage.go +++ b/examples/morpheusvm/storage/storage.go @@ -11,12 +11,13 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" - smath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/state" + smath "github.com/ava-labs/avalanchego/utils/math" mconsts "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" ) @@ -58,7 +59,7 @@ var ( // [txPrefix] + [txID] func TxKey(id ids.ID) (k []byte) { - k = make([]byte, 1+consts.IDLen) + k = make([]byte, 1+ids.IDLen) k[0] = txPrefix copy(k[1:], id[:]) return diff --git a/examples/morpheusvm/tests/e2e/e2e_test.go b/examples/morpheusvm/tests/e2e/e2e_test.go index 1b1ae5c82b..cfa5f481b7 100644 --- a/examples/morpheusvm/tests/e2e/e2e_test.go +++ b/examples/morpheusvm/tests/e2e/e2e_test.go @@ -11,23 +11,25 @@ import ( "testing" "time" - runner_sdk "github.com/ava-labs/avalanche-network-runner/client" "github.com/ava-labs/avalanche-network-runner/rpcpb" "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/fatih/color" + "github.com/onsi/gomega" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" "github.com/ava-labs/hypersdk/examples/morpheusvm/auth" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" - lrpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" - "github.com/fatih/color" + + runner_sdk "github.com/ava-labs/avalanche-network-runner/client" + lrpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" ginkgo "github.com/onsi/ginkgo/v2" - "github.com/onsi/gomega" ) const ( @@ -263,10 +265,10 @@ var _ = ginkgo.BeforeSuite(func() { ) logsDir = resp.GetClusterInfo().GetRootDataDir() - // Name 5 new validators (which should have BLS key registered) + // Add 5 validators (already have BLS key registered) subnet := []string{} for i := 1; i <= int(numValidators); i++ { - n := fmt.Sprintf("node%d-bls", i) + n := fmt.Sprintf("node%d", i) subnet = append(subnet, n) } specs := []*rpcpb.BlockchainSpec{ diff --git a/examples/morpheusvm/tests/integration/integration_test.go b/examples/morpheusvm/tests/integration/integration_test.go index 20c1ef2ccb..45f61ce96a 100644 --- a/examples/morpheusvm/tests/integration/integration_test.go +++ b/examples/morpheusvm/tests/integration/integration_test.go @@ -27,28 +27,28 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" "github.com/fatih/color" - ginkgo "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "go.uber.org/zap" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" - hbls "github.com/ava-labs/hypersdk/crypto/bls" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/crypto/secp256r1" + "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" + "github.com/ava-labs/hypersdk/examples/morpheusvm/auth" + "github.com/ava-labs/hypersdk/examples/morpheusvm/controller" + "github.com/ava-labs/hypersdk/examples/morpheusvm/genesis" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" - hutils "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/vm" - "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" - "github.com/ava-labs/hypersdk/examples/morpheusvm/auth" + hbls "github.com/ava-labs/hypersdk/crypto/bls" lconsts "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" - "github.com/ava-labs/hypersdk/examples/morpheusvm/controller" - "github.com/ava-labs/hypersdk/examples/morpheusvm/genesis" lrpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" + hutils "github.com/ava-labs/hypersdk/utils" + ginkgo "github.com/onsi/ginkgo/v2" ) var ( @@ -432,19 +432,19 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // read: 2 keys reads, 1 had 0 chunks // allocate: 1 key created with 1 chunk // write: 2 keys modified (new + old) - transferTxConsumed := fees.Dimensions{191, 7, 12, 25, 26} + transferTxConsumed := fees.Dimensions{188, 7, 12, 25, 26} gomega.Ω(results[0].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(261))) + gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(258))) }) ginkgo.By("ensure balance is updated", func() { balance, err := instances[1].lcli.Balance(context.Background(), addrStr) gomega.Ω(err).To(gomega.BeNil()) - gomega.Ω(balance).To(gomega.Equal(uint64(9899739))) + gomega.Ω(balance).To(gomega.Equal(uint64(9899742))) balance2, err := instances[1].lcli.Balance(context.Background(), addrStr2) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100000))) @@ -478,13 +478,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // read: 2 keys reads, 1 chunk each // allocate: 0 key created // write: 2 key modified - transferTxConsumed := fees.Dimensions{191, 7, 14, 0, 26} + transferTxConsumed := fees.Dimensions{188, 7, 14, 0, 26} gomega.Ω(results[0].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(238))) + gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(235))) balance2, err := instances[1].lcli.Balance(context.Background(), addrStr2) gomega.Ω(err).To(gomega.BeNil()) @@ -559,12 +559,12 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // allocate: 0 key created // write: 2 key modified gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - transferTxConsumed := fees.Dimensions{191, 7, 14, 0, 26} + transferTxConsumed := fees.Dimensions{188, 7, 14, 0, 26} gomega.Ω(results[0].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(238))) + gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(235))) // Unit explanation // @@ -574,12 +574,12 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // allocate: 0 key created // write: 2 keys modified gomega.Ω(results[1].Success).Should(gomega.BeTrue()) - transferTxConsumed = fees.Dimensions{191, 7, 14, 0, 26} + transferTxConsumed = fees.Dimensions{188, 7, 14, 0, 26} gomega.Ω(results[1].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[1].Fee).Should(gomega.Equal(uint64(238))) + gomega.Ω(results[1].Fee).Should(gomega.Equal(uint64(235))) // Unit explanation // @@ -589,12 +589,12 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // allocate: 1 key created (1 chunk) // write: 2 key modified (1 chunk), both previously modified gomega.Ω(results[2].Success).Should(gomega.BeTrue()) - transferTxConsumed = fees.Dimensions{191, 7, 12, 25, 26} + transferTxConsumed = fees.Dimensions{188, 7, 12, 25, 26} gomega.Ω(results[2].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[2].Fee).Should(gomega.Equal(uint64(261))) + gomega.Ω(results[2].Fee).Should(gomega.Equal(uint64(258))) // Unit explanation // @@ -604,12 +604,12 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // allocate: 0 key created // write: 2 keys modified (1 chunk) gomega.Ω(results[3].Success).Should(gomega.BeTrue()) - transferTxConsumed = fees.Dimensions{191, 7, 12, 0, 26} + transferTxConsumed = fees.Dimensions{188, 7, 12, 0, 26} gomega.Ω(results[3].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[3].Fee).Should(gomega.Equal(uint64(236))) + gomega.Ω(results[3].Fee).Should(gomega.Equal(uint64(233))) // Check end balance balance2, err := instances[1].lcli.Balance(context.Background(), addrStr2) diff --git a/examples/morpheusvm/tests/load/load_test.go b/examples/morpheusvm/tests/load/load_test.go index 85ecf8eab3..0763dec424 100644 --- a/examples/morpheusvm/tests/load/load_test.go +++ b/examples/morpheusvm/tests/load/load_test.go @@ -30,27 +30,27 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/fatih/color" - ginkgo "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "go.uber.org/zap" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" - hconsts "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" - "github.com/ava-labs/hypersdk/fees" - "github.com/ava-labs/hypersdk/pebble" - hutils "github.com/ava-labs/hypersdk/utils" - "github.com/ava-labs/hypersdk/vm" - "github.com/ava-labs/hypersdk/workers" - "github.com/ava-labs/hypersdk/examples/morpheusvm/actions" "github.com/ava-labs/hypersdk/examples/morpheusvm/auth" "github.com/ava-labs/hypersdk/examples/morpheusvm/consts" "github.com/ava-labs/hypersdk/examples/morpheusvm/controller" "github.com/ava-labs/hypersdk/examples/morpheusvm/genesis" - trpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" + "github.com/ava-labs/hypersdk/fees" + "github.com/ava-labs/hypersdk/pebble" "github.com/ava-labs/hypersdk/rpc" + "github.com/ava-labs/hypersdk/vm" + "github.com/ava-labs/hypersdk/workers" + + hconsts "github.com/ava-labs/hypersdk/consts" + trpc "github.com/ava-labs/hypersdk/examples/morpheusvm/rpc" + hutils "github.com/ava-labs/hypersdk/utils" + ginkgo "github.com/onsi/ginkgo/v2" ) const genesisBalance uint64 = hconsts.MaxUint64 @@ -411,13 +411,7 @@ var _ = ginkgo.Describe("load tests vm", func() { for _, result := range blk.Results() { if !result.Success { unitPrices, _ := instances[0].cli.UnitPrices(context.Background(), false) - var resultOutputs string - for i := 0; i < len(result.Outputs); i++ { - for j := 0; j < len(result.Outputs[i]); j++ { - resultOutputs += fmt.Sprintf(" %s", result.Outputs[i][j]) - } - } - fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "output:", resultOutputs) + fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "error:", string(result.Error)) } gomega.Ω(result.Success).Should(gomega.BeTrue()) } diff --git a/examples/tokenvm/README.md b/examples/tokenvm/README.md index 3210c6208b..8d8d051508 100644 --- a/examples/tokenvm/README.md +++ b/examples/tokenvm/README.md @@ -122,9 +122,6 @@ key for this address is `0x323b1d8f4eed5f0da9da93071b034f2dce9d2d22692c172f3cb252a64ddfafd01b057de320297c29ad0c1f589ea216869cf1938d88c9fbd70d6748323dbf2fa7`. For convenience, this key has is also stored at `demo.pk`._ -_If you don't need 2 Subnets for your testing, you can run `MODE="run-single" -./scripts/run.sh`._ - To make it easy to interact with the `tokenvm`, we implemented the `token-cli`. Next, you'll need to build this. You can use the following command from this location to do so: @@ -313,39 +310,6 @@ height:15 txs:1 units:464 root:u2FyTtup4gwPfEFybMNTgL2svvSnajfGH4QKqiJ9vpZBSvx7q ✅ Lsad3MZ8i5V5hrGcRxXsghV5G1o1a9XStHY3bYmg7ha7W511e actor: token1rvzhmceq997zntgvravfagsks6w0ryud3rylh4cdvayry0dl97nsjzf3yp units: 464 summary (*actions.CloseOrder): [orderID: 2Qb172jGBtjTTLhrzYD8ZLatjg6FFmbiFSP6CBq2Xy4aBV2WxL] ``` -### Transfer Assets to Another Subnet -Unlike the mint and trade demo, the AWM demo only requires running a single -command. You can kick off a transfer between the 2 Subnets you created by -running the following command from this location: -```bash -./build/token-cli action export -``` - -When you are done, the output should look something like this: -``` -database: .token-cli -address: token1rvzhmceq997zntgvravfagsks6w0ryud3rylh4cdvayry0dl97nsjzf3yp -chainID: Em2pZtHr7rDCzii43an2bBi1M2mTFyLN33QP1Xfjy7BcWtaH9 -✔ assetID (use TKN for native token): TKN -balance: 997.999988891 TKN -recipient: token1rvzhmceq997zntgvravfagsks6w0ryud3rylh4cdvayry0dl97nsjzf3yp -amount: 10 -reward: 0 -available chains: 1 excluded: [Em2pZtHr7rDCzii43an2bBi1M2mTFyLN33QP1Xfjy7BcWtaH9] -0) chainID: cKVefMmNPSKmLoshR15Fzxmx52Y5yUSPqWiJsNFUg1WgNQVMX -destination: 0 -swap on import (y/n): n -continue (y/n): y -✅ txID: 24Y2zR2qEQZSmyaG1BCqpZZaWMDVDtimGDYFsEkpCcWYH4dUfJ -perform import on destination (y/n): y -22u9zvTa8cRX7nork3koubETsKDn43ydaVEZZWMGcTDerucq4b to: token1rvzhmceq997zntgvravfagsks6w0ryud3rylh4cdvayry0dl97nsjzf3yp source assetID: TKN output assetID: 2rST7KDPjRvDxypr6Q4SwfAwdApLwKXuukrSc42jA3dQDgo7jx value: 10000000000 reward: 10000000000 return: false -✔ switch default chain to destination (y/n): y -``` - -_The `export` command will automatically run the `import` command on the -destination. If you wish to import the AWM message using a separate account, -you can run the `import` command after changing your key._ - ### Running a Load Test _Before running this demo, make sure to stop the network you started using `killall avalanche-network-runner`._ diff --git a/examples/tokenvm/actions/burn_asset.go b/examples/tokenvm/actions/burn_asset.go index 9cdf47239e..5457fae313 100644 --- a/examples/tokenvm/actions/burn_asset.go +++ b/examples/tokenvm/actions/burn_asset.go @@ -6,20 +6,22 @@ package actions import ( "context" - smath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" + + smath "github.com/ava-labs/avalanchego/utils/math" ) var _ chain.Action = (*BurnAsset)(nil) type BurnAsset struct { // Asset is the [ActionID] that created the asset. - Asset codec.LID `json:"asset"` + Asset ids.ID `json:"asset"` // Number of assets to mint to [To]. Value uint64 `json:"value"` @@ -29,7 +31,7 @@ func (*BurnAsset) GetTypeID() uint8 { return burnAssetID } -func (b *BurnAsset) StateKeys(actor codec.Address, _ codec.LID) state.Keys { +func (b *BurnAsset) StateKeys(actor codec.Address, _ ids.ID) state.Keys { return state.Keys{ string(storage.AssetKey(b.Asset)): state.Read | state.Write, string(storage.BalanceKey(actor, b.Asset)): state.Read | state.Write, @@ -46,7 +48,7 @@ func (b *BurnAsset) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.LID, + _ ids.ID, ) (uint64, [][]byte, error) { if b.Value == 0 { return BurnComputeUnits, nil, ErrOutputValueZero @@ -76,17 +78,17 @@ func (*BurnAsset) MaxComputeUnits(chain.Rules) uint64 { } func (*BurnAsset) Size() int { - return codec.LIDLen + consts.Uint64Len + return ids.IDLen + consts.Uint64Len } func (b *BurnAsset) Marshal(p *codec.Packer) { - p.PackLID(b.Asset) + p.PackID(b.Asset) p.PackUint64(b.Value) } func UnmarshalBurnAsset(p *codec.Packer) (chain.Action, error) { var burn BurnAsset - p.UnpackLID(false, &burn.Asset) // can burn native asset + p.UnpackID(false, &burn.Asset) // can burn native asset burn.Value = p.UnpackUint64(true) return &burn, p.Err() } diff --git a/examples/tokenvm/actions/close_order.go b/examples/tokenvm/actions/close_order.go index 95981ec328..ce7a63da0d 100644 --- a/examples/tokenvm/actions/close_order.go +++ b/examples/tokenvm/actions/close_order.go @@ -6,6 +6,8 @@ package actions import ( "context" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" @@ -16,18 +18,18 @@ var _ chain.Action = (*CloseOrder)(nil) type CloseOrder struct { // [Order] is the OrderID you wish to close. - Order codec.LID `json:"order"` + Order ids.ID `json:"order"` // [Out] is the asset locked up in the order. We need to provide this to // populate [StateKeys]. - Out codec.LID `json:"out"` + Out ids.ID `json:"out"` } func (*CloseOrder) GetTypeID() uint8 { return closeOrderID } -func (c *CloseOrder) StateKeys(actor codec.Address, _ codec.LID) state.Keys { +func (c *CloseOrder) StateKeys(actor codec.Address, _ ids.ID) state.Keys { return state.Keys{ string(storage.OrderKey(c.Order)): state.Read | state.Write, string(storage.BalanceKey(actor, c.Out)): state.Read | state.Write, @@ -44,7 +46,7 @@ func (c *CloseOrder) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.LID, + _ ids.ID, ) (uint64, [][]byte, error) { exists, _, _, out, _, remaining, owner, err := storage.GetOrder(ctx, mu, c.Order) if err != nil { @@ -73,18 +75,18 @@ func (*CloseOrder) MaxComputeUnits(chain.Rules) uint64 { } func (*CloseOrder) Size() int { - return codec.LIDLen * 2 + return ids.IDLen * 2 } func (c *CloseOrder) Marshal(p *codec.Packer) { - p.PackLID(c.Order) - p.PackLID(c.Out) + p.PackID(c.Order) + p.PackID(c.Out) } func UnmarshalCloseOrder(p *codec.Packer) (chain.Action, error) { var cl CloseOrder - p.UnpackLID(true, &cl.Order) - p.UnpackLID(false, &cl.Out) // empty ID is the native asset + p.UnpackID(true, &cl.Order) + p.UnpackID(false, &cl.Out) // empty ID is the native asset return &cl, p.Err() } diff --git a/examples/tokenvm/actions/create_asset.go b/examples/tokenvm/actions/create_asset.go index 55b26fc8bd..841f2560b4 100644 --- a/examples/tokenvm/actions/create_asset.go +++ b/examples/tokenvm/actions/create_asset.go @@ -6,6 +6,8 @@ package actions import ( "context" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" @@ -25,7 +27,7 @@ func (*CreateAsset) GetTypeID() uint8 { return createAssetID } -func (*CreateAsset) StateKeys(_ codec.Address, actionID codec.LID) state.Keys { +func (*CreateAsset) StateKeys(_ codec.Address, actionID ids.ID) state.Keys { return state.Keys{ string(storage.AssetKey(actionID)): state.Allocate | state.Write, } @@ -41,7 +43,7 @@ func (c *CreateAsset) Execute( mu state.Mutable, _ int64, actor codec.Address, - actionID codec.LID, + actionID ids.ID, ) (uint64, [][]byte, error) { if len(c.Symbol) == 0 { return CreateAssetComputeUnits, nil, ErrOutputSymbolEmpty diff --git a/examples/tokenvm/actions/create_order.go b/examples/tokenvm/actions/create_order.go index 505a29b84f..20db1210e9 100644 --- a/examples/tokenvm/actions/create_order.go +++ b/examples/tokenvm/actions/create_order.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" @@ -18,7 +20,7 @@ var _ chain.Action = (*CreateOrder)(nil) type CreateOrder struct { // [In] is the asset you trade for [Out]. - In codec.LID `json:"in"` + In ids.ID `json:"in"` // [InTick] is the amount of [In] required to purchase // [OutTick] of [Out]. @@ -27,7 +29,7 @@ type CreateOrder struct { // [Out] is the asset you receive when trading for [In]. // // This is the asset that is actually provided by the creator. - Out codec.LID `json:"out"` + Out ids.ID `json:"out"` // [OutTick] is the amount of [Out] the counterparty gets per [InTick] of // [In]. @@ -48,7 +50,7 @@ func (*CreateOrder) GetTypeID() uint8 { return createOrderID } -func (c *CreateOrder) StateKeys(actor codec.Address, actionID codec.LID) state.Keys { +func (c *CreateOrder) StateKeys(actor codec.Address, actionID ids.ID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor, c.Out)): state.Read | state.Write, string(storage.OrderKey(actionID)): state.Allocate | state.Write, @@ -65,7 +67,7 @@ func (c *CreateOrder) Execute( mu state.Mutable, _ int64, actor codec.Address, - actionID codec.LID, + actionID ids.ID, ) (uint64, [][]byte, error) { if c.In == c.Out { return CreateOrderComputeUnits, nil, ErrOutputSameInOut @@ -96,22 +98,22 @@ func (*CreateOrder) MaxComputeUnits(chain.Rules) uint64 { } func (*CreateOrder) Size() int { - return codec.LIDLen*2 + consts.Uint64Len*3 + return ids.IDLen*2 + consts.Uint64Len*3 } func (c *CreateOrder) Marshal(p *codec.Packer) { - p.PackLID(c.In) + p.PackID(c.In) p.PackUint64(c.InTick) - p.PackLID(c.Out) + p.PackID(c.Out) p.PackUint64(c.OutTick) p.PackUint64(c.Supply) } func UnmarshalCreateOrder(p *codec.Packer) (chain.Action, error) { var create CreateOrder - p.UnpackLID(false, &create.In) // empty ID is the native asset + p.UnpackID(false, &create.In) // empty ID is the native asset create.InTick = p.UnpackUint64(true) - p.UnpackLID(false, &create.Out) // empty ID is the native asset + p.UnpackID(false, &create.Out) // empty ID is the native asset create.OutTick = p.UnpackUint64(true) create.Supply = p.UnpackUint64(true) return &create, p.Err() @@ -122,6 +124,6 @@ func (*CreateOrder) ValidRange(chain.Rules) (int64, int64) { return -1, -1 } -func PairID(in codec.LID, out codec.LID) string { //nolint:interfacer +func PairID(in, out ids.ID) string { return fmt.Sprintf("%s-%s", in.String(), out.String()) } diff --git a/examples/tokenvm/actions/fill_order.go b/examples/tokenvm/actions/fill_order.go index 5883e61bdb..a45e3fc3ad 100644 --- a/examples/tokenvm/actions/fill_order.go +++ b/examples/tokenvm/actions/fill_order.go @@ -6,20 +6,22 @@ package actions import ( "context" - smath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" + + smath "github.com/ava-labs/avalanchego/utils/math" ) var _ chain.Action = (*FillOrder)(nil) type FillOrder struct { // [Order] is the OrderID you wish to close. - Order codec.LID `json:"order"` + Order ids.ID `json:"order"` // [Owner] is the owner of the order and the recipient of the trade // proceeds. @@ -27,11 +29,11 @@ type FillOrder struct { // [In] is the asset that will be sent to the owner from the fill. We need to provide this to // populate [StateKeys]. - In codec.LID `json:"in"` + In ids.ID `json:"in"` // [Out] is the asset that will be received from the fill. We need to provide this to // populate [StateKeys]. - Out codec.LID `json:"out"` + Out ids.ID `json:"out"` // [Value] is the max amount of [In] that will be swapped for [Out]. Value uint64 `json:"value"` @@ -41,7 +43,7 @@ func (*FillOrder) GetTypeID() uint8 { return fillOrderID } -func (f *FillOrder) StateKeys(actor codec.Address, _ codec.LID) state.Keys { +func (f *FillOrder) StateKeys(actor codec.Address, _ ids.ID) state.Keys { return state.Keys{ string(storage.OrderKey(f.Order)): state.Read | state.Write, string(storage.BalanceKey(f.Owner, f.In)): state.All, @@ -60,7 +62,7 @@ func (f *FillOrder) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.LID, + _ ids.ID, ) (uint64, [][]byte, error) { exists, in, inTick, out, outTick, remaining, owner, err := storage.GetOrder(ctx, mu, f.Order) if err != nil { @@ -152,23 +154,23 @@ func (*FillOrder) MaxComputeUnits(chain.Rules) uint64 { } func (*FillOrder) Size() int { - return codec.LIDLen*3 + codec.AddressLen + consts.Uint64Len + return ids.IDLen*3 + codec.AddressLen + consts.Uint64Len } func (f *FillOrder) Marshal(p *codec.Packer) { - p.PackLID(f.Order) - p.PackLID(f.Owner) - p.PackLID(f.In) - p.PackLID(f.Out) + p.PackID(f.Order) + p.PackAddress(f.Owner) + p.PackID(f.In) + p.PackID(f.Out) p.PackUint64(f.Value) } func UnmarshalFillOrder(p *codec.Packer) (chain.Action, error) { var fill FillOrder - p.UnpackLID(true, &fill.Order) - p.UnpackLID(true, &fill.Owner) - p.UnpackLID(false, &fill.In) // empty ID is the native asset - p.UnpackLID(false, &fill.Out) // empty ID is the native asset + p.UnpackID(true, &fill.Order) + p.UnpackAddress(&fill.Owner) + p.UnpackID(false, &fill.In) // empty ID is the native asset + p.UnpackID(false, &fill.Out) // empty ID is the native asset fill.Value = p.UnpackUint64(true) return &fill, p.Err() } diff --git a/examples/tokenvm/actions/mint_asset.go b/examples/tokenvm/actions/mint_asset.go index 68cdc05e51..8e3671fdef 100644 --- a/examples/tokenvm/actions/mint_asset.go +++ b/examples/tokenvm/actions/mint_asset.go @@ -6,13 +6,15 @@ package actions import ( "context" - smath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/state" + + smath "github.com/ava-labs/avalanchego/utils/math" ) var _ chain.Action = (*MintAsset)(nil) @@ -22,7 +24,7 @@ type MintAsset struct { To codec.Address `json:"to"` // Asset is the [ActionID] that created the asset. - Asset codec.LID `json:"asset"` + Asset ids.ID `json:"asset"` // Number of assets to mint to [To]. Value uint64 `json:"value"` @@ -32,7 +34,7 @@ func (*MintAsset) GetTypeID() uint8 { return mintAssetID } -func (m *MintAsset) StateKeys(codec.Address, codec.LID) state.Keys { +func (m *MintAsset) StateKeys(codec.Address, ids.ID) state.Keys { return state.Keys{ string(storage.AssetKey(m.Asset)): state.Read | state.Write, string(storage.BalanceKey(m.To, m.Asset)): state.All, @@ -49,9 +51,9 @@ func (m *MintAsset) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.LID, + _ ids.ID, ) (uint64, [][]byte, error) { - if m.Asset == codec.Empty { + if m.Asset == ids.Empty { return MintAssetComputeUnits, nil, ErrOutputAssetIsNative } if m.Value == 0 { @@ -85,19 +87,19 @@ func (*MintAsset) MaxComputeUnits(chain.Rules) uint64 { } func (*MintAsset) Size() int { - return codec.AddressLen + codec.LIDLen + consts.Uint64Len + return codec.AddressLen + ids.IDLen + consts.Uint64Len } func (m *MintAsset) Marshal(p *codec.Packer) { - p.PackLID(m.To) - p.PackLID(m.Asset) + p.PackAddress(m.To) + p.PackID(m.Asset) p.PackUint64(m.Value) } func UnmarshalMintAsset(p *codec.Packer) (chain.Action, error) { var mint MintAsset - p.UnpackLID(true, &mint.To) - p.UnpackLID(true, &mint.Asset) // empty ID is the native asset + p.UnpackAddress(&mint.To) + p.UnpackID(true, &mint.Asset) // empty ID is the native asset mint.Value = p.UnpackUint64(true) return &mint, p.Err() } diff --git a/examples/tokenvm/actions/transfer.go b/examples/tokenvm/actions/transfer.go index f8359f0b6a..4b8cdb7f6c 100644 --- a/examples/tokenvm/actions/transfer.go +++ b/examples/tokenvm/actions/transfer.go @@ -6,6 +6,8 @@ package actions import ( "context" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" @@ -20,7 +22,7 @@ type Transfer struct { To codec.Address `json:"to"` // Asset to transfer to [To]. - Asset codec.LID `json:"asset"` + Asset ids.ID `json:"asset"` // Amount are transferred to [To]. Value uint64 `json:"value"` @@ -33,7 +35,7 @@ func (*Transfer) GetTypeID() uint8 { return transferID } -func (t *Transfer) StateKeys(actor codec.Address, _ codec.LID) state.Keys { +func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys { return state.Keys{ string(storage.BalanceKey(actor, t.Asset)): state.Read | state.Write, string(storage.BalanceKey(t.To, t.Asset)): state.All, @@ -50,7 +52,7 @@ func (t *Transfer) Execute( mu state.Mutable, _ int64, actor codec.Address, - _ codec.LID, + _ ids.ID, ) (uint64, [][]byte, error) { if t.Value == 0 { return TransferComputeUnits, nil, ErrOutputValueZero @@ -73,20 +75,20 @@ func (*Transfer) MaxComputeUnits(chain.Rules) uint64 { } func (t *Transfer) Size() int { - return codec.AddressLen + codec.LIDLen + consts.Uint64Len + codec.BytesLen(t.Memo) + return codec.AddressLen + ids.IDLen + consts.Uint64Len + codec.BytesLen(t.Memo) } func (t *Transfer) Marshal(p *codec.Packer) { - p.PackLID(t.To) - p.PackLID(t.Asset) + p.PackAddress(t.To) + p.PackID(t.Asset) p.PackUint64(t.Value) p.PackBytes(t.Memo) } func UnmarshalTransfer(p *codec.Packer) (chain.Action, error) { var transfer Transfer - p.UnpackLID(true, &transfer.To) - p.UnpackLID(false, &transfer.Asset) // empty ID is the native asset + p.UnpackAddress(&transfer.To) + p.UnpackID(false, &transfer.Asset) // empty ID is the native asset transfer.Value = p.UnpackUint64(true) p.UnpackBytes(MaxMemoSize, false, &transfer.Memo) return &transfer, p.Err() diff --git a/examples/tokenvm/auth/ed25519.go b/examples/tokenvm/auth/ed25519.go index 61673a18e2..ee78b9a0bc 100644 --- a/examples/tokenvm/auth/ed25519.go +++ b/examples/tokenvm/auth/ed25519.go @@ -28,7 +28,7 @@ type ED25519 struct { } func (d *ED25519) address() codec.Address { - if d.addr == codec.Empty { + if d.addr == codec.EmptyAddress { d.addr = NewED25519Address(d.Signer) } return d.addr diff --git a/examples/tokenvm/cmd/token-cli/cmd/action.go b/examples/tokenvm/cmd/token-cli/cmd/action.go index f9e394c308..1847fa1aaa 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/action.go +++ b/examples/tokenvm/cmd/token-cli/cmd/action.go @@ -7,14 +7,17 @@ package cmd import ( "context" + "github.com/ava-labs/avalanchego/ids" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" + frpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-faucet/rpc" tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" hutils "github.com/ava-labs/hypersdk/utils" - "github.com/spf13/cobra" ) var actionCmd = &cobra.Command{ @@ -47,7 +50,7 @@ var fundFaucetCmd = &cobra.Command{ } // Get balance - _, decimals, balance, _, err := handler.GetAssetInfo(ctx, tcli, priv.Address, codec.Empty, true) + _, decimals, balance, _, err := handler.GetAssetInfo(ctx, tcli, priv.Address, ids.Empty, true) if balance == 0 || err != nil { return err } @@ -71,7 +74,7 @@ var fundFaucetCmd = &cobra.Command{ } if err = sendAndWait(ctx, []chain.Action{&actions.Transfer{ To: addr, - Asset: codec.Empty, + Asset: ids.Empty, Value: amount, }}, cli, scli, tcli, factory, true); err != nil { return err @@ -245,7 +248,7 @@ var closeOrderCmd = &cobra.Command{ } // Select inbound token - orderID, err := handler.Root().PromptLID("orderID") + orderID, err := handler.Root().PromptID("orderID") if err != nil { return err } @@ -289,7 +292,7 @@ var createOrderCmd = &cobra.Command{ if err != nil { return err } - if inAssetID != codec.Empty { + if inAssetID != ids.Empty { if !exists { hutils.Outf("{{red}}%s does not exist{{/}}\n", inAssetID) hutils.Outf("{{red}}exiting...{{/}}\n") diff --git a/examples/tokenvm/cmd/token-cli/cmd/chain.go b/examples/tokenvm/cmd/token-cli/cmd/chain.go index 08888f4d15..8d5cb7dc3b 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/chain.go +++ b/examples/tokenvm/cmd/token-cli/cmd/chain.go @@ -8,9 +8,10 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/chain" "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/chain" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" ) diff --git a/examples/tokenvm/cmd/token-cli/cmd/handler.go b/examples/tokenvm/cmd/token-cli/cmd/handler.go index 9b934d237f..1cfdd542f2 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/handler.go +++ b/examples/tokenvm/cmd/token-cli/cmd/handler.go @@ -7,15 +7,17 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" + + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" hutils "github.com/ava-labs/hypersdk/utils" ) @@ -37,7 +39,7 @@ func (*Handler) GetAssetInfo( ctx context.Context, cli *trpc.JSONRPCClient, addr codec.Address, - assetID codec.LID, + assetID ids.ID, checkBalance bool, ) ([]byte, uint8, uint64, ids.ID, error) { var sourceChainID ids.ID @@ -45,7 +47,7 @@ func (*Handler) GetAssetInfo( if err != nil { return nil, 0, 0, ids.Empty, err } - if assetID != codec.Empty { + if assetID != ids.Empty { if !exists { hutils.Outf("{{red}}%s does not exist{{/}}\n", assetID) hutils.Outf("{{red}}exiting...{{/}}\n") diff --git a/examples/tokenvm/cmd/token-cli/cmd/key.go b/examples/tokenvm/cmd/token-cli/cmd/key.go index acdb423f77..c8e6f4a907 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/key.go +++ b/examples/tokenvm/cmd/token-cli/cmd/key.go @@ -8,15 +8,16 @@ import ( "time" "github.com/ava-labs/avalanchego/ids" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" - "github.com/ava-labs/hypersdk/utils" - "github.com/spf13/cobra" - "github.com/ava-labs/hypersdk/examples/tokenvm/auth" "github.com/ava-labs/hypersdk/examples/tokenvm/challenge" + "github.com/ava-labs/hypersdk/utils" + frpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-faucet/rpc" tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" @@ -89,7 +90,7 @@ var importKeyCmd = &cobra.Command{ func lookupSetKeyBalance(choice int, address string, uri string, networkID uint32, chainID ids.ID) error { // TODO: just load once cli := trpc.NewJSONRPCClient(uri, networkID, chainID) - balance, err := cli.Balance(context.TODO(), address, codec.Empty) + balance, err := cli.Balance(context.TODO(), address, ids.Empty) if err != nil { return err } @@ -110,7 +111,7 @@ var setKeyCmd = &cobra.Command{ }, } -func lookupKeyBalance(addr codec.Address, uri string, networkID uint32, chainID ids.ID, assetID codec.LID) error { +func lookupKeyBalance(addr codec.Address, uri string, networkID uint32, chainID ids.ID, assetID ids.ID) error { _, _, _, _, err := handler.GetAssetInfo( context.TODO(), trpc.NewJSONRPCClient(uri, networkID, chainID), addr, assetID, true) diff --git a/examples/tokenvm/cmd/token-cli/cmd/prometheus.go b/examples/tokenvm/cmd/token-cli/cmd/prometheus.go index f2ac1d94c9..2b1ae414d5 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/prometheus.go +++ b/examples/tokenvm/cmd/token-cli/cmd/prometheus.go @@ -8,9 +8,10 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/utils" - "github.com/spf13/cobra" ) var prometheusCmd = &cobra.Command{ diff --git a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go index 641f5218d9..b4720e3b0c 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go +++ b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go @@ -12,10 +12,11 @@ import ( "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" - tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" + + tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" ) // sendAndWait may not be used concurrently @@ -64,7 +65,7 @@ func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result "❌", tx.ID(), codec.MustAddressBech32(tconsts.HRP, actor), - string(result.Outputs[len(result.Outputs)-1][len(result.Outputs[len(result.Outputs)-1])-1]), // revert error, + string(result.Error), float64(result.Fee)/float64(tx.Base.MaxFee)*100, utils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol, @@ -73,87 +74,81 @@ func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result return } - for i := 0; i < len(result.Outputs); i++ { - for j := 0; j < len(result.Outputs[i]); j++ { - for i, act := range tx.Actions { - var summaryStr string - switch action := act.(type) { - case *actions.CreateAsset: - assetID := codec.CreateLID(uint8(i), tx.ID()) - summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", assetID, action.Symbol, action.Decimals, action.Metadata) - case *actions.MintAsset: - _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - amountStr := utils.FormatBalance(action.Value, decimals) - summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - case *actions.BurnAsset: - summaryStr = fmt.Sprintf("%d %s -> 🔥", action.Value, action.Asset) - - case *actions.Transfer: - _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - amountStr := utils.FormatBalance(action.Value, decimals) - summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) - if len(action.Memo) > 0 { - summaryStr += fmt.Sprintf(" (memo: %s)", action.Memo) - } - - case *actions.CreateOrder: - _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - inTickStr := utils.FormatBalance(action.InTick, inDecimals) - _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - outTickStr := utils.FormatBalance(action.OutTick, outDecimals) - supplyStr := utils.FormatBalance(action.Supply, outDecimals) - summaryStr = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", inTickStr, inSymbol, outTickStr, outSymbol, supplyStr, outSymbol) - case *actions.FillOrder: - or, _ := actions.UnmarshalOrderResult(result.Outputs[i][j]) - _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - inAmtStr := utils.FormatBalance(or.In, inDecimals) - _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) - if err != nil { - utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) - return - } - outAmtStr := utils.FormatBalance(or.Out, outDecimals) - remainingStr := utils.FormatBalance(or.Remaining, outDecimals) - summaryStr = fmt.Sprintf( - "%s %s -> %s %s (remaining: %s %s)", - inAmtStr, inSymbol, outAmtStr, outSymbol, remainingStr, outSymbol, - ) - case *actions.CloseOrder: - summaryStr = fmt.Sprintf("orderID: %s", action.Order) - } - utils.Outf( - "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", - "✅", - tx.ID(), - codec.MustAddressBech32(tconsts.HRP, actor), - reflect.TypeOf(act), - summaryStr, - float64(result.Fee)/float64(tx.Base.MaxFee)*100, - utils.FormatBalance(result.Fee, tconsts.Decimals), - tconsts.Symbol, - cli.ParseDimensions(result.Consumed), - ) + for i, act := range tx.Actions { + assetID := chain.CreateActionID(tx.ID(), uint8(i)) + var summaryStr string + switch action := act.(type) { + case *actions.CreateAsset: + summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", assetID, action.Symbol, action.Decimals, action.Metadata) + case *actions.MintAsset: + _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + amountStr := utils.FormatBalance(action.Value, decimals) + summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + case *actions.BurnAsset: + summaryStr = fmt.Sprintf("%d %s -> 🔥", action.Value, action.Asset) + case *actions.Transfer: + _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + amountStr := utils.FormatBalance(action.Value, decimals) + summaryStr = fmt.Sprintf("%s %s -> %s", amountStr, symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) + if len(action.Memo) > 0 { + summaryStr += fmt.Sprintf(" (memo: %s)", action.Memo) + } + case *actions.CreateOrder: + _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + inTickStr := utils.FormatBalance(action.InTick, inDecimals) + _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return } + outTickStr := utils.FormatBalance(action.OutTick, outDecimals) + supplyStr := utils.FormatBalance(action.Supply, outDecimals) + summaryStr = fmt.Sprintf("%s %s -> %s %s (supply: %s %s)", inTickStr, inSymbol, outTickStr, outSymbol, supplyStr, outSymbol) + case *actions.FillOrder: + or, _ := actions.UnmarshalOrderResult(result.Outputs[i][0]) + _, inSymbol, inDecimals, _, _, _, err := c.Asset(context.TODO(), action.In, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + inAmtStr := utils.FormatBalance(or.In, inDecimals) + _, outSymbol, outDecimals, _, _, _, err := c.Asset(context.TODO(), action.Out, true) + if err != nil { + utils.Outf("{{red}}could not fetch asset info:{{/}} %v", err) + return + } + outAmtStr := utils.FormatBalance(or.Out, outDecimals) + remainingStr := utils.FormatBalance(or.Remaining, outDecimals) + summaryStr = fmt.Sprintf( + "%s %s -> %s %s (remaining: %s %s)", + inAmtStr, inSymbol, outAmtStr, outSymbol, remainingStr, outSymbol, + ) + case *actions.CloseOrder: + summaryStr = fmt.Sprintf("orderID: %s", action.Order) } + utils.Outf( + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary (%s):{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + "✅", + tx.ID(), + codec.MustAddressBech32(tconsts.HRP, actor), + reflect.TypeOf(act), + summaryStr, + float64(result.Fee)/float64(tx.Base.MaxFee)*100, + utils.FormatBalance(result.Fee, tconsts.Decimals), + tconsts.Symbol, + cli.ParseDimensions(result.Consumed), + ) } } diff --git a/examples/tokenvm/cmd/token-cli/cmd/root.go b/examples/tokenvm/cmd/token-cli/cmd/root.go index e55fd77b53..c43b9f2077 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/root.go +++ b/examples/tokenvm/cmd/token-cli/cmd/root.go @@ -7,9 +7,10 @@ import ( "fmt" "time" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/utils" - "github.com/spf13/cobra" ) const ( diff --git a/examples/tokenvm/cmd/token-cli/cmd/spam.go b/examples/tokenvm/cmd/token-cli/cmd/spam.go index 8986f89db1..3ab7066e01 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/spam.go +++ b/examples/tokenvm/cmd/token-cli/cmd/spam.go @@ -7,6 +7,8 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/codec" @@ -14,11 +16,11 @@ import ( "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" - "github.com/spf13/cobra" + + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" ) var spamCmd = &cobra.Command{ @@ -62,7 +64,7 @@ var runSpamCmd = &cobra.Command{ }, nil }, func(choice int, address string) (uint64, error) { // lookupBalance - balance, err := tclient.Balance(context.TODO(), address, codec.Empty) + balance, err := tclient.Balance(context.TODO(), address, ids.Empty) if err != nil { return 0, err } @@ -81,7 +83,7 @@ var runSpamCmd = &cobra.Command{ func(addr codec.Address, amount uint64) []chain.Action { // getTransfer return []chain.Action{&actions.Transfer{ To: addr, - Asset: codec.Empty, + Asset: ids.Empty, Value: amount, }} }, diff --git a/examples/tokenvm/cmd/token-faucet/main.go b/examples/tokenvm/cmd/token-faucet/main.go index a9f04fa2af..82e01cc4f1 100644 --- a/examples/tokenvm/cmd/token-faucet/main.go +++ b/examples/tokenvm/cmd/token-faucet/main.go @@ -14,13 +14,15 @@ import ( "time" "github.com/ava-labs/avalanchego/utils/logging" + "go.uber.org/zap" + "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-faucet/config" "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-faucet/manager" - frpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-faucet/rpc" "github.com/ava-labs/hypersdk/server" "github.com/ava-labs/hypersdk/utils" - "go.uber.org/zap" + + frpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-faucet/rpc" ) var ( diff --git a/examples/tokenvm/cmd/token-faucet/manager/manager.go b/examples/tokenvm/cmd/token-faucet/manager/manager.go index acbc4ba4cd..dfc9b73596 100644 --- a/examples/tokenvm/cmd/token-faucet/manager/manager.go +++ b/examples/tokenvm/cmd/token-faucet/manager/manager.go @@ -14,6 +14,7 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/timer" + "go.uber.org/zap" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" @@ -22,10 +23,10 @@ import ( "github.com/ava-labs/hypersdk/examples/tokenvm/challenge" "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-faucet/config" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" - "go.uber.org/zap" + + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" ) type Manager struct { @@ -61,7 +62,7 @@ func New(logger logging.Logger, config *config.Config) (*Manager, error) { if err != nil { return nil, err } - bal, err := tcli.Balance(ctx, m.config.AddressBech32(), codec.Empty) + bal, err := tcli.Balance(ctx, m.config.AddressBech32(), ids.Empty) if err != nil { return nil, err } @@ -126,7 +127,7 @@ func (m *Manager) sendFunds(ctx context.Context, destination codec.Address, amou } submit, tx, maxFee, err := m.cli.GenerateTransaction(ctx, parser, []chain.Action{&actions.Transfer{ To: destination, - Asset: codec.Empty, + Asset: ids.Empty, Value: amount, }}, m.factory) if err != nil { @@ -136,7 +137,7 @@ func (m *Manager) sendFunds(ctx context.Context, destination codec.Address, amou m.log.Warn("abandoning airdrop because network fee is greater than amount", zap.String("maxFee", utils.FormatBalance(maxFee, consts.Decimals))) return ids.Empty, 0, errors.New("network fee too high") } - bal, err := m.tcli.Balance(ctx, m.config.AddressBech32(), codec.Empty) + bal, err := m.tcli.Balance(ctx, m.config.AddressBech32(), ids.Empty) if err != nil { return ids.Empty, 0, err } diff --git a/examples/tokenvm/cmd/token-faucet/rpc/dependencies.go b/examples/tokenvm/cmd/token-faucet/rpc/dependencies.go index 01cf9e474d..c92cf33012 100644 --- a/examples/tokenvm/cmd/token-faucet/rpc/dependencies.go +++ b/examples/tokenvm/cmd/token-faucet/rpc/dependencies.go @@ -7,6 +7,7 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/codec" ) diff --git a/examples/tokenvm/cmd/token-feed/config/config.go b/examples/tokenvm/cmd/token-feed/config/config.go index 314e95ebd9..7ff17ce4e5 100644 --- a/examples/tokenvm/cmd/token-feed/config/config.go +++ b/examples/tokenvm/cmd/token-feed/config/config.go @@ -25,7 +25,7 @@ type Config struct { } func (c *Config) RecipientAddress() (codec.Address, error) { - if c.recipientAddr != codec.Empty { + if c.recipientAddr != codec.EmptyAddress { return c.recipientAddr, nil } addr, err := codec.ParseAddressBech32(consts.HRP, c.Recipient) diff --git a/examples/tokenvm/cmd/token-feed/main.go b/examples/tokenvm/cmd/token-feed/main.go index 10dcb4e44e..c0e8d46160 100644 --- a/examples/tokenvm/cmd/token-feed/main.go +++ b/examples/tokenvm/cmd/token-feed/main.go @@ -14,12 +14,14 @@ import ( "time" "github.com/ava-labs/avalanchego/utils/logging" + "go.uber.org/zap" + "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-feed/config" "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-feed/manager" - frpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-feed/rpc" "github.com/ava-labs/hypersdk/server" "github.com/ava-labs/hypersdk/utils" - "go.uber.org/zap" + + frpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-feed/rpc" ) var ( diff --git a/examples/tokenvm/cmd/token-feed/manager/manager.go b/examples/tokenvm/cmd/token-feed/manager/manager.go index d1bb6b9eb0..ac34e142de 100644 --- a/examples/tokenvm/cmd/token-feed/manager/manager.go +++ b/examples/tokenvm/cmd/token-feed/manager/manager.go @@ -13,15 +13,17 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/timer" + "go.uber.org/zap" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-feed/config" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" - "go.uber.org/zap" + + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" ) type FeedContent struct { diff --git a/examples/tokenvm/cmd/token-wallet/app.go b/examples/tokenvm/cmd/token-wallet/app.go index 45b0e6ee28..5549a76d73 100644 --- a/examples/tokenvm/cmd/token-wallet/app.go +++ b/examples/tokenvm/cmd/token-wallet/app.go @@ -7,10 +7,10 @@ import ( "context" "runtime/debug" - "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-wallet/backend" - "github.com/wailsapp/wails/v2/pkg/logger" "github.com/wailsapp/wails/v2/pkg/runtime" + + "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-wallet/backend" ) type App struct { diff --git a/examples/tokenvm/cmd/token-wallet/backend/backend.go b/examples/tokenvm/cmd/token-wallet/backend/backend.go index 53407758b8..f424ebf365 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/backend.go +++ b/examples/tokenvm/cmd/token-wallet/backend/backend.go @@ -23,24 +23,26 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/hypersdk/chain" - hcli "github.com/ava-labs/hypersdk/cli" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" "github.com/ava-labs/hypersdk/examples/tokenvm/challenge" - frpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-faucet/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-feed/manager" - ferpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-feed/rpc" - tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" - hutils "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/window" + + hcli "github.com/ava-labs/hypersdk/cli" + frpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-faucet/rpc" + ferpc "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/token-feed/rpc" + tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" + hutils "github.com/ava-labs/hypersdk/utils" ) const ( @@ -136,7 +138,7 @@ func (b *Backend) Start(ctx context.Context) error { if err := b.AddAddressBook("Me", b.addrStr); err != nil { return err } - if err := b.s.StoreAsset(codec.Empty, false); err != nil { + if err := b.s.StoreAsset(ids.Empty, false); err != nil { return err } @@ -221,7 +223,7 @@ func (b *Backend) collectBlocks() { // We should exit action parsing as soon as possible for i, act := range tx.Actions { - actionID := codec.CreateLID(uint8(i), tx.ID()) + actionID := chain.CreateActionID(tx.ID(), uint8(i)) switch action := act.(type) { case *actions.Transfer: if actor != b.addr && action.To != b.addr { @@ -249,11 +251,7 @@ func (b *Backend) collectBlocks() { txInfo.Summary += fmt.Sprintf(" (memo: %s)", action.Memo) } } else { - for j := 0; j < len(result.Outputs); j++ { - for k := 0; k < len(result.Outputs[j]); k++ { - txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) - } - } + txInfo.Summary = string(result.Error) } if action.To == b.addr { if actor != b.addr && result.Success { @@ -304,11 +302,7 @@ func (b *Backend) collectBlocks() { if result.Success { txInfo.Summary = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", actionID, action.Symbol, action.Decimals, action.Metadata) } else { - for j := 0; j < len(result.Outputs); j++ { - for k := 0; k < len(result.Outputs[j]); k++ { - txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) - } - } + txInfo.Summary = string(result.Error) } if err := b.s.StoreTransaction(txInfo); err != nil { b.fatal(err) @@ -337,11 +331,7 @@ func (b *Backend) collectBlocks() { if result.Success { txInfo.Summary = fmt.Sprintf("%s %s -> %s", hutils.FormatBalance(action.Value, decimals), symbol, codec.MustAddressBech32(tconsts.HRP, action.To)) } else { - for j := 0; j < len(result.Outputs); j++ { - for k := 0; k < len(result.Outputs[j]); k++ { - txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) - } - } + txInfo.Summary = string(result.Error) } if action.To == b.addr { if actor != b.addr && result.Success { @@ -405,11 +395,7 @@ func (b *Backend) collectBlocks() { outSymbol, ) } else { - for j := 0; j < len(result.Outputs); j++ { - for k := 0; k < len(result.Outputs[j]); k++ { - txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) - } - } + txInfo.Summary = string(result.Error) } if err := b.s.StoreTransaction(txInfo); err != nil { b.fatal(err) @@ -459,11 +445,7 @@ func (b *Backend) collectBlocks() { } } } else { - for j := 0; j < len(result.Outputs); j++ { - for k := 0; k < len(result.Outputs[j]); k++ { - txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) - } - } + txInfo.Summary = string(result.Error) } if actor == b.addr { if err := b.s.StoreTransaction(txInfo); err != nil { @@ -489,11 +471,7 @@ func (b *Backend) collectBlocks() { if result.Success { txInfo.Summary = fmt.Sprintf("OrderID: %s", action.Order) } else { - for j := 0; j < len(result.Outputs); j++ { - for k := 0; k < len(result.Outputs[j]); k++ { - txInfo.Summary += fmt.Sprintf(" %s", string(result.Outputs[j][k])) - } - } + txInfo.Summary = string(result.Error) } if err := b.s.StoreTransaction(txInfo); err != nil { b.fatal(err) @@ -658,7 +636,7 @@ func (b *Backend) GetMyAssets() []*AssetInfo { func (b *Backend) CreateAsset(symbol string, decimals string, metadata string) error { // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) if err != nil { return err } @@ -692,14 +670,14 @@ func (b *Backend) CreateAsset(symbol string, decimals string, metadata string) e return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) + return fmt.Errorf("transaction failed on-chain: %s", result.Error) } return nil } func (b *Backend) MintAsset(asset string, address string, amount string) error { // Input validation - assetID, err := codec.FromString(asset) + assetID, err := ids.FromString(asset) if err != nil { return err } @@ -717,7 +695,7 @@ func (b *Backend) MintAsset(asset string, address string, amount string) error { } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) if err != nil { return err } @@ -747,14 +725,14 @@ func (b *Backend) MintAsset(asset string, address string, amount string) error { return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) + return fmt.Errorf("transaction failed on-chain: %s", result.Error) } return nil } func (b *Backend) Transfer(asset string, address string, amount string, memo string) error { // Input validation - assetID, err := codec.FromString(asset) + assetID, err := ids.FromString(asset) if err != nil { return err } @@ -781,7 +759,7 @@ func (b *Backend) Transfer(asset string, address string, amount string, memo str } // Ensure have sufficient balance for fees - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) if err != nil { return err } @@ -796,7 +774,7 @@ func (b *Backend) Transfer(asset string, address string, amount string, memo str if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } - if assetID != codec.Empty { + if assetID != ids.Empty { if maxFee > bal { return fmt.Errorf("insufficient balance (have: %s %s, want: %s %s)", hutils.FormatBalance(bal, tconsts.Decimals), tconsts.Symbol, hutils.FormatBalance(maxFee, tconsts.Decimals), tconsts.Symbol) } @@ -818,7 +796,7 @@ func (b *Backend) Transfer(asset string, address string, amount string, memo str return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) + return fmt.Errorf("transaction failed on-chain: %s", result.Error) } return nil } @@ -843,7 +821,7 @@ func (b *Backend) GetBalance() ([]*BalanceInfo, error) { return nil, err } strAsset := asset.String() - if asset == codec.Empty { + if asset == ids.Empty { balances = append(balances, &BalanceInfo{ID: strAsset, Str: fmt.Sprintf("%s %s", hutils.FormatBalance(bal, decimals), symbol), Bal: fmt.Sprintf("%s (Balance: %s)", symbol, hutils.FormatBalance(bal, decimals)), Has: bal > 0}) } else { balances = append(balances, &BalanceInfo{ID: strAsset, Str: fmt.Sprintf("%s %s [%s]", hutils.FormatBalance(bal, decimals), symbol, asset), Bal: fmt.Sprintf("%s [%s..%s] (Balance: %s)", symbol, strAsset[:3], strAsset[len(strAsset)-3:], hutils.FormatBalance(bal, decimals)), Has: bal > 0}) @@ -986,7 +964,7 @@ func (b *Backend) GetAllAssets() []*AssetInfo { } func (b *Backend) AddAsset(asset string) error { - assetID, err := codec.FromString(asset) + assetID, err := ids.FromString(asset) if err != nil { return err } @@ -1060,7 +1038,7 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { } assetIDs := strings.Split(pair, "-") in := assetIDs[0] - inID, err := codec.FromString(in) + inID, err := ids.FromString(in) if err != nil { return nil, err } @@ -1069,7 +1047,7 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { return nil, err } out := assetIDs[1] - outID, err := codec.FromString(out) + outID, err := ids.FromString(out) if err != nil { return nil, err } @@ -1101,11 +1079,11 @@ func (b *Backend) GetOrders(pair string) ([]*Order, error) { } func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, outTick string, supply string) error { - inID, err := codec.FromString(assetIn) + inID, err := ids.FromString(assetIn) if err != nil { return err } - outID, err := codec.FromString(assetOut) + outID, err := ids.FromString(assetOut) if err != nil { return err } @@ -1119,7 +1097,7 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) if err != nil { return err } @@ -1151,7 +1129,7 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } - if inID == codec.Empty { + if inID == ids.Empty { if maxFee+oSupply > bal { return fmt.Errorf("insufficient balance (have: %s %s, want: %s %s)", hutils.FormatBalance(bal, tconsts.Decimals), tconsts.Symbol, hutils.FormatBalance(maxFee+oSupply, tconsts.Decimals), tconsts.Symbol) } @@ -1176,15 +1154,15 @@ func (b *Backend) CreateOrder(assetIn string, inTick string, assetOut string, ou return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) + return fmt.Errorf("transaction failed on-chain: %s", result.Error) } // We rely on order checking to clear backlog - return b.s.StoreOrder(codec.CreateLID(0, tx.ID())) + return b.s.StoreOrder(chain.CreateActionID(tx.ID(), 0)) } func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, inTick string, assetOut string, amount string) error { - oID, err := codec.FromString(orderID) + oID, err := ids.FromString(orderID) if err != nil { return err } @@ -1192,11 +1170,11 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i if err != nil { return err } - inID, err := codec.FromString(assetIn) + inID, err := ids.FromString(assetIn) if err != nil { return err } - outID, err := codec.FromString(assetOut) + outID, err := ids.FromString(assetOut) if err != nil { return err } @@ -1206,7 +1184,7 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) if err != nil { return err } @@ -1237,7 +1215,7 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i if err != nil { return fmt.Errorf("%w: unable to generate transaction", err) } - if inID == codec.Empty { + if inID == ids.Empty { if maxFee+inAmount > bal { return fmt.Errorf("insufficient balance (have: %s %s, want: %s %s)", hutils.FormatBalance(bal, tconsts.Decimals), tconsts.Symbol, hutils.FormatBalance(maxFee+inAmount, tconsts.Decimals), tconsts.Symbol) } @@ -1262,23 +1240,23 @@ func (b *Backend) FillOrder(orderID string, orderOwner string, assetIn string, i return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) + return fmt.Errorf("transaction failed on-chain: %s", result.Error) } return nil } func (b *Backend) CloseOrder(orderID string, assetOut string) error { - oID, err := codec.FromString(orderID) + oID, err := ids.FromString(orderID) if err != nil { return err } - outID, err := codec.FromString(assetOut) + outID, err := ids.FromString(assetOut) if err != nil { return err } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) if err != nil { return err } @@ -1307,7 +1285,7 @@ func (b *Backend) CloseOrder(orderID string, assetOut string) error { return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) + return fmt.Errorf("transaction failed on-chain: %s", result.Error) } return nil } @@ -1418,7 +1396,7 @@ func (b *Backend) Message(message string, url string) error { } // Ensure have sufficient balance - bal, err := b.tcli.Balance(b.ctx, b.addrStr, codec.Empty) + bal, err := b.tcli.Balance(b.ctx, b.addrStr, ids.Empty) if err != nil { return err } @@ -1426,7 +1404,7 @@ func (b *Backend) Message(message string, url string) error { // Generate transaction _, tx, maxFee, err := b.cli.GenerateTransaction(b.ctx, b.parser, []chain.Action{&actions.Transfer{ To: recipientAddr, - Asset: codec.Empty, + Asset: ids.Empty, Value: fee, Memo: data, }}, b.factory) @@ -1449,7 +1427,7 @@ func (b *Backend) Message(message string, url string) error { return err } if !result.Success { - return fmt.Errorf("transaction failed on-chain: %s", result.Outputs) + return fmt.Errorf("transaction failed on-chain: %s", result.Error) } return nil } diff --git a/examples/tokenvm/cmd/token-wallet/backend/models.go b/examples/tokenvm/cmd/token-wallet/backend/models.go index c805a41836..53db90fb61 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/models.go +++ b/examples/tokenvm/cmd/token-wallet/backend/models.go @@ -5,6 +5,7 @@ package backend import ( "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/hypersdk/fees" ) diff --git a/examples/tokenvm/cmd/token-wallet/backend/storage.go b/examples/tokenvm/cmd/token-wallet/backend/storage.go index c68d5d5aac..4092dbf57e 100644 --- a/examples/tokenvm/cmd/token-wallet/backend/storage.go +++ b/examples/tokenvm/cmd/token-wallet/backend/storage.go @@ -12,11 +12,13 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" - tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/pebble" + + tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" hutils "github.com/ava-labs/hypersdk/utils" ) @@ -63,8 +65,8 @@ func (s *Storage) GetKey() (ed25519.PrivateKey, error) { return ed25519.PrivateKey(v), nil } -func (s *Storage) StoreAsset(assetID codec.LID, owned bool) error { - k := make([]byte, 1+codec.LIDLen) +func (s *Storage) StoreAsset(assetID ids.ID, owned bool) error { + k := make([]byte, 1+ids.IDLen) k[0] = assetsPrefix copy(k[1:], assetID[:]) v := []byte{0x0} @@ -74,21 +76,21 @@ func (s *Storage) StoreAsset(assetID codec.LID, owned bool) error { return s.db.Put(k, v) } -func (s *Storage) HasAsset(assetID codec.LID) (bool, error) { - k := make([]byte, 1+codec.LIDLen) +func (s *Storage) HasAsset(assetID ids.ID) (bool, error) { + k := make([]byte, 1+ids.IDLen) k[0] = assetsPrefix copy(k[1:], assetID[:]) return s.db.Has(k) } -func (s *Storage) GetAssets() ([]codec.LID, []bool, error) { +func (s *Storage) GetAssets() ([]ids.ID, []bool, error) { iter := s.db.NewIteratorWithPrefix([]byte{assetsPrefix}) defer iter.Release() - assets := []codec.LID{} + assets := []ids.ID{} owned := []bool{} for iter.Next() { - assets = append(assets, codec.LID(iter.Key()[1:])) + assets = append(assets, ids.ID(iter.Key()[1:])) owned = append(owned, iter.Value()[0] == 0x1) } return assets, owned, iter.Error() @@ -179,24 +181,24 @@ func (s *Storage) GetSolutions() ([]*FaucetSearchInfo, error) { return fs, iter.Error() } -func (s *Storage) StoreOrder(orderID codec.LID) error { +func (s *Storage) StoreOrder(orderID ids.ID) error { inverseTime := consts.MaxUint64 - uint64(time.Now().UnixMilli()) - k := make([]byte, 1+consts.Uint64Len+codec.LIDLen) + k := make([]byte, 1+consts.Uint64Len+ids.IDLen) k[0] = orderPrefix binary.BigEndian.PutUint64(k[1:], inverseTime) copy(k[1+consts.Uint64Len:], orderID[:]) return s.db.Put(k, nil) } -func (s *Storage) GetOrders() ([]codec.LID, [][]byte, error) { +func (s *Storage) GetOrders() ([]ids.ID, [][]byte, error) { iter := s.db.NewIteratorWithPrefix([]byte{orderPrefix}) defer iter.Release() - orders := []codec.LID{} + orders := []ids.ID{} keys := [][]byte{} for iter.Next() { k := iter.Key() - orders = append(orders, codec.LID(k[1+consts.Uint64Len:])) + orders = append(orders, ids.ID(k[1+consts.Uint64Len:])) keys = append(keys, k) } return orders, keys, iter.Error() diff --git a/examples/tokenvm/cmd/tokenvm/main.go b/examples/tokenvm/cmd/tokenvm/main.go index abb7f5c297..2f9a1a35d0 100644 --- a/examples/tokenvm/cmd/tokenvm/main.go +++ b/examples/tokenvm/cmd/tokenvm/main.go @@ -11,9 +11,10 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/ulimit" "github.com/ava-labs/avalanchego/vms/rpcchainvm" + "github.com/spf13/cobra" + "github.com/ava-labs/hypersdk/examples/tokenvm/cmd/tokenvm/version" "github.com/ava-labs/hypersdk/examples/tokenvm/controller" - "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ diff --git a/examples/tokenvm/config/config.go b/examples/tokenvm/config/config.go index 356cb8ed10..2e72900c88 100644 --- a/examples/tokenvm/config/config.go +++ b/examples/tokenvm/config/config.go @@ -12,14 +12,14 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/profiler" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/config" + "github.com/ava-labs/hypersdk/examples/tokenvm/consts" + "github.com/ava-labs/hypersdk/examples/tokenvm/version" "github.com/ava-labs/hypersdk/gossiper" "github.com/ava-labs/hypersdk/trace" "github.com/ava-labs/hypersdk/vm" - - "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - "github.com/ava-labs/hypersdk/examples/tokenvm/version" ) var _ vm.Config = (*Config)(nil) diff --git a/examples/tokenvm/consts/consts.go b/examples/tokenvm/consts/consts.go index b221ebddfc..775fbb8ae8 100644 --- a/examples/tokenvm/consts/consts.go +++ b/examples/tokenvm/consts/consts.go @@ -5,9 +5,9 @@ package consts import ( "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/consts" ) const ( @@ -20,7 +20,7 @@ const ( var ID ids.ID func init() { - b := make([]byte, consts.IDLen) + b := make([]byte, ids.IDLen) copy(b, []byte(Name)) vmID, err := ids.ToID(b) if err != nil { diff --git a/examples/tokenvm/controller/controller.go b/examples/tokenvm/controller/controller.go index 5ef557814d..79d4736cc5 100644 --- a/examples/tokenvm/controller/controller.go +++ b/examples/tokenvm/controller/controller.go @@ -8,18 +8,12 @@ import ( "fmt" "net/http" - ametrics "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/hypersdk/builder" - "github.com/ava-labs/hypersdk/chain" - "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/gossiper" - hrpc "github.com/ava-labs/hypersdk/rpc" - hstorage "github.com/ava-labs/hypersdk/storage" - "github.com/ava-labs/hypersdk/vm" "go.uber.org/zap" + "github.com/ava-labs/hypersdk/builder" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" "github.com/ava-labs/hypersdk/examples/tokenvm/config" @@ -29,6 +23,12 @@ import ( "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/examples/tokenvm/version" + "github.com/ava-labs/hypersdk/gossiper" + "github.com/ava-labs/hypersdk/vm" + + ametrics "github.com/ava-labs/avalanchego/api/metrics" + hrpc "github.com/ava-labs/hypersdk/rpc" + hstorage "github.com/ava-labs/hypersdk/storage" ) var _ vm.Controller = (*Controller)(nil) @@ -192,7 +192,7 @@ func (c *Controller) Accepted(ctx context.Context, blk *chain.StatelessBlock) er c.metrics.transfer.Inc() case *actions.CreateOrder: c.metrics.createOrder.Inc() - c.orderBook.Add(codec.CreateLID(uint8(i), tx.ID()), tx.Auth.Actor(), action) + c.orderBook.Add(chain.CreateActionID(tx.ID(), uint8(i)), tx.Auth.Actor(), action) case *actions.FillOrder: c.metrics.fillOrder.Inc() outputs := result.Outputs[i] diff --git a/examples/tokenvm/controller/metrics.go b/examples/tokenvm/controller/metrics.go index 6b0f72d328..def14e47dc 100644 --- a/examples/tokenvm/controller/metrics.go +++ b/examples/tokenvm/controller/metrics.go @@ -4,10 +4,12 @@ package controller import ( - ametrics "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/prometheus/client_golang/prometheus" + + "github.com/ava-labs/hypersdk/examples/tokenvm/consts" + + ametrics "github.com/ava-labs/avalanchego/api/metrics" ) type metrics struct { diff --git a/examples/tokenvm/controller/resolutions.go b/examples/tokenvm/controller/resolutions.go index eedddc7830..cdb53547e4 100644 --- a/examples/tokenvm/controller/resolutions.go +++ b/examples/tokenvm/controller/resolutions.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" "github.com/ava-labs/hypersdk/examples/tokenvm/orderbook" @@ -37,7 +38,7 @@ func (c *Controller) GetTransaction( func (c *Controller) GetAssetFromState( ctx context.Context, - asset codec.LID, + asset ids.ID, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { return storage.GetAssetFromState(ctx, c.inner.ReadState, asset) } @@ -45,7 +46,7 @@ func (c *Controller) GetAssetFromState( func (c *Controller) GetBalanceFromState( ctx context.Context, addr codec.Address, - asset codec.LID, + asset ids.ID, ) (uint64, error) { return storage.GetBalanceFromState(ctx, c.inner.ReadState, addr, asset) } @@ -56,12 +57,12 @@ func (c *Controller) Orders(pair string, limit int) []*orderbook.Order { func (c *Controller) GetOrderFromState( ctx context.Context, - orderID codec.LID, + orderID ids.ID, ) ( bool, // exists - codec.LID, // in + ids.ID, // in uint64, // inTick - codec.LID, // out + ids.ID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -69,11 +70,3 @@ func (c *Controller) GetOrderFromState( ) { return storage.GetOrderFromState(ctx, c.inner.ReadState, orderID) } - -func (c *Controller) GetLoanFromState( - ctx context.Context, - asset codec.LID, - destination ids.ID, -) (uint64, error) { - return storage.GetLoanFromState(ctx, c.inner.ReadState, asset, destination) -} diff --git a/examples/tokenvm/controller/state_manager.go b/examples/tokenvm/controller/state_manager.go index 2d0c055103..41cb605984 100644 --- a/examples/tokenvm/controller/state_manager.go +++ b/examples/tokenvm/controller/state_manager.go @@ -6,6 +6,8 @@ package controller import ( "context" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" @@ -30,7 +32,7 @@ func (*StateManager) FeeKey() []byte { func (*StateManager) SponsorStateKeys(addr codec.Address) state.Keys { return state.Keys{ - string(storage.BalanceKey(addr, codec.Empty)): state.Read | state.Write, + string(storage.BalanceKey(addr, ids.Empty)): state.Read | state.Write, } } @@ -40,7 +42,7 @@ func (*StateManager) CanDeduct( im state.Immutable, amount uint64, ) error { - bal, err := storage.GetBalance(ctx, im, addr, codec.Empty) + bal, err := storage.GetBalance(ctx, im, addr, ids.Empty) if err != nil { return err } @@ -56,7 +58,7 @@ func (*StateManager) Deduct( mu state.Mutable, amount uint64, ) error { - return storage.SubBalance(ctx, mu, addr, codec.Empty, amount) + return storage.SubBalance(ctx, mu, addr, ids.Empty, amount) } func (*StateManager) Refund( @@ -66,5 +68,5 @@ func (*StateManager) Refund( amount uint64, ) error { // Don't create account if it doesn't exist (may have sent all funds). - return storage.AddBalance(ctx, mu, addr, codec.Empty, amount, false) + return storage.AddBalance(ctx, mu, addr, ids.Empty, amount, false) } diff --git a/examples/tokenvm/genesis/genesis.go b/examples/tokenvm/genesis/genesis.go index 6fe86a5fdd..a5e2af8f17 100644 --- a/examples/tokenvm/genesis/genesis.go +++ b/examples/tokenvm/genesis/genesis.go @@ -8,17 +8,19 @@ import ( "encoding/json" "fmt" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" - smath "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/x/merkledb" "github.com/ava-labs/hypersdk/codec" - hconsts "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/vm" + + smath "github.com/ava-labs/avalanchego/utils/math" + hconsts "github.com/ava-labs/hypersdk/consts" ) var _ vm.Genesis = (*Genesis)(nil) @@ -123,19 +125,19 @@ func (g *Genesis) Load(ctx context.Context, tracer trace.Tracer, mu state.Mutabl if err != nil { return err } - if err := storage.SetBalance(ctx, mu, pk, codec.Empty, alloc.Balance); err != nil { + if err := storage.SetBalance(ctx, mu, pk, ids.Empty, alloc.Balance); err != nil { return fmt.Errorf("%w: addr=%s, bal=%d", err, alloc.Address, alloc.Balance) } } return storage.SetAsset( ctx, mu, - codec.Empty, + ids.Empty, []byte(consts.Symbol), consts.Decimals, []byte(consts.Name), supply, - codec.Empty, + codec.EmptyAddress, ) } diff --git a/examples/tokenvm/genesis/rules.go b/examples/tokenvm/genesis/rules.go index dcbfe39642..9285c091b9 100644 --- a/examples/tokenvm/genesis/rules.go +++ b/examples/tokenvm/genesis/rules.go @@ -5,6 +5,7 @@ package genesis import ( "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" "github.com/ava-labs/hypersdk/fees" diff --git a/examples/tokenvm/orderbook/orderbook.go b/examples/tokenvm/orderbook/orderbook.go index a1c773d226..9e4341b99d 100644 --- a/examples/tokenvm/orderbook/orderbook.go +++ b/examples/tokenvm/orderbook/orderbook.go @@ -6,23 +6,25 @@ package orderbook import ( "sync" + "github.com/ava-labs/avalanchego/ids" + "go.uber.org/zap" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/heap" - "go.uber.org/zap" ) const allPairs = "*" type Order struct { - ID codec.LID `json:"id"` - Owner string `json:"owner"` // we always send address over RPC - InAsset codec.LID `json:"inAsset"` - InTick uint64 `json:"inTick"` - OutAsset codec.LID `json:"outAsset"` - OutTick uint64 `json:"outTick"` - Remaining uint64 `json:"remaining"` + ID ids.ID `json:"id"` + Owner string `json:"owner"` // we always send address over RPC + InAsset ids.ID `json:"inAsset"` + InTick uint64 `json:"inTick"` + OutAsset ids.ID `json:"outAsset"` + OutTick uint64 `json:"outTick"` + Remaining uint64 `json:"remaining"` owner codec.Address } @@ -34,8 +36,8 @@ type OrderBook struct { // dust orders from filling the heap. // // TODO: Allow operator to specify min creation supply per pair to be tracked - orders map[string]*heap.Heap[codec.LID, *Order, float64] - orderToPair map[codec.LID]string // needed to delete from [CloseOrder] actions + orders map[string]*heap.Heap[*Order, float64] + orderToPair map[ids.ID]string // needed to delete from [CloseOrder] actions maxOrdersPerPair int l sync.RWMutex @@ -43,7 +45,7 @@ type OrderBook struct { } func New(c Controller, trackedPairs []string, maxOrdersPerPair int) *OrderBook { - m := map[string]*heap.Heap[codec.LID, *Order, float64]{} + m := map[string]*heap.Heap[*Order, float64]{} trackAll := false if len(trackedPairs) == 1 && trackedPairs[0] == allPairs { trackAll = true @@ -51,20 +53,20 @@ func New(c Controller, trackedPairs []string, maxOrdersPerPair int) *OrderBook { } else { for _, pair := range trackedPairs { // We use a max heap so we return the best rates in order. - m[pair] = heap.New[codec.LID, *Order, float64](maxOrdersPerPair+1, true) + m[pair] = heap.New[*Order, float64](maxOrdersPerPair+1, true) c.Logger().Info("tracking order book", zap.String("pair", pair)) } } return &OrderBook{ c: c, orders: m, - orderToPair: map[codec.LID]string{}, + orderToPair: map[ids.ID]string{}, maxOrdersPerPair: maxOrdersPerPair, trackAll: trackAll, } } -func (o *OrderBook) Add(actionID codec.LID, actor codec.Address, action *actions.CreateOrder) { +func (o *OrderBook) Add(actionID ids.ID, actor codec.Address, action *actions.CreateOrder) { pair := actions.PairID(action.In, action.Out) order := &Order{ actionID, @@ -85,10 +87,10 @@ func (o *OrderBook) Add(actionID codec.LID, actor codec.Address, action *actions return case !ok && o.trackAll: o.c.Logger().Info("tracking order book", zap.String("pair", pair)) - h = heap.New[codec.LID, *Order, float64](o.maxOrdersPerPair+1, true) + h = heap.New[*Order, float64](o.maxOrdersPerPair+1, true) o.orders[pair] = h } - h.Push(&heap.Entry[codec.LID, *Order, float64]{ + h.Push(&heap.Entry[*Order, float64]{ ID: order.ID, Val: float64(order.InTick) / float64(order.OutTick), Item: order, @@ -104,7 +106,7 @@ func (o *OrderBook) Add(actionID codec.LID, actor codec.Address, action *actions } } -func (o *OrderBook) Remove(id codec.LID) { +func (o *OrderBook) Remove(id ids.ID) { o.l.Lock() defer o.l.Unlock() @@ -126,7 +128,7 @@ func (o *OrderBook) Remove(id codec.LID) { h.Remove(entry.Index) // O(log N) } -func (o *OrderBook) UpdateRemaining(id codec.LID, remaining uint64) { +func (o *OrderBook) UpdateRemaining(id ids.ID, remaining uint64) { o.l.Lock() defer o.l.Unlock() diff --git a/examples/tokenvm/registry/registry.go b/examples/tokenvm/registry/registry.go index b330518a5d..95cb021451 100644 --- a/examples/tokenvm/registry/registry.go +++ b/examples/tokenvm/registry/registry.go @@ -5,9 +5,9 @@ package registry import ( "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" diff --git a/examples/tokenvm/rpc/dependencies.go b/examples/tokenvm/rpc/dependencies.go index 7bde2f0fb7..17c709beff 100644 --- a/examples/tokenvm/rpc/dependencies.go +++ b/examples/tokenvm/rpc/dependencies.go @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" "github.com/ava-labs/hypersdk/examples/tokenvm/orderbook" @@ -18,18 +19,17 @@ type Controller interface { Genesis() *genesis.Genesis Tracer() trace.Tracer GetTransaction(context.Context, ids.ID) (bool, int64, bool, fees.Dimensions, uint64, error) - GetAssetFromState(context.Context, codec.LID) (bool, []byte, uint8, []byte, uint64, codec.Address, error) - GetBalanceFromState(context.Context, codec.Address, codec.LID) (uint64, error) + GetAssetFromState(context.Context, ids.ID) (bool, []byte, uint8, []byte, uint64, codec.Address, error) + GetBalanceFromState(context.Context, codec.Address, ids.ID) (uint64, error) Orders(pair string, limit int) []*orderbook.Order - GetOrderFromState(context.Context, codec.LID) ( + GetOrderFromState(context.Context, ids.ID) ( bool, // exists - codec.LID, // in + ids.ID, // in uint64, // inTick - codec.LID, // out + ids.ID, // out uint64, // outTick uint64, // remaining codec.Address, // owner error, ) - GetLoanFromState(context.Context, codec.LID, ids.ID) (uint64, error) } diff --git a/examples/tokenvm/rpc/jsonrpc_client.go b/examples/tokenvm/rpc/jsonrpc_client.go index 76818f34c0..197979bb95 100644 --- a/examples/tokenvm/rpc/jsonrpc_client.go +++ b/examples/tokenvm/rpc/jsonrpc_client.go @@ -11,12 +11,12 @@ import ( "github.com/ava-labs/avalanchego/ids" + _ "github.com/ava-labs/hypersdk/examples/tokenvm/registry" // ensure registry populated + "github.com/ava-labs/hypersdk/chain" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" "github.com/ava-labs/hypersdk/examples/tokenvm/orderbook" - _ "github.com/ava-labs/hypersdk/examples/tokenvm/registry" // ensure registry populated "github.com/ava-labs/hypersdk/requester" "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" @@ -29,7 +29,7 @@ type JSONRPCClient struct { chainID ids.ID g *genesis.Genesis assetsL sync.Mutex - assets map[codec.LID]*AssetReply + assets map[ids.ID]*AssetReply } // New creates a new client object. @@ -41,7 +41,7 @@ func NewJSONRPCClient(uri string, networkID uint32, chainID ids.ID) *JSONRPCClie requester: req, networkID: networkID, chainID: chainID, - assets: map[codec.LID]*AssetReply{}, + assets: map[ids.ID]*AssetReply{}, } } @@ -85,7 +85,7 @@ func (cli *JSONRPCClient) Tx(ctx context.Context, id ids.ID) (bool, bool, int64, func (cli *JSONRPCClient) Asset( ctx context.Context, - asset codec.LID, + asset ids.ID, useCache bool, ) (bool, []byte, uint8, []byte, uint64, string, error) { cli.assetsL.Lock() @@ -117,7 +117,7 @@ func (cli *JSONRPCClient) Asset( return true, resp.Symbol, resp.Decimals, resp.Metadata, resp.Supply, resp.Owner, nil } -func (cli *JSONRPCClient) Balance(ctx context.Context, addr string, asset codec.LID) (uint64, error) { +func (cli *JSONRPCClient) Balance(ctx context.Context, addr string, asset ids.ID) (uint64, error) { resp := new(BalanceReply) err := cli.requester.SendRequest( ctx, @@ -144,7 +144,7 @@ func (cli *JSONRPCClient) Orders(ctx context.Context, pair string) ([]*orderbook return resp.Orders, err } -func (cli *JSONRPCClient) GetOrder(ctx context.Context, orderID codec.LID) (*orderbook.Order, error) { +func (cli *JSONRPCClient) GetOrder(ctx context.Context, orderID ids.ID) (*orderbook.Order, error) { resp := new(GetOrderReply) err := cli.requester.SendRequest( ctx, @@ -157,28 +157,10 @@ func (cli *JSONRPCClient) GetOrder(ctx context.Context, orderID codec.LID) (*ord return resp.Order, err } -func (cli *JSONRPCClient) Loan( - ctx context.Context, - asset codec.LID, - destination ids.ID, -) (uint64, error) { - resp := new(LoanReply) - err := cli.requester.SendRequest( - ctx, - "loan", - &LoanArgs{ - Asset: asset, - Destination: destination, - }, - resp, - ) - return resp.Amount, err -} - func (cli *JSONRPCClient) WaitForBalance( ctx context.Context, addr string, - asset codec.LID, + asset ids.ID, min uint64, ) error { exists, symbol, decimals, _, _, _, err := cli.Asset(ctx, asset, true) diff --git a/examples/tokenvm/rpc/jsonrpc_server.go b/examples/tokenvm/rpc/jsonrpc_server.go index 15a20f3a91..d60a4bb105 100644 --- a/examples/tokenvm/rpc/jsonrpc_server.go +++ b/examples/tokenvm/rpc/jsonrpc_server.go @@ -62,7 +62,7 @@ func (j *JSONRPCServer) Tx(req *http.Request, args *TxArgs, reply *TxReply) erro } type AssetArgs struct { - Asset codec.LID `json:"asset"` + Asset ids.ID `json:"asset"` } type AssetReply struct { @@ -93,8 +93,8 @@ func (j *JSONRPCServer) Asset(req *http.Request, args *AssetArgs, reply *AssetRe } type BalanceArgs struct { - Address string `json:"address"` - Asset codec.LID `json:"asset"` + Address string `json:"address"` + Asset ids.ID `json:"asset"` } type BalanceReply struct { @@ -134,7 +134,7 @@ func (j *JSONRPCServer) Orders(req *http.Request, args *OrdersArgs, reply *Order } type GetOrderArgs struct { - OrderID codec.LID `json:"orderID"` + OrderID ids.ID `json:"orderID"` } type GetOrderReply struct { @@ -163,24 +163,3 @@ func (j *JSONRPCServer) GetOrder(req *http.Request, args *GetOrderArgs, reply *G } return nil } - -type LoanArgs struct { - Destination ids.ID `json:"destination"` - Asset codec.LID `json:"asset"` -} - -type LoanReply struct { - Amount uint64 `json:"amount"` -} - -func (j *JSONRPCServer) Loan(req *http.Request, args *LoanArgs, reply *LoanReply) error { - ctx, span := j.c.Tracer().Start(req.Context(), "Server.Loan") - defer span.End() - - amount, err := j.c.GetLoanFromState(ctx, args.Asset, args.Destination) - if err != nil { - return err - } - reply.Amount = amount - return nil -} diff --git a/examples/tokenvm/scripts/run.sh b/examples/tokenvm/scripts/run.sh index 06c40aa79a..a7b57d6c6c 100755 --- a/examples/tokenvm/scripts/run.sh +++ b/examples/tokenvm/scripts/run.sh @@ -27,7 +27,7 @@ MIN_BLOCK_GAP=${MIN_BLOCK_GAP:-100} STORE_TXS=${STORE_TXS:-false} UNLIMITED_USAGE=${UNLIMITED_USAGE:-false} ADDRESS=${ADDRESS:-token1qrzvk4zlwj9zsacqgtufx7zvapd3quufqpxk5rsdd4633m4wz2fdj73w34s} -if [[ ${MODE} != "run" && ${MODE} != "run-single" ]]; then +if [[ ${MODE} != "run" ]]; then LOGLEVEL=debug STATESYNC_DELAY=100000000 # 100ms MIN_BLOCK_GAP=250 #ms @@ -263,7 +263,7 @@ echo "running e2e tests" --mode="${MODE}" ############################ -if [[ ${MODE} == "run" || ${MODE} == "run-single" ]]; then +if [[ ${MODE} == "run" ]]; then echo "cluster is ready!" # We made it past initialization and should avoid shutting down the network KEEPALIVE=true diff --git a/examples/tokenvm/storage/storage.go b/examples/tokenvm/storage/storage.go index 56273dcd7d..f9bbeaae2d 100644 --- a/examples/tokenvm/storage/storage.go +++ b/examples/tokenvm/storage/storage.go @@ -12,12 +12,14 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" - smath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" - tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/state" + + smath "github.com/ava-labs/avalanchego/utils/math" + tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" ) type ReadState func(context.Context, [][]byte) ([][]byte, []error) @@ -33,11 +35,9 @@ type ReadState func(context.Context, [][]byte) ([][]byte, []error) // -> [asset] => metadataLen|metadata|supply|owner // 0x2/ (orders) // -> [txID] => in|out|rate|remaining|owner -// 0x3/ (loans) -// -> [assetID|destination] => amount -// 0x4/ (hypersdk-height) -// 0x5/ (hypersdk-timestamp) -// 0x6/ (hypersdk-fee) +// 0x3/ (hypersdk-height) +// 0x4/ (hypersdk-timestamp) +// 0x5/ (hypersdk-fee) const ( // metaDB @@ -47,17 +47,15 @@ const ( balancePrefix = 0x0 assetPrefix = 0x1 orderPrefix = 0x2 - loanPrefix = 0x3 - heightPrefix = 0x4 - timestampPrefix = 0x5 - feePrefix = 0x6 + heightPrefix = 0x3 + timestampPrefix = 0x4 + feePrefix = 0x5 ) const ( BalanceChunks uint16 = 1 AssetChunks uint16 = 5 OrderChunks uint16 = 2 - LoanChunks uint16 = 1 ) var ( @@ -69,14 +67,14 @@ var ( balanceKeyPool = sync.Pool{ New: func() any { - return make([]byte, 1+codec.LIDLen*2+consts.Uint16Len) + return make([]byte, 1+codec.AddressLen+ids.IDLen+consts.Uint16Len) }, } ) // [txPrefix] + [txID] func TxKey(id ids.ID) (k []byte) { - k = make([]byte, 1+consts.IDLen) + k = make([]byte, 1+ids.IDLen) k[0] = txPrefix copy(k[1:], id[:]) return @@ -131,12 +129,12 @@ func GetTransaction( } // [accountPrefix] + [address] + [asset] -func BalanceKey(addr codec.Address, asset codec.LID) (k []byte) { +func BalanceKey(addr codec.Address, asset ids.ID) (k []byte) { k = balanceKeyPool.Get().([]byte) k[0] = balancePrefix copy(k[1:], addr[:]) copy(k[1+codec.AddressLen:], asset[:]) - binary.BigEndian.PutUint16(k[1+codec.AddressLen+codec.LIDLen:], BalanceChunks) + binary.BigEndian.PutUint16(k[1+codec.AddressLen+ids.IDLen:], BalanceChunks) return } @@ -145,7 +143,7 @@ func GetBalance( ctx context.Context, im state.Immutable, addr codec.Address, - asset codec.LID, + asset ids.ID, ) (uint64, error) { key, bal, _, err := getBalance(ctx, im, addr, asset) balanceKeyPool.Put(key) @@ -156,7 +154,7 @@ func getBalance( ctx context.Context, im state.Immutable, addr codec.Address, - asset codec.LID, + asset ids.ID, ) ([]byte, uint64, bool, error) { k := BalanceKey(addr, asset) bal, exists, err := innerGetBalance(im.GetValue(ctx, k)) @@ -168,7 +166,7 @@ func GetBalanceFromState( ctx context.Context, f ReadState, addr codec.Address, - asset codec.LID, + asset ids.ID, ) (uint64, error) { k := BalanceKey(addr, asset) values, errs := f(ctx, [][]byte{k}) @@ -194,7 +192,7 @@ func SetBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset codec.LID, + asset ids.ID, balance uint64, ) error { k := BalanceKey(addr, asset) @@ -214,7 +212,7 @@ func DeleteBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset codec.LID, + asset ids.ID, ) error { return mu.Remove(ctx, BalanceKey(addr, asset)) } @@ -223,7 +221,7 @@ func AddBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset codec.LID, + asset ids.ID, amount uint64, create bool, ) error { @@ -254,7 +252,7 @@ func SubBalance( ctx context.Context, mu state.Mutable, addr codec.Address, - asset codec.LID, + asset ids.ID, amount uint64, ) error { key, bal, _, err := getBalance(ctx, mu, addr, asset) @@ -281,11 +279,11 @@ func SubBalance( } // [assetPrefix] + [address] -func AssetKey(asset codec.LID) (k []byte) { - k = make([]byte, 1+codec.LIDLen+consts.Uint16Len) +func AssetKey(asset ids.ID) (k []byte) { + k = make([]byte, 1+ids.IDLen+consts.Uint16Len) k[0] = assetPrefix copy(k[1:], asset[:]) - binary.BigEndian.PutUint16(k[1+codec.LIDLen:], AssetChunks) + binary.BigEndian.PutUint16(k[1+ids.IDLen:], AssetChunks) return } @@ -293,7 +291,7 @@ func AssetKey(asset codec.LID) (k []byte) { func GetAssetFromState( ctx context.Context, f ReadState, - asset codec.LID, + asset ids.ID, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { values, errs := f(ctx, [][]byte{AssetKey(asset)}) return innerGetAsset(values[0], errs[0]) @@ -302,7 +300,7 @@ func GetAssetFromState( func GetAsset( ctx context.Context, im state.Immutable, - asset codec.LID, + asset ids.ID, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { k := AssetKey(asset) return innerGetAsset(im.GetValue(ctx, k)) @@ -313,10 +311,10 @@ func innerGetAsset( err error, ) (bool, []byte, uint8, []byte, uint64, codec.Address, error) { if errors.Is(err, database.ErrNotFound) { - return false, nil, 0, nil, 0, codec.Empty, nil + return false, nil, 0, nil, 0, codec.EmptyAddress, nil } if err != nil { - return false, nil, 0, nil, 0, codec.Empty, err + return false, nil, 0, nil, 0, codec.EmptyAddress, err } symbolLen := binary.BigEndian.Uint16(v) symbol := v[consts.Uint16Len : consts.Uint16Len+symbolLen] @@ -332,7 +330,7 @@ func innerGetAsset( func SetAsset( ctx context.Context, mu state.Mutable, - asset codec.LID, + asset ids.ID, symbol []byte, decimals uint8, metadata []byte, @@ -342,7 +340,7 @@ func SetAsset( k := AssetKey(asset) symbolLen := len(symbol) metadataLen := len(metadata) - v := make([]byte, consts.Uint16Len+symbolLen+consts.Uint8Len+consts.Uint16Len+metadataLen+consts.Uint64Len+codec.LIDLen) + v := make([]byte, consts.Uint16Len+symbolLen+consts.Uint8Len+consts.Uint16Len+metadataLen+consts.Uint64Len+codec.AddressLen) binary.BigEndian.PutUint16(v, uint16(symbolLen)) copy(v[consts.Uint16Len:], symbol) v[consts.Uint16Len+symbolLen] = decimals @@ -353,51 +351,51 @@ func SetAsset( return mu.Insert(ctx, k, v) } -func DeleteAsset(ctx context.Context, mu state.Mutable, asset codec.LID) error { +func DeleteAsset(ctx context.Context, mu state.Mutable, asset ids.ID) error { k := AssetKey(asset) return mu.Remove(ctx, k) } // [orderPrefix] + [actionID] -func OrderKey(actionID codec.LID) (k []byte) { - k = make([]byte, 1+codec.LIDLen+consts.Uint16Len) +func OrderKey(actionID ids.ID) (k []byte) { + k = make([]byte, 1+ids.IDLen+consts.Uint16Len) k[0] = orderPrefix copy(k[1:], actionID[:]) - binary.BigEndian.PutUint16(k[1+codec.LIDLen:], OrderChunks) + binary.BigEndian.PutUint16(k[1+ids.IDLen:], OrderChunks) return } func SetOrder( ctx context.Context, mu state.Mutable, - actionID codec.LID, - in codec.LID, + actionID ids.ID, + in ids.ID, inTick uint64, - out codec.LID, + out ids.ID, outTick uint64, supply uint64, owner codec.Address, ) error { k := OrderKey(actionID) - v := make([]byte, codec.LIDLen*2+consts.Uint64Len*3+codec.AddressLen) + v := make([]byte, ids.IDLen*2+consts.Uint64Len*3+codec.AddressLen) copy(v, in[:]) - binary.BigEndian.PutUint64(v[codec.LIDLen:], inTick) - copy(v[codec.LIDLen+consts.Uint64Len:], out[:]) - binary.BigEndian.PutUint64(v[codec.LIDLen*2+consts.Uint64Len:], outTick) - binary.BigEndian.PutUint64(v[codec.LIDLen*2+consts.Uint64Len*2:], supply) - copy(v[codec.LIDLen*2+consts.Uint64Len*3:], owner[:]) + binary.BigEndian.PutUint64(v[ids.IDLen:], inTick) + copy(v[ids.IDLen+consts.Uint64Len:], out[:]) + binary.BigEndian.PutUint64(v[ids.IDLen*2+consts.Uint64Len:], outTick) + binary.BigEndian.PutUint64(v[ids.IDLen*2+consts.Uint64Len*2:], supply) + copy(v[ids.IDLen*2+consts.Uint64Len*3:], owner[:]) return mu.Insert(ctx, k, v) } func GetOrder( ctx context.Context, im state.Immutable, - order codec.LID, + order ids.ID, ) ( bool, // exists - codec.LID, // in + ids.ID, // in uint64, // inTick - codec.LID, // out + ids.ID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -412,12 +410,12 @@ func GetOrder( func GetOrderFromState( ctx context.Context, f ReadState, - order codec.LID, + order ids.ID, ) ( bool, // exists - codec.LID, // in + ids.ID, // in uint64, // inTick - codec.LID, // out + ids.ID, // out uint64, // outTick uint64, // remaining codec.Address, // owner @@ -429,143 +427,37 @@ func GetOrderFromState( func innerGetOrder(v []byte, err error) ( bool, // exists - codec.LID, // in + ids.ID, // in uint64, // inTick - codec.LID, // out + ids.ID, // out uint64, // outTick uint64, // remaining codec.Address, // owner error, ) { if errors.Is(err, database.ErrNotFound) { - return false, codec.Empty, 0, codec.Empty, 0, 0, codec.Empty, nil + return false, ids.Empty, 0, ids.Empty, 0, 0, codec.EmptyAddress, nil } if err != nil { - return false, codec.Empty, 0, codec.Empty, 0, 0, codec.Empty, err + return false, ids.Empty, 0, ids.Empty, 0, 0, codec.EmptyAddress, err } - var in codec.LID - copy(in[:], v[:codec.LIDLen]) - inTick := binary.BigEndian.Uint64(v[codec.LIDLen:]) - var out codec.LID - copy(out[:], v[codec.LIDLen+consts.Uint64Len:codec.LIDLen*2+consts.Uint64Len]) - outTick := binary.BigEndian.Uint64(v[codec.LIDLen*2+consts.Uint64Len:]) - supply := binary.BigEndian.Uint64(v[codec.LIDLen*2+consts.Uint64Len*2:]) + var in ids.ID + copy(in[:], v[:ids.IDLen]) + inTick := binary.BigEndian.Uint64(v[ids.IDLen:]) + var out ids.ID + copy(out[:], v[ids.IDLen+consts.Uint64Len:ids.IDLen*2+consts.Uint64Len]) + outTick := binary.BigEndian.Uint64(v[ids.IDLen*2+consts.Uint64Len:]) + supply := binary.BigEndian.Uint64(v[ids.IDLen*2+consts.Uint64Len*2:]) var owner codec.Address - copy(owner[:], v[codec.LIDLen*2+consts.Uint64Len*3:]) + copy(owner[:], v[ids.IDLen*2+consts.Uint64Len*3:]) return true, in, inTick, out, outTick, supply, owner, nil } -func DeleteOrder(ctx context.Context, mu state.Mutable, order codec.LID) error { +func DeleteOrder(ctx context.Context, mu state.Mutable, order ids.ID) error { k := OrderKey(order) return mu.Remove(ctx, k) } -// [loanPrefix] + [asset] + [destination] -func LoanKey(asset codec.LID, destination ids.ID) (k []byte) { - k = make([]byte, 1+codec.LIDLen+consts.IDLen+consts.Uint16Len) - k[0] = loanPrefix - copy(k[1:], asset[:]) - copy(k[1+codec.LIDLen:], destination[:]) - binary.BigEndian.PutUint16(k[1+codec.LIDLen+consts.IDLen:], LoanChunks) - return -} - -// Used to serve RPC queries -func GetLoanFromState( - ctx context.Context, - f ReadState, - asset codec.LID, - destination ids.ID, -) (uint64, error) { - values, errs := f(ctx, [][]byte{LoanKey(asset, destination)}) - return innerGetLoan(values[0], errs[0]) -} - -func innerGetLoan(v []byte, err error) (uint64, error) { - if errors.Is(err, database.ErrNotFound) { - return 0, nil - } - if err != nil { - return 0, err - } - return binary.BigEndian.Uint64(v), nil -} - -func GetLoan( - ctx context.Context, - im state.Immutable, - asset codec.LID, - destination ids.ID, -) (uint64, error) { - k := LoanKey(asset, destination) - v, err := im.GetValue(ctx, k) - return innerGetLoan(v, err) -} - -func SetLoan( - ctx context.Context, - mu state.Mutable, - asset codec.LID, - destination ids.ID, - amount uint64, -) error { - k := LoanKey(asset, destination) - return mu.Insert(ctx, k, binary.BigEndian.AppendUint64(nil, amount)) -} - -func AddLoan( - ctx context.Context, - mu state.Mutable, - asset codec.LID, - destination ids.ID, - amount uint64, -) error { - loan, err := GetLoan(ctx, mu, asset, destination) - if err != nil { - return err - } - nloan, err := smath.Add64(loan, amount) - if err != nil { - return fmt.Errorf( - "%w: could not add loan (asset=%s, destination=%s, amount=%d)", - ErrInvalidBalance, - asset, - destination, - amount, - ) - } - return SetLoan(ctx, mu, asset, destination, nloan) -} - -func SubLoan( - ctx context.Context, - mu state.Mutable, - asset codec.LID, - destination ids.ID, - amount uint64, -) error { - loan, err := GetLoan(ctx, mu, asset, destination) - if err != nil { - return err - } - nloan, err := smath.Sub(loan, amount) - if err != nil { - return fmt.Errorf( - "%w: could not subtract loan (asset=%s, destination=%s, amount=%d)", - ErrInvalidBalance, - asset, - destination, - amount, - ) - } - if nloan == 0 { - // If there is no balance left, we should delete the record instead of - // setting it to 0. - return mu.Remove(ctx, LoanKey(asset, destination)) - } - return SetLoan(ctx, mu, asset, destination, nloan) -} - func HeightKey() (k []byte) { return heightKey } diff --git a/examples/tokenvm/tests/e2e/e2e_test.go b/examples/tokenvm/tests/e2e/e2e_test.go index 7243ee5b12..0a1245c383 100644 --- a/examples/tokenvm/tests/e2e/e2e_test.go +++ b/examples/tokenvm/tests/e2e/e2e_test.go @@ -11,23 +11,25 @@ import ( "testing" "time" - runner_sdk "github.com/ava-labs/avalanche-network-runner/client" "github.com/ava-labs/avalanche-network-runner/rpcpb" "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/fatih/color" + "github.com/onsi/gomega" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/rpc" + + runner_sdk "github.com/ava-labs/avalanche-network-runner/client" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" hutils "github.com/ava-labs/hypersdk/utils" - "github.com/fatih/color" ginkgo "github.com/onsi/ginkgo/v2" - "github.com/onsi/gomega" ) const ( @@ -63,8 +65,7 @@ var ( logsDir string - blockchainIDA string - blockchainIDB string + blockchainID string trackSubnetsOpt runner_sdk.OpOption @@ -164,10 +165,9 @@ func init() { } const ( - modeTest = "test" - modeFullTest = "full-test" // runs state sync - modeRun = "run" - modeRunSingle = "run-single" + modeTest = "test" + modeFullTest = "full-test" // runs state sync + modeRun = "run" ) var anrCli runner_sdk.Client @@ -177,7 +177,6 @@ var _ = ginkgo.BeforeSuite(func() { gomega.Equal(modeTest), gomega.Equal(modeFullTest), gomega.Equal(modeRun), - gomega.Equal(modeRunSingle), )) gomega.Expect(numValidators).Should(gomega.BeNumerically(">", 0)) logLevel, err := logging.ToLevel(networkRunnerLogLevel) @@ -254,16 +253,11 @@ var _ = ginkgo.BeforeSuite(func() { ) logsDir = resp.GetClusterInfo().GetRootDataDir() - // Name 10 new validators (which should have BLS key registered) - subnetA := []string{} - subnetB := []string{} - for i := 1; i <= int(numValidators)*2; i++ { - n := fmt.Sprintf("node%d-bls", i) - if i <= int(numValidators) { - subnetA = append(subnetA, n) - } else { - subnetB = append(subnetB, n) - } + // Add 5 validators (already have BLS key registered) + subnet := []string{} + for i := 1; i <= int(numValidators); i++ { + n := fmt.Sprintf("node%d", i) + subnet = append(subnet, n) } specs := []*rpcpb.BlockchainSpec{ { @@ -272,24 +266,12 @@ var _ = ginkgo.BeforeSuite(func() { ChainConfig: vmConfigPath, SubnetSpec: &rpcpb.SubnetSpec{ SubnetConfig: subnetConfigPath, - Participants: subnetA, + Participants: subnet, }, }, - { - VmName: consts.Name, - Genesis: vmGenesisPath, - ChainConfig: vmConfigPath, - SubnetSpec: &rpcpb.SubnetSpec{ - SubnetConfig: subnetConfigPath, - Participants: subnetB, - }, - }, - } - if mode == modeRunSingle { - specs = specs[0:1] } - // Create 2 subnets + // Create subnet ctx, cancel = context.WithTimeout(context.Background(), 5*time.Minute) sresp, err := anrCli.CreateBlockchains( ctx, @@ -298,40 +280,20 @@ var _ = ginkgo.BeforeSuite(func() { cancel() gomega.Expect(err).Should(gomega.BeNil()) - blockchainIDA = sresp.ChainIds[0] - subnetIDA := sresp.ClusterInfo.CustomChains[blockchainIDA].SubnetId + blockchainID = sresp.ChainIds[0] + subnetID := sresp.ClusterInfo.CustomChains[blockchainID].SubnetId hutils.Outf( "{{green}}successfully added chain:{{/}} %s {{green}}subnet:{{/}} %s {{green}}participants:{{/}} %+v\n", - blockchainIDA, - subnetIDA, - subnetA, + blockchainID, + subnetID, + subnet, ) + trackSubnetsOpt = runner_sdk.WithGlobalNodeConfig(fmt.Sprintf(`{"%s":"%s"}`, + config.TrackSubnetsKey, + subnetID, + )) - if mode == modeRunSingle { - trackSubnetsOpt = runner_sdk.WithGlobalNodeConfig(fmt.Sprintf(`{"%s":"%s"}`, - config.TrackSubnetsKey, - subnetIDA, - )) - } else { - blockchainIDB = sresp.ChainIds[1] - subnetIDB := sresp.ClusterInfo.CustomChains[blockchainIDB].SubnetId - hutils.Outf( - "{{green}}successfully added chain:{{/}} %s {{green}}subnet:{{/}} %s {{green}}participants:{{/}} %+v\n", - blockchainIDB, - subnetIDB, - subnetB, - ) - trackSubnetsOpt = runner_sdk.WithGlobalNodeConfig(fmt.Sprintf(`{"%s":"%s,%s"}`, - config.TrackSubnetsKey, - subnetIDA, - subnetIDB, - )) - } - - gomega.Expect(blockchainIDA).Should(gomega.Not(gomega.BeEmpty())) - if mode != modeRunSingle { - gomega.Expect(blockchainIDB).Should(gomega.Not(gomega.BeEmpty())) - } + gomega.Expect(blockchainID).Should(gomega.Not(gomega.BeEmpty())) gomega.Expect(logsDir).Should(gomega.Not(gomega.BeEmpty())) cctx, ccancel := context.WithTimeout(context.Background(), 2*time.Minute) @@ -340,11 +302,11 @@ var _ = ginkgo.BeforeSuite(func() { gomega.Expect(err).Should(gomega.BeNil()) nodeInfos := status.GetClusterInfo().GetNodeInfos() - instancesA = []instance{} - for _, nodeName := range subnetA { + instances = []instance{} + for _, nodeName := range subnet { info := nodeInfos[nodeName] - u := fmt.Sprintf("%s/ext/bc/%s", info.Uri, blockchainIDA) - bid, err := ids.FromString(blockchainIDA) + u := fmt.Sprintf("%s/ext/bc/%s", info.Uri, blockchainID) + bid, err := ids.FromString(blockchainID) gomega.Expect(err).Should(gomega.BeNil()) nodeID, err := ids.NodeIDFromString(info.GetId()) gomega.Expect(err).Should(gomega.BeNil()) @@ -363,7 +325,7 @@ var _ = ginkgo.BeforeSuite(func() { } gomega.Expect(err).Should(gomega.BeNil()) - instancesA = append(instancesA, instance{ + instances = append(instances, instance{ nodeID: nodeID, uri: u, cli: cli, @@ -371,39 +333,6 @@ var _ = ginkgo.BeforeSuite(func() { }) } - if mode != modeRunSingle { - instancesB = []instance{} - for _, nodeName := range subnetB { - info := nodeInfos[nodeName] - u := fmt.Sprintf("%s/ext/bc/%s", info.Uri, blockchainIDB) - bid, err := ids.FromString(blockchainIDB) - gomega.Expect(err).Should(gomega.BeNil()) - nodeID, err := ids.NodeIDFromString(info.GetId()) - gomega.Expect(err).Should(gomega.BeNil()) - cli := rpc.NewJSONRPCClient(u) - - // After returning healthy, the node may not respond right away - // - // TODO: figure out why - var networkID uint32 - for i := 0; i < 10; i++ { - networkID, _, _, err = cli.Network(context.TODO()) - if err != nil { - time.Sleep(1 * time.Second) - continue - } - } - gomega.Expect(err).Should(gomega.BeNil()) - - instancesB = append(instancesB, instance{ - nodeID: nodeID, - uri: u, - cli: cli, - tcli: trpc.NewJSONRPCClient(u, networkID, bid), - }) - } - } - // Load default pk privBytes, err := codec.LoadHex( "323b1d8f4eed5f0da9da93071b034f2dce9d2d22692c172f3cb252a64ddfafd01b057de320297c29ad0c1f589ea216869cf1938d88c9fbd70d6748323dbf2fa7", //nolint:lll @@ -423,8 +352,7 @@ var ( rsender codec.Address sender string - instancesA []instance - instancesB []instance + instances []instance ) type instance struct { @@ -445,19 +373,8 @@ var _ = ginkgo.AfterSuite(func() { case modeRun: hutils.Outf("{{yellow}}skipping cluster shutdown{{/}}\n\n") - hutils.Outf("{{cyan}}Blockchain A:{{/}} %s\n", blockchainIDA) - for _, member := range instancesA { - hutils.Outf("%s URI: %s\n", member.nodeID, member.uri) - } - hutils.Outf("\n{{cyan}}Blockchain B:{{/}} %s\n", blockchainIDB) - for _, member := range instancesB { - hutils.Outf("%s URI: %s\n", member.nodeID, member.uri) - } - - case modeRunSingle: - hutils.Outf("{{yellow}}skipping cluster shutdown{{/}}\n\n") - hutils.Outf("{{cyan}}Blockchain:{{/}} %s\n", blockchainIDA) - for _, member := range instancesA { + hutils.Outf("{{cyan}}Blockchain:{{/}} %s\n", blockchainID) + for _, member := range instances { hutils.Outf("%s URI: %s\n", member.nodeID, member.uri) } } @@ -465,17 +382,8 @@ var _ = ginkgo.AfterSuite(func() { }) var _ = ginkgo.Describe("[Ping]", func() { - ginkgo.It("can ping A", func() { - for _, inst := range instancesA { - cli := inst.cli - ok, err := cli.Ping(context.Background()) - gomega.Ω(ok).Should(gomega.BeTrue()) - gomega.Ω(err).Should(gomega.BeNil()) - } - }) - - ginkgo.It("can ping B", func() { - for _, inst := range instancesB { + ginkgo.It("can ping", func() { + for _, inst := range instances { cli := inst.cli ok, err := cli.Ping(context.Background()) gomega.Ω(ok).Should(gomega.BeTrue()) @@ -485,17 +393,8 @@ var _ = ginkgo.Describe("[Ping]", func() { }) var _ = ginkgo.Describe("[Network]", func() { - ginkgo.It("can get network A", func() { - for _, inst := range instancesA { - cli := inst.cli - _, _, chainID, err := cli.Network(context.Background()) - gomega.Ω(chainID).ShouldNot(gomega.Equal(ids.Empty)) - gomega.Ω(err).Should(gomega.BeNil()) - } - }) - - ginkgo.It("can get network B", func() { - for _, inst := range instancesB { + ginkgo.It("can get network", func() { + for _, inst := range instances { cli := inst.cli _, _, chainID, err := cli.Network(context.Background()) gomega.Ω(chainID).ShouldNot(gomega.Equal(ids.Empty)) @@ -506,13 +405,13 @@ var _ = ginkgo.Describe("[Network]", func() { var _ = ginkgo.Describe("[Test]", func() { switch mode { - case modeRun, modeRunSingle: + case modeRun: hutils.Outf("{{yellow}}skipping tests{{/}}\n") return } ginkgo.It("transfer in a single node (raw)", func() { - nativeBalance, err := instancesA[0].tcli.Balance(context.TODO(), sender, codec.Empty) + nativeBalance, err := instances[0].tcli.Balance(context.TODO(), sender, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(nativeBalance).Should(gomega.Equal(startAmount)) @@ -522,9 +421,9 @@ var _ = ginkgo.Describe("[Test]", func() { ginkgo.By("issue Transfer to the first node", func() { // Generate transaction - parser, err := instancesA[0].tcli.Parser(context.TODO()) + parser, err := instances[0].tcli.Parser(context.TODO()) gomega.Ω(err).Should(gomega.BeNil()) - submit, tx, maxFee, err := instancesA[0].cli.GenerateTransaction( + submit, tx, maxFee, err := instances[0].cli.GenerateTransaction( context.Background(), parser, []chain.Action{&actions.Transfer{ @@ -540,14 +439,14 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, fee, err := instancesA[0].tcli.WaitForTransaction(ctx, tx.ID()) + success, fee, err := instances[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found transaction{{/}}\n") // Check sender balance - balance, err := instancesA[0].tcli.Balance(context.Background(), sender, codec.Empty) + balance, err := instances[0].tcli.Balance(context.Background(), sender, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) hutils.Outf( "{{yellow}}start=%d fee=%d send=%d balance=%d{{/}}\n", @@ -561,7 +460,7 @@ var _ = ginkgo.Describe("[Test]", func() { }) ginkgo.By("check if Transfer has been accepted from all nodes", func() { - for _, inst := range instancesA { + for _, inst := range instances { color.Blue("checking %q", inst.uri) // Ensure all blocks processed @@ -575,7 +474,7 @@ var _ = ginkgo.Describe("[Test]", func() { } // Check balance of recipient - balance, err := inst.tcli.Balance(context.Background(), codec.MustAddressBech32(consts.HRP, aother), codec.Empty) + balance, err := inst.tcli.Balance(context.Background(), codec.MustAddressBech32(consts.HRP, aother), ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(sendAmount)) } @@ -595,7 +494,7 @@ var _ = ginkgo.Describe("[Test]", func() { // Create blocks before bootstrapping starts count := 0 ginkgo.It("supports issuance of 128 blocks", func() { - count += generateBlocks(context.Background(), count, 128, instancesA, true) + count += generateBlocks(context.Background(), count, 128, instances, true) }) // Ensure bootstrapping works @@ -608,15 +507,15 @@ var _ = ginkgo.Describe("[Test]", func() { execPath, trackSubnetsOpt, runner_sdk.WithChainConfigs(map[string]string{ - blockchainIDA: vmConfig, + blockchainID: vmConfig, }), ) gomega.Expect(err).To(gomega.BeNil()) awaitHealthy(anrCli) nodeURI := cluster.ClusterInfo.NodeInfos["bootstrap"].Uri - uri := nodeURI + fmt.Sprintf("/ext/bc/%s", blockchainIDA) - bid, err := ids.FromString(blockchainIDA) + uri := nodeURI + fmt.Sprintf("/ext/bc/%s", blockchainID) + bid, err := ids.FromString(blockchainID) gomega.Expect(err).To(gomega.BeNil()) hutils.Outf("{{blue}}bootstrap node uri: %s{{/}}\n", uri) c := rpc.NewJSONRPCClient(uri) @@ -625,7 +524,7 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Expect(err).Should(gomega.BeNil()) tc := trpc.NewJSONRPCClient(uri, networkID, bid) tsyncClient = tc - instancesA = append(instancesA, instance{ + instances = append(instances, instance{ uri: uri, cli: c, tcli: tc, @@ -641,7 +540,7 @@ var _ = ginkgo.Describe("[Test]", func() { // // We do 1024 so that there are a number of ranges of data to fetch. ginkgo.It("supports issuance of at least 1024 more blocks", func() { - count += generateBlocks(context.Background(), count, 1024, instancesA, true) + count += generateBlocks(context.Background(), count, 1024, instances, true) // TODO: verify all roots are equal }) @@ -652,7 +551,7 @@ var _ = ginkgo.Describe("[Test]", func() { execPath, trackSubnetsOpt, runner_sdk.WithChainConfigs(map[string]string{ - blockchainIDA: vmConfig, + blockchainID: vmConfig, }), ) gomega.Expect(err).To(gomega.BeNil()) @@ -660,8 +559,8 @@ var _ = ginkgo.Describe("[Test]", func() { awaitHealthy(anrCli) nodeURI := cluster.ClusterInfo.NodeInfos["sync"].Uri - uri := nodeURI + fmt.Sprintf("/ext/bc/%s", blockchainIDA) - bid, err := ids.FromString(blockchainIDA) + uri := nodeURI + fmt.Sprintf("/ext/bc/%s", blockchainID) + bid, err := ids.FromString(blockchainID) gomega.Expect(err).To(gomega.BeNil()) hutils.Outf("{{blue}}sync node uri: %s{{/}}\n", uri) syncClient = rpc.NewJSONRPCClient(uri) @@ -690,7 +589,7 @@ var _ = ginkgo.Describe("[Test]", func() { }) ginkgo.It("supports issuance of 256 more blocks", func() { - count += generateBlocks(context.Background(), count, 256, instancesA, true) + count += generateBlocks(context.Background(), count, 256, instances, true) // TODO: verify all roots are equal }) @@ -714,7 +613,7 @@ var _ = ginkgo.Describe("[Test]", func() { // Recover failure if exits defer ginkgo.GinkgoRecover() - count += generateBlocks(ctx, count, 0, instancesA, false) + count += generateBlocks(ctx, count, 0, instances, false) }() // Give time for transactions to start processing @@ -727,15 +626,15 @@ var _ = ginkgo.Describe("[Test]", func() { execPath, trackSubnetsOpt, runner_sdk.WithChainConfigs(map[string]string{ - blockchainIDA: vmConfig, + blockchainID: vmConfig, }), ) gomega.Expect(err).To(gomega.BeNil()) awaitHealthy(anrCli) nodeURI := cluster.ClusterInfo.NodeInfos["sync_concurrent"].Uri - uri := nodeURI + fmt.Sprintf("/ext/bc/%s", blockchainIDA) - bid, err := ids.FromString(blockchainIDA) + uri := nodeURI + fmt.Sprintf("/ext/bc/%s", blockchainID) + bid, err := ids.FromString(blockchainID) gomega.Expect(err).To(gomega.BeNil()) hutils.Outf("{{blue}}sync node uri: %s{{/}}\n", uri) syncClient = rpc.NewJSONRPCClient(uri) diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index 63db0b88bc..bedefb9557 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -27,7 +27,6 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" "github.com/fatih/color" - ginkgo "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "go.uber.org/zap" @@ -35,18 +34,19 @@ import ( "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" + "github.com/ava-labs/hypersdk/examples/tokenvm/actions" + "github.com/ava-labs/hypersdk/examples/tokenvm/auth" + "github.com/ava-labs/hypersdk/examples/tokenvm/controller" + "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/pubsub" "github.com/ava-labs/hypersdk/rpc" - hutils "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/vm" - "github.com/ava-labs/hypersdk/examples/tokenvm/actions" - "github.com/ava-labs/hypersdk/examples/tokenvm/auth" tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - "github.com/ava-labs/hypersdk/examples/tokenvm/controller" - "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" + hutils "github.com/ava-labs/hypersdk/utils" + ginkgo "github.com/onsi/ginkgo/v2" ) var ( @@ -109,19 +109,19 @@ var ( asset1 []byte asset1Symbol []byte asset1Decimals uint8 - asset1ID codec.LID + asset1ID ids.ID asset2 []byte asset2Symbol []byte asset2Decimals uint8 - asset2ID codec.LID + asset2ID ids.ID asset3 []byte asset3Symbol []byte asset3Decimals uint8 - asset3ID codec.LID + asset3ID ids.ID asset4 []byte asset4Symbol []byte asset4Decimals uint8 - asset4ID codec.LID + asset4ID ids.ID // when used with embedded VMs genesisBytes []byte @@ -285,19 +285,19 @@ var _ = ginkgo.BeforeSuite(func() { csupply := uint64(0) for _, alloc := range g.CustomAllocation { - balance, err := cli.Balance(context.Background(), alloc.Address, codec.Empty) + balance, err := cli.Balance(context.Background(), alloc.Address, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(alloc.Balance)) csupply += alloc.Balance } - exists, symbol, decimals, metadata, supply, owner, err := cli.Asset(context.Background(), codec.Empty, false) + exists, symbol, decimals, metadata, supply, owner, err := cli.Asset(context.Background(), ids.Empty, false) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(exists).Should(gomega.BeTrue()) gomega.Ω(string(symbol)).Should(gomega.Equal(tconsts.Symbol)) gomega.Ω(decimals).Should(gomega.Equal(uint8(tconsts.Decimals))) gomega.Ω(string(metadata)).Should(gomega.Equal(tconsts.Name)) gomega.Ω(supply).Should(gomega.Equal(csupply)) - gomega.Ω(owner).Should(gomega.Equal(codec.MustAddressBech32(tconsts.HRP, codec.Empty))) + gomega.Ω(owner).Should(gomega.Equal(codec.MustAddressBech32(tconsts.HRP, codec.EmptyAddress))) } blocks = []snowman.Block{} @@ -453,20 +453,20 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { // read: 2 keys reads, 1 had 0 chunks // allocate: 1 key created // write: 1 key modified, 1 key new - transferTxConsumed := fees.Dimensions{228, 7, 12, 25, 26} + transferTxConsumed := fees.Dimensions{224, 7, 12, 25, 26} gomega.Ω(results[0].Consumed).Should(gomega.Equal(transferTxConsumed)) // Fee explanation // // Multiply all unit consumption by 1 and sum - gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(298))) + gomega.Ω(results[0].Fee).Should(gomega.Equal(uint64(294))) }) ginkgo.By("ensure balance is updated", func() { - balance, err := instances[1].tcli.Balance(context.Background(), sender, codec.Empty) + balance, err := instances[1].tcli.Balance(context.Background(), sender, ids.Empty) gomega.Ω(err).To(gomega.BeNil()) - gomega.Ω(balance).To(gomega.Equal(uint64(9899702))) - balance2, err := instances[1].tcli.Balance(context.Background(), sender2, codec.Empty) + gomega.Ω(balance).To(gomega.Equal(uint64(9899706))) + balance2, err := instances[1].tcli.Balance(context.Background(), sender2, ids.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100000))) }) @@ -493,7 +493,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance2, err := instances[1].tcli.Balance(context.Background(), sender2, codec.Empty) + balance2, err := instances[1].tcli.Balance(context.Background(), sender2, ids.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100101))) }) @@ -636,7 +636,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { time.Sleep(2 * pubsub.MaxMessageWait) // Fetch balances - balance, err := instances[0].tcli.Balance(context.TODO(), sender, codec.Empty) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) // Send tx @@ -669,13 +669,13 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(len(blk.Txs)).Should(gomega.Equal(1)) tx := blk.Txs[0].Actions[0].(*actions.Transfer) - gomega.Ω(tx.Asset).To(gomega.Equal(codec.LID{})) + gomega.Ω(tx.Asset).To(gomega.Equal(ids.Empty)) gomega.Ω(tx.Value).To(gomega.Equal(uint64(1))) gomega.Ω(lresults).Should(gomega.Equal(results)) gomega.Ω(prices).Should(gomega.Equal(fees.Dimensions{1, 1, 1, 1, 1})) // Check balance modifications are correct - balancea, err := instances[0].tcli.Balance(context.TODO(), sender, codec.Empty) + balancea, err := instances[0].tcli.Balance(context.TODO(), sender, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(balancea + lresults[0].Fee + 1)) @@ -794,7 +794,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { ginkgo.It("mint an asset that doesn't exist", func() { other, err := ed25519.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) - assetID := codec.CreateLID(0, ids.GenerateTestID()) + assetID := ids.GenerateTestID() parser, err := instances[0].tcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( @@ -932,7 +932,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset1ID = codec.CreateLID(0, tx.ID()) + asset1ID = chain.CreateActionID(tx.ID(), 0) balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) @@ -1208,7 +1208,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := accept(false) gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset2ID = codec.CreateLID(0, tx.ID()) + asset2ID = chain.CreateActionID(tx.ID(), 0) submit, _, _, err = instances[0].cli.GenerateTransaction( context.Background(), @@ -1251,7 +1251,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := accept(false) gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset3ID = codec.CreateLID(0, tx.ID()) + asset3ID = chain.CreateActionID(tx.ID(), 0) submit, _, _, err = instances[0].cli.GenerateTransaction( context.Background(), @@ -1305,7 +1305,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] - gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) + gomega.Ω(order.ID).Should(gomega.Equal(chain.CreateActionID(tx.ID(), 0))) gomega.Ω(order.InTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order.OutTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order.Owner).Should(gomega.Equal(sender)) @@ -1368,7 +1368,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] - gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) + gomega.Ω(order.ID).Should(gomega.Equal(chain.CreateActionID(tx.ID(), 0))) gomega.Ω(order.InTick).Should(gomega.Equal(uint64(4))) gomega.Ω(order.OutTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order.Owner).Should(gomega.Equal(sender2)) @@ -1612,7 +1612,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] - gomega.Ω(order.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) + gomega.Ω(order.ID).Should(gomega.Equal(chain.CreateActionID(tx.ID(), 0))) gomega.Ω(order.InTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order.OutTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order.Owner).Should(gomega.Equal(sender)) @@ -1697,11 +1697,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance2, err := instances[3].tcli.Balance(context.Background(), sender2, codec.Empty) + balance2, err := instances[3].tcli.Balance(context.Background(), sender2, ids.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(10000))) - balance3, err := instances[3].tcli.Balance(context.Background(), sender3, codec.Empty) + balance3, err := instances[3].tcli.Balance(context.Background(), sender3, ids.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance3).To(gomega.Equal(uint64(5000))) }) @@ -1744,10 +1744,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - asset1ID = codec.CreateLID(0, tx.ID()) - asset2ID = codec.CreateLID(1, tx.ID()) - asset3ID = codec.CreateLID(2, tx.ID()) - asset4ID = codec.CreateLID(3, tx.ID()) + asset1ID = chain.CreateActionID(tx.ID(), 0) + asset2ID = chain.CreateActionID(tx.ID(), 1) + asset3ID = chain.CreateActionID(tx.ID(), 2) + asset4ID = chain.CreateActionID(tx.ID(), 3) // Mint multiple submit, _, _, err = instances[3].cli.GenerateTransaction( @@ -1868,7 +1868,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(orders32).Should(gomega.HaveLen(1)) order32 := orders32[0] - gomega.Ω(order32.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) + gomega.Ω(order32.ID).Should(gomega.Equal(chain.CreateActionID(tx.ID(), 0))) gomega.Ω(order32.InTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order32.OutTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order32.Owner).Should(gomega.Equal(sender)) @@ -1879,7 +1879,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(orders14).Should(gomega.HaveLen(1)) order14 := orders14[0] - gomega.Ω(order14.ID).Should(gomega.Equal(codec.CreateLID(1, tx.ID()))) + gomega.Ω(order14.ID).Should(gomega.Equal(chain.CreateActionID(tx.ID(), 1))) gomega.Ω(order14.InTick).Should(gomega.Equal(uint64(1))) gomega.Ω(order14.OutTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order14.Owner).Should(gomega.Equal(sender)) @@ -1976,7 +1976,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(orders13).Should(gomega.HaveLen(1)) order13 := orders13[0] - gomega.Ω(order13.ID).Should(gomega.Equal(codec.CreateLID(0, tx.ID()))) + gomega.Ω(order13.ID).Should(gomega.Equal(chain.CreateActionID(tx.ID(), 0))) gomega.Ω(order13.InTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order13.OutTick).Should(gomega.Equal(uint64(4))) gomega.Ω(order13.Owner).Should(gomega.Equal(sender)) @@ -1987,7 +1987,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(orders24).Should(gomega.HaveLen(1)) order24 := orders24[0] - gomega.Ω(order24.ID).Should(gomega.Equal(codec.CreateLID(1, tx.ID()))) + gomega.Ω(order24.ID).Should(gomega.Equal(chain.CreateActionID(tx.ID(), 1))) gomega.Ω(order24.InTick).Should(gomega.Equal(uint64(2))) gomega.Ω(order24.OutTick).Should(gomega.Equal(uint64(4))) gomega.Ω(order24.Owner).Should(gomega.Equal(sender)) diff --git a/examples/tokenvm/tests/load/load_test.go b/examples/tokenvm/tests/load/load_test.go index 0591b346d4..f847166c5e 100644 --- a/examples/tokenvm/tests/load/load_test.go +++ b/examples/tokenvm/tests/load/load_test.go @@ -30,27 +30,27 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/fatih/color" - ginkgo "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "go.uber.org/zap" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" - hconsts "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" - "github.com/ava-labs/hypersdk/fees" - "github.com/ava-labs/hypersdk/pebble" - hutils "github.com/ava-labs/hypersdk/utils" - "github.com/ava-labs/hypersdk/vm" - "github.com/ava-labs/hypersdk/workers" - "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/controller" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" - trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" + "github.com/ava-labs/hypersdk/fees" + "github.com/ava-labs/hypersdk/pebble" "github.com/ava-labs/hypersdk/rpc" + "github.com/ava-labs/hypersdk/vm" + "github.com/ava-labs/hypersdk/workers" + + hconsts "github.com/ava-labs/hypersdk/consts" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" + hutils "github.com/ava-labs/hypersdk/utils" + ginkgo "github.com/onsi/ginkgo/v2" ) const genesisBalance uint64 = hconsts.MaxUint64 @@ -303,7 +303,7 @@ var _ = ginkgo.BeforeSuite(func() { gomega.Ω(err).Should(gomega.BeNil()) for _, alloc := range g.CustomAllocation { - bal, err := cli.Balance(context.Background(), alloc.Address, codec.Empty) + bal, err := cli.Balance(context.Background(), alloc.Address, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(bal).Should(gomega.Equal(alloc.Balance)) } diff --git a/heap/heap.go b/heap/heap.go index fe263011cd..b56f030d57 100644 --- a/heap/heap.go +++ b/heap/heap.go @@ -6,72 +6,74 @@ package heap import ( "cmp" "container/heap" + + "github.com/ava-labs/avalanchego/ids" ) // Heap[I,V] is used to track objects of [I] by [Val]. // // This data structure does not perform any synchronization and is not // safe to use concurrently without external locking. -type Heap[T comparable, I any, V cmp.Ordered] struct { - ih *innerHeap[T, I, V] +type Heap[I any, V cmp.Ordered] struct { + ih *innerHeap[I, V] } // New returns an instance of Heap[I,V] -func New[T comparable, I any, V cmp.Ordered](items int, isMinHeap bool) *Heap[T, I, V] { - return &Heap[T, I, V]{newInnerHeap[T, I, V](items, isMinHeap)} +func New[I any, V cmp.Ordered](items int, isMinHeap bool) *Heap[I, V] { + return &Heap[I, V]{newInnerHeap[I, V](items, isMinHeap)} } // Len returns the number of items in ih. -func (h *Heap[T, I, V]) Len() int { return h.ih.Len() } +func (h *Heap[I, V]) Len() int { return h.ih.Len() } // Get returns the entry in th associated with [id], and a bool if [id] was // found in th. -func (h *Heap[T, I, V]) Get(id T) (*Entry[T, I, V], bool) { +func (h *Heap[I, V]) Get(id ids.ID) (*Entry[I, V], bool) { return h.ih.Get(id) } // Has returns whether [id] is found in th. -func (h *Heap[T, I, V]) Has(id T) bool { +func (h *Heap[I, V]) Has(id ids.ID) bool { return h.ih.Has(id) } // Items returns all items in the heap in sorted order. You should not modify // the response. -func (h *Heap[T, I, V]) Items() []*Entry[T, I, V] { +func (h *Heap[I, V]) Items() []*Entry[I, V] { return h.ih.items } // Push can be called by external users instead of using `containers.heap`, // which makes using this heap less error-prone. -func (h *Heap[T, I, V]) Push(e *Entry[T, I, V]) { +func (h *Heap[I, V]) Push(e *Entry[I, V]) { heap.Push(h.ih, e) } // Pop can be called by external users to remove an object from the heap at // a specific index instead of using `containers.heap`, // which makes using this heap less error-prone. -func (h *Heap[T, I, V]) Pop() *Entry[T, I, V] { +func (h *Heap[I, V]) Pop() *Entry[I, V] { if len(h.ih.items) == 0 { return nil } - return heap.Pop(h.ih).(*Entry[T, I, V]) + return heap.Pop(h.ih).(*Entry[I, V]) } // Remove can be called by external users to remove an object from the heap at // a specific index instead of using `containers.heap`, // which makes using this heap less error-prone. -func (h *Heap[T, I, V]) Remove(index int) *Entry[T, I, V] { +func (h *Heap[I, V]) Remove(index int) *Entry[I, V] { if index >= len(h.ih.items) { return nil } - return heap.Remove(h.ih, index).(*Entry[T, I, V]) + return heap.Remove(h.ih, index).(*Entry[I, V]) } // First returns the first item in the heap. This is the smallest item in // a minHeap and the largest item in a maxHeap. // // If no items are in the heap, it will return nil. -func (h *Heap[T, I, V]) First() *Entry[T, I, V] { +func (h *Heap[I, V]) First() *Entry[I, V] { if len(h.ih.items) == 0 { return nil } diff --git a/heap/heap_test.go b/heap/heap_test.go index 5728dc94f4..20c8ed06a3 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -8,8 +8,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/stretchr/testify/require" - - "github.com/ava-labs/hypersdk/codec" ) type testItem[T comparable] struct { @@ -19,28 +17,28 @@ type testItem[T comparable] struct { func TestUnit64HeapPushPopMin(t *testing.T) { require := require.New(t) - minHeap := New[ids.ID, *testItem[ids.ID], uint64](0, true) + minHeap := New[*testItem[ids.ID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") mempoolItem1 := &testItem[ids.ID]{ids.GenerateTestID(), 10} mempoolItem2 := &testItem[ids.ID]{ids.GenerateTestID(), 7} mempoolItem3 := &testItem[ids.ID]{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[ids.ID, *testItem[ids.ID], uint64]{ + med := &Entry[*testItem[ids.ID], uint64]{ ID: mempoolItem1.id, Item: mempoolItem1, Val: mempoolItem1.value, Index: minHeap.Len(), } // Lesser UnitPrice - low := &Entry[ids.ID, *testItem[ids.ID], uint64]{ + low := &Entry[*testItem[ids.ID], uint64]{ ID: mempoolItem2.id, Item: mempoolItem2, Val: mempoolItem2.value, Index: minHeap.Len(), } // Greatest UnitPrice - high := &Entry[ids.ID, *testItem[ids.ID], uint64]{ + high := &Entry[*testItem[ids.ID], uint64]{ ID: mempoolItem3.id, Item: mempoolItem3, Val: mempoolItem3.value, @@ -69,7 +67,7 @@ func TestUnit64HeapPushPopMin(t *testing.T) { func TestUnit64HeapPushPopMax(t *testing.T) { require := require.New(t) - maxHeap := New[ids.ID, *testItem[ids.ID], uint64](0, false) + maxHeap := New[*testItem[ids.ID], uint64](0, false) require.Zero(maxHeap.Len(), "heap not initialized properly.") mempoolItem1 := &testItem[ids.ID]{ids.GenerateTestID(), 10} @@ -77,21 +75,21 @@ func TestUnit64HeapPushPopMax(t *testing.T) { mempoolItem3 := &testItem[ids.ID]{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[ids.ID, *testItem[ids.ID], uint64]{ + med := &Entry[*testItem[ids.ID], uint64]{ ID: mempoolItem1.id, Item: mempoolItem1, Val: mempoolItem1.value, Index: maxHeap.Len(), } // Lesser UnitPrice - low := &Entry[ids.ID, *testItem[ids.ID], uint64]{ + low := &Entry[*testItem[ids.ID], uint64]{ ID: mempoolItem2.id, Item: mempoolItem2, Val: mempoolItem2.value, Index: maxHeap.Len(), } // Greatest UnitPrice - high := &Entry[ids.ID, *testItem[ids.ID], uint64]{ + high := &Entry[*testItem[ids.ID], uint64]{ ID: mempoolItem3.id, Item: mempoolItem3, Val: mempoolItem3.value, @@ -121,10 +119,10 @@ func TestUnit64HeapPushPopMax(t *testing.T) { func TestUnit64HeapPushExists(t *testing.T) { // Push an item already in heap require := require.New(t) - minHeap := New[ids.ID, *testItem[ids.ID], uint64](0, true) + minHeap := New[*testItem[ids.ID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") mempoolItem := &testItem[ids.ID]{ids.GenerateTestID(), 10} - entry := &Entry[ids.ID, *testItem[ids.ID], uint64]{ + entry := &Entry[*testItem[ids.ID], uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -144,11 +142,11 @@ func TestUnit64HeapPushExists(t *testing.T) { func TestUnit64HeapGetID(t *testing.T) { // Push an item and grab its ID require := require.New(t) - minHeap := New[ids.ID, *testItem[ids.ID], uint64](0, true) + minHeap := New[*testItem[ids.ID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") mempoolItem := &testItem[ids.ID]{ids.GenerateTestID(), 10} - entry := &Entry[ids.ID, *testItem[ids.ID], uint64]{ + entry := &Entry[*testItem[ids.ID], uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -166,10 +164,10 @@ func TestUnit64HeapGetID(t *testing.T) { func TestUnit64HeapHasID(t *testing.T) { require := require.New(t) - minHeap := New[ids.ID, *testItem[ids.ID], uint64](0, true) + minHeap := New[*testItem[ids.ID], uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") mempoolItem := &testItem[ids.ID]{ids.GenerateTestID(), 10} - entry := &Entry[ids.ID, *testItem[ids.ID], uint64]{ + entry := &Entry[*testItem[ids.ID], uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -183,54 +181,3 @@ func TestUnit64HeapHasID(t *testing.T) { ok = minHeap.Has(mempoolItem.id) require.True(ok, "Entry was not found in heap.") } - -func TestUnit64HeapPushPopMinForLID(t *testing.T) { - require := require.New(t) - minHeap := New[codec.LID, *testItem[codec.LID], uint64](0, true) - require.Zero(minHeap.Len(), "heap not initialized properly.") - txID := ids.GenerateTestID() - mempoolItem1 := &testItem[codec.LID]{codec.CreateLID(0, txID), 10} - mempoolItem2 := &testItem[codec.LID]{codec.CreateLID(1, txID), 7} - mempoolItem3 := &testItem[codec.LID]{codec.CreateLID(2, txID), 15} - - // Middle UnitPrice - med := &Entry[codec.LID, *testItem[codec.LID], uint64]{ - ID: mempoolItem1.id, - Item: mempoolItem1, - Val: mempoolItem1.value, - Index: minHeap.Len(), - } - // Lesser UnitPrice - low := &Entry[codec.LID, *testItem[codec.LID], uint64]{ - ID: mempoolItem2.id, - Item: mempoolItem2, - Val: mempoolItem2.value, - Index: minHeap.Len(), - } - // Greatest UnitPrice - high := &Entry[codec.LID, *testItem[codec.LID], uint64]{ - ID: mempoolItem3.id, - Item: mempoolItem3, - Val: mempoolItem3.value, - Index: minHeap.Len(), - } - minHeap.Push(med) - minHeap.Push(low) - minHeap.Push(high) - // Added all three - require.Equal(3, minHeap.Len(), "Not pushed correctly.") - // Check if added to lookup table - ok := minHeap.Has(med.ID) - require.True(ok, "Item not found in lookup.") - ok = minHeap.Has(low.ID) - require.True(ok, "Item not found in lookup.") - ok = minHeap.Has(high.ID) - require.True(ok, "Item not found in lookup.") - // Pop and check popped correctly. Order should be 2, 1, 3 - popped := minHeap.Pop() - require.Equal(low, popped, "Incorrect item removed.") - popped = minHeap.Pop() - require.Equal(med, popped, "Incorrect item removed.") - popped = minHeap.Pop() - require.Equal(high, popped, "Incorrect item removed.") -} diff --git a/heap/inner_heap.go b/heap/inner_heap.go index 2e99366076..4c8058c0d7 100644 --- a/heap/inner_heap.go +++ b/heap/inner_heap.go @@ -7,41 +7,43 @@ import ( "cmp" "container/heap" "fmt" + + "github.com/ava-labs/avalanchego/ids" ) -var _ heap.Interface = (*innerHeap[any, any, uint64])(nil) +var _ heap.Interface = (*innerHeap[any, uint64])(nil) -type Entry[T comparable, I any, V cmp.Ordered] struct { - ID T // id of entry - Item I // associated item - Val V // Value to be prioritized +type Entry[I any, V cmp.Ordered] struct { + ID ids.ID // id of entry + Item I // associated item + Val V // Value to be prioritized Index int // Index of the entry in heap } -type innerHeap[T comparable, I any, V cmp.Ordered] struct { - isMinHeap bool // true for Min-Heap, false for Max-Heap - items []*Entry[T, I, V] // items in this heap - lookup map[T]*Entry[T, I, V] // ids in the heap mapping to an entry +type innerHeap[I any, V cmp.Ordered] struct { + isMinHeap bool // true for Min-Heap, false for Max-Heap + items []*Entry[I, V] // items in this heap + lookup map[ids.ID]*Entry[I, V] // ids in the heap mapping to an entry } -func newInnerHeap[T comparable, I any, V cmp.Ordered](items int, isMinHeap bool) *innerHeap[T, I, V] { - return &innerHeap[T, I, V]{ +func newInnerHeap[I any, V cmp.Ordered](items int, isMinHeap bool) *innerHeap[I, V] { + return &innerHeap[I, V]{ isMinHeap: isMinHeap, - items: make([]*Entry[T, I, V], 0, items), - lookup: make(map[T]*Entry[T, I, V], items), + items: make([]*Entry[I, V], 0, items), + lookup: make(map[ids.ID]*Entry[I, V], items), } } // Len returns the number of items in ih. -func (ih *innerHeap[T, I, V]) Len() int { return len(ih.items) } +func (ih *innerHeap[I, V]) Len() int { return len(ih.items) } // Less compares the priority of [i] and [j] based on th.isMinHeap. // // This should never be called by an external caller and is required to // confirm to `heap.Interface`. -func (ih *innerHeap[T, I, V]) Less(i, j int) bool { +func (ih *innerHeap[I, V]) Less(i, j int) bool { if ih.isMinHeap { return ih.items[i].Val < ih.items[j].Val } @@ -52,7 +54,7 @@ func (ih *innerHeap[T, I, V]) Less(i, j int) bool { // // This should never be called by an external caller and is required to // confirm to `heap.Interface`. -func (ih *innerHeap[T, I, V]) Swap(i, j int) { +func (ih *innerHeap[I, V]) Swap(i, j int) { ih.items[i], ih.items[j] = ih.items[j], ih.items[i] ih.items[i].Index = i ih.items[j].Index = j @@ -63,8 +65,8 @@ func (ih *innerHeap[T, I, V]) Swap(i, j int) { // // This should never be called by an external caller and is required to // confirm to `heap.Interface`. -func (ih *innerHeap[T, I, V]) Push(x any) { - entry, ok := x.(*Entry[T, I, V]) +func (ih *innerHeap[I, V]) Push(x any) { + entry, ok := x.(*Entry[I, V]) if !ok { panic(fmt.Errorf("unexpected %T, expected *Entry", x)) } @@ -80,7 +82,7 @@ func (ih *innerHeap[T, I, V]) Push(x any) { // // This should never be called by an external caller and is required to // confirm to `heap.Interface`. -func (ih *innerHeap[T, I, V]) Pop() any { +func (ih *innerHeap[I, V]) Pop() any { n := len(ih.items) item := ih.items[n-1] ih.items[n-1] = nil // avoid memory leak @@ -91,13 +93,13 @@ func (ih *innerHeap[T, I, V]) Pop() any { // Get returns the entry in th associated with [id], and a bool if [id] was // found in th. -func (ih *innerHeap[T, I, V]) Get(id T) (*Entry[T, I, V], bool) { +func (ih *innerHeap[I, V]) Get(id ids.ID) (*Entry[I, V], bool) { entry, ok := ih.lookup[id] return entry, ok } // Has returns whether [id] is found in th. -func (ih *innerHeap[T, I, V]) Has(id T) bool { +func (ih *innerHeap[I, V]) Has(id ids.ID) bool { _, has := ih.Get(id) return has } diff --git a/rpc/websocket_packer.go b/rpc/websocket_packer.go index cf0ff95a15..95b09fdeb3 100644 --- a/rpc/websocket_packer.go +++ b/rpc/websocket_packer.go @@ -65,7 +65,7 @@ func UnpackBlockMessage( // Could be a better place for these methods // Packs an accepted block message func PackAcceptedTxMessage(txID ids.ID, result *chain.Result) ([]byte, error) { - size := consts.IDLen + consts.BoolLen + result.Size() + size := ids.IDLen + consts.BoolLen + result.Size() p := codec.NewWriter(size, consts.MaxInt) p.PackID(txID) p.PackBool(false) @@ -78,7 +78,7 @@ func PackAcceptedTxMessage(txID ids.ID, result *chain.Result) ([]byte, error) { // Packs a removed block message func PackRemovedTxMessage(txID ids.ID, err error) ([]byte, error) { errString := err.Error() - size := consts.IDLen + consts.BoolLen + codec.StringLen(errString) + size := ids.IDLen + consts.BoolLen + codec.StringLen(errString) p := codec.NewWriter(size, consts.MaxInt) p.PackID(txID) p.PackBool(true) diff --git a/scripts/fix.lint.sh b/scripts/fix.lint.sh index 0d9a308764..33bc3d68e7 100755 --- a/scripts/fix.lint.sh +++ b/scripts/fix.lint.sh @@ -17,5 +17,9 @@ go install -v github.com/palantir/go-license@latest go-license --config=./license.yml -- **/*.go echo "gofumpt files" -go install mvdan.cc/gofumpt@latest +go install -v mvdan.cc/gofumpt@latest gofumpt -l -w . + +echo "gci files" +go install -v github.com/daixiang0/gci@v0.12.1 +gci write --skip-generated -s standard -s default -s blank -s dot -s "prefix(github.com/ava-labs/hypersdk)" -s alias --custom-order . diff --git a/vm/storage.go b/vm/storage.go index 9c4d36df0d..fc5beecb4a 100644 --- a/vm/storage.go +++ b/vm/storage.go @@ -48,7 +48,7 @@ func PrefixBlockKey(height uint64) []byte { } func PrefixBlockIDHeightKey(id ids.ID) []byte { - k := make([]byte, 1+consts.IDLen) + k := make([]byte, 1+ids.IDLen) k[0] = blockIDHeightPrefix copy(k[1:], id[:]) return k diff --git a/x/programs/cmd/simulator/cmd/key.go b/x/programs/cmd/simulator/cmd/key.go index c1eaff63d6..e29ef9ca1f 100644 --- a/x/programs/cmd/simulator/cmd/key.go +++ b/x/programs/cmd/simulator/cmd/key.go @@ -7,12 +7,11 @@ import ( "context" "fmt" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/spf13/cobra" - "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" ) diff --git a/x/programs/cmd/simulator/cmd/logger.go b/x/programs/cmd/simulator/cmd/logger.go index 2dd2f989fb..0a1f111718 100644 --- a/x/programs/cmd/simulator/cmd/logger.go +++ b/x/programs/cmd/simulator/cmd/logger.go @@ -11,9 +11,7 @@ import ( "sync" "github.com/ava-labs/avalanchego/utils/logging" - "go.uber.org/zap" - "gopkg.in/natefinch/lumberjack.v2" ) diff --git a/x/programs/cmd/simulator/cmd/plan.go b/x/programs/cmd/simulator/cmd/plan.go index ad40ac92ac..f933e6e281 100644 --- a/x/programs/cmd/simulator/cmd/plan.go +++ b/x/programs/cmd/simulator/cmd/plan.go @@ -15,19 +15,16 @@ import ( "strings" "time" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/actions" - - "github.com/ava-labs/hypersdk/x/programs/program" - - "github.com/spf13/cobra" - "go.uber.org/zap" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/spf13/cobra" + "go.uber.org/zap" "github.com/ava-labs/hypersdk/state" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/actions" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/utils" + "github.com/ava-labs/hypersdk/x/programs/program" ) type runCmd struct { diff --git a/x/programs/cmd/simulator/cmd/program.go b/x/programs/cmd/simulator/cmd/program.go index 3bb91804d7..2ec2343edd 100644 --- a/x/programs/cmd/simulator/cmd/program.go +++ b/x/programs/cmd/simulator/cmd/program.go @@ -8,15 +8,15 @@ import ( "fmt" "os" - "github.com/spf13/cobra" - + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/spf13/cobra" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/state" - hutils "github.com/ava-labs/hypersdk/utils" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/actions" + + hutils "github.com/ava-labs/hypersdk/utils" ) func newProgramCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { @@ -37,7 +37,7 @@ type programCreate struct { db *state.SimpleMutable keyName string path string - id codec.LID + id ids.ID } func newProgramCreateCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { @@ -96,27 +96,26 @@ func (p *programCreate) Run(ctx context.Context) (err error) { } // createProgram simulates a create program transaction and stores the program to disk. -func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string) (codec.LID, error) { +func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string) (ids.ID, error) { programBytes, err := os.ReadFile(path) if err != nil { - return codec.Empty, err + return ids.Empty, err } // simulate create program transaction - id, err := generateRandomID() + programID, err := generateRandomID() if err != nil { - return codec.Empty, err + return ids.Empty, err } - programID := codec.CreateLID(0, id) programCreateAction := actions.ProgramCreate{ Program: programBytes, } // execute the action - _, outputs, err := programCreateAction.Execute(ctx, nil, db, 0, codec.Empty, programID) + _, outputs, err := programCreateAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programID) if err != nil { - return codec.Empty, fmt.Errorf("program creation failed: %w", err) + return ids.Empty, fmt.Errorf("program creation failed: %w", err) } if len(outputs) > 0 { var results []string @@ -129,7 +128,7 @@ func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string // store program to disk only on success err = db.Commit(ctx) if err != nil { - return codec.Empty, err + return ids.Empty, err } return programID, nil @@ -142,13 +141,12 @@ func programExecuteFunc( callParams []actions.CallParam, function string, maxUnits uint64, -) (codec.LID, []int64, uint64, error) { +) (ids.ID, []int64, uint64, error) { // simulate create program transaction - programTxID, err := generateRandomID() + programActionID, err := generateRandomID() if err != nil { - return codec.Empty, nil, 0, err + return ids.Empty, nil, 0, err } - programActionID := codec.CreateLID(0, programTxID) programExecuteAction := actions.ProgramExecute{ Function: function, @@ -158,9 +156,9 @@ func programExecuteFunc( } // execute the action - _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.Empty, programActionID) + _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programActionID) if err != nil { - return codec.Empty, nil, 0, fmt.Errorf("program execution failed: %w", err) + return ids.Empty, nil, 0, fmt.Errorf("program execution failed: %w", err) } var result []int64 @@ -175,7 +173,7 @@ func programExecuteFunc( // store program to disk only on success err = db.Commit(ctx) if err != nil { - return codec.Empty, nil, 0, err + return ids.Empty, nil, 0, err } // get remaining balance from runtime meter diff --git a/x/programs/cmd/simulator/cmd/root.go b/x/programs/cmd/simulator/cmd/root.go index 5377c29683..aeb46a0e52 100644 --- a/x/programs/cmd/simulator/cmd/root.go +++ b/x/programs/cmd/simulator/cmd/root.go @@ -11,19 +11,17 @@ import ( "os" "path" - "github.com/spf13/cobra" - "go.uber.org/zap" - "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/spf13/cobra" + "go.uber.org/zap" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/vm" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/controller" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" ) diff --git a/x/programs/cmd/simulator/vm/actions/program_create.go b/x/programs/cmd/simulator/vm/actions/program_create.go index 4caae4e0dc..248d67f4b0 100644 --- a/x/programs/cmd/simulator/vm/actions/program_create.go +++ b/x/programs/cmd/simulator/vm/actions/program_create.go @@ -6,12 +6,13 @@ package actions import ( "context" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" ) @@ -21,7 +22,7 @@ type ProgramCreate struct { Program []byte `json:"program"` } -func (t *ProgramCreate) StateKeys(_ codec.Address, _ codec.LID) state.Keys { +func (t *ProgramCreate) StateKeys(_ codec.Address, _ ids.ID) state.Keys { return state.Keys{} } @@ -39,7 +40,7 @@ func (t *ProgramCreate) Execute( mu state.Mutable, _ int64, _ codec.Address, - actionID codec.LID, + actionID ids.ID, ) (uint64, [][]byte, error) { if len(t.Program) == 0 { return 1, nil, ErrOutputValueZero diff --git a/x/programs/cmd/simulator/vm/actions/program_execute.go b/x/programs/cmd/simulator/vm/actions/program_execute.go index 92cb43da7a..0559c5a598 100644 --- a/x/programs/cmd/simulator/vm/actions/program_execute.go +++ b/x/programs/cmd/simulator/vm/actions/program_execute.go @@ -6,24 +6,23 @@ package actions import ( "context" - "github.com/near/borsh-go" - - "github.com/ava-labs/hypersdk/crypto/ed25519" - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/host" - "github.com/ava-labs/hypersdk/x/programs/program" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/near/borsh-go" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" + "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/state" - - importProgram "github.com/ava-labs/hypersdk/x/programs/examples/imports/program" + "github.com/ava-labs/hypersdk/x/programs/engine" "github.com/ava-labs/hypersdk/x/programs/examples/imports/pstate" "github.com/ava-labs/hypersdk/x/programs/examples/storage" + "github.com/ava-labs/hypersdk/x/programs/host" + "github.com/ava-labs/hypersdk/x/programs/program" "github.com/ava-labs/hypersdk/x/programs/runtime" + + importProgram "github.com/ava-labs/hypersdk/x/programs/examples/imports/program" ) var _ chain.Action = (*ProgramExecute)(nil) @@ -42,7 +41,7 @@ func (*ProgramExecute) GetTypeID() uint8 { return programExecuteID } -func (t *ProgramExecute) StateKeys(actor codec.Address, _ codec.LID) state.Keys { +func (t *ProgramExecute) StateKeys(actor codec.Address, _ ids.ID) state.Keys { return state.Keys{} } @@ -56,7 +55,7 @@ func (t *ProgramExecute) Execute( mu state.Mutable, _ int64, actor codec.Address, - actionID codec.LID, + actionID ids.ID, ) (computeUnits uint64, output [][]byte, err error) { if len(t.Function) == 0 { return 1, nil, ErrOutputValueZero diff --git a/x/programs/cmd/simulator/vm/config/config.go b/x/programs/cmd/simulator/vm/config/config.go index 2a35565b07..3bb002e7b4 100644 --- a/x/programs/cmd/simulator/vm/config/config.go +++ b/x/programs/cmd/simulator/vm/config/config.go @@ -12,11 +12,11 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/profiler" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/config" "github.com/ava-labs/hypersdk/trace" "github.com/ava-labs/hypersdk/vm" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/version" ) diff --git a/x/programs/cmd/simulator/vm/consts/consts.go b/x/programs/cmd/simulator/vm/consts/consts.go index 67a3270180..fcf4225c9e 100644 --- a/x/programs/cmd/simulator/vm/consts/consts.go +++ b/x/programs/cmd/simulator/vm/consts/consts.go @@ -5,9 +5,9 @@ package consts import ( "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/consts" ) const ( @@ -20,7 +20,7 @@ const ( var ID ids.ID func init() { - b := make([]byte, consts.IDLen) + b := make([]byte, ids.IDLen) copy(b, []byte(Name)) vmID, err := ids.ToID(b) if err != nil { diff --git a/x/programs/cmd/simulator/vm/controller/controller.go b/x/programs/cmd/simulator/vm/controller/controller.go index 3a39cc5ed9..1da602eeeb 100644 --- a/x/programs/cmd/simulator/vm/controller/controller.go +++ b/x/programs/cmd/simulator/vm/controller/controller.go @@ -8,23 +8,24 @@ import ( "fmt" "net/http" - ametrics "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/snow" + "go.uber.org/zap" + "github.com/ava-labs/hypersdk/builder" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/gossiper" - hrpc "github.com/ava-labs/hypersdk/rpc" - hstorage "github.com/ava-labs/hypersdk/storage" "github.com/ava-labs/hypersdk/vm" - "go.uber.org/zap" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/config" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/rpc" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/version" + + ametrics "github.com/ava-labs/avalanchego/api/metrics" + hrpc "github.com/ava-labs/hypersdk/rpc" + hstorage "github.com/ava-labs/hypersdk/storage" ) var _ vm.Controller = (*Controller)(nil) diff --git a/x/programs/cmd/simulator/vm/controller/metrics.go b/x/programs/cmd/simulator/vm/controller/metrics.go index cd2d223ec3..8344372c29 100644 --- a/x/programs/cmd/simulator/vm/controller/metrics.go +++ b/x/programs/cmd/simulator/vm/controller/metrics.go @@ -4,11 +4,12 @@ package controller import ( - ametrics "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" + + ametrics "github.com/ava-labs/avalanchego/api/metrics" ) type metrics struct { diff --git a/x/programs/cmd/simulator/vm/controller/resolutions.go b/x/programs/cmd/simulator/vm/controller/resolutions.go index 995812d265..22621fce0f 100644 --- a/x/programs/cmd/simulator/vm/controller/resolutions.go +++ b/x/programs/cmd/simulator/vm/controller/resolutions.go @@ -9,8 +9,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/hypersdk/fees" + "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" ) diff --git a/x/programs/cmd/simulator/vm/genesis/genesis.go b/x/programs/cmd/simulator/vm/genesis/genesis.go index e730c1b526..fde9567cbb 100644 --- a/x/programs/cmd/simulator/vm/genesis/genesis.go +++ b/x/programs/cmd/simulator/vm/genesis/genesis.go @@ -11,12 +11,12 @@ import ( "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/x/merkledb" - hconsts "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/vm" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" + + hconsts "github.com/ava-labs/hypersdk/consts" ) var _ vm.Genesis = (*Genesis)(nil) diff --git a/x/programs/cmd/simulator/vm/genesis/rules.go b/x/programs/cmd/simulator/vm/genesis/rules.go index 1952a2e81e..203b8128da 100644 --- a/x/programs/cmd/simulator/vm/genesis/rules.go +++ b/x/programs/cmd/simulator/vm/genesis/rules.go @@ -5,6 +5,7 @@ package genesis import ( "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/fees" ) diff --git a/x/programs/cmd/simulator/vm/registry/registry.go b/x/programs/cmd/simulator/vm/registry/registry.go index 00df69e6a1..c7e1cfdb5b 100644 --- a/x/programs/cmd/simulator/vm/registry/registry.go +++ b/x/programs/cmd/simulator/vm/registry/registry.go @@ -5,9 +5,9 @@ package registry import ( "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/actions" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" ) diff --git a/x/programs/cmd/simulator/vm/rpc/dependencies.go b/x/programs/cmd/simulator/vm/rpc/dependencies.go index 2ede1dd20a..c50f2fc57f 100644 --- a/x/programs/cmd/simulator/vm/rpc/dependencies.go +++ b/x/programs/cmd/simulator/vm/rpc/dependencies.go @@ -8,8 +8,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" - "github.com/ava-labs/hypersdk/fees" + "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" ) diff --git a/x/programs/cmd/simulator/vm/rpc/jsonrpc_client.go b/x/programs/cmd/simulator/vm/rpc/jsonrpc_client.go index 3287cdfabc..8004d48b91 100644 --- a/x/programs/cmd/simulator/vm/rpc/jsonrpc_client.go +++ b/x/programs/cmd/simulator/vm/rpc/jsonrpc_client.go @@ -9,13 +9,13 @@ import ( "github.com/ava-labs/avalanchego/ids" + _ "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/registry" // ensure registry populated + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/requester" "github.com/ava-labs/hypersdk/rpc" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" - _ "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/registry" // ensure registry populated "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" ) diff --git a/x/programs/cmd/simulator/vm/rpc/jsonrpc_server.go b/x/programs/cmd/simulator/vm/rpc/jsonrpc_server.go index ae0f241d03..916bd5d9b8 100644 --- a/x/programs/cmd/simulator/vm/rpc/jsonrpc_server.go +++ b/x/programs/cmd/simulator/vm/rpc/jsonrpc_server.go @@ -9,7 +9,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/fees" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" ) diff --git a/x/programs/cmd/simulator/vm/storage/storage.go b/x/programs/cmd/simulator/vm/storage/storage.go index c973bf43d5..e3018cea7f 100644 --- a/x/programs/cmd/simulator/vm/storage/storage.go +++ b/x/programs/cmd/simulator/vm/storage/storage.go @@ -11,12 +11,10 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/x/programs/examples/storage" ) @@ -47,8 +45,8 @@ const ProgramChunks uint16 = 1 // Program // -func ProgramKey(id codec.LID) (k []byte) { - k = make([]byte, 1+codec.LIDLen) +func ProgramKey(id ids.ID) (k []byte) { + k = make([]byte, 1+ids.IDLen) k[0] = programPrefix copy(k[1:], id[:]) return @@ -58,7 +56,7 @@ func ProgramKey(id codec.LID) (k []byte) { func GetProgram( ctx context.Context, db state.Immutable, - programID codec.LID, + programID ids.ID, ) ( []byte, // program bytes bool, // exists @@ -79,7 +77,7 @@ func GetProgram( func SetProgram( ctx context.Context, mu state.Mutable, - programID codec.LID, + programID ids.ID, program []byte, ) error { return storage.SetProgram(ctx, mu, programID, program) @@ -165,7 +163,7 @@ func GetTransaction( // [txPrefix] + [txID] func txKey(id ids.ID) (k []byte) { - k = make([]byte, 1+consts.IDLen) + k = make([]byte, 1+ids.IDLen) k[0] = txPrefix copy(k[1:], id[:]) return diff --git a/x/programs/cmd/simulator/vm/utils/utils.go b/x/programs/cmd/simulator/vm/utils/utils.go index 40c877a373..f221ccbdb0 100644 --- a/x/programs/cmd/simulator/vm/utils/utils.go +++ b/x/programs/cmd/simulator/vm/utils/utils.go @@ -5,10 +5,10 @@ package utils import ( "github.com/ava-labs/avalanchego/utils/formatting/address" + "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto" "github.com/ava-labs/hypersdk/crypto/ed25519" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" ) diff --git a/x/programs/examples/imports/program/program.go b/x/programs/examples/imports/program/program.go index a64929dfd8..b7c051689f 100644 --- a/x/programs/examples/imports/program/program.go +++ b/x/programs/examples/imports/program/program.go @@ -14,7 +14,6 @@ import ( "github.com/near/borsh-go" "go.uber.org/zap" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/x/programs/engine" @@ -169,7 +168,7 @@ func (i *Import) callProgramFn(callContext program.Context) func(*wasmtime.Calle functionName := string(args.Function) res, err := rt.Call(ctx, functionName, program.Context{ - ProgramID: codec.LID(args.ProgramID), + ProgramID: ids.ID(args.ProgramID), // Actor: callContext.ProgramID, // OriginatingActor: callContext.OriginatingActor, }, params...) @@ -208,16 +207,15 @@ func getCallArgs(_ context.Context, memory *program.Memory, buffer []byte) ([]ui } func getProgramWasmBytes(log logging.Logger, db state.Immutable, idBytes []byte) ([]byte, error) { - id, err := ids.ToID(idBytes) + programID, err := ids.ToID(idBytes) if err != nil { return nil, err } - programID := codec.CreateLID(0, id) // get the program bytes from storage bytes, exists, err := storage.GetProgram(context.Background(), db, programID) if !exists { - log.Debug("key does not exist", zap.String("id", id.String())) + log.Debug("key does not exist", zap.Stringer("id", programID)) return nil, errors.New("unknown program") } if err != nil { diff --git a/x/programs/examples/storage/storage.go b/x/programs/examples/storage/storage.go index 110065aa52..2c64e3a3f6 100644 --- a/x/programs/examples/storage/storage.go +++ b/x/programs/examples/storage/storage.go @@ -8,8 +8,8 @@ import ( "errors" "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/state" ) @@ -19,10 +19,10 @@ const ( ) func ProgramPrefixKey(id []byte, key []byte) (k []byte) { - k = make([]byte, codec.LIDLen+1+len(key)) + k = make([]byte, ids.IDLen+1+len(key)) k[0] = programPrefix copy(k, id) - copy(k[codec.LIDLen:], (key)) + copy(k[ids.IDLen:], (key)) return } @@ -30,8 +30,8 @@ func ProgramPrefixKey(id []byte, key []byte) (k []byte) { // Program // -func ProgramKey(id codec.LID) (k []byte) { - k = make([]byte, 1+codec.LIDLen) +func ProgramKey(id ids.ID) (k []byte) { + k = make([]byte, 1+ids.IDLen) copy(k[1:], id[:]) return } @@ -40,7 +40,7 @@ func ProgramKey(id codec.LID) (k []byte) { func GetProgram( ctx context.Context, db state.Immutable, - programID codec.LID, + programID ids.ID, ) ( []byte, // program bytes bool, // exists @@ -61,7 +61,7 @@ func GetProgram( func SetProgram( ctx context.Context, mu state.Mutable, - programID codec.LID, + programID ids.ID, program []byte, ) error { k := ProgramKey(programID) diff --git a/x/programs/examples/token.go b/x/programs/examples/token.go index 48a1c3095f..7bc67c3bb7 100644 --- a/x/programs/examples/token.go +++ b/x/programs/examples/token.go @@ -7,11 +7,11 @@ import ( "context" "fmt" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/near/borsh-go" "go.uber.org/zap" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/x/programs/engine" @@ -34,7 +34,7 @@ const ( Balance ) -func NewToken(programID codec.LID, log logging.Logger, engine *engine.Engine, programBytes []byte, db state.Mutable, cfg *runtime.Config, imports host.SupportedImports, maxUnits uint64) *Token { +func NewToken(programID ids.ID, log logging.Logger, engine *engine.Engine, programBytes []byte, db state.Mutable, cfg *runtime.Config, imports host.SupportedImports, maxUnits uint64) *Token { return &Token{ programID: programID, log: log, @@ -55,7 +55,7 @@ type minter struct { } type Token struct { - programID codec.LID + programID ids.ID log logging.Logger programBytes []byte cfg *runtime.Config @@ -71,7 +71,7 @@ func (t *Token) Context() program.Context { } } -func (t *Token) ProgramID() codec.LID { +func (t *Token) ProgramID() ids.ID { return t.programID } @@ -386,7 +386,7 @@ func (t *Token) RunShort(ctx context.Context) error { return nil } -func (t *Token) GetUserBalanceFromState(ctx context.Context, programID codec.LID, userPublicKey ed25519.PublicKey) (res int64, err error) { +func (t *Token) GetUserBalanceFromState(ctx context.Context, programID ids.ID, userPublicKey ed25519.PublicKey) (res int64, err error) { key := storage.ProgramPrefixKey(programID[:], append([]byte{uint8(Balance)}, userPublicKey[:]...)) b, err := t.db.GetValue(ctx, key) if err != nil { diff --git a/x/programs/program/context.go b/x/programs/program/context.go index d72de09df1..cb3c62dc09 100644 --- a/x/programs/program/context.go +++ b/x/programs/program/context.go @@ -3,12 +3,10 @@ package program -import ( - "github.com/ava-labs/hypersdk/codec" -) +import "github.com/ava-labs/avalanchego/ids" type Context struct { - ProgramID codec.LID `json:"program"` + ProgramID ids.ID `json:"program"` // Actor [32]byte `json:"actor"` // OriginatingActor [32]byte `json:"originating_actor"` } diff --git a/x/programs/runtime/runtime_test.go b/x/programs/runtime/runtime_test.go index 60fb36a906..e8f16e4e18 100644 --- a/x/programs/runtime/runtime_test.go +++ b/x/programs/runtime/runtime_test.go @@ -13,7 +13,6 @@ import ( "github.com/bytecodealliance/wasmtime-go/v14" "github.com/stretchr/testify/require" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/x/programs/engine" "github.com/ava-labs/hypersdk/x/programs/host" "github.com/ava-labs/hypersdk/x/programs/program" @@ -45,7 +44,7 @@ func TestStop(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := codec.CreateLID(0, ids.GenerateTestID()) + id := ids.GenerateTestID() programContext := program.Context{ ProgramID: id, } @@ -91,7 +90,7 @@ func TestCallParams(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := codec.CreateLID(0, ids.GenerateTestID()) + id := ids.GenerateTestID() programContext := program.Context{ ProgramID: id, } @@ -138,7 +137,7 @@ func TestInfiniteLoop(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := codec.CreateLID(0, ids.GenerateTestID()) + id := ids.GenerateTestID() programContext := program.Context{ ProgramID: id, } @@ -177,7 +176,7 @@ func TestMetering(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := codec.CreateLID(0, ids.GenerateTestID()) + id := ids.GenerateTestID() programContext := program.Context{ ProgramID: id, } @@ -225,7 +224,7 @@ func TestMeterAfterStop(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := codec.CreateLID(0, ids.GenerateTestID()) + id := ids.GenerateTestID() programContext := program.Context{ ProgramID: id, } @@ -265,7 +264,7 @@ func TestLimitMaxMemory(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := codec.CreateLID(0, ids.GenerateTestID()) + id := ids.GenerateTestID() programContext := program.Context{ ProgramID: id, } @@ -296,7 +295,7 @@ func TestLimitMaxMemoryGrow(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := codec.CreateLID(0, ids.GenerateTestID()) + id := ids.GenerateTestID() programContext := program.Context{ ProgramID: id, } @@ -336,7 +335,7 @@ func TestWriteExceedsLimitMaxMemory(t *testing.T) { eng := engine.New(engine.NewConfig()) runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := codec.CreateLID(0, ids.GenerateTestID()) + id := ids.GenerateTestID() programContext := program.Context{ ProgramID: id, } @@ -380,7 +379,7 @@ func TestWithMaxWasmStack(t *testing.T) { cfg := NewConfig() runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - id := codec.CreateLID(0, ids.GenerateTestID()) + id := ids.GenerateTestID() programContext := program.Context{ ProgramID: id, } From a915282c08c04b0fe15e0a49c56299dd493a07e0 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Tue, 21 May 2024 14:30:15 -0400 Subject: [PATCH 76/78] multi-action nits (#922) * add key validity check to Keys set * cleanup state key validity checking * reviewed chain/transaction * cli changes * add multi-send example to morpheus * cleanup tokenvm * remove heap casting * fix program execute * fix return * cleanup var names * unify error * update fetcher tests * remove unnecessary conversion --- chain/transaction.go | 18 ++----- cli/spam.go | 4 +- .../cmd/morpheus-cli/cmd/resolutions.go | 4 +- examples/morpheusvm/genesis/genesis.go | 2 +- .../tests/integration/integration_test.go | 23 +++++++-- .../tokenvm/cmd/token-cli/cmd/resolutions.go | 8 +-- examples/tokenvm/genesis/genesis.go | 2 +- .../tests/integration/integration_test.go | 2 + examples/tokenvm/tests/load/load_test.go | 8 +-- fetcher/fetcher_test.go | 12 +++-- heap/heap_test.go | 50 +++++++++---------- state/keys.go | 17 +++++-- state/keys_test.go | 10 +++- x/programs/cmd/simulator/cmd/program.go | 6 +-- .../simulator/vm/actions/program_execute.go | 13 +++-- x/programs/examples/token.go | 2 +- 16 files changed, 102 insertions(+), 79 deletions(-) diff --git a/chain/transaction.go b/chain/transaction.go index b71101f6fb..5b983fcaf2 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -111,22 +111,16 @@ func (t *Transaction) StateKeys(sm StateManager) (state.Keys, error) { // Verify the formatting of state keys passed by the controller for i, action := range t.Actions { - actionKeys := action.StateKeys(t.Auth.Actor(), CreateActionID(t.ID(), uint8(i))) - for k, v := range actionKeys { - if !keys.Valid(k) { + for k, v := range action.StateKeys(t.Auth.Actor(), CreateActionID(t.ID(), uint8(i))) { + if !stateKeys.Add(k, v) { return nil, ErrInvalidKeyValue } - // [Add] will take the union of key permissions - stateKeys.Add(k, v) } } - sponsorKeys := sm.SponsorStateKeys(t.Auth.Sponsor()) - for k, v := range sponsorKeys { - if !keys.Valid(k) { + for k, v := range sm.SponsorStateKeys(t.Auth.Sponsor()) { + if !stateKeys.Add(k, v) { return nil, ErrInvalidKeyValue } - // [Add] will take the union of key permissions - stateKeys.Add(k, v) } // Cache keys if called again @@ -378,7 +372,6 @@ func (t *Transaction) Execute( // so we don't need to check for pre-existing values. maxChunks, ok := keys.MaxChunks([]byte(key)) if !ok { - // TODO: is this already checked in parse? return handleRevert(ErrInvalidKeyValue) } writes[key] = maxChunks @@ -416,9 +409,6 @@ func (t *Transaction) Execute( return handleRevert(err) } used := fees.Dimensions{uint64(t.Size()), computeUnits, readUnits, allocateUnits, writeUnits} - if err != nil { - return handleRevert(err) - } // Check to see if the units consumed are greater than the max units // diff --git a/cli/spam.go b/cli/spam.go index ae6fcc9924..99041f1f68 100644 --- a/cli/spam.go +++ b/cli/spam.go @@ -186,7 +186,7 @@ func (h *Handler) Spam( } if !result.Success { // Should never happen - return fmt.Errorf("%w: %s", ErrTxFailed, result.Outputs) + return fmt.Errorf("%w: %s", ErrTxFailed, result.Error) } } var recipientFunc func() (*PrivateKey, error) @@ -446,7 +446,7 @@ func (h *Handler) Spam( } if !result.Success { // Should never happen - return fmt.Errorf("%w: %s", ErrTxFailed, result.Outputs) + return fmt.Errorf("%w: %s", ErrTxFailed, result.Error) } } utils.Outf( diff --git a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go index 0465ca2849..dd19989ea8 100644 --- a/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go +++ b/examples/morpheusvm/cmd/morpheus-cli/cmd/resolutions.go @@ -62,11 +62,11 @@ func handleTx(tx *chain.Transaction, result *chain.Result) { actor := tx.Auth.Actor() if !result.Success { utils.Outf( - "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary:{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}error:{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", "❌", tx.ID(), codec.MustAddressBech32(consts.HRP, actor), - string(result.Error), // revert error + result.Error, float64(result.Fee)/float64(tx.Base.MaxFee)*100, utils.FormatBalance(result.Fee, consts.Decimals), consts.Symbol, diff --git a/examples/morpheusvm/genesis/genesis.go b/examples/morpheusvm/genesis/genesis.go index a2d6cf9edb..25fc3a6eb7 100644 --- a/examples/morpheusvm/genesis/genesis.go +++ b/examples/morpheusvm/genesis/genesis.go @@ -78,7 +78,7 @@ func Default() *Genesis { // Tx Parameters ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms - MaxActionsPerTx: 1, + MaxActionsPerTx: 16, MaxOutputsPerAction: 1, // Tx Fee Compute Parameters diff --git a/examples/morpheusvm/tests/integration/integration_test.go b/examples/morpheusvm/tests/integration/integration_test.go index 45f61ce96a..2b825d7b6c 100644 --- a/examples/morpheusvm/tests/integration/integration_test.go +++ b/examples/morpheusvm/tests/integration/integration_test.go @@ -926,16 +926,25 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(balance).Should(gomega.Equal(uint64(2000))) }) - ginkgo.By("send back to ed25519", func() { + ginkgo.By("send back to ed25519 (in separate actions)", func() { + bbalance, err := instances[0].lcli.Balance(context.TODO(), codec.MustAddressBech32(lconsts.HRP, addr)) + gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instances[0].lcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), parser, - []chain.Action{&actions.Transfer{ - To: addr, - Value: 100, - }}, + []chain.Action{ + &actions.Transfer{ + To: addr, + Value: 75, + }, + &actions.Transfer{ + To: addr, + Value: 25, + }, + }, r1factory, ) gomega.Ω(err).Should(gomega.BeNil()) @@ -944,6 +953,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { results := accept(false) gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) + + balance, err := instances[0].lcli.Balance(context.TODO(), codec.MustAddressBech32(lconsts.HRP, addr)) + gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(balance).Should(gomega.Equal(bbalance + 100)) }) }) }) diff --git a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go index b4720e3b0c..a474591351 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/resolutions.go +++ b/examples/tokenvm/cmd/token-cli/cmd/resolutions.go @@ -61,11 +61,11 @@ func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result actor := tx.Auth.Actor() if !result.Success { utils.Outf( - "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}summary:{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", + "%s {{yellow}}%s{{/}} {{yellow}}actor:{{/}} %s {{yellow}}error:{{/}} [%s] {{yellow}}fee (max %.2f%%):{{/}} %s %s {{yellow}}consumed:{{/}} [%s]\n", "❌", tx.ID(), codec.MustAddressBech32(tconsts.HRP, actor), - string(result.Error), + result.Error, float64(result.Fee)/float64(tx.Base.MaxFee)*100, utils.FormatBalance(result.Fee, tconsts.Decimals), tconsts.Symbol, @@ -75,11 +75,11 @@ func handleTx(c *trpc.JSONRPCClient, tx *chain.Transaction, result *chain.Result } for i, act := range tx.Actions { - assetID := chain.CreateActionID(tx.ID(), uint8(i)) + actionID := chain.CreateActionID(tx.ID(), uint8(i)) var summaryStr string switch action := act.(type) { case *actions.CreateAsset: - summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", assetID, action.Symbol, action.Decimals, action.Metadata) + summaryStr = fmt.Sprintf("assetID: %s symbol: %s decimals: %d metadata: %s", actionID, action.Symbol, action.Decimals, action.Metadata) case *actions.MintAsset: _, symbol, decimals, _, _, _, err := c.Asset(context.TODO(), action.Asset, true) if err != nil { diff --git a/examples/tokenvm/genesis/genesis.go b/examples/tokenvm/genesis/genesis.go index a5e2af8f17..c2c4b2128d 100644 --- a/examples/tokenvm/genesis/genesis.go +++ b/examples/tokenvm/genesis/genesis.go @@ -79,7 +79,7 @@ func Default() *Genesis { // Tx Parameters ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms - MaxActionsPerTx: 10, + MaxActionsPerTx: 16, MaxOutputsPerAction: 1, // Tx Fee Compute Parameters diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index bedefb9557..f29d93ad41 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -1671,6 +1671,8 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(orders).Should(gomega.HaveLen(0)) }) + // Use new instance to make balance checks easier (note, instances are in different + // states and would never agree) ginkgo.It("transfer to multiple accounts in a single tx", func() { parser, err := instances[3].tcli.Parser(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) diff --git a/examples/tokenvm/tests/load/load_test.go b/examples/tokenvm/tests/load/load_test.go index f847166c5e..f15f6096ce 100644 --- a/examples/tokenvm/tests/load/load_test.go +++ b/examples/tokenvm/tests/load/load_test.go @@ -403,13 +403,7 @@ var _ = ginkgo.Describe("load tests vm", func() { for _, result := range blk.Results() { if !result.Success { unitPrices, _ := instances[0].cli.UnitPrices(context.Background(), false) - var resultOutputs string - for i := 0; i < len(result.Outputs); i++ { - for j := 0; j < len(result.Outputs[i]); j++ { - resultOutputs += fmt.Sprintf(" %s", result.Outputs[i][j]) - } - } - fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "output:", resultOutputs) + fmt.Println("tx failed", "unit prices:", unitPrices, "consumed:", result.Consumed, "fee:", result.Fee, "error:", result.Error) } gomega.Ω(result.Success).Should(gomega.BeTrue()) } diff --git a/fetcher/fetcher_test.go b/fetcher/fetcher_test.go index b8bf42bce1..e599f64e70 100644 --- a/fetcher/fetcher_test.go +++ b/fetcher/fetcher_test.go @@ -17,6 +17,8 @@ import ( "github.com/ava-labs/hypersdk/state" ) +const keyBase = "blah" // key must be long enough to be valid + // Setup parent immutable state var _ state.Immutable = &testDB{} @@ -33,7 +35,7 @@ func newTestDB() *testDB { func newTestDBWithValue() *testDB { db := testDB{storage: make(map[string][]byte)} for i := 0; i < 100; i += 2 { - db.storage[strconv.Itoa(i)] = []byte("value") + db.storage[keyBase+strconv.Itoa(i)] = []byte("value") // key must be long enough to be valid } return &db } @@ -108,7 +110,7 @@ func TestFetchSameKeys(t *testing.T) { stateKeys := make(state.Keys, (i + 1)) for k := 0; k < i+1; k++ { // Generate the same keys - stateKeys.Add(strconv.Itoa(k), state.Read) + stateKeys.Add(keyBase+strconv.Itoa(k), state.Read) } txID := ids.GenerateTestID() // We are fetching the same keys, so we should @@ -149,7 +151,7 @@ func TestFetchSameKeysSlow(t *testing.T) { stateKeys := make(state.Keys, (i + 1)) for k := 0; k < i+1; k++ { // Generate the same keys - stateKeys.Add(strconv.Itoa(k), state.Read) + stateKeys.Add(keyBase+strconv.Itoa(k), state.Read) } txID := ids.GenerateTestID() @@ -199,7 +201,7 @@ func TestFetchKeysWithValues(t *testing.T) { stateKeys := make(state.Keys, (i + 1)) for k := 0; k < i+1; k++ { // Generate the same keys - stateKeys.Add(strconv.Itoa(k), state.Read) + stateKeys.Add(keyBase+strconv.Itoa(k), state.Read) } txID := ids.GenerateTestID() require.NoError(f.Fetch(ctx, txID, stateKeys)) @@ -238,7 +240,7 @@ func TestFetcherStop(t *testing.T) { stateKeys := make(state.Keys, (i + 1)) for k := 0; k < i+1; k++ { // Generate the same keys - stateKeys.Add(strconv.Itoa(k), state.Read) + stateKeys.Add(keyBase+strconv.Itoa(k), state.Read) } txID := ids.GenerateTestID() err := f.Fetch(ctx, txID, stateKeys) diff --git a/heap/heap_test.go b/heap/heap_test.go index 20c8ed06a3..bdf3198411 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -10,35 +10,35 @@ import ( "github.com/stretchr/testify/require" ) -type testItem[T comparable] struct { - id T +type testItem struct { + id ids.ID value uint64 } func TestUnit64HeapPushPopMin(t *testing.T) { require := require.New(t) - minHeap := New[*testItem[ids.ID], uint64](0, true) + minHeap := New[*testItem, uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") - mempoolItem1 := &testItem[ids.ID]{ids.GenerateTestID(), 10} - mempoolItem2 := &testItem[ids.ID]{ids.GenerateTestID(), 7} - mempoolItem3 := &testItem[ids.ID]{ids.GenerateTestID(), 15} + mempoolItem1 := &testItem{ids.GenerateTestID(), 10} + mempoolItem2 := &testItem{ids.GenerateTestID(), 7} + mempoolItem3 := &testItem{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[*testItem[ids.ID], uint64]{ + med := &Entry[*testItem, uint64]{ ID: mempoolItem1.id, Item: mempoolItem1, Val: mempoolItem1.value, Index: minHeap.Len(), } // Lesser UnitPrice - low := &Entry[*testItem[ids.ID], uint64]{ + low := &Entry[*testItem, uint64]{ ID: mempoolItem2.id, Item: mempoolItem2, Val: mempoolItem2.value, Index: minHeap.Len(), } // Greatest UnitPrice - high := &Entry[*testItem[ids.ID], uint64]{ + high := &Entry[*testItem, uint64]{ ID: mempoolItem3.id, Item: mempoolItem3, Val: mempoolItem3.value, @@ -67,29 +67,29 @@ func TestUnit64HeapPushPopMin(t *testing.T) { func TestUnit64HeapPushPopMax(t *testing.T) { require := require.New(t) - maxHeap := New[*testItem[ids.ID], uint64](0, false) + maxHeap := New[*testItem, uint64](0, false) require.Zero(maxHeap.Len(), "heap not initialized properly.") - mempoolItem1 := &testItem[ids.ID]{ids.GenerateTestID(), 10} - mempoolItem2 := &testItem[ids.ID]{ids.GenerateTestID(), 7} - mempoolItem3 := &testItem[ids.ID]{ids.GenerateTestID(), 15} + mempoolItem1 := &testItem{ids.GenerateTestID(), 10} + mempoolItem2 := &testItem{ids.GenerateTestID(), 7} + mempoolItem3 := &testItem{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[*testItem[ids.ID], uint64]{ + med := &Entry[*testItem, uint64]{ ID: mempoolItem1.id, Item: mempoolItem1, Val: mempoolItem1.value, Index: maxHeap.Len(), } // Lesser UnitPrice - low := &Entry[*testItem[ids.ID], uint64]{ + low := &Entry[*testItem, uint64]{ ID: mempoolItem2.id, Item: mempoolItem2, Val: mempoolItem2.value, Index: maxHeap.Len(), } // Greatest UnitPrice - high := &Entry[*testItem[ids.ID], uint64]{ + high := &Entry[*testItem, uint64]{ ID: mempoolItem3.id, Item: mempoolItem3, Val: mempoolItem3.value, @@ -119,10 +119,10 @@ func TestUnit64HeapPushPopMax(t *testing.T) { func TestUnit64HeapPushExists(t *testing.T) { // Push an item already in heap require := require.New(t) - minHeap := New[*testItem[ids.ID], uint64](0, true) + minHeap := New[*testItem, uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") - mempoolItem := &testItem[ids.ID]{ids.GenerateTestID(), 10} - entry := &Entry[*testItem[ids.ID], uint64]{ + mempoolItem := &testItem{ids.GenerateTestID(), 10} + entry := &Entry[*testItem, uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -142,11 +142,11 @@ func TestUnit64HeapPushExists(t *testing.T) { func TestUnit64HeapGetID(t *testing.T) { // Push an item and grab its ID require := require.New(t) - minHeap := New[*testItem[ids.ID], uint64](0, true) + minHeap := New[*testItem, uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") - mempoolItem := &testItem[ids.ID]{ids.GenerateTestID(), 10} - entry := &Entry[*testItem[ids.ID], uint64]{ + mempoolItem := &testItem{ids.GenerateTestID(), 10} + entry := &Entry[*testItem, uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, @@ -164,10 +164,10 @@ func TestUnit64HeapGetID(t *testing.T) { func TestUnit64HeapHasID(t *testing.T) { require := require.New(t) - minHeap := New[*testItem[ids.ID], uint64](0, true) + minHeap := New[*testItem, uint64](0, true) require.Zero(minHeap.Len(), "heap not initialized properly.") - mempoolItem := &testItem[ids.ID]{ids.GenerateTestID(), 10} - entry := &Entry[*testItem[ids.ID], uint64]{ + mempoolItem := &testItem{ids.GenerateTestID(), 10} + entry := &Entry[*testItem, uint64]{ ID: mempoolItem.id, Item: mempoolItem, Val: mempoolItem.value, diff --git a/state/keys.go b/state/keys.go index de0ff525ad..e4a3e32545 100644 --- a/state/keys.go +++ b/state/keys.go @@ -3,6 +3,8 @@ package state +import "github.com/ava-labs/hypersdk/keys" + const ( Read Permissions = 1 Allocate = 1<<1 | Read @@ -20,12 +22,19 @@ type Keys map[string]Permissions // All acceptable permission options type Permissions byte -// Transactions are expected to use this to prevent -// overriding of key permissions -func (k Keys) Add(name string, permission Permissions) { +// Add verifies that a key is well-formatted and adds it to the conflict set. +// +// If the key already exists, the permissions are unioned. +func (k Keys) Add(key string, permission Permissions) bool { + // If a key is not properly formatted, it cannot be added. + if !keys.Valid(key) { + return false + } + // Transaction's permissions are the union of all // state keys from both Actions and Auth - k[name] |= permission + k[key] |= permission + return true } // Has returns true if [p] has all the permissions that are contained in require diff --git a/state/keys_test.go b/state/keys_test.go index dca9c836f3..9938be72a8 100644 --- a/state/keys_test.go +++ b/state/keys_test.go @@ -31,7 +31,7 @@ func TestAddPermissions(t *testing.T) { t.Run(tt.name, func(t *testing.T) { require := require.New(t) keys := make(Keys) - keys.Add(tt.key, tt.permission) + require.True(keys.Add(tt.key, tt.permission)) // Check permission perm := keys[tt.key] @@ -70,7 +70,7 @@ func TestUnionPermissions(t *testing.T) { keys := Keys{tt.key: tt.permission1} // Add new permission this should test the Union part - keys.Add(tt.key, tt.permission2) + require.True(keys.Add(tt.key, tt.permission2)) // Check updated positions perm := keys[tt.key] @@ -78,3 +78,9 @@ func TestUnionPermissions(t *testing.T) { }) } } + +func TestMalformedKey(t *testing.T) { + require := require.New(t) + keys := make(Keys) + require.False(keys.Add("", Read)) +} diff --git a/x/programs/cmd/simulator/cmd/program.go b/x/programs/cmd/simulator/cmd/program.go index 2ec2343edd..2a10a24141 100644 --- a/x/programs/cmd/simulator/cmd/program.go +++ b/x/programs/cmd/simulator/cmd/program.go @@ -143,7 +143,7 @@ func programExecuteFunc( maxUnits uint64, ) (ids.ID, []int64, uint64, error) { // simulate create program transaction - programActionID, err := generateRandomID() + actionID, err := generateRandomID() if err != nil { return ids.Empty, nil, 0, err } @@ -156,7 +156,7 @@ func programExecuteFunc( } // execute the action - _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programActionID) + _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, actionID) if err != nil { return ids.Empty, nil, 0, fmt.Errorf("program execution failed: %w", err) } @@ -178,5 +178,5 @@ func programExecuteFunc( // get remaining balance from runtime meter balance, err := programExecuteAction.GetBalance() - return programActionID, result, balance, err + return actionID, result, balance, err } diff --git a/x/programs/cmd/simulator/vm/actions/program_execute.go b/x/programs/cmd/simulator/vm/actions/program_execute.go index 0559c5a598..9f4c714fcb 100644 --- a/x/programs/cmd/simulator/vm/actions/program_execute.go +++ b/x/programs/cmd/simulator/vm/actions/program_execute.go @@ -5,6 +5,7 @@ package actions import ( "context" + "fmt" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" @@ -55,7 +56,7 @@ func (t *ProgramExecute) Execute( mu state.Mutable, _ int64, actor codec.Address, - actionID ids.ID, + _ ids.ID, ) (computeUnits uint64, output [][]byte, err error) { if len(t.Function) == 0 { return 1, nil, ErrOutputValueZero @@ -64,7 +65,13 @@ func (t *ProgramExecute) Execute( return 1, nil, ErrOutputValueZero } - programBytes, _, err := storage.GetProgram(ctx, mu, actionID) + programID, ok := t.Params[0].Value.(ids.ID) + if !ok { + return 1, nil, fmt.Errorf("invalid call param: must be ID") + } + + // TODO: take fee out of balance? + programBytes, _, err := storage.GetProgram(ctx, mu, programID) if err != nil { return 1, nil, err } @@ -89,7 +96,7 @@ func (t *ProgramExecute) Execute( return pstate.New(logging.NoLog{}, mu) }) callContext := program.Context{ - ProgramID: actionID, + ProgramID: programID, // Actor: [32]byte(actor[1:]), // OriginatingActor: [32]byte(actor[1:]) } diff --git a/x/programs/examples/token.go b/x/programs/examples/token.go index 7bc67c3bb7..6617e6d54f 100644 --- a/x/programs/examples/token.go +++ b/x/programs/examples/token.go @@ -386,7 +386,7 @@ func (t *Token) RunShort(ctx context.Context) error { return nil } -func (t *Token) GetUserBalanceFromState(ctx context.Context, programID ids.ID, userPublicKey ed25519.PublicKey) (res int64, err error) { +func (t *Token) GetUserBalanceFromState(ctx context.Context, programID ids.ID, userPublicKey ed25519.PublicKey) (res uint32, err error) { key := storage.ProgramPrefixKey(programID[:], append([]byte{uint8(Balance)}, userPublicKey[:]...)) b, err := t.db.GetValue(ctx, key) if err != nil { From dec026a6ce6f5821f84f83b5776f6918e5dc513b Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Tue, 21 May 2024 20:24:39 -0400 Subject: [PATCH 77/78] update programs code --- x/programs/cmd/simulator/Cargo.toml | 7 +- x/programs/cmd/simulator/cmd/config.go | 28 +- x/programs/cmd/simulator/cmd/interpreter.go | 31 ++ x/programs/cmd/simulator/cmd/key.go | 70 +-- x/programs/cmd/simulator/cmd/logger.go | 8 +- x/programs/cmd/simulator/cmd/plan.go | 186 ++++---- x/programs/cmd/simulator/cmd/program.go | 142 +++---- x/programs/cmd/simulator/cmd/root.go | 214 +++++++--- x/programs/cmd/simulator/go.mod | 18 +- x/programs/cmd/simulator/go.sum | 40 +- x/programs/cmd/simulator/simulator.go | 9 +- x/programs/cmd/simulator/src/lib.rs | 178 ++++---- .../cmd/simulator/vm/actions/outputs.go | 4 +- .../simulator/vm/actions/program_create.go | 15 +- .../simulator/vm/actions/program_execute.go | 60 +-- x/programs/cmd/simulator/vm/config/config.go | 2 +- x/programs/cmd/simulator/vm/consts/consts.go | 4 +- .../cmd/simulator/vm/controller/controller.go | 11 +- .../cmd/simulator/vm/controller/metrics.go | 3 +- .../simulator/vm/controller/resolutions.go | 2 +- .../cmd/simulator/vm/genesis/genesis.go | 12 +- x/programs/cmd/simulator/vm/genesis/rules.go | 9 - .../cmd/simulator/vm/registry/registry.go | 2 +- .../cmd/simulator/vm/rpc/dependencies.go | 2 +- .../cmd/simulator/vm/rpc/jsonrpc_client.go | 4 +- .../cmd/simulator/vm/rpc/jsonrpc_server.go | 1 + .../cmd/simulator/vm/storage/storage.go | 5 +- x/programs/cmd/simulator/vm/utils/utils.go | 2 +- x/programs/engine/config.go | 320 -------------- x/programs/engine/engine.go | 72 ---- x/programs/engine/engine_test.go | 41 -- x/programs/engine/store.go | 187 -------- x/programs/engine/store_test.go | 77 ---- .../examples/imports/program/program.go | 226 ---------- x/programs/examples/imports/pstate/pstate.go | 262 ------------ x/programs/examples/imports/wrap/wrap.go | 66 --- x/programs/examples/storage/storage.go | 69 --- x/programs/examples/token.go | 400 ------------------ x/programs/examples/utils.go | 68 --- x/programs/host/dependencies.go | 13 - x/programs/host/imports.go | 67 --- x/programs/host/link.go | 153 ------- x/programs/host/link_test.go | 155 ------- x/programs/program/caller.go | 75 ---- x/programs/program/consts.go | 16 - x/programs/program/context.go | 12 - x/programs/program/dependencies.go | 16 - x/programs/program/errors.go | 85 ---- x/programs/program/export.go | 54 --- x/programs/program/function.go | 110 ----- x/programs/program/memory.go | 168 -------- x/programs/program/memory_test.go | 67 --- x/programs/program/types/types.go | 59 --- x/programs/program/utils.go | 16 - x/programs/runtime/config.go | 332 +++++++++++++-- x/programs/runtime/config_test.go | 24 -- x/programs/runtime/dependencies.go | 28 -- x/programs/runtime/import_log_debug.go | 25 ++ x/programs/runtime/import_log_release.go | 17 + x/programs/runtime/import_program.go | 68 +++ x/programs/runtime/import_program_test.go | 49 +++ x/programs/runtime/import_state.go | 81 ++++ x/programs/runtime/imports.go | 136 ++++++ x/programs/runtime/imports_test.go | 60 +++ x/programs/runtime/instance.go | 80 ---- x/programs/runtime/program.go | 122 ++++++ x/programs/runtime/runtime.go | 153 +++---- x/programs/runtime/runtime_test.go | 393 ++--------------- x/programs/rust/examples/counter/Cargo.toml | 13 +- x/programs/rust/examples/counter/src/lib.rs | 48 +-- x/programs/rust/examples/token/Cargo.toml | 15 +- x/programs/rust/examples/token/src/lib.rs | 145 ++++++- x/programs/rust/sdk-macros/Cargo.toml | 17 + .../{sdk_macros => sdk-macros}/src/lib.rs | 78 ++-- .../tests/compile.rs | 0 .../tests/ui/first-param.rs | 0 .../tests/ui/first-param.stderr | 0 .../tests/ui/not-pub.rs | 0 .../tests/ui/not-pub.stderr | 0 .../tests/ui/receiver.rs | 0 .../tests/ui/receiver.stderr | 0 .../tests/ui/user-defined-context-type.rs | 0 .../tests/ui/user-defined-context-type.stderr | 0 x/programs/rust/sdk_macros/Cargo.toml | 17 - .../tests/ui/ignore-second-param.rs | 8 - .../tests/ui/ignore-second-param.stderr | 5 - x/programs/rust/sdk_macros/tests/ui/wild.rs | 8 - .../rust/sdk_macros/tests/ui/wild.stderr | 5 - x/programs/rust/wasmlanche-sdk/Cargo.toml | 18 +- x/programs/rust/wasmlanche-sdk/src/lib.rs | 5 +- x/programs/rust/wasmlanche-sdk/src/logging.rs | 49 +++ x/programs/rust/wasmlanche-sdk/src/program.rs | 26 +- x/programs/rust/wasmlanche-sdk/src/state.rs | 24 +- x/programs/rust/wasmlanche-sdk/src/types.rs | 8 +- .../wasmlanche-sdk/tests/public_function.rs | 83 +++- .../tests/test-crate/Cargo.toml | 9 +- .../tests/test-crate/src/lib.rs | 4 +- .../test/programs/call_program/Cargo.toml | 14 + .../test/programs/call_program/src/lib.rs | 14 + .../programs/return_complex_type/Cargo.toml | 14 + .../programs/return_complex_type/src/lib.rs | 17 + x/programs/test/programs/simple/Cargo.toml | 14 + x/programs/test/programs/simple/src/lib.rs | 6 + x/programs/test/testdb.go | 41 ++ x/programs/test/util.go | 43 ++ x/programs/tests/fixture/memory.wasm | Bin 24059 -> 0 bytes x/programs/tests/utils.go | 23 - 107 files changed, 2028 insertions(+), 4234 deletions(-) create mode 100644 x/programs/cmd/simulator/cmd/interpreter.go delete mode 100644 x/programs/engine/config.go delete mode 100644 x/programs/engine/engine.go delete mode 100644 x/programs/engine/engine_test.go delete mode 100644 x/programs/engine/store.go delete mode 100644 x/programs/engine/store_test.go delete mode 100644 x/programs/examples/imports/program/program.go delete mode 100644 x/programs/examples/imports/pstate/pstate.go delete mode 100644 x/programs/examples/imports/wrap/wrap.go delete mode 100644 x/programs/examples/storage/storage.go delete mode 100644 x/programs/examples/token.go delete mode 100644 x/programs/examples/utils.go delete mode 100644 x/programs/host/dependencies.go delete mode 100644 x/programs/host/imports.go delete mode 100644 x/programs/host/link.go delete mode 100644 x/programs/host/link_test.go delete mode 100644 x/programs/program/caller.go delete mode 100644 x/programs/program/consts.go delete mode 100644 x/programs/program/context.go delete mode 100644 x/programs/program/dependencies.go delete mode 100644 x/programs/program/errors.go delete mode 100644 x/programs/program/export.go delete mode 100644 x/programs/program/function.go delete mode 100644 x/programs/program/memory.go delete mode 100644 x/programs/program/memory_test.go delete mode 100644 x/programs/program/types/types.go delete mode 100644 x/programs/program/utils.go delete mode 100644 x/programs/runtime/config_test.go delete mode 100644 x/programs/runtime/dependencies.go create mode 100644 x/programs/runtime/import_log_debug.go create mode 100644 x/programs/runtime/import_log_release.go create mode 100644 x/programs/runtime/import_program.go create mode 100644 x/programs/runtime/import_program_test.go create mode 100644 x/programs/runtime/import_state.go create mode 100644 x/programs/runtime/imports.go create mode 100644 x/programs/runtime/imports_test.go delete mode 100644 x/programs/runtime/instance.go create mode 100644 x/programs/runtime/program.go create mode 100644 x/programs/rust/sdk-macros/Cargo.toml rename x/programs/rust/{sdk_macros => sdk-macros}/src/lib.rs (78%) rename x/programs/rust/{sdk_macros => sdk-macros}/tests/compile.rs (100%) rename x/programs/rust/{sdk_macros => sdk-macros}/tests/ui/first-param.rs (100%) rename x/programs/rust/{sdk_macros => sdk-macros}/tests/ui/first-param.stderr (100%) rename x/programs/rust/{sdk_macros => sdk-macros}/tests/ui/not-pub.rs (100%) rename x/programs/rust/{sdk_macros => sdk-macros}/tests/ui/not-pub.stderr (100%) rename x/programs/rust/{sdk_macros => sdk-macros}/tests/ui/receiver.rs (100%) rename x/programs/rust/{sdk_macros => sdk-macros}/tests/ui/receiver.stderr (100%) rename x/programs/rust/{sdk_macros => sdk-macros}/tests/ui/user-defined-context-type.rs (100%) rename x/programs/rust/{sdk_macros => sdk-macros}/tests/ui/user-defined-context-type.stderr (100%) delete mode 100644 x/programs/rust/sdk_macros/Cargo.toml delete mode 100644 x/programs/rust/sdk_macros/tests/ui/ignore-second-param.rs delete mode 100644 x/programs/rust/sdk_macros/tests/ui/ignore-second-param.stderr delete mode 100644 x/programs/rust/sdk_macros/tests/ui/wild.rs delete mode 100644 x/programs/rust/sdk_macros/tests/ui/wild.stderr create mode 100644 x/programs/rust/wasmlanche-sdk/src/logging.rs create mode 100644 x/programs/test/programs/call_program/Cargo.toml create mode 100644 x/programs/test/programs/call_program/src/lib.rs create mode 100644 x/programs/test/programs/return_complex_type/Cargo.toml create mode 100644 x/programs/test/programs/return_complex_type/src/lib.rs create mode 100644 x/programs/test/programs/simple/Cargo.toml create mode 100644 x/programs/test/programs/simple/src/lib.rs create mode 100644 x/programs/test/testdb.go create mode 100644 x/programs/test/util.go delete mode 100755 x/programs/tests/fixture/memory.wasm delete mode 100644 x/programs/tests/utils.go diff --git a/x/programs/cmd/simulator/Cargo.toml b/x/programs/cmd/simulator/Cargo.toml index b6a9a8c7f3..dbb1a24c3f 100644 --- a/x/programs/cmd/simulator/Cargo.toml +++ b/x/programs/cmd/simulator/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" include = ["**/*.go"] [dependencies] -serde = { version = "1.0.196", features = ["derive"] } -serde_json = "1.0.113" -serde_with = "3.6.1" +serde = { version = "1.0.202", features = ["derive"] } +serde_json = "1.0.117" +serde_with = "3.8.1" +thiserror = { workspace = true } diff --git a/x/programs/cmd/simulator/cmd/config.go b/x/programs/cmd/simulator/cmd/config.go index f39af1205c..a09ebb0e7d 100644 --- a/x/programs/cmd/simulator/cmd/config.go +++ b/x/programs/cmd/simulator/cmd/config.go @@ -8,6 +8,7 @@ import ( "fmt" "strconv" + "github.com/near/borsh-go" "gopkg.in/yaml.v2" ) @@ -16,14 +17,9 @@ const ( ProgramExecute = "execute" ) -type Plan struct { - // The key of the caller used in each step of the plan. - CallerKey string `yaml:"caller_key" json:"callerKey"` - // Steps to performed during simulation. - Steps []Step `json,yaml:"steps"` -} - type Step struct { + // The key of the caller used. + CallerKey string `yaml:"caller_key" json:"callerKey"` // The API endpoint to call. (required) Endpoint Endpoint `json:"endpoint" yaml:"endpoint"` // The method to call on the endpoint. @@ -94,7 +90,7 @@ func (r *Response) setBalance(balance uint64) { r.Result.Balance = balance } -func (r *Response) setResponse(response []int64) { +func (r *Response) setResponse(response []byte) { r.Result.Response = response } @@ -112,7 +108,7 @@ type Result struct { // The balance after the step has completed. Balance uint64 `json:"balance,omitempty" yaml:"balance,omitempty"` // The response from the call. - Response []int64 `json:"response,omitempty" yaml:"response,omitempty"` + Response []byte `json:"response" yaml:"response"` // An optional message. Msg string `json:"msg,omitempty" yaml:"msg,omitempty"` // Timestamp of the response. @@ -162,11 +158,13 @@ const ( ) // validateAssertion validates the assertion against the actual value. -func validateAssertion(actual int64, require *Require) (bool, error) { +func validateAssertion(bytes []byte, require *Require) (bool, error) { if require == nil { return true, nil } + actual := int64(0) + borsh.Deserialize(&actual, bytes) assertion := require.Result // convert the assertion value(string) to uint64 value, err := strconv.ParseInt(assertion.Value, 10, 64) @@ -206,22 +204,22 @@ func validateAssertion(actual int64, require *Require) (bool, error) { return false, nil } -func unmarshalPlan(bytes []byte) (*Plan, error) { - var p Plan +func unmarshalStep(bytes []byte) (*Step, error) { + var s Step switch { case isJSON(string(bytes)): - if err := json.Unmarshal(bytes, &p); err != nil { + if err := json.Unmarshal(bytes, &s); err != nil { return nil, err } case isYAML(string(bytes)): - if err := yaml.Unmarshal(bytes, &p); err != nil { + if err := yaml.Unmarshal(bytes, &s); err != nil { return nil, err } default: return nil, ErrInvalidConfigFormat } - return &p, nil + return &s, nil } func boolToUint64(b bool) uint64 { diff --git a/x/programs/cmd/simulator/cmd/interpreter.go b/x/programs/cmd/simulator/cmd/interpreter.go new file mode 100644 index 0000000000..152bf360a5 --- /dev/null +++ b/x/programs/cmd/simulator/cmd/interpreter.go @@ -0,0 +1,31 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmd + +import ( + "context" + + "github.com/akamensky/argparse" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/hypersdk/state" +) + +var _ Cmd = (*InterpreterCmd)(nil) + +type InterpreterCmd struct { + cmd *argparse.Command +} + +func (s *InterpreterCmd) New(parser *argparse.Parser) { + s.cmd = parser.NewCommand("interpreter", "Read input from a buffered stdin") +} + +func (s *InterpreterCmd) Run(ctx context.Context, log logging.Logger, db *state.SimpleMutable, args []string) (*Response, error) { + // no-op + return newResponse(0), nil +} + +func (s *InterpreterCmd) Happened() bool { + return s.cmd.Happened() +} diff --git a/x/programs/cmd/simulator/cmd/key.go b/x/programs/cmd/simulator/cmd/key.go index e29ef9ca1f..8958d45a82 100644 --- a/x/programs/cmd/simulator/cmd/key.go +++ b/x/programs/cmd/simulator/cmd/key.go @@ -6,71 +6,47 @@ package cmd import ( "context" "fmt" + "time" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/spf13/cobra" + "github.com/akamensky/argparse" + "go.uber.org/zap" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" ) -func newKeyCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { - cmd := &cobra.Command{ - Use: "key", - Short: "Manage private keys", - } - cmd.AddCommand( - newKeyCreateCmd(log, db), - ) - return cmd -} +var _ Cmd = (*keyCreateCmd)(nil) type keyCreateCmd struct { + cmd *argparse.Command + log logging.Logger - db *state.SimpleMutable - name string + name *string } -func newKeyCreateCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { - c := &keyCreateCmd{ - log: log, - db: db, - } +func (c *keyCreateCmd) New(parser *argparse.Parser) { + c.cmd = parser.NewCommand("key-create", "Creates a new named private key and stores it in the database") + c.name = c.cmd.String("", "name", &argparse.Options{Required: true}) +} - return &cobra.Command{ - Use: "create [name]", - Short: "Creates a new named private key and stores it in the database", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - err := c.Init(args) - if err != nil { - return err - } - err = c.Verify() - if err != nil { - return err - } - return c.Run(cmd.Context()) - }, +func (c *keyCreateCmd) Run(ctx context.Context, log logging.Logger, db *state.SimpleMutable, args []string) (*Response, error) { + resp := newResponse(0) + resp.setTimestamp(time.Now().Unix()) + c.log = log + pkey, err := keyCreateFunc(ctx, db, *c.name) + if err != nil { + return resp, err } -} -func (c *keyCreateCmd) Init(args []string) error { - c.name = args[0] - return nil -} + c.log.Debug("key create successful", zap.String("key", fmt.Sprintf("%x", pkey))) -func (c *keyCreateCmd) Verify() error { - return nil + return resp, nil } -func (c *keyCreateCmd) Run(ctx context.Context) error { - _, err := keyCreateFunc(ctx, c.db, c.name) - if err != nil { - return err - } - return nil +func (c *keyCreateCmd) Happened() bool { + return c.cmd.Happened() } func keyCreateFunc(ctx context.Context, db *state.SimpleMutable, name string) (ed25519.PublicKey, error) { diff --git a/x/programs/cmd/simulator/cmd/logger.go b/x/programs/cmd/simulator/cmd/logger.go index 0a1f111718..08d7aa5a91 100644 --- a/x/programs/cmd/simulator/cmd/logger.go +++ b/x/programs/cmd/simulator/cmd/logger.go @@ -11,7 +11,9 @@ import ( "sync" "github.com/ava-labs/avalanchego/utils/logging" + "go.uber.org/zap" + "gopkg.in/natefinch/lumberjack.v2" ) @@ -46,17 +48,17 @@ func (f *logFactory) makeLogger(config logging.Config) (logging.Logger, error) { if _, ok := f.loggers[config.LoggerName]; ok { return nil, fmt.Errorf("logger with name %q already exists", config.LoggerName) } - consoleEnc := config.LogFormat.ConsoleEncoder() + consoleEnc := logging.Colors.ConsoleEncoder() fileEnc := config.LogFormat.FileEncoder() var consoleWriter io.WriteCloser if config.DisableWriterDisplaying { consoleWriter = newDiscardWriteCloser() } else { - consoleWriter = os.Stdout + consoleWriter = os.Stderr } - consoleCore := logging.NewWrappedCore(config.DisplayLevel, consoleWriter, consoleEnc) + consoleCore := logging.NewWrappedCore(config.LogLevel, consoleWriter, consoleEnc) consoleCore.WriterDisabled = config.DisableWriterDisplaying rw := &lumberjack.Logger{ diff --git a/x/programs/cmd/simulator/cmd/plan.go b/x/programs/cmd/simulator/cmd/plan.go index f933e6e281..46c64f8f44 100644 --- a/x/programs/cmd/simulator/cmd/plan.go +++ b/x/programs/cmd/simulator/cmd/plan.go @@ -4,21 +4,22 @@ package cmd import ( + "bufio" "context" "crypto/rand" "errors" "fmt" - "io" "math" "os" "strconv" "strings" "time" + "github.com/akamensky/argparse" + "go.uber.org/zap" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/spf13/cobra" - "go.uber.org/zap" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/actions" @@ -27,60 +28,71 @@ import ( "github.com/ava-labs/hypersdk/x/programs/program" ) +var _ Cmd = (*runCmd)(nil) + type runCmd struct { - plan *Plan - log logging.Logger - db *state.SimpleMutable + cmd *argparse.Command + + lastStep *int + file *string + planStep *string + + step *Step + log logging.Logger + reader *bufio.Reader // tracks program IDs created during this simulation programIDStrMap map[string]string - stdinReader io.Reader } -func newRunCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { - r := &runCmd{ - log: log, - db: db, - programIDStrMap: make(map[string]string), +func (c *runCmd) New(parser *argparse.Parser, programIDStrMap map[string]string, lastStep *int, reader *bufio.Reader) { + c.programIDStrMap = programIDStrMap + c.cmd = parser.NewCommand("run", "Run a HyperSDK program simulation plan") + c.file = c.cmd.String("", "file", &argparse.Options{ + Required: false, + }) + c.planStep = c.cmd.String("", "step", &argparse.Options{ + Required: false, + }) + c.lastStep = lastStep + c.reader = reader +} + +func (c *runCmd) Run(ctx context.Context, log logging.Logger, db *state.SimpleMutable, args []string) (*Response, error) { + c.log = log + var err error + if err = c.Init(); err != nil { + return newResponse(0), err } - cmd := &cobra.Command{ - Use: "run [path]", - Short: "Run a HyperSDK program simulation plan", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - // if the first argument is "-" read from stdin - r.stdinReader = cmd.InOrStdin() - err := r.Init(args) - if err != nil { - return err - } - err = r.Verify() - if err != nil { - return err - } - return r.Run(cmd.Context()) - }, + if err = c.Verify(); err != nil { + return newResponse(0), err + } + resp, err := c.RunStep(ctx, db) + if err != nil { + return newResponse(0), err } + return resp, nil +} - return cmd +func (c *runCmd) Happened() bool { + return c.cmd.Happened() } -func (c *runCmd) Init(args []string) (err error) { - var planBytes []byte - if args[0] == "-" { - // read simulation plan from stdin - planBytes, err = io.ReadAll(os.Stdin) +func (c *runCmd) Init() (err error) { + var planStep []byte + if c.planStep != nil && len(*c.planStep) > 0 { + planStep = []byte(*c.planStep) + } else if len(*c.file) > 0 { + // read simulation step from file + planStep, err = os.ReadFile(*c.file) if err != nil { return err } } else { - // read simulation plan from arg[0] - planBytes, err = os.ReadFile(args[0]) - if err != nil { - return err - } + return errors.New("please specify either a --plan or a --file flag") } - c.plan, err = unmarshalPlan(planBytes) + + c.step, err = unmarshalStep(planStep) if err != nil { return err } @@ -89,29 +101,27 @@ func (c *runCmd) Init(args []string) (err error) { } func (c *runCmd) Verify() error { - steps := c.plan.Steps - if steps == nil { + step := c.step + if step == nil { return fmt.Errorf("%w: %s", ErrInvalidPlan, "no steps found") } - if steps[0].Params == nil { + if step.Params == nil { return fmt.Errorf("%w: %s", ErrInvalidStep, "no params found") } // verify endpoint requirements - for i, step := range steps { - err := verifyEndpoint(i, &step) + err := verifyEndpoint(*c.lastStep, step) + if err != nil { + return err + } + + // verify assertions + if step.Require != nil { + err = verifyAssertion(*c.lastStep, step.Require) if err != nil { return err } - - // verify assertions - if step.Require != nil { - err = verifyAssertion(i, step.Require) - if err != nil { - return err - } - } } return nil @@ -162,42 +172,40 @@ func verifyEndpoint(i int, step *Step) error { return nil } -func (c *runCmd) Run(ctx context.Context) error { - for i, step := range c.plan.Steps { - c.log.Info("simulation", - zap.Int("step", i), - zap.String("endpoint", string(step.Endpoint)), - zap.String("method", step.Method), - zap.Uint64("maxUnits", step.MaxUnits), - zap.Any("params", step.Params), - ) - - params, err := c.createCallParams(ctx, c.db, step.Params) - if err != nil { - return err - } - - resp := newResponse(i) - err = runStepFunc(ctx, c.log, c.db, step.Endpoint, step.MaxUnits, step.Method, params, step.Require, resp) - if err != nil { - resp.setError(err) - c.log.Error("simulation", zap.Error(err)) - } +func (c *runCmd) RunStep(ctx context.Context, db *state.SimpleMutable) (*Response, error) { + index := *c.lastStep + step := c.step + c.log.Info("simulation", + zap.Int("step", index), + zap.String("endpoint", string(step.Endpoint)), + zap.String("method", step.Method), + zap.Uint64("maxUnits", step.MaxUnits), + zap.Any("params", step.Params), + ) + + params, err := c.createCallParams(ctx, db, step.Params) + if err != nil { + c.log.Error("simulation call", zap.Error(err)) + return newResponse(0), err + } - // map all transactions to their step_N identifier - txID, found := resp.getTxID() - if found { - c.programIDStrMap[fmt.Sprintf("step_%d", i)] = txID - } + resp := newResponse(index) + err = runStepFunc(ctx, c.log, db, step.Endpoint, step.MaxUnits, step.Method, params, step.Require, resp) + if err != nil { + c.log.Debug("simulation step", zap.Error(err)) + resp.setError(err) + } - // print response to stdout - err = resp.Print() - if err != nil { - return err - } + // map all transactions to their step_N identifier + txID, found := resp.getTxID() + if found { + c.programIDStrMap[fmt.Sprintf("step_%d", index)] = txID } - return nil + lastStep := index + 1 + *c.lastStep = lastStep + + return resp, nil } func runStepFunc( @@ -242,7 +250,7 @@ func runStepFunc( return err } resp.setResponse(response) - ok, err := validateAssertion(response[0], require) + ok, err := validateAssertion(response, require) if !ok { return fmt.Errorf("%w", ErrResultAssertionFailed) } @@ -259,8 +267,10 @@ func runStepFunc( if err != nil { return err } + resp.setResponse(response) - ok, err := validateAssertion(response[0], require) + ok, err := validateAssertion(response, require) + if !ok { return fmt.Errorf("%w", ErrResultAssertionFailed) } diff --git a/x/programs/cmd/simulator/cmd/program.go b/x/programs/cmd/simulator/cmd/program.go index 2a10a24141..363dcae250 100644 --- a/x/programs/cmd/simulator/cmd/program.go +++ b/x/programs/cmd/simulator/cmd/program.go @@ -7,92 +7,65 @@ import ( "context" "fmt" "os" + "time" + + "github.com/akamensky/argparse" + "go.uber.org/zap" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/spf13/cobra" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/actions" - - hutils "github.com/ava-labs/hypersdk/utils" ) -func newProgramCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { - cmd := &cobra.Command{ - Use: "program", - Short: "Manage HyperSDK programs", - } +var _ Cmd = (*programCreateCmd)(nil) - // add subcommands - cmd.AddCommand( - newProgramCreateCmd(log, db), - ) +type programCreateCmd struct { + cmd *argparse.Command - return cmd + log logging.Logger + keyName *string + path *string } -type programCreate struct { - db *state.SimpleMutable - keyName string - path string - id ids.ID +func (c *programCreateCmd) New(parser *argparse.Parser) { + c.cmd = parser.NewCommand("program-create", "Create a HyperSDK program transaction") + c.keyName = c.cmd.String("k", "key", &argparse.Options{ + Help: "name of the key to use to deploy the program", + Required: true, + }) + c.path = c.cmd.String("p", "path", &argparse.Options{ + Help: "path", + Required: true, + }) } -func newProgramCreateCmd(log logging.Logger, db *state.SimpleMutable) *cobra.Command { - p := &programCreate{ - db: db, - } - cmd := &cobra.Command{ - Use: "create --path [path] --key [key name]", - Short: "Create a HyperSDK program transaction", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - err := p.Init(args) - if err != nil { - return err - } - err = p.Verify(cmd.Context()) - if err != nil { - return err - } - err = p.Run(cmd.Context()) - if err != nil { - return err - } - - hutils.Outf("{{green}}create program transaction successful: {{/}}%s\n", p.id.String()) - return nil - }, +func (c *programCreateCmd) Run(ctx context.Context, log logging.Logger, db *state.SimpleMutable, args []string) (*Response, error) { + c.log = log + exists, err := hasKey(ctx, db, *c.keyName) + if err != nil { + return newResponse(0), err } - - cmd.PersistentFlags().StringVarP(&p.keyName, "key", "k", p.keyName, "name of the key to use to deploy the program") - cmd.MarkPersistentFlagRequired("key") - - return cmd -} - -func (p *programCreate) Init(args []string) error { - return nil -} - -func (p *programCreate) Verify(ctx context.Context) error { - exists, err := hasKey(ctx, p.db, p.keyName) if !exists { - return fmt.Errorf("%w: %s", ErrNamedKeyNotFound, p.keyName) + return newResponse(0), fmt.Errorf("%w: %s", ErrNamedKeyNotFound, c.keyName) } - return err -} - -func (p *programCreate) Run(ctx context.Context) (err error) { - p.id, err = programCreateFunc(ctx, p.db, p.path) + id, err := programCreateFunc(ctx, db, *c.path) if err != nil { - return err + return newResponse(0), err } - return nil + c.log.Debug("create program transaction successful", zap.String("id", id.String())) + + resp := newResponse(0) + resp.setTimestamp(time.Now().Unix()) + return resp, nil +} + +func (c *programCreateCmd) Happened() bool { + return c.cmd.Happened() } // createProgram simulates a create program transaction and stores the program to disk. @@ -113,16 +86,15 @@ func programCreateFunc(ctx context.Context, db *state.SimpleMutable, path string } // execute the action - _, outputs, err := programCreateAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programID) - if err != nil { - return ids.Empty, fmt.Errorf("program creation failed: %w", err) + success, _, output, err := programCreateAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programID) + if output != nil { + fmt.Println(string(output)) } - if len(outputs) > 0 { - var results []string - for _, output := range outputs { - results = append(results, string(output)) - } - fmt.Println(results) + if !success { + return ids.Empty, fmt.Errorf("program creation failed: %s", err) + } + if err != nil { + return ids.Empty, err } // store program to disk only on success @@ -141,9 +113,9 @@ func programExecuteFunc( callParams []actions.CallParam, function string, maxUnits uint64, -) (ids.ID, []int64, uint64, error) { +) (ids.ID, []byte, uint64, error) { // simulate create program transaction - actionID, err := generateRandomID() + programTxID, err := generateRandomID() if err != nil { return ids.Empty, nil, 0, err } @@ -156,18 +128,13 @@ func programExecuteFunc( } // execute the action - _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, actionID) - if err != nil { - return ids.Empty, nil, 0, fmt.Errorf("program execution failed: %w", err) - } + success, _, resp, err := programExecuteAction.Execute(ctx, nil, db, 0, codec.EmptyAddress, programTxID) - var result []int64 - for _, r := range resp { - p := codec.NewReader(r, len(r)) - for !p.Empty() { - v := p.UnpackInt64(true) - result = append(result, v) - } + if !success { + return ids.Empty, nil, 0, fmt.Errorf("program execution failed: %s", string(resp)) + } + if err != nil { + return ids.Empty, nil, 0, err } // store program to disk only on success @@ -178,5 +145,6 @@ func programExecuteFunc( // get remaining balance from runtime meter balance, err := programExecuteAction.GetBalance() - return actionID, result, balance, err + + return programTxID, resp, balance, err } diff --git a/x/programs/cmd/simulator/cmd/root.go b/x/programs/cmd/simulator/cmd/root.go index aeb46a0e52..695f027494 100644 --- a/x/programs/cmd/simulator/cmd/root.go +++ b/x/programs/cmd/simulator/cmd/root.go @@ -4,24 +4,30 @@ package cmd import ( + "bufio" "context" "crypto/rand" "encoding/json" + "errors" "fmt" + "io" "os" "path" + "github.com/akamensky/argparse" + "github.com/mattn/go-shellwords" + "go.uber.org/zap" + "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/spf13/cobra" - "go.uber.org/zap" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/vm" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/controller" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" ) @@ -30,53 +36,112 @@ const ( simulatorFolder = ".simulator" ) -type simulator struct { - log logging.Logger - logLevel string +const ( + LogDisableDisplayLogsKey = "log-disable-display-plugin-logs" + LogLevelKey = "log-level" + CleanupKey = "cleanup" + InterpreterKey = "interpreter" +) + +type Cmd interface { + Run(context.Context, logging.Logger, *state.SimpleMutable, []string) (*Response, error) + Happened() bool +} + +type Simulator struct { + log logging.Logger + + logLevel *string + cleanup *bool + disableWriterDisplaying *bool + lastStep int + programIDStrMap map[string]string vm *vm.VM db *state.SimpleMutable genesis *genesis.Genesis - cleanup func() -} -func NewRootCmd() *cobra.Command { - s := &simulator{} - cmd := &cobra.Command{ - Use: "simulator", - Short: "HyperSDK program VM simulator", - Run: func(cmd *cobra.Command, args []string) { - cmd.Help() - }, - } + cleanupFn func() - cmd.PersistentFlags().Bool("cleanup", false, "remove simulator directory on exit") + reader *bufio.Reader +} - cobra.EnablePrefixMatching = true - cmd.CompletionOptions.HiddenDefaultCmd = true - cmd.DisableAutoGenTag = true - cmd.SilenceErrors = true - cmd.SetHelpCommand(&cobra.Command{Hidden: true}) - cmd.PersistentFlags().StringVar(&s.logLevel, "log-level", "info", "log level") +func (s *Simulator) ParseCommandArgs(ctx context.Context, args []string, interpreterMode bool) error { + parser, subcommands := s.BaseParser() + + // if it's our first time parsing args, there is the possibility to enter in interpreter mode + if !interpreterMode { + stdinCmd := InterpreterCmd{} + stdinCmd.New(parser) + subcommands = append(subcommands, &stdinCmd) + } - // initialize simulator vm - err := s.Init() + err := parser.Parse(args) if err != nil { - fmt.Fprintln(os.Stderr, err) + fmt.Println(parser.Usage(err)) os.Exit(1) } - // add subcommands - cmd.AddCommand( - newRunCmd(s.log, s.db), - newProgramCmd(s.log, s.db), - newKeyCmd(s.log, s.db), - ) + if !interpreterMode { + s.Init() + } + s.log.Debug("simulator args", zap.Any("args", args)) - // ensure vm and databases are properly closed on simulator exit - cobra.OnFinalize(func() { + for _, cmd := range subcommands { + if cmd.Happened() { + resp, err := cmd.Run(ctx, s.log, s.db, args) + if err != nil { + return err + } + + s.log.Debug("a step has ben ran", zap.Any("resp", resp)) + + if interpreterMode { + // we need feedback, so print response to stdout + resp.Print() + } + + if _, ok := cmd.(*InterpreterCmd); ok || interpreterMode { + s.log.Debug("reading next cmd from stdin") + readString, err := s.reader.ReadString('\n') + if err == io.EOF { + // happens when the caller dropped, we should stop here + return nil + } else if err != nil { + s.log.Error("error while reading from stdin", zap.Error(err)) + return err + } + + rawArgs := []string{"simulator"} + parsed, err := shellwords.Parse(readString) + if err != nil { + return err + } + rawArgs = append(rawArgs, parsed...) + + err = s.ParseCommandArgs(ctx, rawArgs, true) + if err != nil { + return err + } else { + return nil + } + } else { + return nil + } + } + } + + return errors.New("unreachable") +} + +func (s *Simulator) Execute(ctx context.Context) error { + s.lastStep = 0 + s.programIDStrMap = make(map[string]string) + + defer func() { + // ensure vm and databases are properly closed on simulator exit if s.vm != nil { - err := s.vm.Shutdown(cmd.Context()) + err := s.vm.Shutdown(ctx) if err != nil { s.log.Error("simulator vm closed with error", zap.Error(err), @@ -84,16 +149,40 @@ func NewRootCmd() *cobra.Command { } } - cleanup, _ := cmd.Flags().GetBool("cleanup") - if cleanup { - s.cleanup() + if *s.cleanup { + s.cleanupFn() } - }) + }() + + err := s.ParseCommandArgs(ctx, os.Args, false) + if err != nil { + s.log.Error("error when parsing command args", zap.Error(err)) + os.Exit(1) + } + + return nil +} - return cmd +func (s *Simulator) BaseParser() (*argparse.Parser, []Cmd) { + parser := argparse.NewParser("simulator", "HyperSDK program VM simulator") + s.cleanup = parser.Flag("", CleanupKey, &argparse.Options{Help: "remove simulator directory on exit", Default: true}) + s.logLevel = parser.String("", LogLevelKey, &argparse.Options{Help: "log level", Default: "info"}) + s.disableWriterDisplaying = parser.Flag("", LogDisableDisplayLogsKey, &argparse.Options{Help: "disable displaying logs in stdout", Default: false}) + + stdin := os.Stdin + s.reader = bufio.NewReader(stdin) + + runCmd := runCmd{} + runCmd.New(parser, s.programIDStrMap, &s.lastStep, s.reader) + programCmd := programCreateCmd{} + programCmd.New(parser) + keyCmd := keyCreateCmd{} + keyCmd.New(parser) + + return parser, []Cmd{&runCmd, &programCmd, &keyCmd} } -func (s *simulator) Init() error { +func (s *Simulator) Init() error { homeDir, err := os.UserHomeDir() if err != nil { return err @@ -115,31 +204,14 @@ func (s *simulator) Init() error { dbPath := path.Join(basePath, fmt.Sprintf("db-%s", nodeID.String())) loggingConfig := logging.Config{} - loggingConfig.LogLevel, err = logging.ToLevel(s.logLevel) + typedLogLevel, err := logging.ToLevel(*s.logLevel) if err != nil { return err } + loggingConfig.LogLevel = typedLogLevel loggingConfig.Directory = path.Join(basePath, fmt.Sprintf("logs-%s", nodeID.String())) loggingConfig.LogFormat = logging.JSON - loggingConfig.DisableWriterDisplaying = true - - s.cleanup = func() { - if err := os.RemoveAll(dbPath); err != nil { - fmt.Fprintf(os.Stderr, "failed to remove simulator directory: %s\n", err) - } - - if err := os.RemoveAll(loggingConfig.Directory); err != nil { - fmt.Fprintf(os.Stderr, "failed to remove simulator logs: %s\n", err) - } - } - - // setup simulator logger - logFactory := newLogFactory(loggingConfig) - s.log, err = logFactory.Make("simulator") - if err != nil { - logFactory.Close() - return err - } + loggingConfig.DisableWriterDisplaying = *s.disableWriterDisplaying sk, err := bls.NewSecretKey() if err != nil { @@ -192,8 +264,26 @@ func (s *simulator) Init() error { s.db = state.NewSimpleMutable(stateDB) s.genesis = genesis.Default() + s.cleanupFn = func() { + if err := os.RemoveAll(dbPath); err != nil { + fmt.Fprintf(os.Stderr, "failed to remove simulator directory: %s\n", err) + } + + if err := os.RemoveAll(loggingConfig.Directory); err != nil { + fmt.Fprintf(os.Stderr, "failed to remove simulator logs: %s\n", err) + } + } + + // setup simulator logger + logFactory := newLogFactory(loggingConfig) + s.log, err = logFactory.Make("simulator") + if err != nil { + logFactory.Close() + return err + } + s.log.Info("simulator initialized", - zap.String("log-level", s.logLevel), + zap.String("log-level", *s.logLevel), ) return nil diff --git a/x/programs/cmd/simulator/go.mod b/x/programs/cmd/simulator/go.mod index d378684bdc..b9ac48048b 100644 --- a/x/programs/cmd/simulator/go.mod +++ b/x/programs/cmd/simulator/go.mod @@ -7,16 +7,18 @@ require ( github.com/ava-labs/hypersdk v0.0.1 github.com/near/borsh-go v0.3.1 github.com/prometheus/client_golang v1.16.0 - github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.26.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 ) +require github.com/mattn/go-shellwords v1.0.12 + require ( filippo.io/edwards25519 v1.0.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect + github.com/akamensky/argparse v1.4.0 github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/bytecodealliance/wasmtime-go/v14 v14.0.0 // indirect @@ -39,7 +41,6 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -53,8 +54,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/supranational/blst v0.3.11 // indirect go.opentelemetry.io/otel v1.11.2 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect @@ -68,12 +68,12 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.18.0 // indirect golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect diff --git a/x/programs/cmd/simulator/go.sum b/x/programs/cmd/simulator/go.sum index 58d7c37d30..783db9f247 100644 --- a/x/programs/cmd/simulator/go.sum +++ b/x/programs/cmd/simulator/go.sum @@ -47,6 +47,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc= +github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanchego v1.10.18 h1:ErJ+SJBtN9tVqk3OPRXffpf+MWeQnNZJlBNWQIgAg8A= @@ -110,7 +112,6 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -259,8 +260,6 @@ github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:q github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= @@ -307,6 +306,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -372,10 +373,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= @@ -388,12 +388,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -476,8 +472,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -549,8 +545,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -567,8 +563,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -616,11 +612,11 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -681,8 +677,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/x/programs/cmd/simulator/simulator.go b/x/programs/cmd/simulator/simulator.go index 680ea5640d..5c332fcde5 100644 --- a/x/programs/cmd/simulator/simulator.go +++ b/x/programs/cmd/simulator/simulator.go @@ -5,17 +5,20 @@ package main import ( "context" + "fmt" "os" - "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/cmd" ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - if err := cmd.NewRootCmd().ExecuteContext(ctx); err != nil { - utils.Outf("{{red}}error: {{/}}%+v\n", err) + s := &cmd.Simulator{} + // initialize simulator vm + err := s.Execute(ctx) + if err != nil { + fmt.Fprintln(os.Stderr, err) os.Exit(1) } os.Exit(0) diff --git a/x/programs/cmd/simulator/src/lib.rs b/x/programs/cmd/simulator/src/lib.rs index a79a2f9e86..4dd1bcf7a5 100644 --- a/x/programs/cmd/simulator/src/lib.rs +++ b/x/programs/cmd/simulator/src/lib.rs @@ -6,19 +6,18 @@ use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; use std::{ - error::Error, - ffi::OsStr, - io::Write, + io::{BufRead, BufReader, Write}, path::Path, - process::{Command, Output, Stdio}, + process::{Child, Command, Stdio}, }; +use thiserror::Error; mod id; pub use id::Id; /// The endpoint to call for a [Step]. -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(rename_all = "lowercase")] pub enum Endpoint { /// Perform an operation against the key api. @@ -32,7 +31,7 @@ pub enum Endpoint { } /// A [Plan] is made up of [Step]s. Each step is a call to the API and can include verification. -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(rename_all = "camelCase")] pub struct Step { /// The API endpoint to call. @@ -48,6 +47,15 @@ pub struct Step { pub require: Option, } +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SimulatorStep<'a> { + /// The key of the caller used in each step of the plan. + pub caller_key: &'a str, + #[serde(flatten)] + pub step: &'a Step, +} + impl Step { /// Create a [Step] that creates a key. #[must_use] @@ -122,14 +130,14 @@ impl From for Param { } } -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct Require { /// If defined the result of the step must match this assertion. pub result: ResultAssertion, } #[serde_as] -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(tag = "operator", content = "value")] pub enum ResultAssertion { #[serde(rename = "==")] @@ -190,7 +198,19 @@ pub struct PlanResult { /// The timestamp of the function call response. pub timestamp: u64, /// The result of the function call. - pub response: Option>, + pub response: Option, +} + +#[derive(Error, Debug)] +pub enum ClientError { + #[error("Read error: {0}")] + Read(#[from] std::io::Error), + #[error("Deserialization error: {0}")] + Deserialization(#[from] serde_json::Error), + #[error("EOF")] + Eof, + #[error("Missing handle")] + StdIo, } /// A [Client] is required to pass a [Plan] to the simulator, then to [run](Self::run_plan) the actual simulation. @@ -199,14 +219,7 @@ pub struct Client { path: &'static str, } -impl Default for Client { - fn default() -> Self { - Self::new() - } -} - impl Client { - #[must_use] pub fn new() -> Self { let path = env!("SIMULATOR_PATH"); @@ -227,105 +240,66 @@ impl Client { /// # Errors /// /// Returns an error if the serialization or plan fails. - pub fn run_plan(&self, plan: &Plan) -> Result, Box> { - run_steps(self.path, plan) - } - - /// Performs a single `Execute` step against the simulator and returns the result. - /// # Errors - /// - /// Returns an error if the serialization or single-[Step]-[Plan] fails. - pub fn execute_step(&self, key: &str, step: Step) -> Result> { - let plan = &Plan { - caller_key: key.into(), - steps: vec![step], - }; - - run_step(self.path, plan) + pub fn run_plan(self, plan: Plan) -> Result, ClientError> { + let Child { + mut stdin, + mut stdout, + .. + } = Command::new(self.path) + .arg("interpreter") + .arg("--cleanup") + .arg("--log-level") + .arg("error") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + + let writer = stdin.as_mut().ok_or(ClientError::StdIo)?; + let reader = stdout.as_mut().ok_or(ClientError::StdIo)?; + + let responses = BufReader::new(reader) + .lines() + .map(|line| serde_json::from_str(&line?).map_err(Into::into)); + + let mut child = SimulatorChild { writer, responses }; + + plan.steps + .iter() + .map(|step| child.run_step(&plan.caller_key, step)) + .collect() } } -fn cmd_output

(path: P, plan: &Plan) -> Result> -where - P: AsRef, -{ - let mut child = Command::new(path) - .arg("run") - .arg("--cleanup") - .arg("-") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - - // write json to stdin - let input = - serde_json::to_string(plan).map_err(|e| format!("failed to serialize json: {e}"))?; - if let Some(ref mut stdin) = child.stdin { - stdin - .write_all(input.as_bytes()) - .map_err(|e| format!("failed to write to stdin: {e}"))?; +impl Default for Client { + fn default() -> Self { + Self::new() } - - child - .wait_with_output() - .map_err(|e| format!("failed to wait for child: {e}").into()) } -fn run_steps(path: P, plan: &Plan) -> Result, Box> -where - T: serde::de::DeserializeOwned + serde::Serialize, - P: AsRef, -{ - let output = cmd_output(path, plan)?; - let mut items: Vec = Vec::new(); - - if !output.status.success() { - println!("stderr"); - for line in String::from_utf8_lossy(&output.stderr).lines() { - println!("{line}"); - } - println!("stdout"); - for line in String::from_utf8_lossy(&output.stdout).lines() { - println!("{line}"); - } - return Err(String::from_utf8(output.stdout)?.into()); - } - - for line in String::from_utf8(output.stdout)? - .lines() - .filter(|line| !line.trim().is_empty()) - { - let item = serde_json::from_str(line) - .map_err(|e| format!("failed to parse output to json: {e}"))?; - items.push(item); - } - - Ok(items) +struct SimulatorChild { + writer: W, + responses: R, } -fn run_step(path: P, plan: &Plan) -> Result> +impl SimulatorChild where - T: serde::de::DeserializeOwned + serde::Serialize, - P: AsRef, + W: Write, + R: Iterator>, { - let output = cmd_output(path, plan)?; + fn run_step(&mut self, caller_key: &str, step: &Step) -> Result { + let run_command = b"run --step '"; + self.writer.write_all(run_command)?; - if !output.status.success() { - println!("stderr"); - for line in String::from_utf8_lossy(&output.stderr).lines() { - println!("{line}"); - } - println!("stdout"); - for line in String::from_utf8_lossy(&output.stdout).lines() { - println!("{line}"); - } - return Err(String::from_utf8(output.stdout)?.into()); - } + let step = SimulatorStep { caller_key, step }; + let input = serde_json::to_vec(&step)?; + self.writer.write_all(&input)?; + self.writer.write_all(b"'\n")?; + self.writer.flush()?; - let resp: T = serde_json::from_str(String::from_utf8(output.stdout)?.as_ref()) - .map_err(|e| format!("failed to parse output to json: {e}"))?; + let resp = self.responses.next().ok_or(ClientError::Eof)??; - Ok(resp) + Ok(resp) + } } #[cfg(test)] diff --git a/x/programs/cmd/simulator/vm/actions/outputs.go b/x/programs/cmd/simulator/vm/actions/outputs.go index 6b9fcf79de..231f36c44c 100644 --- a/x/programs/cmd/simulator/vm/actions/outputs.go +++ b/x/programs/cmd/simulator/vm/actions/outputs.go @@ -3,6 +3,4 @@ package actions -import "errors" - -var ErrOutputValueZero = errors.New("value is zero") +var OutputValueZero = []byte("value is zero") diff --git a/x/programs/cmd/simulator/vm/actions/program_create.go b/x/programs/cmd/simulator/vm/actions/program_create.go index 248d67f4b0..f010f1cb31 100644 --- a/x/programs/cmd/simulator/vm/actions/program_create.go +++ b/x/programs/cmd/simulator/vm/actions/program_create.go @@ -7,12 +7,13 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/state" + "github.com/ava-labs/hypersdk/utils" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" ) @@ -40,17 +41,17 @@ func (t *ProgramCreate) Execute( mu state.Mutable, _ int64, _ codec.Address, - actionID ids.ID, -) (uint64, [][]byte, error) { + id ids.ID, +) (bool, uint64, []byte, error) { if len(t.Program) == 0 { - return 1, nil, ErrOutputValueZero + return false, 1, OutputValueZero, nil } - if err := storage.SetProgram(ctx, mu, actionID, t.Program); err != nil { - return 1, nil, err + if err := storage.SetProgram(ctx, mu, id, t.Program); err != nil { + return false, 1, utils.ErrBytes(err), nil } - return 1, nil, nil + return true, 1, nil, nil } func (*ProgramCreate) MaxComputeUnits(chain.Rules) uint64 { diff --git a/x/programs/cmd/simulator/vm/actions/program_execute.go b/x/programs/cmd/simulator/vm/actions/program_execute.go index 9f4c714fcb..e880e6dd76 100644 --- a/x/programs/cmd/simulator/vm/actions/program_execute.go +++ b/x/programs/cmd/simulator/vm/actions/program_execute.go @@ -5,25 +5,28 @@ package actions import ( "context" + "errors" "fmt" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/logging" "github.com/near/borsh-go" + "github.com/ava-labs/hypersdk/crypto/ed25519" + "github.com/ava-labs/hypersdk/x/programs/engine" + "github.com/ava-labs/hypersdk/x/programs/host" + "github.com/ava-labs/hypersdk/x/programs/program" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" - "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/x/programs/engine" + "github.com/ava-labs/hypersdk/utils" + + importProgram "github.com/ava-labs/hypersdk/x/programs/examples/imports/program" "github.com/ava-labs/hypersdk/x/programs/examples/imports/pstate" "github.com/ava-labs/hypersdk/x/programs/examples/storage" - "github.com/ava-labs/hypersdk/x/programs/host" - "github.com/ava-labs/hypersdk/x/programs/program" "github.com/ava-labs/hypersdk/x/programs/runtime" - - importProgram "github.com/ava-labs/hypersdk/x/programs/examples/imports/program" ) var _ chain.Action = (*ProgramExecute)(nil) @@ -42,7 +45,7 @@ func (*ProgramExecute) GetTypeID() uint8 { return programExecuteID } -func (t *ProgramExecute) StateKeys(actor codec.Address, _ ids.ID) state.Keys { +func (t *ProgramExecute) StateKeys(actor codec.Address, txID ids.ID) state.Keys { return state.Keys{} } @@ -57,36 +60,39 @@ func (t *ProgramExecute) Execute( _ int64, actor codec.Address, _ ids.ID, -) (computeUnits uint64, output [][]byte, err error) { +) (success bool, computeUnits uint64, output []byte, err error) { if len(t.Function) == 0 { - return 1, nil, ErrOutputValueZero + return false, 1, OutputValueZero, nil } if len(t.Params) == 0 { - return 1, nil, ErrOutputValueZero + return false, 1, OutputValueZero, nil } programID, ok := t.Params[0].Value.(ids.ID) if !ok { - return 1, nil, fmt.Errorf("invalid call param: must be ID") + return false, 1, utils.ErrBytes(fmt.Errorf("invalid call param: must be ID")), nil } // TODO: take fee out of balance? - programBytes, _, err := storage.GetProgram(ctx, mu, programID) + programBytes, exists, err := storage.GetProgram(context.Background(), mu, programID) + if !exists { + err = errors.New("unknown program") + } if err != nil { - return 1, nil, err + return false, 1, utils.ErrBytes(err), nil } // TODO: get cfg from genesis cfg := runtime.NewConfig() if err != nil { - return 1, nil, err + return false, 1, utils.ErrBytes(err), nil } ecfg, err := engine.NewConfigBuilder(). WithDefaultCache(true). Build() if err != nil { - return 1, nil, err + return false, 1, utils.ErrBytes(err), nil } eng := engine.New(ecfg) @@ -95,45 +101,39 @@ func (t *ProgramExecute) Execute( importsBuilder.Register("state", func() host.Import { return pstate.New(logging.NoLog{}, mu) }) - callContext := program.Context{ + callContext := &program.Context{ ProgramID: programID, // Actor: [32]byte(actor[1:]), // OriginatingActor: [32]byte(actor[1:]) } importsBuilder.Register("program", func() host.Import { - return importProgram.New(logging.NoLog{}, eng, mu, cfg, &callContext) + return importProgram.New(logging.NoLog{}, eng, mu, cfg, callContext) }) imports := importsBuilder.Build() t.rt = runtime.New(logging.NoLog{}, eng, imports, cfg) err = t.rt.Initialize(ctx, callContext, programBytes, t.MaxUnits) if err != nil { - return 1, nil, err + return false, 1, utils.ErrBytes(err), nil } defer t.rt.Stop() mem, err := t.rt.Memory() if err != nil { - return 1, nil, err + return false, 1, utils.ErrBytes(err), nil } params, err := WriteParams(mem, t.Params) if err != nil { - return 1, nil, err + return false, 1, utils.ErrBytes(err), nil } resp, err := t.rt.Call(ctx, t.Function, callContext, params[1:]...) if err != nil { - return 1, nil, err - } - - // TODO: remove this is to support readonly response for now. - p := codec.NewWriter(len(resp), consts.MaxInt) - for _, r := range resp { - p.PackInt64(r) + return false, 1, utils.ErrBytes(err), nil } - return 1, [][]byte{p.Bytes()}, nil + return true, 1, resp, nil } func (*ProgramExecute) MaxComputeUnits(chain.Rules) uint64 { diff --git a/x/programs/cmd/simulator/vm/config/config.go b/x/programs/cmd/simulator/vm/config/config.go index 3bb002e7b4..2a35565b07 100644 --- a/x/programs/cmd/simulator/vm/config/config.go +++ b/x/programs/cmd/simulator/vm/config/config.go @@ -12,11 +12,11 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/profiler" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/config" "github.com/ava-labs/hypersdk/trace" "github.com/ava-labs/hypersdk/vm" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/version" ) diff --git a/x/programs/cmd/simulator/vm/consts/consts.go b/x/programs/cmd/simulator/vm/consts/consts.go index fcf4225c9e..67a3270180 100644 --- a/x/programs/cmd/simulator/vm/consts/consts.go +++ b/x/programs/cmd/simulator/vm/consts/consts.go @@ -5,9 +5,9 @@ package consts import ( "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/consts" ) const ( @@ -20,7 +20,7 @@ const ( var ID ids.ID func init() { - b := make([]byte, ids.IDLen) + b := make([]byte, consts.IDLen) copy(b, []byte(Name)) vmID, err := ids.ToID(b) if err != nil { diff --git a/x/programs/cmd/simulator/vm/controller/controller.go b/x/programs/cmd/simulator/vm/controller/controller.go index 1da602eeeb..3a39cc5ed9 100644 --- a/x/programs/cmd/simulator/vm/controller/controller.go +++ b/x/programs/cmd/simulator/vm/controller/controller.go @@ -8,24 +8,23 @@ import ( "fmt" "net/http" + ametrics "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/snow" - "go.uber.org/zap" - "github.com/ava-labs/hypersdk/builder" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/gossiper" + hrpc "github.com/ava-labs/hypersdk/rpc" + hstorage "github.com/ava-labs/hypersdk/storage" "github.com/ava-labs/hypersdk/vm" + "go.uber.org/zap" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/config" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/rpc" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/version" - - ametrics "github.com/ava-labs/avalanchego/api/metrics" - hrpc "github.com/ava-labs/hypersdk/rpc" - hstorage "github.com/ava-labs/hypersdk/storage" ) var _ vm.Controller = (*Controller)(nil) diff --git a/x/programs/cmd/simulator/vm/controller/metrics.go b/x/programs/cmd/simulator/vm/controller/metrics.go index 8344372c29..cd2d223ec3 100644 --- a/x/programs/cmd/simulator/vm/controller/metrics.go +++ b/x/programs/cmd/simulator/vm/controller/metrics.go @@ -4,12 +4,11 @@ package controller import ( + ametrics "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" - - ametrics "github.com/ava-labs/avalanchego/api/metrics" ) type metrics struct { diff --git a/x/programs/cmd/simulator/vm/controller/resolutions.go b/x/programs/cmd/simulator/vm/controller/resolutions.go index 22621fce0f..995812d265 100644 --- a/x/programs/cmd/simulator/vm/controller/resolutions.go +++ b/x/programs/cmd/simulator/vm/controller/resolutions.go @@ -9,8 +9,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/hypersdk/fees" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" ) diff --git a/x/programs/cmd/simulator/vm/genesis/genesis.go b/x/programs/cmd/simulator/vm/genesis/genesis.go index fde9567cbb..958ca59817 100644 --- a/x/programs/cmd/simulator/vm/genesis/genesis.go +++ b/x/programs/cmd/simulator/vm/genesis/genesis.go @@ -11,12 +11,12 @@ import ( "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/x/merkledb" + hconsts "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/state" "github.com/ava-labs/hypersdk/vm" - "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" - hconsts "github.com/ava-labs/hypersdk/consts" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" ) var _ vm.Genesis = (*Genesis)(nil) @@ -44,9 +44,7 @@ type Genesis struct { MaxBlockUnits fees.Dimensions `json:"maxBlockUnits"` // must be possible to reach before block too large // Tx Parameters - ValidityWindow int64 `json:"validityWindow"` // ms - MaxActionsPerTx uint8 `json:"maxActionsPerTx"` - MaxOutputsPerAction uint8 `json:"maxOutputsPerAction"` + ValidityWindow int64 `json:"validityWindow"` // ms // Tx Fee Parameters BaseComputeUnits uint64 `json:"baseUnits"` @@ -96,9 +94,7 @@ func Default() *Genesis { StorageValueWriteUnits: 3, // Tx Parameters - ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms - MaxActionsPerTx: 1, - MaxOutputsPerAction: 1, + ValidityWindow: 60 * hconsts.MillisecondsPerSecond, // ms // program Runtime Parameters EnableDebugMode: true, diff --git a/x/programs/cmd/simulator/vm/genesis/rules.go b/x/programs/cmd/simulator/vm/genesis/rules.go index 203b8128da..f9b1c10d87 100644 --- a/x/programs/cmd/simulator/vm/genesis/rules.go +++ b/x/programs/cmd/simulator/vm/genesis/rules.go @@ -5,7 +5,6 @@ package genesis import ( "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/fees" ) @@ -40,14 +39,6 @@ func (r *Rules) GetValidityWindow() int64 { return r.g.ValidityWindow } -func (r *Rules) GetMaxActionsPerTx() uint8 { - return r.g.MaxActionsPerTx -} - -func (r *Rules) GetMaxOutputsPerAction() uint8 { - return r.g.MaxOutputsPerAction -} - func (r *Rules) GetMinUnitPrice() fees.Dimensions { return r.g.MinUnitPrice } diff --git a/x/programs/cmd/simulator/vm/registry/registry.go b/x/programs/cmd/simulator/vm/registry/registry.go index c7e1cfdb5b..00df69e6a1 100644 --- a/x/programs/cmd/simulator/vm/registry/registry.go +++ b/x/programs/cmd/simulator/vm/registry/registry.go @@ -5,9 +5,9 @@ package registry import ( "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/actions" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" ) diff --git a/x/programs/cmd/simulator/vm/rpc/dependencies.go b/x/programs/cmd/simulator/vm/rpc/dependencies.go index c50f2fc57f..2ede1dd20a 100644 --- a/x/programs/cmd/simulator/vm/rpc/dependencies.go +++ b/x/programs/cmd/simulator/vm/rpc/dependencies.go @@ -8,8 +8,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/trace" - "github.com/ava-labs/hypersdk/fees" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" ) diff --git a/x/programs/cmd/simulator/vm/rpc/jsonrpc_client.go b/x/programs/cmd/simulator/vm/rpc/jsonrpc_client.go index 8004d48b91..3287cdfabc 100644 --- a/x/programs/cmd/simulator/vm/rpc/jsonrpc_client.go +++ b/x/programs/cmd/simulator/vm/rpc/jsonrpc_client.go @@ -9,13 +9,13 @@ import ( "github.com/ava-labs/avalanchego/ids" - _ "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/registry" // ensure registry populated - "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/requester" "github.com/ava-labs/hypersdk/rpc" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" + _ "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/registry" // ensure registry populated "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/storage" ) diff --git a/x/programs/cmd/simulator/vm/rpc/jsonrpc_server.go b/x/programs/cmd/simulator/vm/rpc/jsonrpc_server.go index 916bd5d9b8..ae0f241d03 100644 --- a/x/programs/cmd/simulator/vm/rpc/jsonrpc_server.go +++ b/x/programs/cmd/simulator/vm/rpc/jsonrpc_server.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/fees" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/genesis" ) diff --git a/x/programs/cmd/simulator/vm/storage/storage.go b/x/programs/cmd/simulator/vm/storage/storage.go index e3018cea7f..f8de82fd93 100644 --- a/x/programs/cmd/simulator/vm/storage/storage.go +++ b/x/programs/cmd/simulator/vm/storage/storage.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/hypersdk/crypto/ed25519" "github.com/ava-labs/hypersdk/fees" "github.com/ava-labs/hypersdk/state" + "github.com/ava-labs/hypersdk/x/programs/examples/storage" ) @@ -46,7 +47,7 @@ const ProgramChunks uint16 = 1 // func ProgramKey(id ids.ID) (k []byte) { - k = make([]byte, 1+ids.IDLen) + k = make([]byte, 1+consts.IDLen) k[0] = programPrefix copy(k[1:], id[:]) return @@ -163,7 +164,7 @@ func GetTransaction( // [txPrefix] + [txID] func txKey(id ids.ID) (k []byte) { - k = make([]byte, 1+ids.IDLen) + k = make([]byte, 1+consts.IDLen) k[0] = txPrefix copy(k[1:], id[:]) return diff --git a/x/programs/cmd/simulator/vm/utils/utils.go b/x/programs/cmd/simulator/vm/utils/utils.go index f221ccbdb0..40c877a373 100644 --- a/x/programs/cmd/simulator/vm/utils/utils.go +++ b/x/programs/cmd/simulator/vm/utils/utils.go @@ -5,10 +5,10 @@ package utils import ( "github.com/ava-labs/avalanchego/utils/formatting/address" - "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/crypto" "github.com/ava-labs/hypersdk/crypto/ed25519" + "github.com/ava-labs/hypersdk/x/programs/cmd/simulator/vm/consts" ) diff --git a/x/programs/engine/config.go b/x/programs/engine/config.go deleted file mode 100644 index 31d6feb7e0..0000000000 --- a/x/programs/engine/config.go +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package engine - -import ( - "github.com/ava-labs/avalanchego/utils/units" - "github.com/bytecodealliance/wasmtime-go/v14" -) - -type CompileStrategy uint8 - -const ( - // CompileWasm will compile the wasm module before instantiating it. - CompileWasm CompileStrategy = iota - // PrecompiledWasm accepts a precompiled wasm module serialized by an Engine. - PrecompiledWasm -) - -var ( - DefaultMaxWasmStack = 256 * units.MiB // 256 MiB - DefaultLimitMaxMemory = uint32(18 * 64 * units.KiB) // 18 pages - DefaultSIMD = false - DefaultEnableReferenceTypes = false - DefaultEnableBulkMemory = false - DefaultProfilingStrategy = wasmtime.ProfilingStrategyNone - DefaultMultiValue = false - DefaultCompileStrategy = CompileWasm - - defaultWasmThreads = false - defaultFuelMetering = true - defaultWasmMultiMemory = false - defaultWasmMemory64 = false - defaultCompilerStrategy = wasmtime.StrategyCranelift - defaultEpochInterruption = true - defaultNaNCanonicalization = "true" - defaultCraneliftOptLevel = wasmtime.OptLevelSpeed - defaultEnableCraneliftDebugVerifier = false - defaultEnableDebugInfo = false -) - -// NewConfig creates a new engine config with default settings -func NewConfig() *Config { - return &Config{ - wasmConfig: DefaultWasmtimeConfig(), - } -} - -// Config is wrapper for wasmtime.Config -type Config struct { - wasmConfig *wasmtime.Config -} - -// Get returns the underlying wasmtime config. -func (c *Config) Get() *wasmtime.Config { - return c.wasmConfig -} - -// CacheConfigLoad enables compiled code caching for this `Config` using the -// settings specified in the configuration file `path`. -func (c *Config) CacheConfigLoad(path string) error { - return c.wasmConfig.CacheConfigLoad(path) -} - -// CacheConfigLoadDefault enables compiled code caching for this `Config` using -// the default settings configuration can be found. -func (c *Config) CacheConfigLoadDefault() error { - return c.wasmConfig.CacheConfigLoadDefault() -} - -// EnableCraneliftFlag enables a target-specific flag in Cranelift. -func (c *Config) EnableCraneliftFlag(flag string) { - c.wasmConfig.EnableCraneliftFlag(flag) -} - -// SetConsumFuel configures whether fuel is enabled. -func (c *Config) SetConsumeFuel(enabled bool) { - c.wasmConfig.SetConsumeFuel(enabled) -} - -// SetCraneliftDebugVerifier configures whether the cranelift debug verifier -// will be active when cranelift is used to compile wasm code. -func (c *Config) SetCraneliftDebugVerifier(enabled bool) { - c.wasmConfig.SetCraneliftDebugVerifier(enabled) -} - -// SetCraneliftFlag sets a target-specific flag in Cranelift to the specified value. -func (c *Config) SetCraneliftFlag(name string, value string) { - c.wasmConfig.SetCraneliftFlag(name, value) -} - -// SetCraneliftOptLevel configures the cranelift optimization level for generated code. -func (c *Config) SetCraneliftOptLevel(level wasmtime.OptLevel) { - c.wasmConfig.SetCraneliftOptLevel(level) -} - -// SetDebugInfo configures whether dwarf debug information for JIT code is enabled -func (c *Config) SetDebugInfo(enabled bool) { - c.wasmConfig.SetDebugInfo(enabled) -} - -// SetEpochInterruption enables epoch-based instrumentation of generated code to -// interrupt WebAssembly execution when the current engine epoch exceeds a -// defined threshold. -func (c *Config) SetEpochInterruption(enable bool) { - c.wasmConfig.SetEpochInterruption(enable) -} - -// SetMaxWasmStack configures the maximum stack size, in bytes, that JIT code can use. -func (c *Config) SetMaxWasmStack(size int) { - c.wasmConfig.SetMaxWasmStack(size) -} - -// SetProfiler configures what profiler strategy to use for generated code. -func (c *Config) SetProfiler(profiler wasmtime.ProfilingStrategy) { - c.wasmConfig.SetProfiler(profiler) -} - -// SetStrategy configures what compilation strategy is used to compile wasm code. -func (c *Config) SetStrategy(strategy wasmtime.Strategy) { - c.wasmConfig.SetStrategy(strategy) -} - -// SetTarget configures the target triple that this configuration will produce machine code for. -func (c *Config) SetTarget(target string) error { - return c.wasmConfig.SetTarget(target) -} - -// SetWasmBulkMemory configures whether the wasm bulk memory proposal is enabled. -func (c *Config) SetWasmBulkMemory(enabled bool) { - c.wasmConfig.SetWasmBulkMemory(enabled) -} - -// SetWasmMemory64 configures whether the wasm memory64 proposal is enabled. -func (c *Config) SetWasmMemory64(enabled bool) { - c.wasmConfig.SetWasmMemory64(enabled) -} - -// SetWasmMultiMemory configures whether the wasm multi memory proposal is enabled. -func (c *Config) SetWasmMultiMemory(enabled bool) { - c.wasmConfig.SetWasmMultiMemory(enabled) -} - -// SetWasmMultiValue configures whether the wasm multi value proposal is enabled. -func (c *Config) SetWasmMultiValue(enabled bool) { - c.wasmConfig.SetWasmMultiValue(enabled) -} - -// SetWasmReferenceTypes configures whether the wasm reference types proposal is enabled. -func (c *Config) SetWasmReferenceTypes(enabled bool) { - c.wasmConfig.SetWasmReferenceTypes(enabled) -} - -// SetWasmSIMD configures whether the wasm SIMD proposal is enabled. -func (c *Config) SetWasmSIMD(enabled bool) { - c.wasmConfig.SetWasmSIMD(enabled) -} - -// SetWasmThreads configures whether the wasm threads proposal is enabled. -func (c *Config) SetWasmThreads(enabled bool) { - c.wasmConfig.SetWasmThreads(enabled) -} - -// DefaultWasmtimeConfig returns a new wasmtime config with default settings. -func DefaultWasmtimeConfig() *wasmtime.Config { - cfg := wasmtime.NewConfig() - - // non configurable defaults - cfg.SetCraneliftOptLevel(defaultCraneliftOptLevel) - cfg.SetConsumeFuel(defaultFuelMetering) - cfg.SetWasmThreads(defaultWasmThreads) - cfg.SetWasmMultiMemory(defaultWasmMultiMemory) - cfg.SetWasmMemory64(defaultWasmMemory64) - cfg.SetStrategy(defaultCompilerStrategy) - cfg.SetEpochInterruption(defaultEpochInterruption) - cfg.SetCraneliftFlag("enable_nan_canonicalization", defaultNaNCanonicalization) - - // TODO: expose these knobs for developers - cfg.SetCraneliftDebugVerifier(defaultEnableCraneliftDebugVerifier) - cfg.SetDebugInfo(defaultEnableDebugInfo) - return cfg -} - -// NewConfigBuilder returns a new engine configuration builder with default settings. -// All instances of ConfigBuilder should be created with this constructor. -func NewConfigBuilder() *ConfigBuilder { - return &ConfigBuilder{ - EnableBulkMemory: DefaultEnableBulkMemory, - EnableWasmMultiValue: DefaultMultiValue, - EnableWasmReferenceTypes: DefaultEnableReferenceTypes, - EnableWasmSIMD: DefaultSIMD, - MaxWasmStack: DefaultMaxWasmStack, - ProfilingStrategy: DefaultProfilingStrategy, - EnableDefaultCache: false, - } -} - -type ConfigBuilder struct { - // Configures whether the WebAssembly bulk memory operations proposal will - // be enabled for compilation. This feature gates items such as the - // memory.copy instruction, passive data/table segments, etc, being in a - // module. - // This is false by default. - EnableBulkMemory bool `json:"enableBulkMemory,omitempty" yaml:"enable_bulk_memory,omitempty"` - // Configures whether the WebAssembly multi-value proposal will be enabled for compilation. - // This feature gates functions and blocks returning multiple values in a module, for example. - // This is false by default. - EnableWasmMultiValue bool `json:"enableWasmMultiValue,omitempty" yaml:"enable_wasm_multi_value,omitempty"` - // Configures whether the WebAssembly reference types proposal will be - // enabled for compilation. This feature gates items such as the externref - // and funcref types as well as allowing a module to define multiple tables. - // Note that the reference types proposal depends on the bulk memory - // proposal. - // This is false by default. - EnableWasmReferenceTypes bool `json:"enableWasmReferenceTypes,omitempty" yaml:"enable_wasm_reference_types,omitempty"` - // Configures whether the WebAssembly SIMD proposal will be enabled for - // compilation. The WebAssembly SIMD proposal. This feature gates items - // such as the v128 type and all of its operators being in a module. Note - // that this does not enable the relaxed simd proposal. - // This is false by default. - EnableWasmSIMD bool `json:"enableWasmSIMD,omitempty" yaml:"enable_wasm_simd,omitempty"` - // EnableDefaultCache enables compiled code caching for this `Config` using the default settings - // configuration can be found. - // - // For more information about caching see - // https://bytecodealliance.github.io/wasmtime/cli-cache.html - // This is false by default. - EnableDefaultCache bool `json:"enableDefaultCache,omitempty" yaml:"enable_default_cache,omitempty"` - // SetMaxWasmStack configures the maximum stack size, in bytes, that JIT code can use. - // The amount of stack space that wasm takes is always relative to the first invocation of wasm on the stack. - // Recursive calls with host frames in the middle will all need to fit within this setting. - // Note that this setting is not interpreted with 100% precision. - // This is 256 MiB by default. - MaxWasmStack int `json:"maxWasmStack,omitempty" yaml:"max_wasm_stack,omitempty"` - // ProfilingStrategy decides what sort of profiling to enable, if any. - // Default is `wasmtime.ProfilingStrategyNone`. - ProfilingStrategy wasmtime.ProfilingStrategy -} - -// WithMaxWasmStack defines the maximum amount of stack space available for -// executing WebAssembly code. -// -// Default is 256 MiB. -func (c *ConfigBuilder) WithMaxWasmStack(max int) *ConfigBuilder { - c.MaxWasmStack = max - return c -} - -// WithMultiValue enables modules that can return multiple values. -// ref. https://github.com/webassembly/multi-value -// -// Default is false. -func (c *ConfigBuilder) WithMultiValue(enable bool) *ConfigBuilder { - c.EnableWasmMultiValue = enable - return c -} - -// WithBulkMemory enables `memory.copy` instruction, tables and passive data. -// ref. https://github.com/WebAssembly/bulk-memory-operations -// -// Default is false. -func (c *ConfigBuilder) WithBulkMemory(enable bool) *ConfigBuilder { - c.EnableBulkMemory = enable - return c -} - -// WithReferenceTypes Enables the `externref` and `funcref` types as well as -// allowing a module to define multiple tables. -// ref. https://github.com/webassembly/reference-types -// -// Note: depends on bulk memory being enabled. -// -// Default is false. -func (c *ConfigBuilder) WithReferenceTypes(enable bool) *ConfigBuilder { - c.EnableWasmReferenceTypes = enable - return c -} - -// WithSIMD enables SIMD instructions including v128. -// ref. https://github.com/webassembly/simd -// -// Default is false. -func (c *ConfigBuilder) WithSIMD(enable bool) *ConfigBuilder { - c.EnableWasmSIMD = enable - return c -} - -// WithProfilingStrategy defines the profiling strategy used for defining the -// default profiler. -// -// Default is `wasmtime.ProfilingStrategyNone`. -func (c *ConfigBuilder) WithProfilingStrategy(strategy wasmtime.ProfilingStrategy) *ConfigBuilder { - c.ProfilingStrategy = strategy - return c -} - -// WithDefaultCache enables the default caching strategy. -// -// Default is false. -func (c *ConfigBuilder) WithDefaultCache(enabled bool) *ConfigBuilder { - c.EnableDefaultCache = enabled - return c -} - -func (c *ConfigBuilder) Build() (*Config, error) { - cfg := NewConfig() - cfg.SetWasmBulkMemory(c.EnableBulkMemory) - cfg.SetWasmMultiValue(c.EnableWasmMultiValue) - cfg.SetWasmReferenceTypes(c.EnableWasmReferenceTypes) - cfg.SetWasmSIMD(c.EnableWasmSIMD) - cfg.SetMaxWasmStack(c.MaxWasmStack) - cfg.SetProfiler(c.ProfilingStrategy) - if c.EnableDefaultCache { - if err := cfg.CacheConfigLoadDefault(); err != nil { - return nil, err - } - } - - return cfg, nil -} diff --git a/x/programs/engine/engine.go b/x/programs/engine/engine.go deleted file mode 100644 index 9891381b74..0000000000 --- a/x/programs/engine/engine.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package engine - -import ( - "errors" - - "github.com/bytecodealliance/wasmtime-go/v14" -) - -// Engine is a wrapper around a wasmtime.Engine and manages the lifecycle of a -// programs execution. It is expected that a single engine can have multiple -// stores. For example in the case of program to program calls. -type Engine struct { - wasmEngine *wasmtime.Engine -} - -// New creates a new Wasm engine. -func New(cfg *Config) *Engine { - return &Engine{ - wasmEngine: wasmtime.NewEngineWithConfig(cfg.Get()), - } -} - -// Stop will increase the current epoch number by 1 within the current -// engine which will cause any connected stores to be interrupted. -func (e *Engine) Stop() { - e.wasmEngine.IncrementEpoch() -} - -// PreCompileModule will deserialize a precompiled module. -func (e *Engine) PreCompileModule(bytes []byte) (*wasmtime.Module, error) { - // Note: that to deserialize successfully the bytes provided must have been - // produced with an `Engine` that has the same compilation options as the - // provided engine, and from the same version of this library. - // - // A precompile is not something we would store on chain. - // Instead we would prefetch programs and precompile them. - return wasmtime.NewModuleDeserialize(e.wasmEngine, bytes) -} - -// CompileModule will compile a module. -func (e *Engine) CompileModule(bytes []byte) (*wasmtime.Module, error) { - return wasmtime.NewModule(e.wasmEngine, bytes) -} - -// PreCompileWasm returns a precompiled wasm module. -// -// Note: these bytes can be deserialized by an `Engine` that has the same version. -// For that reason precompiled wasm modules should not be stored on chain. -func PreCompileWasmBytes(engine *Engine, programBytes []byte, limitMaxMemory uint32) ([]byte, error) { - store := NewStore(engine, NewStoreConfig().SetLimitMaxMemory(limitMaxMemory)) - module, err := wasmtime.NewModule(store.GetEngine(), programBytes) - if err != nil { - return nil, err - } - - return module.Serialize() -} - -// NewModule creates a new wasmtime module and handles the Wasm bytes based on compile strategy. -func NewModule(engine *Engine, bytes []byte, strategy CompileStrategy) (*wasmtime.Module, error) { - switch strategy { - case CompileWasm: - return engine.CompileModule(bytes) - case PrecompiledWasm: - return engine.PreCompileModule(bytes) - default: - return nil, errors.New("unknown compile strategy") - } -} diff --git a/x/programs/engine/engine_test.go b/x/programs/engine/engine_test.go deleted file mode 100644 index d0724f494b..0000000000 --- a/x/programs/engine/engine_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package engine - -import ( - "testing" - - "github.com/stretchr/testify/require" - - _ "embed" - - "github.com/ava-labs/hypersdk/x/programs/tests" -) - -// go test -v -benchmem -run=^$ -bench ^BenchmarkCompileModule$ github.com/ava-labs/hypersdk/x/programs/engine -memprofile benchvset.mem -cpuprofile benchvset.cpu -func BenchmarkCompileModule(b *testing.B) { - wasmBytes := tests.ReadFixture(b, "../tests/fixture/token.wasm") - require := require.New(b) - eng := New(NewConfig()) - b.Run("benchmark_compile_wasm_no_cache", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := eng.CompileModule(wasmBytes) - require.NoError(err) - } - }) - - cfg := NewConfig() - err := cfg.CacheConfigLoadDefault() - require.NoError(err) - eng = New(cfg) - require.NoError(err) - b.Run("benchmark_compile_wasm_with_cache", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := eng.CompileModule(wasmBytes) - require.NoError(err) - } - }) -} diff --git a/x/programs/engine/store.go b/x/programs/engine/store.go deleted file mode 100644 index e8bd12f264..0000000000 --- a/x/programs/engine/store.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package engine - -import ( - "errors" - "fmt" - - "github.com/bytecodealliance/wasmtime-go/v14" -) - -const ( - NoUnits = uint64(0) - - defaultLimitMaxTableElements = 4096 - defaultLimitMaxTables = 1 - defaultLimitMaxInstances = 32 - defaultLimitMaxMemories = 1 -) - -var ( - ErrInsufficientUnits = errors.New("insufficient units") - ErrMeteredStore = errors.New("store is already metered") -) - -// NewStore creates a new engine store. -func NewStore(e *Engine, cfg *StoreConfig) *Store { - wasmStore := wasmtime.NewStore(e.wasmEngine) - wasmStore.Limiter( - int64(cfg.limitMaxMemory), - cfg.limitMaxTableElements, - cfg.limitMaxInstances, - cfg.limitMaxTables, - cfg.limitMaxMemories, - ) - // set initial epoch deadline to 1. This ensures that any stop calls to the - // engine will affect this store. - wasmStore.SetEpochDeadline(1) - - return &Store{wasmStore: wasmStore} -} - -// Store is a wrapper around a wasmtime.Store. -type Store struct { - wasmStore *wasmtime.Store - // metered indicates whether this store is metered or not. - metered bool - // maxUnits is the maximum number of units that can be consumed by this store. - maxUnits uint64 -} - -// SetEpochDeadline will configure the relative deadline, from the current -// engine's epoch number, after which wasm code will be interrupted. -func (s *Store) SetEpochDeadline(epochDeadline uint64) { - s.wasmStore.SetEpochDeadline(epochDeadline) -} - -// GetEngine returns the underlying wasmtime engine associated with this store. -func (s *Store) GetEngine() *wasmtime.Engine { - return s.wasmStore.Engine -} - -// UnitsConsumed returns the amount of fuel consumed by this store. -func (s *Store) UnitsConsumed() (uint64, bool) { - return s.wasmStore.FuelConsumed() -} - -// ConsumeUnits will consume the provided units. -func (s *Store) ConsumeUnits(units uint64) (uint64, error) { - return s.wasmStore.ConsumeFuel(units) -} - -// GetMaxUnits returns the maximum number of units that can be consumed by this store. -func (s *Store) GetMaxUnits() uint64 { - return s.maxUnits -} - -// GetBalanceUnits returns the balance of the maximum number of units that can -// be consumed by this store and the number of units consumed. -func (s *Store) GetBalanceUnits() (uint64, error) { - consumed, ok := s.UnitsConsumed() - if !ok { - return 0, errors.New("failed to get units consumed: metering not enabled") - } - return (s.maxUnits - consumed), nil -} - -// AddUnits will add the provided units to the meter. -func (s *Store) AddUnits(units uint64) error { - s.maxUnits += units - return s.wasmStore.AddFuel(units) -} - -// SetWasi will configure the Wasi configuration for this store. -func (s *Store) SetWasi(cfg *wasmtime.WasiConfig) { - s.wasmStore.SetWasi(cfg) -} - -// Get returns the underlying wasmtime store. -func (s *Store) Get() *wasmtime.Store { - return s.wasmStore -} - -// NewStoreConfig returns a new store config. -func NewStoreConfig() *StoreConfig { - return &StoreConfig{ - limitMaxMemory: DefaultLimitMaxMemory, - limitMaxTableElements: defaultLimitMaxTableElements, - limitMaxTables: defaultLimitMaxTables, - limitMaxInstances: defaultLimitMaxInstances, - limitMaxMemories: defaultLimitMaxMemories, - } -} - -// StoreConfig is the configuration for an engine store. -type StoreConfig struct { - limitMaxMemory uint32 - limitMaxTableElements int64 - limitMaxTables int64 - limitMaxInstances int64 - limitMaxMemories int64 -} - -// SetLimitMaxMemory sets the limit max memory. -func (c *StoreConfig) SetLimitMaxMemory(limitMaxMemory uint32) *StoreConfig { - c.limitMaxMemory = limitMaxMemory - return c -} - -// NewMeter returns a new meter. A store can only be registered to a single -// meter. -func NewMeter(store *Store) (*Meter, error) { - if store.metered { - return nil, ErrMeteredStore - } - store.metered = true - return &Meter{ - store: store, - }, nil -} - -// Meter is an abstraction of a store. -type Meter struct { - store *Store -} - -// GetBalance returns the balance in units of the meter. -func (m *Meter) GetBalance() (uint64, error) { - return m.store.GetBalanceUnits() -} - -// Spend will spend the provided units from the meter. If the balance is less -// than the provided units, the balance will be spent. -func (m *Meter) Spend(units uint64) (uint64, error) { - balance, err := m.GetBalance() - if err != nil { - return 0, err - } - if balance < units { - _, err := m.store.ConsumeUnits(balance) - if err != nil { - return 0, fmt.Errorf("%w: %w", ErrInsufficientUnits, err) - } - return 0, ErrInsufficientUnits - } - return m.store.ConsumeUnits(units) -} - -func (m *Meter) addUnits(units uint64) error { - return m.store.AddUnits(units) -} - -// TransferUnitsTo moves units from this meter to another meter. -func (m *Meter) TransferUnitsTo(to *Meter, units uint64) (uint64, error) { - // spend units from this meter - _, err := m.Spend(units) - if err != nil { - return 0, err - } - // add units to the other meter - err = to.addUnits(units) - if err != nil { - return 0, err - } - return m.GetBalance() -} diff --git a/x/programs/engine/store_test.go b/x/programs/engine/store_test.go deleted file mode 100644 index 32be16d4c4..0000000000 --- a/x/programs/engine/store_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package engine - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -// go test -v -benchmem -run=^$ -bench ^BenchmarkNewStore$ github.com/ava-labs/hypersdk/x/programs/engine -memprofile benchvset.mem -cpuprofile benchvset.cpu -func BenchmarkNewStore(b *testing.B) { - eng := New(NewConfig()) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = NewStore(eng, NewStoreConfig()) - } -} - -func TestMeteredStore(t *testing.T) { - require := require.New(t) - eng := New(NewConfig()) - // create a store - maxUnits := uint64(100) - store1 := NewStore(eng, NewStoreConfig()) - // add units to the store - err := store1.AddUnits(maxUnits) - require.NoError(err) - // ensure balance is correct - units, err := store1.GetBalanceUnits() - require.NoError(err) - require.Equal(maxUnits, units) - // create a meter - meter1, err := NewMeter(store1) - require.NoError(err) - // ensure balance is correct - balance, err := meter1.GetBalance() - require.NoError(err) - require.Equal(maxUnits, balance) - // add units to the store - err = store1.AddUnits(maxUnits) - require.NoError(err) - // ensure balance is correct - units, err = store1.GetBalanceUnits() - require.NoError(err) - require.Equal(maxUnits*2, units) - // spend units from the store - balance, err = store1.ConsumeUnits(maxUnits) - require.NoError(err) - require.Equal(maxUnits, balance) - // ensure store can not be registered to two meters - _, err = NewMeter(store1) - require.ErrorIs(err, ErrMeteredStore) - // create a new store with same engine - store2 := NewStore(eng, NewStoreConfig()) - // ensure balance is correct - units, err = store2.GetBalanceUnits() - require.NoError(err) - require.Equal(NoUnits, units) - // create second meter - meter2, err := NewMeter(store2) - require.NoError(err) - // transfer balance from meter 1 to meter 2 - balance, err = meter1.TransferUnitsTo(meter2, maxUnits) - require.NoError(err) - require.Equal(NoUnits, balance) - // ensure balance is correct - balance, err = meter2.GetBalance() - require.NoError(err) - require.Equal(maxUnits, balance) - // attempt to overspend - balance, err = meter2.Spend(maxUnits * 2) - require.ErrorIs(err, ErrInsufficientUnits) - // ensure balance is now zero - require.Equal(NoUnits, balance) -} diff --git a/x/programs/examples/imports/program/program.go b/x/programs/examples/imports/program/program.go deleted file mode 100644 index b7c051689f..0000000000 --- a/x/programs/examples/imports/program/program.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import ( - "context" - "encoding/binary" - "errors" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/bytecodealliance/wasmtime-go/v14" - "github.com/near/borsh-go" - "go.uber.org/zap" - - "github.com/ava-labs/hypersdk/consts" - "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/examples/storage" - "github.com/ava-labs/hypersdk/x/programs/host" - "github.com/ava-labs/hypersdk/x/programs/program" - "github.com/ava-labs/hypersdk/x/programs/runtime" -) - -var _ host.Import = (*Import)(nil) - -const Name = "program" - -type Import struct { - mu state.Mutable - log logging.Logger - cfg *runtime.Config - - engine *engine.Engine - meter *engine.Meter - imports host.SupportedImports - ctx *program.Context -} - -// New returns a new program invoke host module which can perform program to program calls. -func New(log logging.Logger, engine *engine.Engine, mu state.Mutable, cfg *runtime.Config, ctx *program.Context) *Import { - return &Import{ - cfg: cfg, - mu: mu, - log: log, - engine: engine, - ctx: ctx, - } -} - -func (*Import) Name() string { - return Name -} - -func (i *Import) Register(link *host.Link, callContext program.Context) error { - i.meter = link.Meter() - i.imports = link.Imports() - return link.RegisterImportFn(Name, "call_program", i.callProgramFn(callContext)) -} - -type callProgramFnArgs struct { - ProgramID []byte - Function []byte - Args []byte - MaxUnits int64 -} - -// callProgramFn makes a call to an entry function of a program in the context of another program's ID. -func (i *Import) callProgramFn(callContext program.Context) func(*wasmtime.Caller, int32, int32) int64 { - return func( - wasmCaller *wasmtime.Caller, - memOffset int32, - size int32, - ) int64 { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - caller := program.NewCaller(wasmCaller) - memory, err := caller.Memory() - if err != nil { - i.log.Error("failed to get memory from caller", - zap.Error(err), - ) - return -1 - } - - bytes, err := memory.Range(uint32(memOffset), uint32(size)) - if err != nil { - i.log.Error("failed to read call arguments from memory", - zap.Error(err), - ) - return -1 - } - - args := callProgramFnArgs{} - if err := borsh.Deserialize(&args, bytes); err != nil { - i.log.Error("failed to unmarshal call arguments", - - zap.Error(err), - ) - return -1 - } - - // get the program bytes from storage - programWasmBytes, err := getProgramWasmBytes(i.log, i.mu, args.ProgramID) - if err != nil { - i.log.Error("failed to get program bytes from storage", - zap.Error(err), - ) - return -1 - } - - // create a new runtime for the program to be invoked with a zero balance. - rt := runtime.New(i.log, i.engine, i.imports, i.cfg) - err = rt.Initialize(context.Background(), callContext, programWasmBytes, engine.NoUnits) - if err != nil { - i.log.Error("failed to initialize runtime", - zap.Error(err), - ) - return -1 - } - - // transfer the units from the caller to the new runtime before any calls are made. - balance, err := i.meter.TransferUnitsTo(rt.Meter(), uint64(args.MaxUnits)) - if err != nil { - i.log.Error("failed to transfer units", - zap.Uint64("balance", balance), - zap.Int64("required", args.MaxUnits), - zap.Error(err), - ) - return -1 - } - - // transfer remaining balance back to parent runtime - defer func() { - balance, err := rt.Meter().GetBalance() - if err != nil { - i.log.Error("failed to get balance from runtime", - zap.Error(err), - ) - return - } - _, err = rt.Meter().TransferUnitsTo(i.meter, balance) - if err != nil { - i.log.Error("failed to transfer remaining balance to caller", - zap.Error(err), - ) - } - }() - - rtMemory, err := rt.Memory() - if err != nil { - i.log.Error("failed to get memory from runtime", - zap.Error(err), - ) - return -1 - } - - // sync args to new runtime and return arguments to the invoke call - params, err := getCallArgs(ctx, rtMemory, args.Args) - if err != nil { - i.log.Error("failed to unmarshal call arguments", - zap.Error(err), - ) - return -1 - } - - functionName := string(args.Function) - res, err := rt.Call(ctx, functionName, program.Context{ - ProgramID: ids.ID(args.ProgramID), - // Actor: callContext.ProgramID, - // OriginatingActor: callContext.OriginatingActor, - }, params...) - if err != nil { - i.log.Error("failed to call entry function", - zap.Error(err), - ) - return -1 - } - - return res[0] - } -} - -// getCallArgs returns the arguments to be passed to the program being invoked from [buffer]. -func getCallArgs(_ context.Context, memory *program.Memory, buffer []byte) ([]uint32, error) { - var args []uint32 - - for i := 0; i < len(buffer); { - // unpacks uint32 - lenBytes := buffer[i : i+consts.Uint32Len] - length := binary.BigEndian.Uint32(lenBytes) - - valueBytes := buffer[i+consts.Uint32Len : i+consts.Uint32Len+int(length)] - i += int(length) + consts.Uint32Len - - // every argument is a pointer - ptr, err := program.WriteBytes(memory, valueBytes) - if err != nil { - return nil, err - } - args = append(args, ptr) - } - - return args, nil -} - -func getProgramWasmBytes(log logging.Logger, db state.Immutable, idBytes []byte) ([]byte, error) { - programID, err := ids.ToID(idBytes) - if err != nil { - return nil, err - } - - // get the program bytes from storage - bytes, exists, err := storage.GetProgram(context.Background(), db, programID) - if !exists { - log.Debug("key does not exist", zap.Stringer("id", programID)) - return nil, errors.New("unknown program") - } - if err != nil { - return nil, err - } - - return bytes, nil -} diff --git a/x/programs/examples/imports/pstate/pstate.go b/x/programs/examples/imports/pstate/pstate.go deleted file mode 100644 index 498afc72b1..0000000000 --- a/x/programs/examples/imports/pstate/pstate.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package pstate - -import ( - "context" - "errors" - - "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/near/borsh-go" - "go.uber.org/zap" - - "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/examples/imports/wrap" - "github.com/ava-labs/hypersdk/x/programs/examples/storage" - "github.com/ava-labs/hypersdk/x/programs/host" - "github.com/ava-labs/hypersdk/x/programs/program" - "github.com/ava-labs/hypersdk/x/programs/program/types" -) - -var _ host.Import = (*Import)(nil) - -const Name = "state" - -// New returns a program storage module capable of storing arbitrary bytes -// in the program's namespace. -func New(log logging.Logger, mu state.Mutable) host.Import { - return &Import{mu: mu, log: log} -} - -type Import struct { - mu state.Mutable - log logging.Logger - meter *engine.Meter -} - -func (*Import) Name() string { - return Name -} - -func (i *Import) Register(link *host.Link, _ program.Context) error { - i.meter = link.Meter() - wrap := wrap.New(link) - if err := wrap.RegisterAnyParamFn(Name, "put", 2, i.putFnVariadic); err != nil { - return err - } - if err := wrap.RegisterAnyParamFn(Name, "get", 2, i.getFnVariadic); err != nil { - return err - } - - return wrap.RegisterAnyParamFn(Name, "delete", 2, i.deleteFnVariadic) -} - -func (i *Import) putFnVariadic(caller *program.Caller, args ...int32) (*types.Val, error) { - if len(args) != 2 { - return nil, errors.New("expected 2 arguments") - } - return i.putFn(caller, args[0], args[1]) -} - -func (i *Import) getFnVariadic(caller *program.Caller, args ...int32) (*types.Val, error) { - if len(args) != 2 { - return nil, errors.New("expected 2 arguments") - } - return i.getFn(caller, args[0], args[1]) -} - -func (i *Import) deleteFnVariadic(caller *program.Caller, args ...int32) (*types.Val, error) { - if len(args) != 2 { - return nil, errors.New("expected 2 arguments") - } - return i.deleteFn(caller, args[0], args[1]) -} - -type putArgs struct { - ProgramID [32]byte - Key []byte - Value []byte -} - -func (i *Import) putFn(caller *program.Caller, memOffset int32, size int32) (*types.Val, error) { - memory, err := caller.Memory() - if err != nil { - i.log.Error("failed to get memory from caller", - zap.Error(err), - ) - return nil, err - } - - bytes, err := memory.Range(uint32(memOffset), uint32(size)) - if err != nil { - i.log.Error("failed to read args from program memory", - zap.Error(err), - ) - return nil, err - } - - args := putArgs{} - err = borsh.Deserialize(&args, bytes) - if err != nil { - i.log.Error("failed to deserialize args", - zap.Error(err), - ) - return nil, err - } - - k := storage.ProgramPrefixKey(args.ProgramID[:], args.Key) - err = i.mu.Insert(context.Background(), k, args.Value) - if err != nil { - i.log.Error("failed to insert into storage", - zap.Error(err), - ) - return nil, err - } - - return types.ValI32(0), nil -} - -type getAndDeleteArgs struct { - ProgramID [32]byte - Key []byte -} - -func (i *Import) getFn(caller *program.Caller, memOffset int32, size int32) (*types.Val, error) { - memory, err := caller.Memory() - if err != nil { - i.log.Error("failed to get memory from caller", - zap.Error(err), - ) - return nil, err - } - - bytes, err := memory.Range(uint32(memOffset), uint32(size)) - if err != nil { - i.log.Error("failed to read args from program memory", - zap.Error(err), - ) - return nil, err - } - - args := getAndDeleteArgs{} - err = borsh.Deserialize(&args, bytes) - if err != nil { - i.log.Error("failed to deserialize args", - zap.Error(err), - ) - return nil, err - } - - k := storage.ProgramPrefixKey(args.ProgramID[:], args.Key) - val, err := i.mu.GetValue(context.Background(), k) - if err != nil { - if errors.Is(err, database.ErrNotFound) { - // TODO: return a more descriptive error - return types.ValI32(-1), nil - } - i.log.Error("failed to get value from storage", - zap.Error(err), - ) - return nil, err - } - - if err != nil { - i.log.Error("failed to convert program id to id", - zap.Error(err), - ) - return nil, err - } - - valPtr, err := program.WriteBytes(memory, val) - if err != nil { - { - i.log.Error("failed to write to memory", - zap.Error(err), - ) - } - return nil, err - } - _, err = memory.Range(valPtr, uint32(len(val))) - if err != nil { - i.log.Error("failed to convert ptr to argument", - zap.Error(err), - ) - return nil, err - } - - return types.ValI32(int32(valPtr)), nil -} - -func (i *Import) deleteFn(caller *program.Caller, memOffset int32, size int32) (*types.Val, error) { - memory, err := caller.Memory() - if err != nil { - i.log.Error("failed to get memory from caller", - zap.Error(err), - ) - return nil, err - } - - bytes, err := memory.Range(uint32(memOffset), uint32(size)) - if err != nil { - i.log.Error("failed to read args from program memory", - zap.Error(err), - ) - return nil, err - } - - args := getAndDeleteArgs{} - err = borsh.Deserialize(&args, bytes) - if err != nil { - i.log.Error("failed to deserialize args", - zap.Error(err), - ) - return nil, err - } - - k := storage.ProgramPrefixKey(args.ProgramID[:], args.Key) - bytes, err = i.mu.GetValue(context.Background(), k) - if err != nil { - if errors.Is(err, database.ErrNotFound) { - // [0] represents `None` - val, err := program.WriteBytes(memory, []byte{0}) - if err != nil { - i.log.Error("failed to write to memory", - zap.Error(err), - ) - return nil, err - } - - return types.ValI32(int32(val)), nil - } - - i.log.Error("failed to get value from storage", - zap.Error(err), - ) - return nil, err - } - - // 1 is the discriminant for `Some` - bytes = append([]byte{1}, bytes...) - - ptr, err := program.WriteBytes(memory, bytes) - if err != nil { - { - i.log.Error("failed to write to memory", - zap.Error(err), - ) - } - return nil, err - } - - if err := i.mu.Remove(context.Background(), k); err != nil { - i.log.Error("failed to delete value from storage", - zap.Error(err), - ) - return nil, err - } - - return types.ValI32(int32(ptr)), nil -} diff --git a/x/programs/examples/imports/wrap/wrap.go b/x/programs/examples/imports/wrap/wrap.go deleted file mode 100644 index 2b0eec99ac..0000000000 --- a/x/programs/examples/imports/wrap/wrap.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package wrap - -import ( - "errors" - - "github.com/bytecodealliance/wasmtime-go/v14" - - "github.com/ava-labs/hypersdk/x/programs/host" - "github.com/ava-labs/hypersdk/x/programs/program" - "github.com/ava-labs/hypersdk/x/programs/program/types" -) - -// New returns a new import wrap helper. -func New(link *host.Link) *Wrap { - return &Wrap{ - link: link, - } -} - -// Wrap is a helper for registering import functions. -type Wrap struct { - link *host.Link -} - -// RegisterAnyParamFn is a helper method for registering a function with one int64 parameter. -func (w *Wrap) RegisterAnyParamFn(name, module string, paramCount int, fn AnyParamFn) error { - return w.link.RegisterImportWrapFn(name, module, paramCount, NewImportFn[AnyParamFn](fn)) -} - -// AnyParamFn is a generic type that satisfies AnyParamFnType -type AnyParamFn func(*program.Caller, ...int32) (*types.Val, error) - -// ImportFn is a generic type that satisfies ImportFnType -type ImportFn[F AnyParamFn] struct { - fn F -} - -// Invoke calls the underlying function with the given arguments. Currently only -// supports int32 arguments and return values. -func (i ImportFn[F]) Invoke(c *program.Caller, args ...int32) (*types.Val, error) { - switch fn := any(i.fn).(type) { - case AnyParamFn: - return fn.Call(c, args...) - default: - return nil, errors.New("unsupported function type") - } -} - -func (fn AnyParamFn) Call(c *program.Caller, args ...int32) (*types.Val, error) { - return fn(c, args...) -} - -func NewImportFn[F AnyParamFn](src F) func(caller *program.Caller, wargs ...wasmtime.Val) (*types.Val, error) { - importFn := ImportFn[F]{fn: src} - fn := func(c *program.Caller, wargs ...wasmtime.Val) (*types.Val, error) { - args := make([]int32, 0, len(wargs)) - for _, arg := range wargs { - args = append(args, arg.I32()) - } - return importFn.Invoke(c, args...) - } - return fn -} diff --git a/x/programs/examples/storage/storage.go b/x/programs/examples/storage/storage.go deleted file mode 100644 index 2c64e3a3f6..0000000000 --- a/x/programs/examples/storage/storage.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package storage - -import ( - "context" - "errors" - - "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/avalanchego/ids" - - "github.com/ava-labs/hypersdk/state" -) - -const ( - programPrefix = 0x0 - ProgramChunks uint16 = 1 -) - -func ProgramPrefixKey(id []byte, key []byte) (k []byte) { - k = make([]byte, ids.IDLen+1+len(key)) - k[0] = programPrefix - copy(k, id) - copy(k[ids.IDLen:], (key)) - return -} - -// -// Program -// - -func ProgramKey(id ids.ID) (k []byte) { - k = make([]byte, 1+ids.IDLen) - copy(k[1:], id[:]) - return -} - -// [programID] -> [programBytes] -func GetProgram( - ctx context.Context, - db state.Immutable, - programID ids.ID, -) ( - []byte, // program bytes - bool, // exists - error, -) { - k := ProgramKey(programID) - v, err := db.GetValue(ctx, k) - if errors.Is(err, database.ErrNotFound) { - return nil, false, nil - } - if err != nil { - return nil, false, err - } - return v, true, nil -} - -// SetProgram stores [program] at [programID] -func SetProgram( - ctx context.Context, - mu state.Mutable, - programID ids.ID, - program []byte, -) error { - k := ProgramKey(programID) - return mu.Insert(ctx, k, program) -} diff --git a/x/programs/examples/token.go b/x/programs/examples/token.go deleted file mode 100644 index 6617e6d54f..0000000000 --- a/x/programs/examples/token.go +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package examples - -import ( - "context" - "fmt" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/near/borsh-go" - "go.uber.org/zap" - - "github.com/ava-labs/hypersdk/crypto/ed25519" - "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/examples/storage" - "github.com/ava-labs/hypersdk/x/programs/host" - "github.com/ava-labs/hypersdk/x/programs/program" - "github.com/ava-labs/hypersdk/x/programs/runtime" -) - -type TokenStateKey uint8 - -const ( - /// The total supply of the token. Key prefix 0x0. - TotalSupply TokenStateKey = iota - /// The name of the token. Key prefix 0x1. - Name - /// The symbol of the token. Key prefix 0x2. - Symbol - /// The balance of the token by address. Key prefix 0x3 + address. - Balance -) - -func NewToken(programID ids.ID, log logging.Logger, engine *engine.Engine, programBytes []byte, db state.Mutable, cfg *runtime.Config, imports host.SupportedImports, maxUnits uint64) *Token { - return &Token{ - programID: programID, - log: log, - programBytes: programBytes, - cfg: cfg, - imports: imports, - db: db, - maxUnits: maxUnits, - engine: engine, - } -} - -type minter struct { - // TODO: use a HyperSDK.Address instead - To ed25519.PublicKey - // note: a production program would use a uint64 for amount - Amount int32 -} - -type Token struct { - programID ids.ID - log logging.Logger - programBytes []byte - cfg *runtime.Config - imports host.SupportedImports - db state.Mutable - maxUnits uint64 - engine *engine.Engine -} - -func (t *Token) Context() program.Context { - return program.Context{ - ProgramID: t.programID, - } -} - -func (t *Token) ProgramID() ids.ID { - return t.programID -} - -func (t *Token) Run(ctx context.Context) error { - rt := runtime.New(t.log, t.engine, t.imports, t.cfg) - programContext := t.Context() - - err := rt.Initialize(ctx, programContext, t.programBytes, t.maxUnits) - if err != nil { - return err - } - - balance, err := rt.Meter().GetBalance() - if err != nil { - return err - } - - t.log.Debug("initial meter", - zap.Uint64("balance", balance), - ) - - // simulate create program transaction - programID := t.ProgramID() - err = storage.SetProgram(ctx, t.db, programID, t.programBytes) - if err != nil { - return err - } - - mem, err := rt.Memory() - if err != nil { - return err - } - - t.log.Debug("new token program created", - zap.String("id", programID.String()), - ) - - // initialize program - resp, err := rt.Call(ctx, "init", programContext) - if err != nil { - return fmt.Errorf("failed to initialize program: %w", err) - } - - t.log.Debug("init response", - zap.Int64("init", resp[0]), - ) - - result, err := rt.Call(ctx, "get_total_supply", programContext) - if err != nil { - return err - } - t.log.Debug("total supply", - zap.Int64("minted", result[0]), - ) - - // generate alice keys - alicePublicKey, err := newKey() - if err != nil { - return err - } - - // write alice's key to stack and get pointer - alicePtr, err := writeToMem(alicePublicKey, mem) - if err != nil { - return err - } - - // generate bob keys - bobPublicKey, err := newKey() - if err != nil { - return err - } - - // write bob's key to stack and get pointer - bobPtr, err := writeToMem(bobPublicKey, mem) - if err != nil { - return err - } - - // check balance of bob - result, err = rt.Call(ctx, "get_balance", programContext, bobPtr) - if err != nil { - return err - } - t.log.Debug("balance", - zap.Int64("bob", result[0]), - ) - - // mint 100 tokens to alice - mintAlice := int64(1000) - mintAlicePtr, err := writeToMem(mintAlice, mem) - if err != nil { - return err - } - - _, err = rt.Call(ctx, "mint_to", programContext, alicePtr, mintAlicePtr) - if err != nil { - return err - } - t.log.Debug("minted", - zap.Int64("alice", mintAlice), - ) - - alicePtr, err = writeToMem(alicePublicKey, mem) - if err != nil { - return err - } - - // check balance of alice - result, err = rt.Call(ctx, "get_balance", programContext, alicePtr) - if err != nil { - return err - } - t.log.Debug("balance", - zap.Int64("alice", result[0]), - ) - - bobPtr, err = writeToMem(bobPublicKey, mem) - if err != nil { - return err - } - - // check balance of bob - result, err = rt.Call(ctx, "get_balance", programContext, bobPtr) - if err != nil { - return err - } - t.log.Debug("balance", - zap.Int64("bob", result[0]), - ) - - // transfer 50 from alice to bob - transferToBob := int64(50) - transferToBobPtr, err := writeToMem(transferToBob, mem) - if err != nil { - return err - } - bobPtr, err = writeToMem(bobPublicKey, mem) - if err != nil { - return err - } - - alicePtr, err = writeToMem(alicePublicKey, mem) - if err != nil { - return err - } - - _, err = rt.Call(ctx, "transfer", programContext, alicePtr, bobPtr, transferToBobPtr) - if err != nil { - return err - } - t.log.Debug("transferred", - zap.Int64("alice", transferToBob), - zap.Int64("to bob", transferToBob), - ) - - onePtr, err := writeToMem(int64(1), mem) - if err != nil { - return err - } - - bobPtr, err = writeToMem(bobPublicKey, mem) - if err != nil { - return err - } - - alicePtr, err = writeToMem(alicePublicKey, mem) - if err != nil { - return err - } - - _, err = rt.Call(ctx, "transfer", programContext, alicePtr, bobPtr, onePtr) - if err != nil { - return err - } - t.log.Debug("transferred", - zap.Int64("alice", 1), - zap.Int64("to bob", 1), - ) - - alicePtr, err = writeToMem(alicePublicKey, mem) - if err != nil { - return err - } - - // get balance alice - result, err = rt.Call(ctx, "get_balance", programContext, alicePtr) - if err != nil { - return err - } - t.log.Debug("balance", - zap.Int64("alice", result[0]), - ) - - bobPtr, err = writeToMem(bobPublicKey, mem) - if err != nil { - return err - } - - // get balance bob - result, err = rt.Call(ctx, "get_balance", programContext, bobPtr) - if err != nil { - return err - } - t.log.Debug("balance", zap.Int64("bob", result[0])) - - balance, err = rt.Meter().GetBalance() - if err != nil { - return err - } - - t.log.Debug("remaining balance", - zap.Uint64("unit", balance), - ) - - // combine alice and bobs addresses - minters := []minter{ - { - To: alicePublicKey, - Amount: 10, - }, - { - To: bobPublicKey, - Amount: 12, - }, - } - - mintersPtr, err := writeToMem(minters, mem) - if err != nil { - return err - } - - // perform bulk mint - _, err = rt.Call(ctx, "mint_to_many", programContext, mintersPtr) - if err != nil { - return err - } - t.log.Debug("minted many", - zap.Int32("alice", minters[0].Amount), - zap.Int32("to bob", minters[1].Amount), - ) - - alicePtr, err = writeToMem(alicePublicKey, mem) - if err != nil { - return err - } - - // get balance alice - result, err = rt.Call(ctx, "get_balance", programContext, alicePtr) - if err != nil { - return err - } - t.log.Debug("balance", - zap.Int64("alice", result[0]), - ) - - bobPtr, err = writeToMem(bobPublicKey, mem) - if err != nil { - return err - } - - // get balance bob - result, err = rt.Call(ctx, "get_balance", programContext, bobPtr) - if err != nil { - return err - } - t.log.Debug("balance", zap.Int64("bob", result[0])) - - return nil -} - -// RunShort performs the steps of initialization only, used for benchmarking. -func (t *Token) RunShort(ctx context.Context) error { - rt := runtime.New(t.log, t.engine, t.imports, t.cfg) - - programContext := t.Context() - - err := rt.Initialize(ctx, programContext, t.programBytes, t.maxUnits) - if err != nil { - return err - } - - balance, err := rt.Meter().GetBalance() - if err != nil { - return err - } - - t.log.Debug("initial meter", - zap.Uint64("balance", balance), - ) - - programID := t.ProgramID() - // simulate create program transaction - err = storage.SetProgram(ctx, t.db, programID, t.programBytes) - if err != nil { - return err - } - - t.log.Debug("new token program created", - zap.String("id", programID.String()), - ) - - // initialize program - resp, err := rt.Call(ctx, "init", program.Context{ProgramID: programID}) - if err != nil { - return fmt.Errorf("failed to initialize program: %w", err) - } - - t.log.Debug("init response", - zap.Int64("init", resp[0]), - ) - return nil -} - -func (t *Token) GetUserBalanceFromState(ctx context.Context, programID ids.ID, userPublicKey ed25519.PublicKey) (res uint32, err error) { - key := storage.ProgramPrefixKey(programID[:], append([]byte{uint8(Balance)}, userPublicKey[:]...)) - b, err := t.db.GetValue(ctx, key) - if err != nil { - return 0, err - } - err = borsh.Deserialize(&res, b) - if err != nil { - return 0, err - } - return res, nil -} diff --git a/x/programs/examples/utils.go b/x/programs/examples/utils.go deleted file mode 100644 index 24a7c9914b..0000000000 --- a/x/programs/examples/utils.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package examples - -import ( - "context" - "os" - - "github.com/ava-labs/avalanchego/database/memdb" - "github.com/near/borsh-go" - - "github.com/ava-labs/hypersdk/crypto/ed25519" - "github.com/ava-labs/hypersdk/state" - "github.com/ava-labs/hypersdk/x/programs/program" -) - -func newKey() (ed25519.PublicKey, error) { - priv, err := ed25519.GeneratePrivateKey() - if err != nil { - return ed25519.EmptyPublicKey, err - } - - return priv.PublicKey(), nil -} - -// Serialize the parameter and create a smart ptr -func writeToMem(obj interface{}, memory *program.Memory) (uint32, error) { - bytes, err := borsh.Serialize(obj) - if err != nil { - return 0, err - } - - return program.AllocateBytes(bytes, memory) -} - -var ( - _ state.Mutable = &testDB{} - _ state.Immutable = &testDB{} -) - -type testDB struct { - db *memdb.Database -} - -func (c *testDB) GetValue(_ context.Context, key []byte) ([]byte, error) { - return c.db.Get(key) -} - -func (c *testDB) Insert(_ context.Context, key []byte, value []byte) error { - return c.db.Put(key, value) -} - -func (c *testDB) Put(key []byte, value []byte) error { - return c.db.Put(key, value) -} - -func (c *testDB) Remove(_ context.Context, key []byte) error { - return c.db.Delete(key) -} - -func GetProgramBytes(filePath string) ([]byte, error) { - return os.ReadFile(filePath) -} - -func GetGuestFnName(name string) string { - return name + "_guest" -} diff --git a/x/programs/host/dependencies.go b/x/programs/host/dependencies.go deleted file mode 100644 index ef64aadd45..0000000000 --- a/x/programs/host/dependencies.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package host - -import "github.com/ava-labs/hypersdk/x/programs/program" - -type Import interface { - // Name returns the name of this import module. - Name() string - // Register registers this import module with the provided link. - Register(*Link, program.Context) error -} diff --git a/x/programs/host/imports.go b/x/programs/host/imports.go deleted file mode 100644 index b816b0ba97..0000000000 --- a/x/programs/host/imports.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package host - -import ( - "github.com/bytecodealliance/wasmtime-go/v14" - - "github.com/ava-labs/hypersdk/x/programs/engine" -) - -const ( - wasiPreview1ModName = "wasi_snapshot_preview1" -) - -var NoSupportedImports = make(SupportedImports) - -type ImportFnCallback struct { - // beforeRequest is called before the import function request is made. - BeforeRequest func(module, name string, meter *engine.Meter) error - // afterResponse is called after the import function response is received. - AfterResponse func(module, name string, meter *engine.Meter) error -} - -// Supported is a map of supported import modules. The runtime will enable these imports -// during initialization only if implemented by the `program`. -type SupportedImports map[string]func() Import - -type ImportsBuilder struct { - imports map[string]func() Import -} - -func NewImportsBuilder() *ImportsBuilder { - return &ImportsBuilder{ - imports: make(map[string]func() Import), - } -} - -// Register registers a supported import by name. -func (s *ImportsBuilder) Register(name string, f func() Import) *ImportsBuilder { - s.imports[name] = f - return s -} - -// Build returns the supported imports. -func (s *ImportsBuilder) Build() SupportedImports { - return s.imports -} - -// getRegisteredImports returns the unique names of all import modules registered -// by the wasm module. -func getRegisteredImports(importTypes []*wasmtime.ImportType) []string { - u := make(map[string]struct{}, len(importTypes)) - imports := []string{} - for _, t := range importTypes { - mod := t.Module() - if mod == wasiPreview1ModName { - continue - } - if _, ok := u[mod]; ok { - continue - } - u[mod] = struct{}{} - imports = append(imports, mod) - } - return imports -} diff --git a/x/programs/host/link.go b/x/programs/host/link.go deleted file mode 100644 index 9d183a0dfd..0000000000 --- a/x/programs/host/link.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package host - -import ( - "errors" - "fmt" - - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/bytecodealliance/wasmtime-go/v14" - "go.uber.org/zap" - - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/program" - "github.com/ava-labs/hypersdk/x/programs/program/types" -) - -var ErrMissingImportModule = errors.New("failed to find import module") - -// NewLink returns a new host module program link. -func NewLink(log logging.Logger, engine *wasmtime.Engine, imports SupportedImports, meter *engine.Meter, debugMode bool) *Link { - return &Link{ - log: log, - wasmLink: wasmtime.NewLinker(engine), - imports: imports, - meter: meter, - debugMode: debugMode, - } -} - -type Link struct { - wasmLink *wasmtime.Linker - meter *engine.Meter - imports SupportedImports - log logging.Logger - debugMode bool - - // cb is a global callback for import function requests and responses. - cb ImportFnCallback -} - -// Instantiate registers a module with all imports defined in this linker. -// This can only be called once after all imports have been registered. -func (l *Link) Instantiate(store *engine.Store, mod *wasmtime.Module, cb ImportFnCallback, callContext program.Context) (*wasmtime.Instance, error) { - l.cb = cb - if l.debugMode { - err := l.EnableWasi() - if err != nil { - return nil, err - } - } - - imports := getRegisteredImports(mod.Imports()) - // register host functions exposed to the guest (imports) - for _, imp := range imports { - importFn, ok := l.imports[imp] - if !ok { - return nil, fmt.Errorf("%w: %s", ErrMissingImportModule, imp) - } - err := importFn().Register(l, callContext) - if err != nil { - return nil, err - } - } - return l.wasmLink.Instantiate(store.Get(), mod) -} - -// Meter returns the meter for the module the link is linking to. -func (l *Link) Meter() *engine.Meter { - return l.meter -} - -// Imports returns the supported imports for the link instance. -func (l *Link) Imports() SupportedImports { - return l.imports -} - -// RegisterImportFn registers a host function exposed to the guest (import). -func (l *Link) RegisterImportFn(module, name string, f interface{}) error { - wrapper := func() interface{} { - if l.cb.BeforeRequest != nil { - err := l.cb.BeforeRequest(module, name, l.meter) - if err != nil { - l.log.Error("before request callback failed", - zap.Error(err), - ) - } - } - if l.cb.AfterResponse != nil { - defer func() { - err := l.cb.AfterResponse(module, name, l.meter) - if err != nil { - l.log.Error("after response callback failed", - zap.Error(err), - ) - } - }() - } - return f - } - return l.wasmLink.FuncWrap(module, name, wrapper()) -} - -// RegisterImportWrapFn registers a wrapped host function exposed to the guest (import). RegisterImportWrapFn allows for -// more control over the function wrapper than RegisterImportFn. -func (l *Link) RegisterImportWrapFn(module, name string, paramCount int, f func(caller *program.Caller, args ...wasmtime.Val) (*types.Val, error)) error { - fn := func(caller *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { - if l.cb.BeforeRequest != nil { - err := l.cb.BeforeRequest(module, name, l.meter) - if err != nil { - // fail fast - return nil, wasmtime.NewTrap(err.Error()) - } - } - if l.cb.AfterResponse != nil { - defer func() { - err := l.cb.AfterResponse(module, name, l.meter) - if err != nil { - l.log.Error("after response callback failed", - zap.Error(err), - ) - l.wasmLink.Engine.IncrementEpoch() - } - }() - } - - val, err := f(program.NewCaller(caller), args...) - if err != nil { - return nil, wasmtime.NewTrap(err.Error()) - } - - return []wasmtime.Val{val.Wasmtime()}, nil - } - - // TODO: support other types? - valType := make([]*wasmtime.ValType, paramCount) - for i := 0; i < paramCount; i++ { - valType[i] = wasmtime.NewValType(wasmtime.KindI32) - } - - funcType := wasmtime.NewFuncType( - valType, - []*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)}, - ) - - return l.wasmLink.FuncNew(module, name, funcType, fn) -} - -// EnableWasi enables wasi support for the link. -func (l *Link) EnableWasi() error { - return l.wasmLink.DefineWasi() -} diff --git a/x/programs/host/link_test.go b/x/programs/host/link_test.go deleted file mode 100644 index 5d38fa7029..0000000000 --- a/x/programs/host/link_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package host - -import ( - "testing" - - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/bytecodealliance/wasmtime-go/v14" - "github.com/stretchr/testify/require" - - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/program" -) - -func TestLinkMissingImport(t *testing.T) { - require := require.New(t) - - wasm, err := wasmtime.Wat2Wasm(` - (module - (import "env" "alert" (func $alert (param i32))) - ) - `) - require.NoError(err) - eng := engine.New(engine.NewConfig()) - mod, err := eng.CompileModule(wasm) - require.NoError(err) - store := engine.NewStore(eng, engine.NewStoreConfig()) - link, err := newTestLink(store, NoSupportedImports) - require.NoError(err) - _, err = link.Instantiate(store, mod, ImportFnCallback{}, program.Context{}) - require.ErrorIs(err, ErrMissingImportModule) -} - -func TestLinkImport(t *testing.T) { - wasm, err := wasmtime.Wat2Wasm(` - (module - (import "env" "one" (func $one (param i64) (result i64))) - ) - `) - require.NoError(t, err) - - tests := []struct { - name, - module, - errMsg string - fn interface{} - }{ - { - name: "happy path", - module: "env", - fn: func(int64) int64 { return 0 }, - }, - { - name: "missing module", - module: "oops", - fn: func() {}, - errMsg: "failed to find import module: env", - }, - { - name: "invalid module function signature", - module: "env", - fn: func(int64) int32 { return 0 }, - errMsg: "function types incompatible", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - imports := NewImportsBuilder() - imports.Register(tt.module, func() Import { - return newTestImport(tt.module, tt.fn) - }) - eng := engine.New(engine.NewConfig()) - mod, err := eng.CompileModule(wasm) - require.NoError(err) - store := engine.NewStore(eng, engine.NewStoreConfig()) - require.NoError(err) - link, err := newTestLink(store, imports.Build()) - require.NoError(err) - _, err = link.Instantiate(store, mod, ImportFnCallback{}, program.Context{}) - if tt.errMsg != "" { - require.ErrorContains(err, tt.errMsg) //nolint:forbidigo - return - } - require.NoError(err) - }) - } -} - -// go test -v -benchmem -run=^$ -bench ^BenchmarkInstantiate$ github.com/ava-labs/hypersdk/x/programs/host -memprofile benchvset.mem -cpuprofile benchvset.cpu -func BenchmarkInstantiate(b *testing.B) { - require := require.New(b) - imports := NewImportsBuilder() - imports.Register("env", func() Import { - return newTestImport("env", nil) - }) - wasm, err := wasmtime.Wat2Wasm(` - (module - (import "env" "one" (func $one (param i64) (result i64))) - ) - `) - require.NoError(err) - eng := engine.New(engine.NewConfig()) - require.NoError(err) - mod, err := eng.CompileModule(wasm) - require.NoError(err) - b.Run("benchmark_instantiate_instance", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - store := engine.NewStore(eng, engine.NewStoreConfig()) - link, err := newTestLink(store, imports.Build()) - require.NoError(err) - _, err = link.Instantiate(store, mod, ImportFnCallback{}, program.Context{}) - require.NoError(err) - } - }) -} - -func newTestLink(store *engine.Store, supported SupportedImports) (*Link, error) { - meter, err := engine.NewMeter(store) - if err != nil { - return nil, err - } - return NewLink(logging.NoLog{}, store.GetEngine(), supported, meter, false), nil -} - -type testImport struct { - module string - fn interface{} -} - -func newTestImport(module string, fn interface{}) *testImport { - return &testImport{ - module: module, - fn: fn, - } -} - -func (i *testImport) Name() string { - return i.module -} - -func (i *testImport) Register(link *Link, _ program.Context) error { - if i.fn != nil { - return link.RegisterImportFn(i.module, "one", i.fn) - } - return link.RegisterImportFn(i.module, "one", testOneParamFnWrap) -} - -func testOneParamFnWrap(*wasmtime.Caller, int64) int64 { - return 0 -} diff --git a/x/programs/program/caller.go b/x/programs/program/caller.go deleted file mode 100644 index 611cff1980..0000000000 --- a/x/programs/program/caller.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import ( - "fmt" - - "github.com/bytecodealliance/wasmtime-go/v14" -) - -var _ Instance = (*Caller)(nil) - -// Caller is a wrapper around a wasmtime.Caller -type Caller struct { - caller *wasmtime.Caller -} - -func (c *Caller) GetStore() wasmtime.Storelike { - return c.caller -} - -// NewCaller creates a new program instance. -func NewCaller(caller *wasmtime.Caller) *Caller { - return &Caller{ - caller: caller, - } -} - -func (c *Caller) GetFunc(name string) (*Func, error) { - exp, err := c.GetExport(name) - if err != nil { - return nil, err - } - - fn, err := exp.Func() - if err != nil { - return nil, err - } - - return NewFunc(fn, c), nil -} - -func (c *Caller) GetExport(name string) (*Export, error) { - exp := c.caller.GetExport(FuncName(name)) - if exp == nil { - return nil, fmt.Errorf("failed to create export %w: %s", ErrInvalidType, name) - } - - return NewExport(exp), nil -} - -func (c *Caller) Memory() (*Memory, error) { - exp, err := c.GetExport(MemoryFnName) - if err != nil { - return nil, err - } - - mem, err := exp.Memory() - if err != nil { - return nil, err - } - - exp, err = c.GetExport(AllocFnName) - if err != nil { - return nil, err - } - - allocFn, err := exp.Func() - if err != nil { - return nil, err - } - - return NewMemory(mem, allocFn, c.caller), nil -} diff --git a/x/programs/program/consts.go b/x/programs/program/consts.go deleted file mode 100644 index 828030ae78..0000000000 --- a/x/programs/program/consts.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import ( - "github.com/ava-labs/avalanchego/utils/units" -) - -const ( - AllocFnName = "alloc" - DeallocFnName = "dealloc" - MemoryFnName = "memory" - GuestSuffix = "_guest" - MemoryPageSize = 64 * units.KiB -) diff --git a/x/programs/program/context.go b/x/programs/program/context.go deleted file mode 100644 index cb3c62dc09..0000000000 --- a/x/programs/program/context.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import "github.com/ava-labs/avalanchego/ids" - -type Context struct { - ProgramID ids.ID `json:"program"` - // Actor [32]byte `json:"actor"` - // OriginatingActor [32]byte `json:"originating_actor"` -} diff --git a/x/programs/program/dependencies.go b/x/programs/program/dependencies.go deleted file mode 100644 index 8d4e43ced3..0000000000 --- a/x/programs/program/dependencies.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import "github.com/bytecodealliance/wasmtime-go/v14" - -type Instance interface { - // GetFunc returns a function exported by the program. - GetFunc(name string) (*Func, error) - // GetExport returns an export exported by the program. - GetExport(name string) (*Export, error) - // Memory returns the memory exported by the program. - Memory() (*Memory, error) - GetStore() wasmtime.Storelike -} diff --git a/x/programs/program/errors.go b/x/programs/program/errors.go deleted file mode 100644 index 85fedd4b57..0000000000 --- a/x/programs/program/errors.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import ( - "errors" - "fmt" - - "github.com/bytecodealliance/wasmtime-go/v14" -) - -var ( - ErrMissingExportedFunction = errors.New("failed to find exported function") - ErrMissingInvalidMemoryFunction = errors.New("memory function is invalid") - ErrRuntimeStoreSet = errors.New("runtime store has already been set") - ErrOverflow = errors.New("overflow") - ErrUnderflow = errors.New("underflow") - ErrInvalidType = errors.New("invalid type") - ErrNegativeValue = errors.New("negative value") - - // Memory - ErrInvalidMemorySize = errors.New("invalid memory size") - ErrInvalidMemoryAddress = errors.New("invalid memory address: must be positive") - - // Func - ErrInvalidParamCount = errors.New("invalid param count") - ErrInvalidParamType = errors.New("invalid param type") - - // Trap errors - ErrTrapStackOverflow = errors.New("the current stack space was exhausted") - ErrTrapMemoryOutOfBounds = errors.New("out-of-bounds memory access") - ErrTrapHeapMisaligned = errors.New("a wasm atomic operation was presented with a not-naturally-aligned linear-memory address") - ErrTrapTableOutOfBounds = errors.New("out-of-bounds table access") - ErrTrapIndirectCallToNull = errors.New("an indirect call to a null table entry was executed") - ErrTrapIndirectCallBadSig = errors.New("an indirect call signature mismatch was detected") - ErrTrapBadSignature = errors.New("signature mismatch on indirect call") - ErrTrapIntegerOverflow = errors.New("an integer arithmetic operation caused an overflow") - ErrTrapIntegerDivisionByZero = errors.New("an integer divide-by-zero was executed") - ErrTrapBadConversionToInteger = errors.New("failed float-to-int conversion") - ErrTrapUnreachableCodeReached = errors.New("code that was supposed to have been unreachable was reached") - ErrTrapInterrupt = errors.New("an interrupt was received") - ErrTrapOutOfFuel = errors.New("the program ran out of units") -) - -// HandleTrapError returns the error message from a wasmtime Trap -func HandleTrapError(err error) error { - trap, ok := err.(*wasmtime.Trap) - if !ok { - return fmt.Errorf("error is not a wasmtime.Trap error: %w", err) - } - - var trapErr error - code := trap.Code() - switch *code { - case 0: - trapErr = ErrTrapStackOverflow - case 1: - trapErr = ErrTrapMemoryOutOfBounds - case 2: - trapErr = ErrTrapHeapMisaligned - case 3: - trapErr = ErrTrapTableOutOfBounds - case 4: - trapErr = ErrTrapIndirectCallToNull - case 5: - trapErr = ErrTrapBadSignature - case 6: - trapErr = ErrTrapIntegerOverflow - case 7: - trapErr = ErrTrapIntegerDivisionByZero - case 8: - trapErr = ErrTrapBadConversionToInteger - case 9: - trapErr = ErrTrapUnreachableCodeReached - case 10: - trapErr = ErrTrapInterrupt - case 11: - trapErr = ErrTrapOutOfFuel - default: - return fmt.Errorf("unknown runtime engine trap: %v", *code) - } - - return fmt.Errorf("%w", trapErr) -} diff --git a/x/programs/program/export.go b/x/programs/program/export.go deleted file mode 100644 index 55d3446f85..0000000000 --- a/x/programs/program/export.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import ( - "fmt" - - "github.com/bytecodealliance/wasmtime-go/v14" -) - -// NewExport creates a new export wrapper. -func NewExport(inner *wasmtime.Extern) *Export { - return &Export{ - inner: inner, - } -} - -// Export is a wrapper around a wasmtime.Extern -type Export struct { - inner *wasmtime.Extern -} - -func (e *Export) Func() (*wasmtime.Func, error) { - fn := e.inner.Func() - if fn == nil { - return nil, fmt.Errorf("export is not a function: %w", ErrInvalidType) - } - return fn, nil -} - -func (e *Export) Global() (*wasmtime.Global, error) { - gbl := e.inner.Global() - if gbl == nil { - return nil, fmt.Errorf("export is not a global: %w", ErrInvalidType) - } - return gbl, nil -} - -func (e *Export) Memory() (*wasmtime.Memory, error) { - mem := e.inner.Memory() - if mem == nil { - return nil, fmt.Errorf("export is not a memory: %w", ErrInvalidType) - } - return mem, nil -} - -func (e *Export) Table() (*wasmtime.Table, error) { - tbl := e.inner.Table() - if tbl == nil { - return nil, fmt.Errorf("export is not a table: %w", ErrInvalidType) - } - return tbl, nil -} diff --git a/x/programs/program/function.go b/x/programs/program/function.go deleted file mode 100644 index 09fd8faa12..0000000000 --- a/x/programs/program/function.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import ( - "fmt" - - "github.com/bytecodealliance/wasmtime-go/v14" - "github.com/near/borsh-go" -) - -// Func is a wrapper around a wasmtime.Func -type Func struct { - inner *wasmtime.Func - inst Instance -} - -// NewFunc creates a new func wrapper. -func NewFunc(inner *wasmtime.Func, inst Instance) *Func { - return &Func{ - inner: inner, - inst: inst, - } -} - -func (f *Func) Call(context Context, params ...uint32) ([]int64, error) { - fnParams := f.Type().Params()[1:] // strip program_id - if len(params) != len(fnParams) { - return nil, fmt.Errorf("%w for function: %d expected: %d", ErrInvalidParamCount, len(params), len(fnParams)) - } - - // convert the args to the expected wasm types - callParams, err := mapFunctionParams(params, fnParams) - if err != nil { - return nil, err - } - - mem, err := f.inst.Memory() - if err != nil { - return nil, err - } - contextPtr, err := writeToMem(context, mem) - if err != nil { - return nil, err - } - - result, err := f.inner.Call(f.inst.GetStore(), append([]interface{}{int32(contextPtr)}, callParams...)...) - if err != nil { - return nil, HandleTrapError(err) - } - switch v := result.(type) { - case int32: - value := int64(result.(int32)) - return []int64{value}, nil - case int64: - value := result.(int64) - return []int64{value}, nil - case nil: - // the function had no return values - return nil, nil - default: - return nil, fmt.Errorf("invalid result type: %T", v) - } -} - -func writeToMem(obj interface{}, memory *Memory) (uint32, error) { - bytes, err := borsh.Serialize(obj) - if err != nil { - return 0, err - } - - return AllocateBytes(bytes, memory) -} - -func (f *Func) Type() *wasmtime.FuncType { - return f.inner.Type(f.inst.GetStore()) -} - -// mapFunctionParams maps call input to the expected wasm function params. -func mapFunctionParams(input []uint32, values []*wasmtime.ValType) ([]interface{}, error) { - params := make([]interface{}, len(values)) - for i, v := range values { - switch v.Kind() { - case wasmtime.KindI32: - // ensure this value is within the range of an int32 - if !EnsureIntToInt32(int(input[i])) { - return nil, fmt.Errorf("%w: %d", ErrOverflow, input[i]) - } - params[i] = int32(input[i]) - case wasmtime.KindI64: - params[i] = int64(input[i]) - default: - return nil, fmt.Errorf("%w: %v", ErrInvalidParamType, v.Kind()) - } - } - - return params, nil -} - -// FuncName returns the name of the function which is support by the SDK. -func FuncName(name string) string { - switch name { - case AllocFnName, DeallocFnName, MemoryFnName: - return name - default: - // the SDK will append the guest suffix to the function name - return name + GuestSuffix - } -} diff --git a/x/programs/program/memory.go b/x/programs/program/memory.go deleted file mode 100644 index 32e4746b4a..0000000000 --- a/x/programs/program/memory.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import ( - "fmt" - "math" - "runtime" - - "github.com/bytecodealliance/wasmtime-go/v14" -) - -// Memory is a wrapper around a wasmtime.Memory -type Memory struct { - store wasmtime.Storelike - inner *wasmtime.Memory - allocFn *wasmtime.Func -} - -// NewMemory creates a new memory wrapper. -func NewMemory(inner *wasmtime.Memory, allocFn *wasmtime.Func, store wasmtime.Storelike) *Memory { - return &Memory{ - inner: inner, - store: store, - allocFn: allocFn, - } -} - -func (m *Memory) Range(offset uint32, length uint32) ([]byte, error) { - err := m.ensureValidOffset(offset, length) - if err != nil { - return nil, err - } - mem := m.inner - runtime.KeepAlive(mem) - - data := mem.UnsafeData(m.store) - buf := make([]byte, length) - - // copy data from memory to buf to ensure it is not GCed. - copy(buf, data[offset:offset+length]) - - return buf, nil -} - -func (m *Memory) Write(offset uint32, buf []byte) error { - if len(buf) > math.MaxUint32 { - return ErrOverflow - } - - err := m.ensureValidOffset(offset, uint32(len(buf))) - if err != nil { - return err - } - - mem := m.inner - runtime.KeepAlive(mem) - - data := mem.UnsafeData(m.store) - copy(data[offset:], buf) - - return nil -} - -func (m *Memory) ensureValidOffset(offset uint32, length uint32) error { - if offset == 0 && length == 0 { - return nil - } - memLen, err := m.Len() - if err != nil { - return err - } - - // verify available memory is large enough - if offset+length > memLen { - return ErrOverflow - } - - return nil -} - -func (m *Memory) Alloc(length uint32) (uint32, error) { - err := m.ensureValidOffset(0, length) - if err != nil { - return 0, err - } - - runtime.KeepAlive(m.inner) - - allocFn := m.allocFn - if allocFn == nil { - return 0, fmt.Errorf("%w: function not found: %s", ErrInvalidType, AllocFnName) - } - - if !EnsureUint32ToInt32(length) { - return 0, fmt.Errorf("%w: %d", ErrOverflow, length) - } - - result, err := allocFn.Call(m.store, int32(length)) - if err != nil { - return 0, HandleTrapError(err) - } - - addr, ok := result.(int32) - if !ok { - return 0, fmt.Errorf("%w: invalid result type: %T", ErrInvalidType, result) - } - if addr < 0 { - return 0, ErrUnderflow - } - - return uint32(addr), nil -} - -func (m *Memory) Grow(delta uint64) (uint32, error) { - mem := m.inner - runtime.KeepAlive(mem) - length, err := mem.Grow(m.store, delta) - if err != nil { - return 0, err - } - if length > math.MaxUint32 { - return 0, ErrOverflow - } - return uint32(length), nil -} - -func (m *Memory) Len() (uint32, error) { - mem := m.inner - runtime.KeepAlive(mem) - - size := mem.DataSize(m.store) - - if uint64(size) > math.MaxUint32 { - return 0, ErrOverflow - } - - return uint32(size), nil -} - -// WriteBytes is a helper function that allocates memory and writes the given -// bytes to the memory returning the offset. -func WriteBytes(m *Memory, buf []byte) (uint32, error) { - if len(buf) > math.MaxUint32 { - return 0, ErrOverflow - } - offset, err := m.Alloc(uint32(len(buf))) - if err != nil { - return 0, err - } - err = m.Write(offset, buf) - if err != nil { - return 0, err - } - - return offset, nil -} - -// AllocateBytes writes [bytes] to memory and returns the resulting pointer. -func AllocateBytes(bytes []byte, memory *Memory) (uint32, error) { - ptr, err := WriteBytes(memory, bytes) - if err != nil { - return 0, err - } - - return ptr, nil -} diff --git a/x/programs/program/memory_test.go b/x/programs/program/memory_test.go deleted file mode 100644 index eb6d051a86..0000000000 --- a/x/programs/program/memory_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import ( - "testing" - - "github.com/bytecodealliance/wasmtime-go/v14" - "github.com/stretchr/testify/require" - - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/tests" -) - -func TestMemory(t *testing.T) { - require := require.New(t) - mem := newTestMemory(t) - - // verify memory size set by program is 17 pages - memLen, err := mem.Len() - require.NoError(err) - require.Equal(uint32(17*MemoryPageSize), memLen) - - // grow memory by 1 page which is the default max memory (18 pages) - // _, err = mem.Grow(1) - // require.NoError(err) - // memLen, err = mem.Len() - // require.NoError(err) - // require.Equal(uint32(engine.DefaultLimitMaxMemory), memLen) - - // allocate entire memory - ptr, err := mem.Alloc(1) - require.NoError(err) - - // write to memory - bytes := make([]byte, 2) - err = mem.Write(ptr, bytes) - require.NoError(err) -} - -func newTestMemory(t *testing.T) *Memory { - require := require.New(t) - wasmBytes := tests.ReadFixture(t, "../tests/fixture/memory.wasm") - - // create new instance - eng := engine.New(engine.NewConfig()) - store := engine.NewStore(eng, engine.NewStoreConfig()) - err := store.AddUnits(3000000) - require.NoError(err) - mod, err := wasmtime.NewModule(store.GetEngine(), wasmBytes) - require.NoError(err) - inst, err := wasmtime.NewInstance(store.Get(), mod, nil) - require.NoError(err) - // get alloc export func - alloc := inst.GetExport(store.Get(), AllocFnName) - require.NotNil(alloc) - allocFunc := alloc.Func() - require.NotNil(allocFunc) - - // get memory export func - wmem := inst.GetExport(store.Get(), MemoryFnName).Memory() - require.NotNil(wmem) - mem := NewMemory(wmem, allocFunc, store.Get()) - require.NotNil(mem) - return mem -} diff --git a/x/programs/program/types/types.go b/x/programs/program/types/types.go deleted file mode 100644 index ca7095003e..0000000000 --- a/x/programs/program/types/types.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package types - -import ( - "github.com/bytecodealliance/wasmtime-go/v14" -) - -type ValKind = wasmtime.ValKind - -const ( - TypeI64 ValKind = wasmtime.KindI64 - TypeI32 ValKind = wasmtime.KindI32 -) - -type Val struct { - inner wasmtime.Val -} - -func (v Val) I32() int32 { - if v.Kind() != wasmtime.KindI32 { - panic("not an i32") - } - return v.inner.I32() -} - -// I64 returns the underlying 64-bit integer if this is an `i64`, or panics. -func (v Val) I64() int64 { - if v.Kind() != wasmtime.KindI64 { - panic("not an i64") - } - return v.inner.I64() -} - -func (v Val) Wasmtime() wasmtime.Val { - return v.inner -} - -func (v Val) Kind() ValKind { - switch v.inner.Kind() { - case wasmtime.KindI32: - return TypeI32 - case wasmtime.KindI64: - return TypeI64 - default: - panic("unknown val kind") - } -} - -// ValI32 converts a int32 to a i32 Val -func ValI32(val int32) *Val { - return &Val{inner: wasmtime.ValI32(val)} -} - -// ValI64 converts a go int64 to a i64 Val -func ValI64(val int64) *Val { - return &Val{inner: wasmtime.ValI64(val)} -} diff --git a/x/programs/program/utils.go b/x/programs/program/utils.go deleted file mode 100644 index e65a14c16b..0000000000 --- a/x/programs/program/utils.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package program - -import "math" - -// EnsureUint64ToInt32 returns true if [v] (uint32) can be safely converted to an int32. -func EnsureUint32ToInt32(v uint32) bool { - return v <= math.MaxInt32 -} - -// EnsureIntToInt32 returns true if [v] (int) can be safely converted to an int32. -func EnsureIntToInt32(v int) bool { - return v >= math.MinInt32 && v <= math.MaxInt32 -} diff --git a/x/programs/runtime/config.go b/x/programs/runtime/config.go index 33664ff432..97585c895d 100644 --- a/x/programs/runtime/config.go +++ b/x/programs/runtime/config.go @@ -1,73 +1,321 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package runtime import ( - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/host" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/bytecodealliance/wasmtime-go/v14" ) +type CompileStrategy uint8 + +const ( + // CompileWasm will compile the wasm module before instantiating it. + CompileWasm CompileStrategy = iota + // PrecompiledWasm accepts a precompiled wasm module serialized by an Engine. + PrecompiledWasm +) + +var ( + DefaultMaxWasmStack = 256 * units.MiB // 256 MiB + DefaultSIMD = false + DefaultEnableReferenceTypes = false + DefaultEnableBulkMemory = false + DefaultProfilingStrategy = wasmtime.ProfilingStrategyNone + DefaultMultiValue = false + + defaultWasmThreads = false + defaultFuelMetering = true + defaultWasmMultiMemory = false + defaultWasmMemory64 = false + defaultCompilerStrategy = wasmtime.StrategyCranelift + defaultEpochInterruption = true + defaultNaNCanonicalization = "true" + defaultCraneliftOptLevel = wasmtime.OptLevelSpeed + defaultEnableCraneliftDebugVerifier = false + defaultEnableDebugInfo = false +) + +// NewConfig creates a new engine config with default settings +func NewConfig() *Config { + return &Config{ + wasmConfig: DefaultWasmtimeConfig(), + } +} + +// Config is wrapper for wasmtime.Config type Config struct { - // EnableDebugMode is a test mode which provides access to non production debugging features. - // This should not be set for a live system as it has both performance and security considerations. - // Note: This requires associated Engine to enable support for BulkMemory. - // This is false by default. - EnableDebugMode bool `json:"enableTestingOnlyMode,omitempty" yaml:"enable_testing_only_mode,omitempty"` - // LimitMaxMemory defines the maximum number of pages of memory that can be used. - // Each page represents 64KiB of memory. - // This is 18 pages by default. - LimitMaxMemory uint32 `json:"limitMaxMemory,omitempty" yaml:"limit_max_memory,omitempty"` + wasmConfig *wasmtime.Config + // CompileStrategy helps the engine to understand if the files has been precompiled. - CompileStrategy engine.CompileStrategy `json:"compileStrategy,omitempty" yaml:"compile_strategy,omitempty"` - // ImportFnCallback is a global callback for all import function requests and responses. - ImportFnCallback host.ImportFnCallback `json:"-" yaml:"-"` + CompileStrategy CompileStrategy `json:"compileStrategy,omitempty" yaml:"compile_strategy,omitempty"` } -// NewConfig returns a new runtime configuration with default settings. -func NewConfig() *Config { - return &Config{ - LimitMaxMemory: engine.DefaultLimitMaxMemory, - CompileStrategy: engine.DefaultCompileStrategy, - EnableDebugMode: false, - ImportFnCallback: host.ImportFnCallback{}, +// Get returns the underlying wasmtime config. +func (c *Config) Get() *wasmtime.Config { + return c.wasmConfig +} + +// CacheConfigLoad enables compiled code caching for this `Config` using the +// settings specified in the configuration file `path`. +func (c *Config) CacheConfigLoad(path string) error { + return c.wasmConfig.CacheConfigLoad(path) +} + +// CacheConfigLoadDefault enables compiled code caching for this `Config` using +// the default settings configuration can be found. +func (c *Config) CacheConfigLoadDefault() error { + return c.wasmConfig.CacheConfigLoadDefault() +} + +// EnableCraneliftFlag enables a target-specific flag in Cranelift. +func (c *Config) EnableCraneliftFlag(flag string) { + c.wasmConfig.EnableCraneliftFlag(flag) +} + +// SetConsumFuel configures whether fuel is enabled. +func (c *Config) SetConsumeFuel(enabled bool) { + c.wasmConfig.SetConsumeFuel(enabled) +} + +// SetCraneliftDebugVerifier configures whether the cranelift debug verifier +// will be active when cranelift is used to compile wasm code. +func (c *Config) SetCraneliftDebugVerifier(enabled bool) { + c.wasmConfig.SetCraneliftDebugVerifier(enabled) +} + +// SetCraneliftFlag sets a target-specific flag in Cranelift to the specified value. +func (c *Config) SetCraneliftFlag(name string, value string) { + c.wasmConfig.SetCraneliftFlag(name, value) +} + +// SetCraneliftOptLevel configures the cranelift optimization level for generated code. +func (c *Config) SetCraneliftOptLevel(level wasmtime.OptLevel) { + c.wasmConfig.SetCraneliftOptLevel(level) +} + +// SetDebugInfo configures whether dwarf debug information for JIT code is enabled +func (c *Config) SetDebugInfo(enabled bool) { + c.wasmConfig.SetDebugInfo(enabled) +} + +// SetEpochInterruption enables epoch-based instrumentation of generated code to +// interrupt WebAssembly execution when the current engine epoch exceeds a +// defined threshold. +func (c *Config) SetEpochInterruption(enable bool) { + c.wasmConfig.SetEpochInterruption(enable) +} + +// SetMaxWasmStack configures the maximum stack size, in bytes, that JIT code can use. +func (c *Config) SetMaxWasmStack(size int) { + c.wasmConfig.SetMaxWasmStack(size) +} + +// SetProfiler configures what profiler strategy to use for generated code. +func (c *Config) SetProfiler(profiler wasmtime.ProfilingStrategy) { + c.wasmConfig.SetProfiler(profiler) +} + +// SetStrategy configures what compilation strategy is used to compile wasm code. +func (c *Config) SetStrategy(strategy wasmtime.Strategy) { + c.wasmConfig.SetStrategy(strategy) +} + +// SetTarget configures the target triple that this configuration will produce machine code for. +func (c *Config) SetTarget(target string) error { + return c.wasmConfig.SetTarget(target) +} + +// SetWasmBulkMemory configures whether the wasm bulk memory proposal is enabled. +func (c *Config) SetWasmBulkMemory(enabled bool) { + c.wasmConfig.SetWasmBulkMemory(enabled) +} + +// SetWasmMemory64 configures whether the wasm memory64 proposal is enabled. +func (c *Config) SetWasmMemory64(enabled bool) { + c.wasmConfig.SetWasmMemory64(enabled) +} + +// SetWasmMultiMemory configures whether the wasm multi memory proposal is enabled. +func (c *Config) SetWasmMultiMemory(enabled bool) { + c.wasmConfig.SetWasmMultiMemory(enabled) +} + +// SetWasmMultiValue configures whether the wasm multi value proposal is enabled. +func (c *Config) SetWasmMultiValue(enabled bool) { + c.wasmConfig.SetWasmMultiValue(enabled) +} + +// SetWasmReferenceTypes configures whether the wasm reference types proposal is enabled. +func (c *Config) SetWasmReferenceTypes(enabled bool) { + c.wasmConfig.SetWasmReferenceTypes(enabled) +} + +// SetWasmSIMD configures whether the wasm SIMD proposal is enabled. +func (c *Config) SetWasmSIMD(enabled bool) { + c.wasmConfig.SetWasmSIMD(enabled) +} + +// SetWasmThreads configures whether the wasm threads proposal is enabled. +func (c *Config) SetWasmThreads(enabled bool) { + c.wasmConfig.SetWasmThreads(enabled) +} + +// DefaultWasmtimeConfig returns a new wasmtime config with default settings. +func DefaultWasmtimeConfig() *wasmtime.Config { + cfg := wasmtime.NewConfig() + + // non configurable defaults + cfg.SetCraneliftOptLevel(defaultCraneliftOptLevel) + cfg.SetConsumeFuel(defaultFuelMetering) + cfg.SetWasmThreads(defaultWasmThreads) + cfg.SetWasmMultiMemory(defaultWasmMultiMemory) + cfg.SetWasmMemory64(defaultWasmMemory64) + cfg.SetStrategy(defaultCompilerStrategy) + cfg.SetEpochInterruption(defaultEpochInterruption) + cfg.SetCraneliftFlag("enable_nan_canonicalization", defaultNaNCanonicalization) + + // TODO: expose these knobs for developers + cfg.SetCraneliftDebugVerifier(defaultEnableCraneliftDebugVerifier) + cfg.SetDebugInfo(defaultEnableDebugInfo) + return cfg +} + +// NewConfigBuilder returns a new engine configuration builder with default settings. +// All instances of ConfigBuilder should be created with this constructor. +func NewConfigBuilder() *ConfigBuilder { + return &ConfigBuilder{ + EnableBulkMemory: DefaultEnableBulkMemory, + EnableWasmMultiValue: DefaultMultiValue, + EnableWasmReferenceTypes: DefaultEnableReferenceTypes, + EnableWasmSIMD: DefaultSIMD, + MaxWasmStack: DefaultMaxWasmStack, + ProfilingStrategy: DefaultProfilingStrategy, + EnableDefaultCache: false, } } -// SetCompileStrategy defines the EngineCompileStrategy. +type ConfigBuilder struct { + // Configures whether the WebAssembly bulk memory operations proposal will + // be enabled for compilation. This feature gates items such as the + // memory.copy instruction, passive data/table segments, etc, being in a + // module. + // This is false by default. + EnableBulkMemory bool `json:"enableBulkMemory,omitempty" yaml:"enable_bulk_memory,omitempty"` + // Configures whether the WebAssembly multi-value proposal will be enabled for compilation. + // This feature gates functions and blocks returning multiple values in a module, for example. + // This is false by default. + EnableWasmMultiValue bool `json:"enableWasmMultiValue,omitempty" yaml:"enable_wasm_multi_value,omitempty"` + // Configures whether the WebAssembly reference types proposal will be + // enabled for compilation. This feature gates items such as the externref + // and funcref types as well as allowing a module to define multiple tables. + // Note that the reference types proposal depends on the bulk memory + // proposal. + // This is false by default. + EnableWasmReferenceTypes bool `json:"enableWasmReferenceTypes,omitempty" yaml:"enable_wasm_reference_types,omitempty"` + // Configures whether the WebAssembly SIMD proposal will be enabled for + // compilation. The WebAssembly SIMD proposal. This feature gates items + // such as the v128 type and all of its operators being in a module. Note + // that this does not enable the relaxed simd proposal. + // This is false by default. + EnableWasmSIMD bool `json:"enableWasmSIMD,omitempty" yaml:"enable_wasm_simd,omitempty"` + // EnableDefaultCache enables compiled code caching for this `Config` using the default settings + // configuration can be found. + // + // For more information about caching see + // https://bytecodealliance.github.io/wasmtime/cli-cache.html + // This is false by default. + EnableDefaultCache bool `json:"enableDefaultCache,omitempty" yaml:"enable_default_cache,omitempty"` + // SetMaxWasmStack configures the maximum stack size, in bytes, that JIT code can use. + // The amount of stack space that wasm takes is always relative to the first invocation of wasm on the stack. + // Recursive calls with imports frames in the middle will all need to fit within this setting. + // Note that this setting is not interpreted with 100% precision. + // This is 256 MiB by default. + MaxWasmStack int `json:"maxWasmStack,omitempty" yaml:"max_wasm_stack,omitempty"` + // ProfilingStrategy decides what sort of profiling to enable, if any. + // Default is `wasmtime.ProfilingStrategyNone`. + ProfilingStrategy wasmtime.ProfilingStrategy +} + +// WithMaxWasmStack defines the maximum amount of stack space available for +// executing WebAssembly code. // -// Default is CompileWasm. -func (c *Config) SetCompileStrategy(strategy engine.CompileStrategy) *Config { - c.CompileStrategy = strategy +// Default is 256 MiB. +func (c *ConfigBuilder) WithMaxWasmStack(max int) *ConfigBuilder { + c.MaxWasmStack = max return c } -// SetLimitMaxMemory defines the maximum number of pages of memory that can be used. -// Each page represents 64KiB of memory. +// WithMultiValue enables modules that can return multiple values. +// ref. https://github.com/webassembly/multi-value // -// Default is 18 pages. -func (c *Config) SetLimitMaxMemory(max uint32) *Config { - c.LimitMaxMemory = max +// Default is false. +func (c *ConfigBuilder) WithMultiValue(enable bool) *ConfigBuilder { + c.EnableWasmMultiValue = enable return c } -// SetDebugMode enables debug mode which provides access to useful debugging -// information. If [enabled] is true the associated `Engine` must enable -// BulkMemory to support useful IO operations during debugging. +// WithBulkMemory enables `memory.copy` instruction, tables and passive data. +// ref. https://github.com/WebAssembly/bulk-memory-operations // -// This should not be set for a live system as it has -// both performance and security considerations. +// Default is false. +func (c *ConfigBuilder) WithBulkMemory(enable bool) *ConfigBuilder { + c.EnableBulkMemory = enable + return c +} + +// WithReferenceTypes Enables the `externref` and `funcref` types as well as +// allowing a module to define multiple tables. +// ref. https://github.com/webassembly/reference-types +// +// Note: depends on bulk memory being enabled. // -// Note: This requires Rust programs to be compiled with the wasm32-wasi target. +// Default is false. +func (c *ConfigBuilder) WithReferenceTypes(enable bool) *ConfigBuilder { + c.EnableWasmReferenceTypes = enable + return c +} + +// WithSIMD enables SIMD instructions including v128. +// ref. https://github.com/webassembly/simd // // Default is false. -func (c *Config) SetDebugMode(enabled bool) *Config { - c.EnableDebugMode = enabled +func (c *ConfigBuilder) WithSIMD(enable bool) *ConfigBuilder { + c.EnableWasmSIMD = enable return c } -// SetImportFnCallback sets the global import function callback. -func (c *Config) SetImportFnCallback(callback host.ImportFnCallback) *Config { - c.ImportFnCallback = callback +// WithProfilingStrategy defines the profiling strategy used for defining the +// default profiler. +// +// Default is `wasmtime.ProfilingStrategyNone`. +func (c *ConfigBuilder) WithProfilingStrategy(strategy wasmtime.ProfilingStrategy) *ConfigBuilder { + c.ProfilingStrategy = strategy return c } + +// WithDefaultCache enables the default caching strategy. +// +// Default is false. +func (c *ConfigBuilder) WithDefaultCache(enabled bool) *ConfigBuilder { + c.EnableDefaultCache = enabled + return c +} + +func (c *ConfigBuilder) Build() (*Config, error) { + cfg := NewConfig() + cfg.SetWasmBulkMemory(c.EnableBulkMemory) + cfg.SetWasmMultiValue(c.EnableWasmMultiValue) + cfg.SetWasmReferenceTypes(c.EnableWasmReferenceTypes) + cfg.SetWasmSIMD(c.EnableWasmSIMD) + cfg.SetMaxWasmStack(c.MaxWasmStack) + cfg.SetProfiler(c.ProfilingStrategy) + if c.EnableDefaultCache { + if err := cfg.CacheConfigLoadDefault(); err != nil { + return nil, err + } + } + + return cfg, nil +} diff --git a/x/programs/runtime/config_test.go b/x/programs/runtime/config_test.go deleted file mode 100644 index fa74a62380..0000000000 --- a/x/programs/runtime/config_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package runtime - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestDefaultSerialization(t *testing.T) { - require := require.New(t) - // marshal default builder - cfg := NewConfig() - defaultBytes, err := json.Marshal(cfg) - require.NoError(err) - // unmarshal and ensure defaults - builder := &Config{} - err = json.Unmarshal(defaultBytes, builder) - require.NoError(err) - require.Equal(cfg, builder) -} diff --git a/x/programs/runtime/dependencies.go b/x/programs/runtime/dependencies.go deleted file mode 100644 index df75d03eda..0000000000 --- a/x/programs/runtime/dependencies.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package runtime - -import ( - "context" - - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/program" -) - -type Runtime interface { - // Initialize initializes the runtime with the given program bytes and max - // units. The engine will handle the compile strategy and instantiate the - // module with the given imports. Initialize should only be called once. - Initialize(context.Context, program.Context, []byte, uint64) error - // Call invokes an exported guest function with the given parameters. - // Returns the results of the call or an error if the call failed. - // If the function called does not return a result this value is set to nil. - Call(context.Context, string, program.Context, ...uint32) ([]int64, error) - // Memory returns the program memory. - Memory() (*program.Memory, error) - // Meter returns the engine meter. - Meter() *engine.Meter - // Stop stops the runtime. - Stop() -} diff --git a/x/programs/runtime/import_log_debug.go b/x/programs/runtime/import_log_debug.go new file mode 100644 index 0000000000..dbc9153b83 --- /dev/null +++ b/x/programs/runtime/import_log_debug.go @@ -0,0 +1,25 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +//go:build debug + +package runtime + +import ( + "fmt" + "os" +) + +const logCost = 1000 + +func NewLogModule() *ImportModule { + return &ImportModule{ + Name: "log", + HostFunctions: map[string]*HostFunction{ + "write": {FuelCost: logCost, Funciton: FunctionNoOutput(func(_ *CallInfo, input []byte) error { + _, err := fmt.Fprintf(os.Stderr, "%s\n", input) + return err + })}, + }, + } +} diff --git a/x/programs/runtime/import_log_release.go b/x/programs/runtime/import_log_release.go new file mode 100644 index 0000000000..acc76e87fa --- /dev/null +++ b/x/programs/runtime/import_log_release.go @@ -0,0 +1,17 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +//go:build !debug + +package runtime + +const logCost = 1000 + +func NewLogModule() *ImportModule { + return &ImportModule{ + Name: "log", + HostFunctions: map[string]HostFunction{ + "write": {FuelCost: logCost, Function: FunctionNoOutput(func(*CallInfo, []byte) error { return nil })}, + }, + } +} diff --git a/x/programs/runtime/import_program.go b/x/programs/runtime/import_program.go new file mode 100644 index 0000000000..6eba28982f --- /dev/null +++ b/x/programs/runtime/import_program.go @@ -0,0 +1,68 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package runtime + +import ( + "context" + "errors" + + "github.com/ava-labs/avalanchego/ids" + "github.com/near/borsh-go" +) + +const ( + callProgramCost = 10000 + setResultCost = 10000 +) + +type callProgramInput struct { + ProgramID ids.ID + FunctionName string + Params []byte + Fuel uint64 +} + +func NewProgramModule(r *WasmRuntime) *ImportModule { + return &ImportModule{ + Name: "program", + HostFunctions: map[string]HostFunction{ + "call_program": {FuelCost: callProgramCost, Function: FunctionWithOutput(func(callInfo *CallInfo, input []byte) ([]byte, error) { + newInfo := *callInfo + parsedInput := &callProgramInput{} + if err := borsh.Deserialize(parsedInput, input); err != nil { + return nil, err + } + + // make sure there is enough fuel in current store to give to the new call + if callInfo.RemainingFuel() < parsedInput.Fuel { + return nil, errors.New("remaining fuel is less than requested fuel") + } + + newInfo.ProgramID = parsedInput.ProgramID + newInfo.FunctionName = parsedInput.FunctionName + newInfo.Params = parsedInput.Params + newInfo.Fuel = parsedInput.Fuel + + result, err := r.CallProgram( + context.Background(), + &newInfo) + if err != nil { + return nil, err + } + + // subtract the fuel used during this call from the calling program + remainingFuel := newInfo.RemainingFuel() + if err := callInfo.ConsumeFuel(parsedInput.Fuel - remainingFuel); err != nil { + return nil, err + } + + return result, nil + })}, + "set_call_result": {FuelCost: setResultCost, Function: FunctionNoOutput(func(callInfo *CallInfo, input []byte) error { + callInfo.inst.result = input + return nil + })}, + }, + } +} diff --git a/x/programs/runtime/import_program_test.go b/x/programs/runtime/import_program_test.go new file mode 100644 index 0000000000..4697167657 --- /dev/null +++ b/x/programs/runtime/import_program_test.go @@ -0,0 +1,49 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package runtime + +import ( + "context" + "testing" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/near/borsh-go" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/hypersdk/x/programs/test" +) + +func TestImportProgramCallProgram(t *testing.T) { + require := require.New(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + runtime := NewRuntime( + NewConfig(), + logging.NoLog{}, + test.Loader{ProgramName: "call_program"}) + + state := test.NewTestDB() + programID := ids.GenerateTestID() + result, err := runtime.CallProgram(ctx, &CallInfo{ProgramID: programID, State: state, FunctionName: "get_value", Params: nil, Fuel: 10000000}) + require.NoError(err) + expected, err := borsh.Serialize(0) + require.NoError(err) + require.Equal(expected, result) + + params := struct { + Program ids.ID + MaxUnits uint64 + }{ + Program: programID, + MaxUnits: 1000000, + } + paramBytes, err := borsh.Serialize(params) + require.NoError(err) + result, err = runtime.CallProgram(ctx, &CallInfo{ProgramID: programID, State: state, FunctionName: "get_value_external", Params: paramBytes, Fuel: 10000000}) + require.NoError(err) + require.Equal(expected, result) +} diff --git a/x/programs/runtime/import_state.go b/x/programs/runtime/import_state.go new file mode 100644 index 0000000000..5f9ead99bf --- /dev/null +++ b/x/programs/runtime/import_state.go @@ -0,0 +1,81 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package runtime + +import ( + "context" + "errors" + + "github.com/ava-labs/avalanchego/database" + "github.com/near/borsh-go" + + "github.com/ava-labs/hypersdk/codec" +) + +const ( + readCost = 10000 + writeCost = 10000 + deleteCost = 10000 +) + +type keyInput struct { + Key []byte +} + +type keyValueInput struct { + Key []byte + Value []byte +} + +// prependAccountToKey makes the key relative to the account +func prependAccountToKey(account codec.Address, key []byte) []byte { + result := make([]byte, len(account)+len(key)+1) + copy(result, account[:]) + copy(result[len(account):], "/") + copy(result[len(account)+1:], key) + return result +} + +func NewStateAccessModule() *ImportModule { + return &ImportModule{ + Name: "state", + HostFunctions: map[string]HostFunction{ + "get": {FuelCost: readCost, Function: FunctionWithOutput(func(callInfo *CallInfo, input []byte) ([]byte, error) { + parsedInput := &keyInput{} + if err := borsh.Deserialize(parsedInput, input); err != nil { + return nil, err + } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + val, err := callInfo.State.GetValue(ctx, prependAccountToKey(callInfo.Account, parsedInput.Key)) + if err != nil { + if errors.Is(err, database.ErrNotFound) { + return nil, nil + } + return nil, err + } + return val, nil + })}, + "put": {FuelCost: writeCost, Function: FunctionWithOutput(func(callInfo *CallInfo, input []byte) ([]byte, error) { + parsedInput := &keyValueInput{} + if err := borsh.Deserialize(parsedInput, input); err != nil { + return nil, err + } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + return nil, callInfo.State.Insert(ctx, prependAccountToKey(callInfo.Account, parsedInput.Key), parsedInput.Value) + })}, + "delete": {FuelCost: deleteCost, Function: FunctionWithOutput(func(callInfo *CallInfo, input []byte) ([]byte, error) { + parsedInput := &keyInput{} + if err := borsh.Deserialize(parsedInput, input); err != nil { + return nil, err + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + return nil, callInfo.State.Remove(ctx, prependAccountToKey(callInfo.Account, parsedInput.Key)) + })}, + }, + } +} diff --git a/x/programs/runtime/imports.go b/x/programs/runtime/imports.go new file mode 100644 index 0000000000..8795d37a98 --- /dev/null +++ b/x/programs/runtime/imports.go @@ -0,0 +1,136 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package runtime + +import ( + "github.com/bytecodealliance/wasmtime-go/v14" + "golang.org/x/exp/maps" +) + +type Imports struct { + Modules map[string]*ImportModule +} + +type ImportModule struct { + Name string + HostFunctions map[string]HostFunction +} + +type HostFunction struct { + Function HostFunctionType + FuelCost uint64 +} + +type HostFunctionType interface { + isHostFunctionType() +} + +type FunctionWithOutput func(*CallInfo, []byte) ([]byte, error) + +func (FunctionWithOutput) isHostFunctionType() {} + +type FunctionNoOutput func(*CallInfo, []byte) error + +func (FunctionNoOutput) isHostFunctionType() {} + +var ( + typeI32 = wasmtime.NewValType(wasmtime.KindI32) + functionWithOutputType = wasmtime.NewFuncType([]*wasmtime.ValType{typeI32, typeI32}, []*wasmtime.ValType{typeI32}) + FunctionNoOutputType = wasmtime.NewFuncType([]*wasmtime.ValType{typeI32, typeI32}, []*wasmtime.ValType{}) +) + +func (i *ImportModule) SetFuelCost(functionName string, fuelCost uint64) bool { + hostFunction, ok := i.HostFunctions[functionName] + if ok { + hostFunction.FuelCost = fuelCost + i.HostFunctions[functionName] = hostFunction + } + + return ok +} + +func NewImports() *Imports { + return &Imports{Modules: map[string]*ImportModule{}} +} + +func (i *Imports) AddModule(mod *ImportModule) { + i.Modules[mod.Name] = mod +} + +func (i *Imports) SetFuelCost(moduleName string, functionName string, fuelCost uint64) bool { + if module, ok := i.Modules[moduleName]; ok { + return module.SetFuelCost(functionName, fuelCost) + } + + return false +} + +func (i *Imports) Clone() *Imports { + return &Imports{ + Modules: maps.Clone(i.Modules), + } +} + +func (i *Imports) createLinker(engine *wasmtime.Engine, info *CallInfo) (*wasmtime.Linker, error) { + linker := wasmtime.NewLinker(engine) + for moduleName, module := range i.Modules { + for funcName, hostFunction := range module.HostFunctions { + if err := linker.FuncNew(moduleName, funcName, getFunctType(hostFunction), convertFunction(info, hostFunction)); err != nil { + return nil, err + } + } + } + return linker, nil +} + +func getFunctType(hf HostFunction) *wasmtime.FuncType { + switch hf.Function.(type) { + case FunctionWithOutput: + return functionWithOutputType + case FunctionNoOutput: + return FunctionNoOutputType + } + return nil +} + +var nilResult = []wasmtime.Val{wasmtime.ValI32(0)} + +func convertFunction(callInfo *CallInfo, hf HostFunction) func(*wasmtime.Caller, []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { + return func(caller *wasmtime.Caller, vals []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) { + memExport := caller.GetExport(MemoryName) + inputBytes := memExport.Memory().UnsafeData(caller)[vals[0].I32() : vals[0].I32()+vals[1].I32()] + + if err := callInfo.ConsumeFuel(hf.FuelCost); err != nil { + return nil, wasmtime.NewTrap(err.Error()) + } + switch f := hf.Function.(type) { + case FunctionWithOutput: + results, err := f(callInfo, inputBytes) + if err != nil { + return nilResult, wasmtime.NewTrap(err.Error()) + } + if results == nil { + return nilResult, nil + } + resultLength := int32(len(results)) + allocExport := caller.GetExport(AllocName) + offsetIntf, err := allocExport.Func().Call(caller, resultLength) + if err != nil { + return nilResult, wasmtime.NewTrap(err.Error()) + } + offset := offsetIntf.(int32) + copy(memExport.Memory().UnsafeData(caller)[offset:], results) + return []wasmtime.Val{wasmtime.ValI32(offset)}, nil + case FunctionNoOutput: + err := f(callInfo, inputBytes) + if err != nil { + return []wasmtime.Val{}, wasmtime.NewTrap(err.Error()) + } + + return []wasmtime.Val{}, nil + default: + return []wasmtime.Val{}, nil + } + } +} diff --git a/x/programs/runtime/imports_test.go b/x/programs/runtime/imports_test.go new file mode 100644 index 0000000000..1b14c9489e --- /dev/null +++ b/x/programs/runtime/imports_test.go @@ -0,0 +1,60 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package runtime + +import ( + "testing" + + "github.com/bytecodealliance/wasmtime-go/v14" + "github.com/stretchr/testify/require" +) + +// TestImportsLinking ensures that the linker created by [createLinker] correctly creates callable host functions +func TestImportsLinking(t *testing.T) { + require := require.New(t) + + wasm, err := wasmtime.Wat2Wasm(` + (module + (import "env" "alert" (func $alert (param i32) (param i32))) + (memory 1) ;; 1 pages + (func $run + i32.const 0 + i32.const 0 + call $alert + ) + (export "memory" (memory 0)) + (export "run" (func $run)) + ) + `) + require.NoError(err) + + called := false + + imports := NewImports() + imports.AddModule(&ImportModule{ + Name: "env", + HostFunctions: map[string]HostFunction{ + "alert": {Function: FunctionNoOutput(func(_ *CallInfo, _ []byte) error { + called = true + return nil + })}, + }, + }) + engine := wasmtime.NewEngine() + store := wasmtime.NewStore(engine) + callInfo := &CallInfo{inst: &ProgramInstance{store: store}} + linker, err := imports.createLinker(engine, callInfo) + require.NoError(err) + + module, err := wasmtime.NewModule(engine, wasm) + require.NoError(err) + + inst, err := linker.Instantiate(store, module) + require.NoError(err) + require.NotNil(inst) + + _, err = inst.GetExport(store, "run").Func().Call(store) + require.NoError(err) + require.True(called) +} diff --git a/x/programs/runtime/instance.go b/x/programs/runtime/instance.go deleted file mode 100644 index 937959bb60..0000000000 --- a/x/programs/runtime/instance.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package runtime - -import ( - "fmt" - - "github.com/bytecodealliance/wasmtime-go/v14" - - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/program" -) - -var _ program.Instance = (*Instance)(nil) - -// NewInstance creates a new instance wrapper. -func NewInstance(store *engine.Store, inner *wasmtime.Instance) *Instance { - return &Instance{ - inner: inner, - store: store.Get(), - } -} - -// Instance is a wrapper around a wasmtime.Instance -type Instance struct { - inner *wasmtime.Instance - store wasmtime.Storelike -} - -func (i *Instance) GetStore() wasmtime.Storelike { - return i.store -} - -func (i *Instance) GetFunc(name string) (*program.Func, error) { - exp, err := i.GetExport(program.FuncName(name)) - if err != nil { - return nil, err - } - - fn, err := exp.Func() - if err != nil { - return nil, err - } - - return program.NewFunc(fn, i), nil -} - -func (i *Instance) GetExport(name string) (*program.Export, error) { - exp := i.inner.GetExport(i.store, name) - if exp == nil { - return nil, fmt.Errorf("failed to create export %w: %s", program.ErrInvalidType, name) - } - - return program.NewExport(exp), nil -} - -func (i *Instance) Memory() (*program.Memory, error) { - exp, err := i.GetExport(program.MemoryFnName) - if err != nil { - return nil, err - } - - mem, err := exp.Memory() - if err != nil { - return nil, err - } - - exp, err = i.GetExport(program.AllocFnName) - if err != nil { - return nil, err - } - - allocFn, err := exp.Func() - if err != nil { - return nil, err - } - - return program.NewMemory(mem, allocFn, i.store), nil -} diff --git a/x/programs/runtime/program.go b/x/programs/runtime/program.go new file mode 100644 index 0000000000..437076566b --- /dev/null +++ b/x/programs/runtime/program.go @@ -0,0 +1,122 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package runtime + +import "C" + +import ( + "context" + + "github.com/ava-labs/avalanchego/ids" + "github.com/bytecodealliance/wasmtime-go/v14" + "github.com/near/borsh-go" + + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/state" +) + +const ( + AllocName = "alloc" + MemoryName = "memory" +) + +type Context struct { + ProgramID ids.ID `json:"program"` + Actor codec.Address `json:"actor"` +} + +type CallInfo struct { + // the state that the program will run against + State state.Mutable + + // the address that originated the initial program call + Actor codec.Address + + // the identifier of what state space the program is being run within + Account codec.Address + + // the identifier of what program is being called + ProgramID ids.ID + + // the name of the function within the program that is being called + FunctionName string + + // the serialized parameters that will be passed to the called function + Params []byte + + // the amount of fuel allowed to be consumed by wasm for this call + Fuel uint64 + + inst *ProgramInstance +} + +func (c *CallInfo) RemainingFuel() uint64 { + remaining := c.Fuel + usedFuel, fuelEnabled := c.inst.store.FuelConsumed() + if fuelEnabled { + remaining -= usedFuel + } + return remaining +} + +func (c *CallInfo) ConsumeFuel(fuel uint64) error { + _, err := c.inst.store.ConsumeFuel(fuel) + return err +} + +type Program struct { + module *wasmtime.Module + programID ids.ID +} + +type ProgramInstance struct { + inst *wasmtime.Instance + store *wasmtime.Store + result []byte +} + +func newProgram(engine *wasmtime.Engine, programID ids.ID, programBytes []byte) (*Program, error) { + module, err := wasmtime.NewModule(engine, programBytes) + if err != nil { + return nil, err + } + return &Program{module: module, programID: programID}, nil +} + +func (p *ProgramInstance) call(_ context.Context, callInfo *CallInfo) ([]byte, error) { + if err := p.store.AddFuel(callInfo.Fuel); err != nil { + return nil, err + } + + // create the program context + programCtx := Context{ProgramID: callInfo.ProgramID, Actor: callInfo.Actor} + paramsBytes, err := borsh.Serialize(programCtx) + if err != nil { + return nil, err + } + paramsBytes = append(paramsBytes, callInfo.Params...) + + // copy params into store linear memory + paramsOffset, err := p.setParams(paramsBytes) + if err != nil { + return nil, err + } + + _, err = p.inst.GetFunc(p.store, callInfo.FunctionName).Call(p.store, paramsOffset) + + return p.result, err +} + +func (p *ProgramInstance) setParams(data []byte) (int32, error) { + allocFn := p.inst.GetExport(p.store, AllocName).Func() + programMemory := p.inst.GetExport(p.store, MemoryName).Memory() + dataOffsetIntf, err := allocFn.Call(p.store, int32(len(data))) + if err != nil { + return 0, wasmtime.NewTrap(err.Error()) + } + dataOffset := dataOffsetIntf.(int32) + linearMem := programMemory.UnsafeData(p.store) + copy(linearMem[dataOffset:], data) + return dataOffset, nil +} diff --git a/x/programs/runtime/runtime.go b/x/programs/runtime/runtime.go index 98227f8878..e58dc20946 100644 --- a/x/programs/runtime/runtime.go +++ b/x/programs/runtime/runtime.go @@ -5,122 +5,91 @@ package runtime import ( "context" - "sync" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/bytecodealliance/wasmtime-go/v14" - - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/host" - "github.com/ava-labs/hypersdk/x/programs/program" ) -var _ Runtime = &WasmRuntime{} +type WasmRuntime struct { + log logging.Logger + engine *wasmtime.Engine + hostImports *Imports + cfg *Config + programs map[ids.ID]*Program + programLoader ProgramLoader +} -// New returns a new wasm runtime. -func New( - log logging.Logger, - engine *engine.Engine, - imports host.SupportedImports, - cfg *Config, -) Runtime { - return &WasmRuntime{ - log: log, - engine: engine, - imports: imports, - cfg: cfg, - } +type ProgramLoader interface { + GetProgramBytes(programID ids.ID) ([]byte, error) } -type WasmRuntime struct { - engine *engine.Engine - meter *engine.Meter - inst program.Instance - imports host.SupportedImports - cfg *Config +func NewRuntime( + cfg *Config, + log logging.Logger, + loader ProgramLoader, +) *WasmRuntime { + runtime := &WasmRuntime{ + log: log, + cfg: cfg, + engine: wasmtime.NewEngineWithConfig(cfg.wasmConfig), + hostImports: NewImports(), + programs: map[ids.ID]*Program{}, + programLoader: loader, + } - once sync.Once - cancelFn context.CancelFunc + runtime.AddImportModule(NewLogModule()) + runtime.AddImportModule(NewStateAccessModule()) + runtime.AddImportModule(NewProgramModule(runtime)) - log logging.Logger + return runtime } -func (r *WasmRuntime) Initialize(ctx context.Context, callContext program.Context, programBytes []byte, maxUnits uint64) (err error) { - ctx, r.cancelFn = context.WithCancel(ctx) - go func(ctx context.Context) { - <-ctx.Done() - // send immediate interrupt to engine - r.Stop() - }(ctx) - - // create store - cfg := engine.NewStoreConfig() - cfg.SetLimitMaxMemory(r.cfg.LimitMaxMemory) - store := engine.NewStore(r.engine, cfg) - - // enable wasi logging support only in debug mode - if r.cfg.EnableDebugMode { - wasiConfig := wasmtime.NewWasiConfig() - wasiConfig.InheritStderr() - wasiConfig.InheritStdout() - store.SetWasi(wasiConfig) - } +func (r *WasmRuntime) AddImportModule(mod *ImportModule) { + r.hostImports.AddModule(mod) +} - // add metered units to the store - err = store.AddUnits(maxUnits) +func (r *WasmRuntime) AddProgram(programID ids.ID, bytes []byte) error { + programModule, err := newProgram(r.engine, programID, bytes) if err != nil { return err } + r.programs[programID] = programModule + return nil +} - // setup metering - r.meter, err = engine.NewMeter(store) - if err != nil { - return err +func (r *WasmRuntime) CallProgram(ctx context.Context, callInfo *CallInfo) ([]byte, error) { + program, ok := r.programs[callInfo.ProgramID] + if !ok { + bytes, err := r.programLoader.GetProgramBytes(callInfo.ProgramID) + if err != nil { + return nil, err + } + program, err = newProgram(r.engine, callInfo.ProgramID, bytes) + if err != nil { + return nil, err + } + r.programs[callInfo.ProgramID] = program } - - // create module - mod, err := engine.NewModule(r.engine, programBytes, r.cfg.CompileStrategy) + inst, err := r.getInstance(callInfo, program, r.hostImports) if err != nil { - return err + return nil, err } + callInfo.inst = inst + callInfo.FunctionName += "_guest" + return inst.call(ctx, callInfo) +} - // create linker - link := host.NewLink(r.log, store.GetEngine(), r.imports, r.meter, r.cfg.EnableDebugMode) - - // instantiate the module with all of the imports defined by the linker - inst, err := link.Instantiate(store, mod, r.cfg.ImportFnCallback, callContext) +func (r *WasmRuntime) getInstance(callInfo *CallInfo, program *Program, imports *Imports) (*ProgramInstance, error) { + linker, err := imports.createLinker(r.engine, callInfo) if err != nil { - return err + return nil, err } - - // set the instance - r.inst = NewInstance(store, inst) - - return nil -} - -func (r *WasmRuntime) Call(_ context.Context, name string, context program.Context, params ...uint32) ([]int64, error) { - fn, err := r.inst.GetFunc(name) + store := wasmtime.NewStore(r.engine) + store.SetEpochDeadline(1) + inst, err := linker.Instantiate(store, program.module) if err != nil { return nil, err } - - return fn.Call(context, params...) -} - -func (r *WasmRuntime) Memory() (*program.Memory, error) { - return r.inst.Memory() -} - -func (r *WasmRuntime) Meter() *engine.Meter { - return r.meter -} - -func (r *WasmRuntime) Stop() { - r.once.Do(func() { - r.log.Debug("shutting down runtime engine...") - // send immediate interrupt to engine and all children stores. - r.engine.Stop() - r.cancelFn() - }) + return &ProgramInstance{inst: inst, store: store}, nil } diff --git a/x/programs/runtime/runtime_test.go b/x/programs/runtime/runtime_test.go index e8f16e4e18..44658b0b58 100644 --- a/x/programs/runtime/runtime_test.go +++ b/x/programs/runtime/runtime_test.go @@ -8,397 +8,54 @@ import ( "testing" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/bytecodealliance/wasmtime-go/v14" + "github.com/near/borsh-go" "github.com/stretchr/testify/require" - "github.com/ava-labs/hypersdk/x/programs/engine" - "github.com/ava-labs/hypersdk/x/programs/host" - "github.com/ava-labs/hypersdk/x/programs/program" + "github.com/ava-labs/hypersdk/x/programs/test" ) -func TestStop(t *testing.T) { +func TestRuntimeCallProgramBasic(t *testing.T) { require := require.New(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // infinite loop - wasm, err := wasmtime.Wat2Wasm(` - (module - (memory 1) ;; 1 pages - (func $run (param i32) - (loop br 0) - ) - (func $alloc (param i32) (result i32) - i32.const 0 - ) - (export "memory" (memory 0)) - (export "run_guest" (func $run)) - (export "alloc" (func $alloc)) - ) - `) - require.NoError(err) - maxUnits := uint64(10000) - cfg := NewConfig().SetLimitMaxMemory(1 * program.MemoryPageSize) - eng := engine.New(engine.NewConfig()) - runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - - id := ids.GenerateTestID() - programContext := program.Context{ - ProgramID: id, - } - - err = runtime.Initialize(ctx, programContext, wasm, maxUnits) - require.NoError(err) - // stop the runtime - runtime.Stop() - - _, err = runtime.Call(ctx, "run", programContext) - require.ErrorIs(err, program.ErrTrapInterrupt) - // ensure no fees were consumed - balance, err := runtime.Meter().GetBalance() - require.NoError(err) - require.Equal(balance, maxUnits) -} -func TestCallParams(t *testing.T) { - require := require.New(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // add param[0] + param[1] - wasm, err := wasmtime.Wat2Wasm(` - (module - (memory 1) ;; 1 pages - ;; first argument is always the pointer to the context - (func $add (param i32 i64 i64) (result i64) - (i64.add local.get 1 local.get 2) - ) - (func $alloc (param i32) (result i32) - i32.const 0 - ) - (export "memory" (memory 0)) - (export "add_guest" (func $add)) - (export "alloc" (func $alloc)) - ) - `) - require.NoError(err) - maxUnits := uint64(10000) - cfg := NewConfig().SetLimitMaxMemory(1 * program.MemoryPageSize) - require.NoError(err) - eng := engine.New(engine.NewConfig()) - runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - - id := ids.GenerateTestID() - programContext := program.Context{ - ProgramID: id, - } + runtime := NewRuntime( + NewConfig(), + logging.NoLog{}, + test.Loader{ProgramName: "simple"}) - err = runtime.Initialize(ctx, programContext, wasm, maxUnits) + state := test.NewTestDB() + programID := ids.GenerateTestID() + result, err := runtime.CallProgram(ctx, &CallInfo{ProgramID: programID, State: state, FunctionName: "get_value", Params: nil, Fuel: 10000000}) require.NoError(err) - - arg := uint32(10) - - // all arguments are smart-pointers so this is a bit of a hack - resp, err := runtime.Call(ctx, "add", programContext, arg, arg) + expected, err := borsh.Serialize(0) require.NoError(err) - require.Equal(int64(arg+arg), resp[0]) - - // pass 3 params when 2 are expected. - _, err = runtime.Call(ctx, "add", programContext, 10, 10, 10) - require.ErrorIs(err, program.ErrInvalidParamCount) + require.Equal(expected, result) } -func TestInfiniteLoop(t *testing.T) { - require := require.New(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // infinite loop - wasm, err := wasmtime.Wat2Wasm(` - (module - (memory 1) ;; 1 pages - (func $run (param i32) - (loop br 0) - ) - (func $alloc (param i32) (result i32) - i32.const 0 - ) - (export "memory" (memory 0)) - (export "run_guest" (func $run)) - (export "alloc" (func $alloc)) - ) - `) - require.NoError(err) - maxUnits := uint64(10000) - cfg := NewConfig().SetLimitMaxMemory(1 * program.MemoryPageSize) - require.NoError(err) - eng := engine.New(engine.NewConfig()) - runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - - id := ids.GenerateTestID() - programContext := program.Context{ - ProgramID: id, - } - - err = runtime.Initialize(ctx, programContext, wasm, maxUnits) - require.NoError(err) - - _, err = runtime.Call(ctx, "run", programContext) - require.ErrorIs(err, program.ErrTrapOutOfFuel) +type ComplexReturn struct { + Program ids.ID + MaxUnits uint64 } -func TestMetering(t *testing.T) { +func TestRuntimeCallProgramComplexReturn(t *testing.T) { require := require.New(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // example has 2 ops codes and should cost 2 units - wasm, err := wasmtime.Wat2Wasm(` - (module - (memory 1) ;; 1 pages - (func $get (param i32) (result i32) - i32.const 0 - ) - (func $alloc (param i32) (result i32) - i32.const 0 - ) - (export "memory" (memory 0)) - (export "get_guest" (func $get)) - (export "alloc" (func $alloc)) - ) - `) - require.NoError(err) - maxUnits := uint64(20) - cfg := NewConfig().SetLimitMaxMemory(1 * program.MemoryPageSize) - require.NoError(err) - eng := engine.New(engine.NewConfig()) - runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - - id := ids.GenerateTestID() - programContext := program.Context{ - ProgramID: id, - } - - err = runtime.Initialize(ctx, programContext, wasm, maxUnits) - require.NoError(err) - balance, err := runtime.Meter().GetBalance() - require.NoError(err) - require.Equal(balance, maxUnits) - // spend 2 units for alloc and 2 units for get_guest - for i := 0; i < 5; i++ { - _, err = runtime.Call(ctx, "get", programContext) - require.NoError(err) - } - balance, err = runtime.Meter().GetBalance() - require.NoError(err) - require.Zero(balance) -} - -func TestMeterAfterStop(t *testing.T) { - require := require.New(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // example has 2 ops codes and should cost 2 units - wasm, err := wasmtime.Wat2Wasm(` - (module - (memory 1) ;; 1 pages - (func $get (param i32) (result i32) - i32.const 0 - ) - (func $alloc (param i32) (result i32) - i32.const 0 - ) - (export "memory" (memory 0)) - (export "get_guest" (func $get)) - (export "alloc" (func $alloc)) - ) - `) - require.NoError(err) - maxUnits := uint64(20) - cfg := NewConfig().SetLimitMaxMemory(1 * program.MemoryPageSize) - require.NoError(err) - eng := engine.New(engine.NewConfig()) - runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - - id := ids.GenerateTestID() - programContext := program.Context{ - ProgramID: id, - } - - err = runtime.Initialize(ctx, programContext, wasm, maxUnits) - require.NoError(err) - - // spend 4 units - _, err = runtime.Call(ctx, "get", programContext) - require.NoError(err) - // stop engine - runtime.Stop() - _, err = runtime.Call(ctx, "get", programContext) - require.ErrorIs(err, program.ErrTrapInterrupt) - // ensure meter is still operational - balance, err := runtime.Meter().GetBalance() - require.NoError(err) - require.Equal(balance, maxUnits-4) -} - -func TestLimitMaxMemory(t *testing.T) { - require := require.New(t) - - // memory has a single page - wasm, err := wasmtime.Wat2Wasm(` - (module - (memory 2) ;; 2 pages - (export "memory" (memory 0)) - ) - `) - require.NoError(err) - - // wasm defines 2 pages of memory but runtime set max 1 page - maxUnits := uint64(1) - cfg := NewConfig().SetLimitMaxMemory(1 * program.MemoryPageSize) - require.NoError(err) - eng := engine.New(engine.NewConfig()) - runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - - id := ids.GenerateTestID() - programContext := program.Context{ - ProgramID: id, - } - - err = runtime.Initialize(context.Background(), programContext, wasm, maxUnits) - require.ErrorContains(err, "memory minimum size of 2 pages exceeds memory limits") //nolint:forbidigo -} - -func TestLimitMaxMemoryGrow(t *testing.T) { - require := require.New(t) - - // we require an exported alloc function - wasm, err := wasmtime.Wat2Wasm(` - (module - (memory 1) ;; 1 pages - (func $alloc (param i32) (result i32) - i32.const 0 - ) - (export "memory" (memory 0)) - (export "alloc" (func $alloc)) - ) - `) - require.NoError(err) - - maxUnits := uint64(1) - cfg := NewConfig().SetLimitMaxMemory(1 * program.MemoryPageSize) - require.NoError(err) - eng := engine.New(engine.NewConfig()) - runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - - id := ids.GenerateTestID() - programContext := program.Context{ - ProgramID: id, - } - - err = runtime.Initialize(context.Background(), programContext, wasm, maxUnits) - require.NoError(err) - - mem, err := runtime.Memory() - require.NoError(err) - length, err := mem.Len() - require.NoError(err) - require.Equal(uint32(0x10000), length) - - // attempt to grow memory to 2 pages which exceeds the limit - _, err = mem.Grow(1) - require.ErrorContains(err, "failed to grow memory by `1`") //nolint:forbidigo -} - -func TestWriteExceedsLimitMaxMemory(t *testing.T) { - require := require.New(t) - - // we require an exported alloc function - wasm, err := wasmtime.Wat2Wasm(` - (module - (func (result i32) - (i32.const 42) - ) - (export "alloc" (func 0)) - (memory 1) ;; 1 pages - (export "memory" (memory 0)) - ) - `) - require.NoError(err) - - maxUnits := uint64(1) - cfg := NewConfig() - eng := engine.New(engine.NewConfig()) - runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - - id := ids.GenerateTestID() - programContext := program.Context{ - ProgramID: id, - } - - err = runtime.Initialize(context.Background(), programContext, wasm, maxUnits) - require.NoError(err) - mem, err := runtime.Memory() - require.NoError(err) - maxMemory, err := mem.Len() - require.NoError(err) - - bytes := utils.RandomBytes(int(maxMemory) + 1) - err = mem.Write(0, bytes) - require.ErrorIs(err, program.ErrOverflow) -} - -func TestWithMaxWasmStack(t *testing.T) { - require := require.New(t) - wasm, err := wasmtime.Wat2Wasm(` - (module - (memory 1) ;; 1 pages - (func $get (param i32) (result i32) - i32.const 0 - ) - (func $alloc (param i32) (result i32) - i32.const 0 - ) - (export "memory" (memory 0)) - (export "get_guest" (func $get)) - (export "alloc" (func $alloc)) - ) - `) - require.NoError(err) - - maxUnits := uint64(4) - ecfg, err := engine.NewConfigBuilder(). - WithMaxWasmStack(1000). - Build() - require.NoError(err) - eng := engine.New(ecfg) - cfg := NewConfig() - runtime := New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - - id := ids.GenerateTestID() - programContext := program.Context{ - ProgramID: id, - } - - err = runtime.Initialize(context.Background(), programContext, wasm, maxUnits) - require.NoError(err) - _, err = runtime.Call(context.Background(), "get", programContext) - require.NoError(err) + runtime := NewRuntime( + NewConfig(), + logging.NoLog{}, + test.Loader{ProgramName: "return_complex_type"}) - // stack is ok for 1 call. - ecfg, err = engine.NewConfigBuilder(). - WithMaxWasmStack(580). - Build() + state := test.NewTestDB() + programID := ids.GenerateTestID() + result, err := runtime.CallProgram(ctx, &CallInfo{ProgramID: programID, State: state, FunctionName: "get_value", Params: nil, Fuel: 10000000}) require.NoError(err) - eng = engine.New(ecfg) - runtime = New(logging.NoLog{}, eng, host.NoSupportedImports, cfg) - err = runtime.Initialize(context.Background(), programContext, wasm, maxUnits) + expected, err := borsh.Serialize(ComplexReturn{Program: programID, MaxUnits: 1000}) require.NoError(err) - // exceed the stack limit - _, err = runtime.Call(context.Background(), "get", programContext) - require.ErrorIs(err, program.ErrTrapStackOverflow) + require.Equal(expected, result) } diff --git a/x/programs/rust/examples/counter/Cargo.toml b/x/programs/rust/examples/counter/Cargo.toml index 07861114e8..f46a04f7ad 100644 --- a/x/programs/rust/examples/counter/Cargo.toml +++ b/x/programs/rust/examples/counter/Cargo.toml @@ -3,19 +3,18 @@ name = "counter" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] [dependencies] -wasmlanche-sdk = { version = "0.1.0", path = "../../wasmlanche-sdk" } +wasmlanche-sdk = { workspace = true } +borsh = { workspace = true } [dev-dependencies] -simulator = { path = "../../../cmd/simulator/" } +simulator = { workspace = true } [build-dependencies] -wasmlanche-sdk = { path = "../../wasmlanche-sdk", features = ["build"] } - -[lib] -crate-type = ["cdylib"] # set the crate(needed for cargo build to work properly) +wasmlanche-sdk = { workspace = true, features = ["build"] } # when creating a new project, you can uncomment the following lines to enable optimizations # [profile.release] diff --git a/x/programs/rust/examples/counter/src/lib.rs b/x/programs/rust/examples/counter/src/lib.rs index 9937b1ba05..286c0e2a21 100644 --- a/x/programs/rust/examples/counter/src/lib.rs +++ b/x/programs/rust/examples/counter/src/lib.rs @@ -9,7 +9,7 @@ enum StateKeys { /// Initializes the program address a count of 0. #[public] pub fn initialize_address(context: Context, address: Address) -> bool { - let Context { program } = context; + let Context { program, .. } = context; if program .state() @@ -31,7 +31,7 @@ pub fn initialize_address(context: Context, address: Address) -> bool { #[public] pub fn inc(context: Context, to: Address, amount: i64) -> bool { let counter = amount + get_value(context, to); - let Context { program } = context; + let Context { program, .. } = context; program .state() @@ -43,7 +43,7 @@ pub fn inc(context: Context, to: Address, amount: i64) -> bool { /// Increments the count at the address by the amount for an external program. #[public] -pub fn inc_external(_: Context, target: Program, max_units: i64, of: Address, amount: i64) -> i64 { +pub fn inc_external(_: Context, target: Program, max_units: i64, of: Address, amount: i64) -> bool { let params = params!(&of, &amount).unwrap(); target.call_function("inc", ¶ms, max_units).unwrap() } @@ -51,7 +51,7 @@ pub fn inc_external(_: Context, target: Program, max_units: i64, of: Address, am /// Gets the count at the address. #[public] pub fn get_value(context: Context, of: Address) -> i64 { - let Context { program } = context; + let Context { program, .. } = context; program .state() .get(StateKeys::Counter(of)) @@ -74,6 +74,7 @@ mod tests { const PROGRAM_PATH: &str = env!("PROGRAM_PATH"); #[test] + #[ignore] fn init_program() { let simulator = simulator::Client::new(); @@ -109,9 +110,8 @@ mod tests { }); // run plan - let plan_responses = simulator.run_plan(&plan).unwrap(); + let plan_responses = simulator.run_plan(plan).unwrap(); - // ensure no errors assert!( plan_responses.iter().all(|resp| resp.error.is_none()), "error: {:?}", @@ -123,6 +123,7 @@ mod tests { } #[test] + #[ignore] fn increment() { let simulator = simulator::Client::new(); @@ -141,22 +142,7 @@ mod tests { require: None, }); - let counter1_id = plan.add_step(Step { - endpoint: Endpoint::Execute, - method: "program_create".into(), - max_units: 1000000, - params: vec![Param::String(PROGRAM_PATH.into())], - require: None, - }); - plan.add_step(Step { - endpoint: Endpoint::Execute, - method: "initialize_address".into(), - max_units: 1000000, - params: vec![counter1_id.into(), bob_key.clone()], - require: None, - }); - - let counter2_id = plan.add_step(Step { + let counter_id = plan.add_step(Step { endpoint: Endpoint::Execute, method: "program_create".into(), max_units: 1000000, @@ -167,7 +153,7 @@ mod tests { endpoint: Endpoint::Execute, method: "initialize_address".into(), max_units: 1000000, - params: vec![counter2_id.into(), bob_key.clone()], + params: vec![counter_id.into(), bob_key.clone()], require: None, }); @@ -175,7 +161,7 @@ mod tests { endpoint: Endpoint::Execute, method: "inc".into(), max_units: 1000000, - params: vec![counter2_id.into(), bob_key.clone(), 10.into()], + params: vec![counter_id.into(), bob_key.clone(), 10.into()], require: None, }); @@ -183,14 +169,14 @@ mod tests { endpoint: Endpoint::ReadOnly, method: "get_value".into(), max_units: 0, - params: vec![counter2_id.into(), bob_key.clone()], + params: vec![counter_id.into(), bob_key.clone()], require: Some(Require { result: ResultAssertion::NumericEq(10), }), }); // run plan - let plan_responses = simulator.run_plan(&plan).unwrap(); + let plan_responses = simulator.run_plan(plan).unwrap(); // ensure no errors assert!( @@ -204,6 +190,7 @@ mod tests { } #[test] + #[ignore] fn external_call() { let simulator = simulator::Client::new(); @@ -229,13 +216,6 @@ mod tests { params: vec![Param::String(PROGRAM_PATH.into())], require: None, }); - plan.add_step(Step { - endpoint: Endpoint::Execute, - method: "initialize_address".into(), - max_units: 1000000, - params: vec![counter1_id.into(), bob_key.clone()], - require: None, - }); let counter2_id = plan.add_step(Step { endpoint: Endpoint::Execute, @@ -291,7 +271,7 @@ mod tests { }); // run plan - let plan_responses = simulator.run_plan(&plan).unwrap(); + let plan_responses = simulator.run_plan(plan).unwrap(); // ensure no errors assert!( diff --git a/x/programs/rust/examples/token/Cargo.toml b/x/programs/rust/examples/token/Cargo.toml index 683eb2f196..bb3f75dae1 100644 --- a/x/programs/rust/examples/token/Cargo.toml +++ b/x/programs/rust/examples/token/Cargo.toml @@ -3,21 +3,18 @@ name = "token" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] [dependencies] -wasmlanche-sdk = { path = "../../wasmlanche-sdk" } -borsh = { version = "1.2.0", features = ["derive"] } +wasmlanche-sdk = { workspace = true } +borsh = { workspace = true } [dev-dependencies] -serde_json = "1.0.68" -simulator = { path = "../../../cmd/simulator" } +simulator = { workspace = true } [build-dependencies] -wasmlanche-sdk = { path = "../../wasmlanche-sdk", features = ["build"] } - -[lib] -crate-type = ["cdylib"] # set the crate(needed for cargo build to work properly) +wasmlanche-sdk = { workspace = true, features = ["build"] } # when creating a new project, you can uncomment the following lines to enable optimizations # [profile.release] diff --git a/x/programs/rust/examples/token/src/lib.rs b/x/programs/rust/examples/token/src/lib.rs index 0e8259145b..50ae8c5b4d 100644 --- a/x/programs/rust/examples/token/src/lib.rs +++ b/x/programs/rust/examples/token/src/lib.rs @@ -19,8 +19,8 @@ enum StateKey { /// Initializes the program with a name, symbol, and total supply. #[public] -pub fn init(context: Context) -> bool { - let Context { program } = context; +pub fn init(context: Context) { + let Context { program, .. } = context; // set total supply program @@ -39,14 +39,12 @@ pub fn init(context: Context) -> bool { .state() .store(StateKey::Symbol, b"WACK") .expect("failed to store symbol"); - - true } /// Returns the total supply of the token. #[public] pub fn get_total_supply(context: Context) -> i64 { - let Context { program } = context; + let Context { program, .. } = context; program .state() .get(StateKey::TotalSupply) @@ -56,7 +54,7 @@ pub fn get_total_supply(context: Context) -> i64 { /// Transfers balance from the token owner to the recipient. #[public] pub fn mint_to(context: Context, recipient: Address, amount: i64) -> bool { - let Context { program } = context; + let Context { program, .. } = context; let balance = program .state() .get::(StateKey::Balance(recipient)) @@ -73,7 +71,7 @@ pub fn mint_to(context: Context, recipient: Address, amount: i64) -> bool { /// Burn the token from the recipient. #[public] pub fn burn_from(context: Context, recipient: Address) -> i64 { - let Context { program } = context; + let Context { program, .. } = context; program .state() .delete::(StateKey::Balance(recipient)) @@ -84,7 +82,7 @@ pub fn burn_from(context: Context, recipient: Address) -> i64 { /// Transfers balance from the sender to the the recipient. #[public] pub fn transfer(context: Context, sender: Address, recipient: Address, amount: i64) -> bool { - let Context { program } = context; + let Context { program, .. } = context; assert_ne!(sender, recipient, "sender and recipient must be different"); // ensure the sender has adequate balance @@ -133,7 +131,7 @@ pub fn mint_to_many(context: Context, minters: Vec) -> bool { /// Gets the balance of the recipient. #[public] pub fn get_balance(context: Context, recipient: Address) -> i64 { - let Context { program } = context; + let Context { program, .. } = context; program .state() .get(StateKey::Balance(recipient)) @@ -149,6 +147,7 @@ mod tests { const PROGRAM_PATH: &str = env!("PROGRAM_PATH"); #[test] + #[ignore] fn create_program() { let simulator = simulator::Client::new(); @@ -160,7 +159,7 @@ mod tests { plan.add_step(Step::create_program(PROGRAM_PATH)); // run plan - let plan_responses = simulator.run_plan(&plan).unwrap(); + let plan_responses = simulator.run_plan(plan).unwrap(); // ensure no errors assert!( @@ -174,6 +173,130 @@ mod tests { } #[test] + #[ignore] + fn init_token() { + let simulator = simulator::Client::new(); + + let owner_key_id = String::from("owner"); + let owner_key = Key::Ed25519(owner_key_id.clone()); + + let mut plan = Plan::new(owner_key_id); + + plan.add_step(Step::create_key(owner_key.clone())); + let program_id = plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "program_create".into(), + max_units: 0, + params: vec![Param::String(PROGRAM_PATH.into())], + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "init".into(), + params: vec![program_id.into()], + max_units: 1000000, + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::ReadOnly, + method: "get_total_supply".into(), + max_units: 0, + params: vec![program_id.into()], + require: Some(Require { + result: ResultAssertion::NumericEq(INITIAL_SUPPLY as u64), + }), + }); + + let plan_responses = simulator.run_plan(plan).unwrap(); + + assert!( + plan_responses.iter().all(|resp| resp.error.is_none()), + "error: {:?}", + plan_responses + .iter() + .filter_map(|resp| resp.error.as_ref()) + .next() + ); + } + + #[test] + #[ignore] + fn mint() { + let simulator = simulator::Client::new(); + + let owner_key_id = String::from("owner"); + let [alice_key] = ["alice"] + .map(String::from) + .map(Key::Ed25519) + .map(Param::Key); + let alice_initial_balance = 1000; + + let mut plan = Plan::new(owner_key_id.clone()); + + plan.add_step(Step::create_key(Key::Ed25519(owner_key_id))); + + let program_id = plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "program_create".into(), + max_units: 0, + params: vec![Param::String(PROGRAM_PATH.into())], + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::Key, + method: "key_create".into(), + params: vec![alice_key.clone()], + max_units: 0, + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "init".into(), + params: vec![program_id.into()], + max_units: 1000000, + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::Execute, + method: "mint_to".into(), + params: vec![ + program_id.into(), + alice_key.clone(), + Param::U64(alice_initial_balance), + ], + max_units: 1000000, + require: None, + }); + + plan.add_step(Step { + endpoint: Endpoint::ReadOnly, + method: "get_balance".into(), + max_units: 0, + params: vec![program_id.into(), alice_key], + require: Some(Require { + result: ResultAssertion::NumericEq(alice_initial_balance), + }), + }); + + let plan_responses = simulator.run_plan(plan).unwrap(); + + assert!( + plan_responses.iter().all(|resp| resp.error.is_none()), + "error: {:?}", + plan_responses + .iter() + .filter_map(|resp| resp.error.as_ref()) + .next() + ); + } + + #[test] + #[ignore] fn mint_and_transfer() { let simulator = simulator::Client::new(); @@ -297,7 +420,7 @@ mod tests { }), }); - let plan_responses = simulator.run_plan(&plan).unwrap(); + let plan_responses = simulator.run_plan(plan).unwrap(); assert!( plan_responses.iter().all(|resp| resp.error.is_none()), diff --git a/x/programs/rust/sdk-macros/Cargo.toml b/x/programs/rust/sdk-macros/Cargo.toml new file mode 100644 index 0000000000..7113fd89a8 --- /dev/null +++ b/x/programs/rust/sdk-macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "sdk-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.82" +quote = "1.0.36" +syn = { version = "2.0.63", features = ["full", "extra-traits"] } + +[dev-dependencies] +borsh = { workpsace = true } +trybuild = "1.0.96" +wasmlanche-sdk = { workspace = true } diff --git a/x/programs/rust/sdk_macros/src/lib.rs b/x/programs/rust/sdk-macros/src/lib.rs similarity index 78% rename from x/programs/rust/sdk_macros/src/lib.rs rename to x/programs/rust/sdk-macros/src/lib.rs index 43bbc22da3..6a185cbba1 100644 --- a/x/programs/rust/sdk_macros/src/lib.rs +++ b/x/programs/rust/sdk-macros/src/lib.rs @@ -1,7 +1,7 @@ extern crate proc_macro; use proc_macro::TokenStream; -use quote::quote; +use quote::{format_ident, quote, ToTokens}; use syn::{ parse_macro_input, parse_str, spanned::Spanned, Fields, FnArg, Ident, ItemEnum, ItemFn, Pat, PatType, Path, Type, Visibility, @@ -34,7 +34,7 @@ pub fn public(_: TokenStream, item: TokenStream) -> TokenStream { Ident::new(&format!("{name}_guest"), name.span()) }; - let (input, context_type, first_arg_err) = { + let (input, user_specified_context_type, first_arg_err) = { let mut context_type: Box = Box::new(parse_str(CONTEXT_TYPE).unwrap()); let mut input = input; @@ -73,28 +73,28 @@ pub fn public(_: TokenStream, item: TokenStream) -> TokenStream { (input, context_type, first_arg_err) }; - let input_args = input.sig.inputs.iter(); - - let param_idents = input_args.enumerate().skip(1).map(|(i, fn_arg)| { - match fn_arg { - FnArg::Receiver(_) => Err(syn::Error::new( - fn_arg.span(), - "Functions with the `#[public]` attribute cannot have a `self` parameter.", - )), - FnArg::Typed(PatType { pat, .. }) => match pat.as_ref() { - // TODO: - // we should likely remove this constraint. If we provide upgradability - // in the future, we may not want to change the function signature - // which means we might want wildcards in order to help produce stable APIs - Pat::Wild(_) => Err(syn::Error::new( - fn_arg.span(), - "Functions with the `#[public]` attribute can only ignore the first parameter.", - )), - _ => Ok(Ident::new(&format!("param_{i}"), fn_arg.span())), - }, - } + let input_types_iter = input.sig.inputs.iter().skip(1).map(|fn_arg| match fn_arg { + FnArg::Receiver(_) => Err(syn::Error::new( + fn_arg.span(), + "Functions with the `#[public]` attribute cannot have a `self` parameter.", + )), + FnArg::Typed(PatType { ty, .. }) => Ok(ty.clone()), }); + let arg_props = std::iter::once(Ok(user_specified_context_type)) + .chain(input_types_iter) + .enumerate() + .map(|(i, ty)| { + ty.map(|ty| PatType { + attrs: vec![], + pat: Box::new(Pat::Verbatim( + format_ident!("param_{}", i).into_token_stream(), + )), + colon_token: Default::default(), + ty, + }) + }); + let result = match (vis_err, first_arg_err) { (None, None) => Ok(vec![]), (Some(err), None) | (None, Some(err)) => Err(err), @@ -104,7 +104,7 @@ pub fn public(_: TokenStream, item: TokenStream) -> TokenStream { } }; - let param_names_or_err = param_idents.fold(result, |result, param| match (result, param) { + let arg_props_or_err = arg_props.fold(result, |result, param| match (result, param) { // ignore Ok or first error encountered (Err(errors), Ok(_)) | (Ok(_), Err(errors)) => Err(errors), // combine errors @@ -119,34 +119,42 @@ pub fn public(_: TokenStream, item: TokenStream) -> TokenStream { } }); - let param_names = match param_names_or_err { + let args_props = match arg_props_or_err { Ok(param_names) => param_names, Err(errors) => return errors.to_compile_error().into(), }; - let converted_params = param_names.iter().map(|param_name| { + let converted_params = args_props.iter().map(|PatType { pat: name, .. }| { quote! { - unsafe { - wasmlanche_sdk::from_host_ptr(#param_name).expect("error serializing ptr") - } + args.#name } }); - let param_types = std::iter::repeat(quote! { *const u8 }).take(param_names.len()); - - let return_type = &input.sig.output; let name = &input.sig.ident; let external_call = quote! { mod private { use super::*; + #[derive(borsh::BorshDeserialize)] + struct Args { + #(#args_props),* + } + + #[link(wasm_import_module = "program")] + extern "C" { + #[link_name = "set_call_result"] + fn set_call_result(ptr: *const u8, len: usize); + } #[no_mangle] - unsafe extern "C" fn #new_name(param_0: *const u8, #(#param_names: #param_types), *) #return_type { - let param_0: #context_type = unsafe { - wasmlanche_sdk::from_host_ptr(param_0).expect("error serializing ptr") + unsafe extern "C" fn #new_name(args: *const u8) { + let args: Args = unsafe { + wasmlanche_sdk::from_host_ptr(args).expect("error fetching serialized args") }; - super::#name(param_0, #(#converted_params),*) + + let result = super::#name(#(#converted_params),*); + let result = borsh::to_vec(&result).expect("error serializing result"); + unsafe { set_call_result(result.as_ptr(), result.len()) }; } } }; diff --git a/x/programs/rust/sdk_macros/tests/compile.rs b/x/programs/rust/sdk-macros/tests/compile.rs similarity index 100% rename from x/programs/rust/sdk_macros/tests/compile.rs rename to x/programs/rust/sdk-macros/tests/compile.rs diff --git a/x/programs/rust/sdk_macros/tests/ui/first-param.rs b/x/programs/rust/sdk-macros/tests/ui/first-param.rs similarity index 100% rename from x/programs/rust/sdk_macros/tests/ui/first-param.rs rename to x/programs/rust/sdk-macros/tests/ui/first-param.rs diff --git a/x/programs/rust/sdk_macros/tests/ui/first-param.stderr b/x/programs/rust/sdk-macros/tests/ui/first-param.stderr similarity index 100% rename from x/programs/rust/sdk_macros/tests/ui/first-param.stderr rename to x/programs/rust/sdk-macros/tests/ui/first-param.stderr diff --git a/x/programs/rust/sdk_macros/tests/ui/not-pub.rs b/x/programs/rust/sdk-macros/tests/ui/not-pub.rs similarity index 100% rename from x/programs/rust/sdk_macros/tests/ui/not-pub.rs rename to x/programs/rust/sdk-macros/tests/ui/not-pub.rs diff --git a/x/programs/rust/sdk_macros/tests/ui/not-pub.stderr b/x/programs/rust/sdk-macros/tests/ui/not-pub.stderr similarity index 100% rename from x/programs/rust/sdk_macros/tests/ui/not-pub.stderr rename to x/programs/rust/sdk-macros/tests/ui/not-pub.stderr diff --git a/x/programs/rust/sdk_macros/tests/ui/receiver.rs b/x/programs/rust/sdk-macros/tests/ui/receiver.rs similarity index 100% rename from x/programs/rust/sdk_macros/tests/ui/receiver.rs rename to x/programs/rust/sdk-macros/tests/ui/receiver.rs diff --git a/x/programs/rust/sdk_macros/tests/ui/receiver.stderr b/x/programs/rust/sdk-macros/tests/ui/receiver.stderr similarity index 100% rename from x/programs/rust/sdk_macros/tests/ui/receiver.stderr rename to x/programs/rust/sdk-macros/tests/ui/receiver.stderr diff --git a/x/programs/rust/sdk_macros/tests/ui/user-defined-context-type.rs b/x/programs/rust/sdk-macros/tests/ui/user-defined-context-type.rs similarity index 100% rename from x/programs/rust/sdk_macros/tests/ui/user-defined-context-type.rs rename to x/programs/rust/sdk-macros/tests/ui/user-defined-context-type.rs diff --git a/x/programs/rust/sdk_macros/tests/ui/user-defined-context-type.stderr b/x/programs/rust/sdk-macros/tests/ui/user-defined-context-type.stderr similarity index 100% rename from x/programs/rust/sdk_macros/tests/ui/user-defined-context-type.stderr rename to x/programs/rust/sdk-macros/tests/ui/user-defined-context-type.stderr diff --git a/x/programs/rust/sdk_macros/Cargo.toml b/x/programs/rust/sdk_macros/Cargo.toml deleted file mode 100644 index 7a0a474f49..0000000000 --- a/x/programs/rust/sdk_macros/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "sdk_macros" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0.67" -quote = "1.0.33" -syn = { version = "2.0.37", features = ["full", "extra-traits"] } - -[dev-dependencies] -borsh = "1.5.0" -trybuild = "1.0.89" -wasmlanche-sdk = { path = "../wasmlanche-sdk" } diff --git a/x/programs/rust/sdk_macros/tests/ui/ignore-second-param.rs b/x/programs/rust/sdk_macros/tests/ui/ignore-second-param.rs deleted file mode 100644 index 623a62ce22..0000000000 --- a/x/programs/rust/sdk_macros/tests/ui/ignore-second-param.rs +++ /dev/null @@ -1,8 +0,0 @@ -use sdk_macros::public; -#[allow(unused_imports)] -use wasmlanche_sdk::Context; - -#[public] -pub fn test(_: Context, _: u32) {} - -fn main() {} diff --git a/x/programs/rust/sdk_macros/tests/ui/ignore-second-param.stderr b/x/programs/rust/sdk_macros/tests/ui/ignore-second-param.stderr deleted file mode 100644 index b757ee3819..0000000000 --- a/x/programs/rust/sdk_macros/tests/ui/ignore-second-param.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Functions with the `#[public]` attribute can only ignore the first parameter. - --> tests/ui/ignore-second-param.rs:6:25 - | -6 | pub fn test(_: Context, _: u32) {} - | ^ diff --git a/x/programs/rust/sdk_macros/tests/ui/wild.rs b/x/programs/rust/sdk_macros/tests/ui/wild.rs deleted file mode 100644 index 7cc4ee381b..0000000000 --- a/x/programs/rust/sdk_macros/tests/ui/wild.rs +++ /dev/null @@ -1,8 +0,0 @@ -use sdk_macros::public; - -struct Context; - -#[public] -pub fn test(_: Context, _: usize) {} - -fn main() {} diff --git a/x/programs/rust/sdk_macros/tests/ui/wild.stderr b/x/programs/rust/sdk_macros/tests/ui/wild.stderr deleted file mode 100644 index f7e8b5b56d..0000000000 --- a/x/programs/rust/sdk_macros/tests/ui/wild.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Functions with the `#[public]` attribute can only ignore the first parameter. - --> tests/ui/wild.rs:6:25 - | -6 | pub fn test(_: Context, _: usize) {} - | ^ diff --git a/x/programs/rust/wasmlanche-sdk/Cargo.toml b/x/programs/rust/wasmlanche-sdk/Cargo.toml index 0904c89d3a..5cacccc60a 100644 --- a/x/programs/rust/wasmlanche-sdk/Cargo.toml +++ b/x/programs/rust/wasmlanche-sdk/Cargo.toml @@ -3,19 +3,13 @@ name = "wasmlanche-sdk" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -borsh = { version = "1.2.0", features = ["derive"] } -sdk_macros = { version = "0.1.0", path = "../sdk_macros" } -thiserror = "1.0.46" - -# optional dependencies -serde_json = { version = "1.0.64", optional = true } - - -[features] -build = ["serde_json"] +borsh = { workspace = true } +sdk-macros = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] wasmtime = "14" + +[features] +build = [] diff --git a/x/programs/rust/wasmlanche-sdk/src/lib.rs b/x/programs/rust/wasmlanche-sdk/src/lib.rs index 544c271ca8..3420cc0585 100644 --- a/x/programs/rust/wasmlanche-sdk/src/lib.rs +++ b/x/programs/rust/wasmlanche-sdk/src/lib.rs @@ -4,10 +4,12 @@ pub mod params; pub mod state; pub mod types; +mod logging; mod memory; mod program; pub use self::{ + logging::log, memory::from_host_ptr, params::{serialize_param, Params}, program::Program, @@ -26,7 +28,8 @@ pub enum Error { Param(#[from] std::io::Error), } -#[derive(Clone, Copy, borsh::BorshSerialize, borsh::BorshDeserialize)] +#[derive(Clone, Copy, borsh::BorshSerialize, borsh::BorshDeserialize, Debug)] pub struct Context { pub program: program::Program, + pub actor: types::Address, } diff --git a/x/programs/rust/wasmlanche-sdk/src/logging.rs b/x/programs/rust/wasmlanche-sdk/src/logging.rs new file mode 100644 index 0000000000..f0f7ec50b3 --- /dev/null +++ b/x/programs/rust/wasmlanche-sdk/src/logging.rs @@ -0,0 +1,49 @@ +#[macro_export] +macro_rules! dbg { + () => { + #[cfg(debug_assertions)] + { + let as_string = format!("[{}:{}:{}]", file!(), line!(), column!()); + $crate::log(as_string.as_str()); + } + }; + ($val:expr $(,)?) => {{ + match $val { + tmp => { + #[cfg(debug_assertions)] + { + let as_string = format!( + "[{}:{}:{}] {} = {:#?}", + file!(), + line!(), + column!(), + stringify!($val), + &tmp + ); + $crate::log(as_string.as_str()); + } + tmp + } + } + }}; + ($($val:expr),+ $(,)?) => { + ($($crate::dbg!($val)),+,) + }; +} + +/// # Panics +/// Panics if there was an issue regarding memory allocation on the host +pub fn log(text: &str) { + log_bytes(text.as_bytes()); +} + +/// Logging facility for debugging purposes +pub(super) fn log_bytes(bytes: &[u8]) { + #[link(wasm_import_module = "log")] + extern "C" { + #[link_name = "write"] + fn ffi(ptr: *const u8, len: usize); + } + + unsafe { ffi(bytes.as_ptr(), bytes.len()) }; +} diff --git a/x/programs/rust/wasmlanche-sdk/src/program.rs b/x/programs/rust/wasmlanche-sdk/src/program.rs index 70b4006b9e..0fae85e7b7 100644 --- a/x/programs/rust/wasmlanche-sdk/src/program.rs +++ b/x/programs/rust/wasmlanche-sdk/src/program.rs @@ -1,4 +1,5 @@ use crate::{ + memory::into_bytes, state::{Error as StateError, Key, State}, Params, }; @@ -7,7 +8,7 @@ use std::hash::Hash; /// Represents the current Program in the context of the caller. Or an external /// program that is being invoked. -#[derive(Clone, Copy, BorshDeserialize, BorshSerialize)] +#[derive(Clone, Copy, BorshDeserialize, BorshSerialize, Debug)] pub struct Program([u8; Self::LEN]); impl Program { @@ -20,11 +21,6 @@ impl Program { &self.0 } - #[must_use] - pub(crate) fn new(id: [u8; Self::LEN]) -> Self { - Self(id) - } - /// Returns a State object that can be used to interact with persistent /// storage exposed by the host. #[must_use] @@ -32,7 +28,7 @@ impl Program { where K: Into + Hash + PartialEq + Eq + Clone, { - State::new(Program::new(*self.id())) + State::new() } /// Attempts to call a function `name` with `args` on the given program. This method @@ -41,20 +37,20 @@ impl Program { /// Returns a [`StateError`] if the call fails. /// # Safety /// The caller must ensure that `function_name` + `args` point to valid memory locations. - pub fn call_function( + pub fn call_function( &self, function_name: &str, args: &Params, max_units: i64, - ) -> Result { + ) -> Result { #[link(wasm_import_module = "program")] extern "C" { #[link_name = "call_program"] - fn ffi(ptr: *const u8, len: usize) -> i64; + fn ffi(ptr: *const u8, len: usize) -> *const u8; } let args = CallProgramArgs { - target_id: self.id(), + target_id: self, function: function_name.as_bytes(), args_ptr: args, max_units, @@ -62,13 +58,17 @@ impl Program { let args_bytes = borsh::to_vec(&args).map_err(|_| StateError::Serialization)?; - Ok(unsafe { ffi(args_bytes.as_ptr(), args_bytes.len()) }) + let ptr = unsafe { ffi(args_bytes.as_ptr(), args_bytes.len()) }; + + let bytes = into_bytes(ptr).unwrap_or_default(); + + borsh::from_slice(&bytes).map_err(|_| StateError::Deserialization) } } #[derive(BorshSerialize)] struct CallProgramArgs<'a> { - target_id: &'a [u8], + target_id: &'a Program, function: &'a [u8], args_ptr: &'a [u8], max_units: i64, diff --git a/x/programs/rust/wasmlanche-sdk/src/state.rs b/x/programs/rust/wasmlanche-sdk/src/state.rs index e54acafca6..749dc6df4a 100644 --- a/x/programs/rust/wasmlanche-sdk/src/state.rs +++ b/x/programs/rust/wasmlanche-sdk/src/state.rs @@ -1,4 +1,4 @@ -use crate::{memory::into_bytes, program::Program, state::Error as StateError}; +use crate::{memory::into_bytes, state::Error as StateError}; use borsh::{from_slice, to_vec, BorshDeserialize, BorshSerialize}; use std::{collections::HashMap, hash::Hash, ops::Deref}; @@ -42,7 +42,6 @@ pub struct State where K: Into + Hash + PartialEq + Eq + Clone, { - program: Program, cache: HashMap>, } @@ -58,14 +57,22 @@ where } } +impl Default for State +where + K: Into + Hash + PartialEq + Eq + Clone, +{ + fn default() -> Self { + Self::new() + } +} + impl State where K: Into + Hash + PartialEq + Eq + Clone, { #[must_use] - pub fn new(program: Program) -> Self { + pub fn new() -> Self { Self { - program, cache: HashMap::new(), } } @@ -103,7 +110,6 @@ where val } else { let args = GetAndDeleteArgs { - caller: self.program, // TODO: shouldn't have to clone here key: key.clone().into().0, }; @@ -134,10 +140,7 @@ where pub fn delete(&mut self, key: K) -> Result, Error> { self.cache.remove(&key); - let args = GetAndDeleteArgs { - caller: self.program, - key: key.into().0, - }; + let args = GetAndDeleteArgs { key: key.into().0 }; let args_bytes = borsh::to_vec(&args).map_err(|_| StateError::Serialization)?; @@ -151,7 +154,6 @@ where .drain() .map(|(key, val)| (key.into(), val)) .map(|(key, val)| PutArgs { - caller: self.program, key: key.0, bytes: val, }) @@ -187,14 +189,12 @@ impl Key { #[derive(BorshSerialize)] struct PutArgs { - caller: Program, key: Vec, bytes: Vec, } #[derive(BorshSerialize)] struct GetAndDeleteArgs { - caller: Program, key: Vec, } diff --git a/x/programs/rust/wasmlanche-sdk/src/types.rs b/x/programs/rust/wasmlanche-sdk/src/types.rs index 9edc9539d6..e6446bd9ad 100644 --- a/x/programs/rust/wasmlanche-sdk/src/types.rs +++ b/x/programs/rust/wasmlanche-sdk/src/types.rs @@ -7,7 +7,7 @@ pub struct Address([u8; Self::LEN]); impl Address { // TODO: move to HyperSDK.Address which will be 33 bytes - pub const LEN: usize = 32; + pub const LEN: usize = 33; // Constructor function for Address #[must_use] pub fn new(bytes: [u8; Self::LEN]) -> Self { @@ -19,6 +19,12 @@ impl Address { } } +impl Default for Address { + fn default() -> Self { + Self([0; Self::LEN]) + } +} + impl IntoIterator for Address { type Item = u8; type IntoIter = std::array::IntoIter; diff --git a/x/programs/rust/wasmlanche-sdk/tests/public_function.rs b/x/programs/rust/wasmlanche-sdk/tests/public_function.rs index 4a3372414e..cc918a8ae5 100644 --- a/x/programs/rust/wasmlanche-sdk/tests/public_function.rs +++ b/x/programs/rust/wasmlanche-sdk/tests/public_function.rs @@ -2,8 +2,8 @@ use std::{ path::{Path, PathBuf}, process::Command, }; -use wasmlanche_sdk::{Context, Program}; -use wasmtime::{Instance, Module, Store, TypedFunc}; +use wasmlanche_sdk::{types::Address, Context, Program}; +use wasmtime::{Caller, Extern, Func, Instance, Module, Store, TypedFunc}; const WASM_TARGET: &str = "wasm32-unknown-unknown"; const TEST_PKG: &str = "test-crate"; @@ -55,31 +55,59 @@ fn public_functions() { } type AllocParam = i32; -type AllocReturn = i32; +type AllocReturn = u32; +type AllocFn = TypedFunc; +type UserDefinedFnParam = u32; +type UserDefinedFnReturn = (); +type UserDefinedFn = TypedFunc; +type StoreData = Option>; struct TestCrate { - store: Store<()>, + store: Store, instance: Instance, - allocate_func: TypedFunc, - always_true_func: TypedFunc, - combine_last_bit_of_each_id_byte_func: TypedFunc, + allocate_func: AllocFn, + always_true_func: UserDefinedFn, + combine_last_bit_of_each_id_byte_func: UserDefinedFn, } impl TestCrate { fn new(wasm_path: impl AsRef) -> Self { - let mut store: Store<()> = Store::default(); + let mut store: Store = Store::default(); let module = Module::from_file(store.engine(), wasm_path).expect("failed to load wasm"); - let instance = Instance::new(&mut store, &module, &[]).expect("failed to instantiate wasm"); + + let set_result_fn = Func::wrap( + &mut store, + |mut caller: Caller<'_, StoreData>, ptr: u32, len: u32| { + let Extern::Memory(memory) = caller + .get_export("memory") + .expect("memory should be exported") + else { + panic!("export `memory` should be of type `Memory`"); + }; + + let (ptr, len) = (ptr as usize, len as usize); + + let result = memory + .data(&mut caller) + .get(ptr..ptr + len) + .expect("data should exist"); + + *caller.data_mut() = Some(result.to_vec()); + }, + ); + + let instance = Instance::new(&mut store, &module, &[set_result_fn.into()]) + .expect("failed to instantiate wasm"); let allocate_func = instance - .get_typed_func::(&mut store, "alloc") + .get_typed_func(&mut store, "alloc") .expect("failed to find `alloc` function"); - let always_true_func: TypedFunc = instance - .get_typed_func::(&mut store, "always_true_guest") + let always_true_func = instance + .get_typed_func(&mut store, "always_true_guest") .expect("failed to find `always_true` function"); let combine_last_bit_of_each_id_byte_func = instance - .get_typed_func::(&mut store, "combine_last_bit_of_each_id_byte_guest") + .get_typed_func(&mut store, "combine_last_bit_of_each_id_byte_guest") .expect("combine_last_bit_of_each_id_byte should be a function"); Self { @@ -91,18 +119,19 @@ impl TestCrate { } } - fn write_context(&mut self) -> i32 { + fn write_context(&mut self) -> AllocReturn { let program_id: [u8; Program::LEN] = std::array::from_fn(|_| 1); // this is a hack to create a program since the constructor is private let program: Program = borsh::from_slice(&program_id).expect("the program should deserialize"); - let context = Context { program }; + let actor = Address::default(); + let context = Context { program, actor }; let serialized_context = borsh::to_vec(&context).expect("failed to serialize context"); self.allocate(serialized_context) } - fn allocate(&mut self, data: Vec) -> i32 { + fn allocate(&mut self, data: Vec) -> AllocReturn { let offset = self .allocate_func .call(&mut self.store, data.len() as i32) @@ -119,16 +148,28 @@ impl TestCrate { offset } - fn always_true(&mut self, ptr: i32) -> bool { + fn always_true(&mut self, ptr: UserDefinedFnParam) -> bool { self.always_true_func .call(&mut self.store, ptr) - .expect("failed to call `always_true` function") - == true as i32 + .expect("failed to call `always_true` function"); + let result = self + .store + .data_mut() + .take() + .expect("always_true should always return something"); + + borsh::from_slice(&result).expect("failed to deserialize result") } - fn combine_last_bit_of_each_id_byte(&mut self, ptr: i32) -> u32 { + fn combine_last_bit_of_each_id_byte(&mut self, ptr: UserDefinedFnParam) -> u32 { self.combine_last_bit_of_each_id_byte_func .call(&mut self.store, ptr) - .expect("failed to call `combine_last_bit_of_each_id_byte` function") + .expect("failed to call `combine_last_bit_of_each_id_byte` function"); + let result = self + .store + .data_mut() + .take() + .expect("combine_last_bit_of_each_id_byte should always return something"); + borsh::from_slice(&result).expect("failed to deserialize result") } } diff --git a/x/programs/rust/wasmlanche-sdk/tests/test-crate/Cargo.toml b/x/programs/rust/wasmlanche-sdk/tests/test-crate/Cargo.toml index d614e1047e..925c6bb288 100644 --- a/x/programs/rust/wasmlanche-sdk/tests/test-crate/Cargo.toml +++ b/x/programs/rust/wasmlanche-sdk/tests/test-crate/Cargo.toml @@ -3,10 +3,9 @@ name = "test-crate" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -wasmlanche-sdk = { path = "../../" } - [lib] crate-type = ["cdylib"] + +[dependencies] +wasmlanche-sdk = { workspace = true } +borsh = { workspace = true } diff --git a/x/programs/rust/wasmlanche-sdk/tests/test-crate/src/lib.rs b/x/programs/rust/wasmlanche-sdk/tests/test-crate/src/lib.rs index caef966296..d40dcdc306 100644 --- a/x/programs/rust/wasmlanche-sdk/tests/test-crate/src/lib.rs +++ b/x/programs/rust/wasmlanche-sdk/tests/test-crate/src/lib.rs @@ -3,8 +3,8 @@ use wasmlanche_sdk::{public, Context}; #[public] -pub fn always_true(_: Context) -> i32 { - true as i32 +pub fn always_true(_: Context) -> bool { + true } #[public] diff --git a/x/programs/test/programs/call_program/Cargo.toml b/x/programs/test/programs/call_program/Cargo.toml new file mode 100644 index 0000000000..d43bdfa0a6 --- /dev/null +++ b/x/programs/test/programs/call_program/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "call_program" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasmlanche-sdk = { workspace = true } +borsh = { workspace = true } + +[build-dependencies] +wasmlanche-sdk = { workspace = true, features = ["build"] } diff --git a/x/programs/test/programs/call_program/src/lib.rs b/x/programs/test/programs/call_program/src/lib.rs new file mode 100644 index 0000000000..25d33d97b3 --- /dev/null +++ b/x/programs/test/programs/call_program/src/lib.rs @@ -0,0 +1,14 @@ +use wasmlanche_sdk::{params::Param, public, Context, Program}; + +#[public] +pub fn get_value(_: Context) -> i64 { + 0 +} + +#[public] +pub fn get_value_external(_: Context, target: Program, max_units: i64) -> i64 { + let v: Vec = vec![]; + target + .call_function("get_value", &v.into_iter().collect(), max_units) + .unwrap() +} diff --git a/x/programs/test/programs/return_complex_type/Cargo.toml b/x/programs/test/programs/return_complex_type/Cargo.toml new file mode 100644 index 0000000000..b00683e7aa --- /dev/null +++ b/x/programs/test/programs/return_complex_type/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "return_complex_type" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasmlanche-sdk = { workspace = true } +borsh = { workspace = true } + +[build-dependencies] +wasmlanche-sdk = { workspace = true, features = ["build"] } diff --git a/x/programs/test/programs/return_complex_type/src/lib.rs b/x/programs/test/programs/return_complex_type/src/lib.rs new file mode 100644 index 0000000000..da76088265 --- /dev/null +++ b/x/programs/test/programs/return_complex_type/src/lib.rs @@ -0,0 +1,17 @@ +use borsh::BorshSerialize; +use wasmlanche_sdk::{public, Context, Program}; + +#[derive(BorshSerialize)] +pub struct ComplexReturn { + program: Program, + max_units: i64, +} + +#[public] +pub fn get_value(ctx: Context) -> ComplexReturn { + let Context { program, .. } = ctx; + ComplexReturn { + program, + max_units: 1000, + } +} diff --git a/x/programs/test/programs/simple/Cargo.toml b/x/programs/test/programs/simple/Cargo.toml new file mode 100644 index 0000000000..f0637e1803 --- /dev/null +++ b/x/programs/test/programs/simple/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "simple" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasmlanche-sdk = { workspace = true } +borsh = { workspace = true } + +[build-dependencies] +wasmlanche-sdk = { workspace = true, features = ["build"] } diff --git a/x/programs/test/programs/simple/src/lib.rs b/x/programs/test/programs/simple/src/lib.rs new file mode 100644 index 0000000000..e6563264f3 --- /dev/null +++ b/x/programs/test/programs/simple/src/lib.rs @@ -0,0 +1,6 @@ +use wasmlanche_sdk::{public, Context}; + +#[public] +pub fn get_value(_: Context) -> i64 { + 0 +} diff --git a/x/programs/test/testdb.go b/x/programs/test/testdb.go new file mode 100644 index 0000000000..1be499ff6e --- /dev/null +++ b/x/programs/test/testdb.go @@ -0,0 +1,41 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package test + +import ( + "context" + + "github.com/ava-labs/avalanchego/database/memdb" + + "github.com/ava-labs/hypersdk/state" +) + +var ( + _ state.Mutable = &DB{} + _ state.Immutable = &DB{} +) + +func NewTestDB() *DB { + return &DB{db: memdb.New()} +} + +type DB struct { + db *memdb.Database +} + +func (c *DB) GetValue(_ context.Context, key []byte) ([]byte, error) { + return c.db.Get(key) +} + +func (c *DB) Insert(_ context.Context, key []byte, value []byte) error { + return c.db.Put(key, value) +} + +func (c *DB) Put(key []byte, value []byte) error { + return c.db.Put(key, value) +} + +func (c *DB) Remove(_ context.Context, key []byte) error { + return c.db.Delete(key) +} diff --git a/x/programs/test/util.go b/x/programs/test/util.go new file mode 100644 index 0000000000..0d0ac8ee63 --- /dev/null +++ b/x/programs/test/util.go @@ -0,0 +1,43 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package test + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/ava-labs/avalanchego/ids" +) + +func CompileTest(programName string) error { + cmd := exec.Command("cargo", "build", "-p", programName, "--target", "wasm32-unknown-unknown", "--target-dir", "./") + var out bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + fmt.Println(fmt.Sprint(err) + ": " + stderr.String()) + return err + } + return nil +} + +type Loader struct { + ProgramName string +} + +func (t Loader) GetProgramBytes(_ ids.ID) ([]byte, error) { + if err := CompileTest(t.ProgramName); err != nil { + return nil, err + } + dir, err := os.Getwd() + if err != nil { + return nil, err + } + return os.ReadFile(filepath.Join(dir, "/wasm32-unknown-unknown/debug/"+t.ProgramName+".wasm")) +} diff --git a/x/programs/tests/fixture/memory.wasm b/x/programs/tests/fixture/memory.wasm deleted file mode 100755 index 9c4300ffb18984e5e25bf37746ad751e191d6c8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24059 zcmeHvd6->AwePOl)0w*abjZ+2canV$0XiW=ce-g?E70%vTebH&-AT(+ z@B8mP@*QgLs#U92t+i^cRjX>Z)jfEzvMfuTUe%>WtS)4DuU~&*PxrR<8@dO3 zY@N(0D^%(FV0PVMP0gJh?bfk1sZ4D)Icdt&{1HbU)i`%v>WR;*WqC_43#TM zm?$=CYXu)0wUgaZDwV8s`rmxzj-c_^0|FhYe)Q6z-M^wr z1xg?SG!wfDbZ=gTKzaY&`%2a-0NOwg*w9{8g@(W?=)G(TY}0Z@e?dFwhZzj;$tHwV zla`T6dJQnB>_3vajAEqQcm;_az61lw zJFox`$->MuCP7+scrYO#sVKC_xa`AT#QG4ia!`4biLL}EgiTJw3BWp`A>STLzFj8Y zj>xwKQ%t`7eO;iMybWCnt(?-!SSMuV-Ec0% zAMU;r;5o})2A-k~`WV z9GxxyJE^{)pA^18k%X{tq+kdYRkR{mqJQ@5rBX?d8jeV}a75T?P*_#bH2y{!YRIQc z`@@?!g=pDvN}|2cS#sPS9?DxrcQK(H7YF78mS7PHTwv#{G!z@MPXwX@nLq_lFBXtZ zBAvd+cAXJgOMQhNIUKXXv;r*OIr123ytRDfEHE2=mIqheo7dT0gyXVza=;FG1lGNQ z&OX8FoD=oZVZfEE!>(dZ2hLn8odaVRSgD-HH!TE%^NC{k_KtlRcSd%%!8_N67?+Q# zUCW(QMJ2rDR?fu_vjGupb{2)#Y1IXKMU@7MY%$zZB8o{B6j?w+>>Azt<$f+f%@LgT zsi4Zt#G@)D0~Ag^h38tCwFOYTIkXDN$SMY2^uPtN?=~Sfyy|w8*-wyYQa0DBHDu?& z?d7E_cIEs~;Zy1~CNC>lP83i4Kg+h*6H@Pou|nIq_km_9)O|P_v&j*g)>XO4+f%v%PuS z5CU_SC_<7(z_5>*9LzDa3uMLebt|LkWnm^7Psxlp4q;(|F*FSt3{gy58CGVm!zrCs z0_yP$g-JdNW}lu+nZrn-6>ZsT2%LUBqcV}|N?RV1Kx^I^fW~OJ$VhRD zunv~-jI`1o)~+8Yu3H%H1k})@f%O<~z{9Vs(p(IIGvC4r<|4rQV!uR?3`bReF3Hv5 zEU;8~e19&5%Gv$Ig@^TY9Es2y3Q_~PBzkG|B6Ny%=%UeOAeR#0ZR-G>0(fS~0NV!v z+XP1Yq#>xr0jqxi)Ran3A~vvr+5vPL3)<-o5=G3%v>_yd83B2mhwy;2NQ5IN3T0h1 zn~886i-??vGERn+usR+XldTLM4b3PCJ~47)5lm4+aP*?bzS!j# z&J&!^`-}uIt%RT|V?i_VKvO&vdC$ZPfeU5@NtlPr`-|lvp*e1-7@a`}=0lk{vQ&bP zQKgP5;b}uX3@uIt5IgBl!aD*j#2oH}O~n7!)aO&=Ld#jTH$)Nb zpc<`a4Zwiq98Mq#8lmfhp+5{w@`eY(9Xod?`q3tVJlDYlZH~ms6ShH)6Lv}z@gErE zz=Gwm@}RKbBxcr&>DfV55$h%qCWgb&9q;)4c$AWM(77ng4nwV=>lrYV(TB_>pWs+d zM4(e49@5B=!#V(jGa)tLCV{|l0ZMSC$UL!#D?9)Y78P|+80ly1G}IBi#;O<1bg-1E zSonDoDv5o8+o6_vxg?m3g<94%T9Yv^V{{E$nQ{wo$SQXrkjsntq%bNWQAyx|r13}v z8mdPPgfUCN4%lGq`>rhrmwaxZm?vPfS?O4~AA~-l=#>d#g~_bpdxL66Til}teh$t! z=hM_7Fc}6FD2jaYVT-sV=yhdvGlFT%;{;>Dw6zax0O8V;msJawGM~GtSB8y=YdnoFGY+bw-hIRsb#~$i=|QB~ijl&_M}o zGM|RRDb06R%`RQHnf*!uZvs%#8ib}5Q>9uAuB#3f_OuEgOYOhGGH1OkVm43~=5&e|#h0tPMx zodN3iQUn#v0Hp}*6U98p0nNt9G0JkVe}JvXB1lI6?7c!~NkMlogWY%$0b!9ZCb5G>itpeAs*004pb($2!9VA8RU zV2;L*6T2T`24U)?dKh<(ldlFOeyFI06{rn-+Ap)_^3^gnt1uUi4U1V5RAU?+)PRby zL!_ZeIXC08@G)OVluQjZpz%zH8``16=aRFgo{QNJQn0{?Xo7Takfg(cy+-autPQ3d zzVq3A2n|0k{#&$~UBaubWbIc>Ek=R^rK&@B@q%E1ffI^V2yIaGVxpiZ$mF7>VKm?g z#9|0;*$$nLgo|D0ILYcTU!GzP>f@BRLV?S8g8dN_y#0wW1pO|j6IjL6nfM>f)gOcx zR!-sh+0llIRP8_@EoaUDK&bxtx$pupbik?MUaWFF%wAbP zWV{X)GjQSNm`uaghf4@WFP;_S%milP!sfy+0F`-mu}y}ha}oE`gTgMOiwjMgIUG9u zMd%Kz-BtB$sK@xQ=@^c2ht8BUAtr6$huj&Yix{3If)OUg@@g2|VkXZbU(o{Ziuofp zLh}$(Ft^?r&zJj(yzH78&zR(dNxtEo+Ywii4bT{{%xBiEl5zuqf)D?bEFWGWCOQ}} zOKLobUQ=XyDY6gJ;EeqjAhlBwDEv+;ur3@g5ANhSJ4nmebyQ@T5<;wJ7>I5vZDW>Y zNbuBMBP$Da**q*hSRlYuU}1JW_a^Xqem$h4z+?cyZ+j-I2TouSYP82w!xNZ?dNVIP z6=h3Yo*QCQnoZ75c7n6}^NO5C60>&zL#d(u3z0(w_q~I;FM*Vc`9$3JXy5Qo?t7Kg zr*K~#%>4@Exx9d0QT=p8C&tC*A4ac2rwc7YAmxLpaV|iNX-r=oi9jKr$`m zOInIM#K@k4kA68V1*^ey2h1yKS!6h6Q~0Rlh?t7#N2DxRo8y&*Fz#PO6{37Vk}9`C zm1|TKql}>nhA5&6;vPp8Gs>dU1(VcI4kVWlr zlMpya+yT()9Y9ij1aDsI0K!i6cE_9tH|aGXk27 z!pa)2NEfr94Z@^>cO5r@kxElBp&+uEJ4meoK2}|rvJu#lphBxjjxvH%W8@>-^@^%s zX;1+n6eU8@R2BLW@-N`BF&Ru9IT1;|#6khDBaM;m5|-rqs!Bx;oYwe%<9GoZjP(e0 zHo0do1s+cz^qvR36)e_Y0 zG0qh1bCFo4g5jJB3xL{Mgh%s2%mto)t~NAb`+x?%OD3K_Vx9;{%Wex{g)F4r99Ap%3!w5jm%NFETZs^hK@9y9wg;=K%m$l_9o#o_bH|={ zxl!r@uGGZvc}HS$@;?K|fGqK$$8|IgW4sqVDzZ}Wb5JD=4qG7x854^%h}0V8q3?~4 zwXlW_|08hPPq0P+%|b9M4sbvmAtM*dHZJz*41F+a3VIuzZ05jM;uH68Lvv!ok4Hs( z;I(iJY>h(!bTOd-A&hTBc!v&vl@*xrC?&*zl+8E_!y2kPxDJJnDMd$&)mxc63=X#R z_&@L&ZZK9fVF(~6T1ps^Xv+SJ$c$$*j5N<>Msm(3N`UDx3nX%U<>Nlg4GJr<2Iq@a z0+dmZN3TGqC_FX`WdhWC8MqUB6df%SZYRbp6GiJpV$Cw4M*&CSQOtR+1Rp#j!Q|jJ z%N|AHM1}-%nP7KPb|v&EP#dOeg+NSgbar$i2ow|A3KJ3)^qt0vm{1Ljk6tB0gC~n# z_p*eFjR%%m40gY1ePlea)S~t4rgdM`8Uaf!S|2m5Pe-j0u+*aUNz=MNYK?%U7Ol^i z)|aBz2v};-3VOx-;qe6n_-X_g^^@94>nl;Klm-39_}AB?S1Akn!1&iUqgN>l`t9+r zzmHy}Ea=i*Vz|TT^_{3yN|^PiHG170y-JB0jeos1dX=)EZyEo3Q}ik&L>;}}$`Ya; z-zv4>#$MC<(0E{}MeF^h^^vGG0+w2|e%-Y0i&`UKsYUB!ruFHlH3F7ev_5HC_eZS} zu+*aU8Pobw)EWUxEm~hNt*=I{5wO&v^%c|lM${SsOD$SoH?0Sv)(BW?(fX!oeLHH6 zfTb3#zc;NVy7daM)Z+CW(|T3Z8Uaf!T6eP=0*zWDV5vpxwWjr!s5JtXTC8YT$k=s6 zfHxVyzcv0lUZ`ISF;>udM@ zE1d+0n~S>7utyksHQ_YSAkJ{ZYtH<_ zqr-=IvRygR4y^3MrBVs!uE3Dvh;Kjo$46iI!wsLDVFvuxzQ23;x8Hi|M_0>$AFZ4* zHjOz&rICg2LE4Jph6?8w!(GH`YNN+Fu*nJ`RY5oMsh|UK6yY;hf+E=?2V+l5d1+u5 zX^oC>Ye3u*j*|TXQKxTG6drxJiM+(-i(4LeBRDZW35$>yq48(zjGC@d>~Z|DV^+=K(KVUF6Kt|6KneUekqh9D4Fb!j$!w_{Z~sQy8i}*=aX*IEX6W#! z=t7sZe9v>%IphX%?{Jx@x52A0iK{h;HgL27*#o4Mca|Ucs(nRZU4Acuz0pxbfUjG9 zNPeoJ@T%P)rMSqlmRe-k71*hSw~kzdkj(%d41%LJ+-w2j(Qm)F=*%3_vQROX8&!=& z+r1d}e;^42!3Y_^m$xB}apNL4_2&nW^a=<`QOx6zKy2g5#hhaZoN{0o7l-qm(0eGM z+$6wqj<^vX71+JmTd7VUUIQ)`WBM9bC2(`DQ8$2g9O>bn3(~L*Q-IIGjX4?$3?(&M zUK#kJ8G#Ebo;YK_nezlS z?F6s`35l7Uha%%Jgcn_~v~>11(vGbh??&Lvm z6*lo=fwjO2a!9+AC|)cG-j_BI&@1zm?IH8wb~VVzP>jv9 zU~P($CUTP>1nWsxJ|%}_79vAePNB!Ytu$*@J0L$ZTNO(N`{ z;FwHEMLK?*Fy!PJ!Z`S33|c_t2~s>4!?tS;tWT^5hoVQ*2vk>q(kF_EH>xK3fjt+- zhCE;c5gf-XWY)))TUMB$NxoAp01=PxaEnP8N8coKmzY$ru`Y0amtEa(Pk1MW7_LDHzAphZP7*%TSF&ajVZVb~>5Ztk5jH5ZtS6?EAi z%3*I0&hsciPRw|aie@Wk*}#A{a|F+`GoFJ4hJ2sTb1G~`YKB|~2)zz{Azq@X@x%pe z%PAm3!E6L}fnId}q@&!nxq}tDL$n|fLAWp?2fIRUG9n98_r{SXJu9Tiam`rLkZi?O z25!EYW&vH`A8HhGA`2`_c#0P}Iu-|R7$r-I22E@rI1ydft+th=p?0_mF$9yq(^MRk z7V{bI5_eOUII9ZqMlt8G2x>>Ew`63?15W@TWShlv@nI5{=5UnSPu^cH;2wVP)B`sjVQA-OdW&1lByCj zcVNv=qfo;QcR)d)RMjEgXJPOs;nTz{27YgJTKV*DSN}OaxsQz z^nwg{j{`g`B(Nrf%L6bMRk+m5v^-yORp^!BL0F0_4=z>G+Jor!shjTGZw-c{@BZnM zVt-h|6{Ab3SjotW@p$mhLuJ6Od?vt9W>sKlx-N|ycJ!YZ3ky>R8Qh2K&CxU*M7f4@ z9z!|%$$bKFBgVsY*j(VNLevx`%8LM9KCR+=!Ii)phq{pP;R*URm5^L zqRPPtxj~w1unK0-A&bVIl{33e`-iNcm%dV09C5a653nYn+7)-jjTR$blsLapEK9IU z@VkO<0fBL$K87s?}o&O)>Z}{i<3$FcTEPiQ>>)MAK)vhtw&?D?n z$oLEsaVOeDPr;rDp)ThITU5|${T8_vLShPJM z3>S8iV9Fu=pA;Yn8$c%Ce<;jz0%Nw@73QFaIdWEEdPr_W>M#c{oim$fTlg4`fbD8H z&^Sop4U=m>2k9{GMIRhxI;$XW+_mC1pKi&Q8>Q&N@P8ClxFtq+1Nv}q#Y?j&;Nzj4 zR8MJ%DFQtKno#H=#AKC`rVK-6(TG?WSCw@5qKmr}i0E?ocmKHiTTZyAOdon7+Kw*4 z!YhzNi0z~tHxcYrEZmTyW5SI#IQ1eOnu^H9XsCSA#qBj-Spx zg?nx$SK!Fxvdc*j6fq&}kf4}c!TiB(aftjHi05jf7H$%vglu_fh7X8w6dv|Jf-B(r zA3fT@J^WL=;dGUKUb|+f7`~P^FL&IBmpkgr%gW1yb&@(W5-dTljh29}5s+b)jIi+ztY=U4Y3@&J(JFeW;^oP%o%bXuQB&*>s$5#Ub zsAe!La0(-9V+K4TD7tl3WKu8FBir9xB&qr5Ki}@TyM%Z8$%CRmvw- zxc6FC{EMA^fObc24g&1g*R!@U{Kg)Zcj)lPH}c^@ZNPBljA$jBiRB1mON~d6^r9O+ zHcn&zKi{c)aZHjvj55R0gY=#3gXFlCeoHHFgq6ruV)X@J=zGYvCWg%}0`D+3@oqu( z6bd?$OLG^Ci^kBb9AZK~TX}3O7%f<3SX#XLY;rk!&4h?PdwcW@>XAD}ZzdHa zElAq}a~f@cB*M^CM7VCn+{}ZxPxa0a6KlTnQxWk zD-cwTGSIN*Tch%=2rAx-D&C7L-i<2WWd-iaaszs@@R6JOgk$QYR*O;V>JMDvfK_({wd(ZYzo3HZ@08v{+tk9Ka0 z@E{ko%{M_d9fwRk2o%o>BCrr(;m46F2R40DJxcHp#z-)tPA(-PTqyz{(hk_`N!&+& znlveGez3(ay39P#A5sN+$!_bhAXh{n*ptN|w3}l=K7J?&Xd4HDlLqn6XY|f=-&yJeJ>FF&=ImH9`qr!EGhUMw&yT7skIv zXI*Q-QWx2>g8r~P@iW0wup1K+s{DiAW5d>TmX=sQ6+VesfYi35iZiJtF` z^xVaH%s3JzpeP~(8Ra-lTcN0{Op&3DF|DDEH&Po|@!}vfou+y(=<;R)MN@BxML;c}?(Fu==F-tx^PRN2j zBDY|Y$9&JC;r1X(2InLLR1sw?hQ+X-U>);!?+u@fXDU|p!1j-ml z1Sr}B*Tw)>1jl4A+>YjFAqb{0x4?}^g0{l}Vv0nq!W78nXCY<~+4o}aiwb_1h(tYa zRZ`htKYbaPRp2vjYhf|?7z80VrZWz|d_D?}#A6zk#sNiOBQ=T%jQ`HVu@F{#hdXmJ ztS+a>46qk38QfN9t`MCj%SQr}VL!ptyj{;-Pu`IOBD3z0)XalXq6G2XJ>eTZW3lVy z;MmS#dxZ53EPzCu!>?$71+94H2u_R_yhDZ*OJS^`AW03CoBetA4zO0Cm0>oNZ@X}y zxU$~dLqn7zHbRI3m*I5rFZ%EU-yjefuM*yQHxw3(0cXWEvLVY)+R%p+0act<9a(2S z7BFpiF_!>Lb4@z^h*r)BH)T{#9{ZS2V?qYw{DMTAFG#Rd%oijyS6o5SH3NK8U74IY zfj6QkQw?p0Qp7jbaWDhsLE&)wEr3o#Bg6*63&RHEW}D0m&OkI11{RZG2*%i8I>w4V zMS*$crzkemjt5`m4@h{S64scX_SvAAGBO8P@py>pSdIPQ>`rvc@YSb20okprhy*! z!n-C$%%wXZR;)7xn<&q~f|(;~-&a~YCu}W~e_n?NEt%(PGzw?^EaXbhSR27YP`Ev(0&-_>TIp)$~jJU(3(G z`P?g#`UK;>A3OiH;r=3S=CH!Jp7}pM!NZ7ChQ*IQhb@dt@2W^Ruv|sl_mXggU2qfv zRVsTFD?JISEukEsc5pGI)3@5=P-%mzvH~hkqIK0tOKJR3Ef0T93v7RKi6PQq4RUuxx?V%Ns|yp5#KP=5itI zK6a+|GHxnhBA8@iG7m8byaKm+@~MM_;`n zc3hZYiUJ+kXJTl{wIa#DFV;b*Y(-?N3kQdZqc>(8%`{44jGw8Mt_G(9g>$0Ejl%Dq ztq@k0_F3uwoRzXHD~q3lUmCxYw+;pPLv6u^%Z7RegMpszO+5nxTL*%{o`Egh7j60I zmcGrwwxNMb7i}BZx_O}c;=x4&+Xsgh^<3J0@wSV41{aw>0=H;zVB?~Twrp59FbJG& z7-3#ChC+#kf$o9J7Reuk6VQR~OV$tfYy|km?rq&0w+vktY#r_yxZt9#mjHeb`q!cy zLfL?FDm<%;G86qsd(}bj)luDJ=u?Ad&XG9yJPE%u`g$(i*0XV_XH#%dPv7RD3xll} z1Y7#HZ6BJurEeHB-4t{WY#y`@Q&|}&rg`JmfgU0Gf{TY1UA%P@NM5)Q_&xwQkYyc) zAOGV#C*!xJZ&S~u!Pf0V#J^$d_P$Mnox#wBJsdUIG8hCK(8PBGh#Jf40xlF*2_ElsUWOPUHzZB6Y>9nDS6&CQFOTbf&&moyie+nU>(I~F%BZeF~2am(V?#Y+|! z7Pl>KU)<5s)Y9CtxTU3~wPi_5p{1>*y``hIskOOvacfI!YwMEMLTg)Vduzv%rX|fw z7B6X8(z;~HlERXNC>i`!b-THBVi724X` z+S@wXo7$V(7q_>xx3({7FSNI{x3_n6fW!_A-vQJe=+=QM@`XI%Z#sT6@Z&riqxmj@ zV5lqcdwtTt#P5^wo=5vYci-loU~s5=U?>tzEJV|C0oTMuTQ>Gku8{`S*+yRTP*!l@ z?;J)L&O<$=7`L1LM({GyV9-5eGy7o2RO6u^2 zp3PhOf@6*iIE_X=%ndHN5V}j^X$-ocb57RcdRvH+d|!-`^NewjkZ~(OsHo7e3Gkd_ z41b8?4h{`OYt6a|u#~3_;*fqR+miCS+}^ikI1QwCnpgVNB{2zH6>_vZ_{3au2JWtfuH`dU{oZJ2Np`%~3_CS3hWf zRX?Vm*FV%RWPY6diGESPtX_40sej|Vq2CG)IB)C!wEv_sM=d?>8HuIJvZFvr3y4_uTu;vv>N_XXKAK?)cxl z`PO?7j2y{neBJ14n|oRpvRmjy>TnPkE4bZ7WZ#y@3ETT4}ozc7*7apb8d z9i5mrb;gkmlcyz5#sDW&P4`pY>cmmW?dj#m&GVMJDeps`as^@R(zr%O+MYVF3V-A&o4*MDi}s=NMaXNNz}InO&XwKCP{9=7wLb9+v5 zI{eyYREeA4PVD~4yyV@#+0jy?W_s05V#l>tJH2j|o%FLecCAVd9aH*aYA~^F%F2&U z&P>jnm7HGs{^T8}XJ&$yLZTX=Nx$^3N2GRpma<*f^R(|J{A6vaAw4}aqbggSsd2LQ zq)CS*r>bdAotkdX@at8Bo;@|NkG2=27pf+^Sua-i>M!aqIbTlvr~Z@su71xRO@8Il z%dWZZ?xwRoe9g6c8vc8A%}Hzj^v=RX$Dj9+^{?%|?$bBi_{Fb2`mOIi_5C0G{Pkar zT8=1mTj$ba*PQr~-JeF|*B|}XQ$Kj_`PYACmDPPI_5J)!J-cuC!foGw?)j?Pxt&W_ ztU2r4^Uhztspq;IzKAa0{r)dr|J8x2+7)Xy^^|sh?Xkz7`0l!^RrJq_xzfZ*PV6t`RlL#^z~o;#y6k%_EX<`v3BaTbI<$Zf4nzZy14)6 zzo?$wx3yvB`j21nz=I>-dTi>nS+iHJI(Z%S|B8`^zx%?EUpesae;63NerWsWj$F9t z-Uq+=#P^m)+UP9rgC~gmM-+t&VRi-}7rzr_@aH z&+u)hA(gZfwr^|Owlj`vr#)3Y#XZ@t_s{aRH!X9D^8tGS%u}sblj(G39XzCmuRXCck@1Gov+Q%6vy-J=bq%Sh$+b@DQ{I>N zW~MpKcR4#=KGM&)Zt0%vjz9P+nCGGOI;ZrwU2oS^9b)fe8y=PN_kv-@cc-#eBTV%v z__2)N&%-nQM^p3SmewVOw)T$h4I4N0TyO~99)_262pic|*Y|ZTnDwWwh1PpP;r7w4!n^91 z1(nLR&8hlj+n1}mI~LX5)zQ?@z4~{v?pnL7wfmF<#k)SVZfow;+a9~?RO|Wf(|Ue* z*J;+vxznvze|g3OuXUgG=Ign$_rG!1*@5-e*$33fIo3AIw-zWQw((D`PB%@-svhWo zMzZd3HLL#IbZ0WD>Kv7XyK;}Vk4em{Q$ZU#I0?wwPwANo2`QzW1VB=HhSItNehq)= z*|yazjX$}}cNYL^vYraB2Wa3v|6VMI5r<|Nexj`Bd1o~|7`i z;SjG- zQk6O~F+IJ|Zo=HOK1zK6oY(jOH=!1(7GTxd#k}X~gnEl?QwWl>S%&`VHT78+VTpE~ zM%z*U0Q{Cd#a@|ic0Q&GHFGh~l-&%RzB<;PgKun(L%O#$34^cJ+hiq}qHeRr+=Ao?DXCzotBtOuKzo6|X*Gek=2S^?oq4r=rM`4HR ziKIMjF9gFa9}H2aP6a8z@=*^1falOQ4y7zex7Bs|q`VpnX24R%J0C*5wNOv9zyk*t zva~N?IGS*~4C|Yu&b?EnE9K zJGb{;GSI!Pac)$PUwmE%<{UX TZ>VQ;&%g(^_F-c?i1z;tf?#uk diff --git a/x/programs/tests/utils.go b/x/programs/tests/utils.go deleted file mode 100644 index 0807dd58ae..0000000000 --- a/x/programs/tests/utils.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package tests - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -// ReadFixture reads a file from this fixture directory and returns its content. -func ReadFixture(tb testing.TB, filename string) []byte { - require := require.New(tb) - tb.Helper() - dir, err := os.Getwd() - require.NoError(err) - bytes, err := os.ReadFile(filepath.Join(dir, filename)) - require.NoError(err) - return bytes -} From 7acbe89969253acbdcb3b70ddcc7f8587c11c0ee Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Tue, 21 May 2024 20:52:00 -0400 Subject: [PATCH 78/78] update action batches section --- README.md | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d04c7f744a..84f4876ceb 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,26 @@ for a single account and ensure they are ordered) and makes the network layer more efficient (we can gossip any valid transaction to any node instead of just the transactions for each account that can be executed at the moment). +### Action Batches and Arbitrary Outputs +Each `hypersdk` transaction specifies an array of `Actions` that +must all execute successfully for any state changes to be committed. +Additionally, each `Action` is permitted to return an array of outputs (each +output is arbitrary bytes defined by the `hypervm`) upon successful execution. + +The `tokenvm` uses `Action` batches to offer complex, atomic interactions over simple +primitives (i.e. create order, fill order, and cancel order). For example, a user +can create a transaction that fills 8 orders. If any of the fills fail, all pending +state changes in the transaction are rolled back. The `tokenvm` uses `Action` outputs to +return the remaining units on any partially filled order to power an in-memory orderbook. + +The outcome of execution is not stored/indexed by the `hypersdk`. Unlike most other +blockchains/blockchain frameworks, which provide an optional "archival mode" for historical access, +the `hypersdk` only stores what is necessary to validate the next valid block and to help new nodes +sync to the current state. Rather, the `hypersdk` invokes the `hypervm` with all execution +results whenever a block is accepted for it to perform arbitrary operations (as +required by a developer's use case). In this callback, a `hypervm` could store +results in a SQL database or write to a Kafka stream. + ### Easy Functionality Upgrades Every object that can appear on-chain (i.e. `Actions` and/or `Auth`) and every chain parameter (i.e. `Unit Price`) is scoped by block timestamp. This makes it @@ -417,23 +437,6 @@ override the default gossip technique with your own. For example, you may wish to not have any node-to-node gossip and just require validators to propose blocks only with the transactions they've received over RPC. -### Transaction Results and Execution Rollback -The `hypersdk` allows for any `Action` to return a result from execution -(which can be any arbitrary bytes), the amount of fee units it consumed, and -whether or not it was successful (if unsuccessful, all state changes are rolled -back). This support is typically required by anyone using the `hypersdk` to -implement a smart contract-based runtime that allows for cost-effective -conditional execution (exiting early if a condition does not hold can be much -cheaper than the full execution of the transaction). - -The outcome of execution is not stored/indexed by the `hypersdk`. Unlike most other -blockchains/blockchain frameworks, which provide an optional "archival mode" for historical access, -the `hypersdk` only stores what is necessary to validate the next valid block and to help new nodes -sync to the current state. Rather, the `hypersdk` invokes the `hypervm` with all execution -results whenever a block is accepted for it to perform arbitrary operations (as -required by a developer's use case). In this callback, a `hypervm` could store -results in a SQL database or write to a Kafka stream. - ### Support for Generic Storage Backends When initializing a `hypervm`, the developer explicitly specifies which storage backends to use for each object type (state vs blocks vs metadata). As noted above, this