From 123047bebdce6bf4f366509c0d5838b1c51bb1e5 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sat, 4 Apr 2026 23:09:26 +0000 Subject: [PATCH] refactor(wire): unify HF5 asset operation parsing Co-Authored-By: Charon --- wire/transaction.go | 108 +------------------- wire/transaction_v3_test.go | 17 ++++ wire/variant.go | 191 ++++++++++++++++++++++++++++-------- 3 files changed, 170 insertions(+), 146 deletions(-) diff --git a/wire/transaction.go b/wire/transaction.go index d37ea95..b6865f8 100644 --- a/wire/transaction.go +++ b/wire/transaction.go @@ -1029,113 +1029,7 @@ func readZarcanumSig(dec *Decoder) []byte { // decimal_point(uint8) + meta_info(string) + owner_key(32 bytes) + // etc(vector). func readAssetDescriptorOperation(dec *Decoder) []byte { - var raw []byte - - // ver: uint8 - ver := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, ver) - - // operation_type: uint8 - opType := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, opType) - - // opt_asset_id: uint8 presence marker + 32 bytes if present - assetMarker := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, assetMarker) - if assetMarker != 0 { - b := dec.ReadBytes(32) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - } - - // opt_descriptor: uint8 presence marker + descriptor if present - descMarker := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, descMarker) - if descMarker != 0 { - // AssetDescriptorBase - // ticker: string - s := readStringBlob(dec) - if dec.err != nil { - return nil - } - raw = append(raw, s...) - // full_name: string - s = readStringBlob(dec) - if dec.err != nil { - return nil - } - raw = append(raw, s...) - // total_max_supply: uint64 LE - b := dec.ReadBytes(8) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - // current_supply: uint64 LE - b = dec.ReadBytes(8) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - // decimal_point: uint8 - dp := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, dp) - // meta_info: string - s = readStringBlob(dec) - if dec.err != nil { - return nil - } - raw = append(raw, s...) - // owner_key: 32 bytes - b = dec.ReadBytes(32) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - // etc: vector - v := readVariantVectorFixed(dec, 1) - if dec.err != nil { - return nil - } - raw = append(raw, v...) - } - - // amount_to_emit: uint64 LE - b := dec.ReadBytes(8) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - // amount_to_burn: uint64 LE - b = dec.ReadBytes(8) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - // etc: vector - v := readVariantVectorFixed(dec, 1) - if dec.err != nil { - return nil - } - raw = append(raw, v...) - + raw, _ := parseAssetDescriptorOperation(dec) return raw } diff --git a/wire/transaction_v3_test.go b/wire/transaction_v3_test.go index 6d54956..754d5cf 100644 --- a/wire/transaction_v3_test.go +++ b/wire/transaction_v3_test.go @@ -127,6 +127,23 @@ func TestReadAssetDescriptorOperationEmit_Good(t *testing.T) { if !bytes.Equal(got, blob) { t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(got), len(blob)) } + + op, err := DecodeAssetDescriptorOperation(blob) + if err != nil { + t.Fatalf("DecodeAssetDescriptorOperation (emit) failed: %v", err) + } + if op.Version != 1 || op.OperationType != 1 { + t.Fatalf("unexpected operation header: %+v", op) + } + if op.Descriptor != nil { + t.Fatalf("emit operation should not carry descriptor: %+v", op) + } + if op.AmountToEmit != 500000 || op.AmountToBurn != 0 { + t.Fatalf("unexpected emit amounts: %+v", op) + } + if op.AssetID[0] != 0xAB || op.AssetID[31] != 0xAB { + t.Fatalf("unexpected asset id: %x", op.AssetID) + } } func TestVariantVectorWithTag40_Good(t *testing.T) { diff --git a/wire/variant.go b/wire/variant.go index 0e75a10..501fdc8 100644 --- a/wire/variant.go +++ b/wire/variant.go @@ -53,45 +53,7 @@ func DecodeVariantVector(raw []byte) ([]VariantElement, error) { // payload into its typed representation. func DecodeAssetDescriptorOperation(raw []byte) (types.AssetDescriptorOperation, error) { dec := NewDecoder(bytes.NewReader(raw)) - var op types.AssetDescriptorOperation - - op.Version = dec.ReadUint8() - op.OperationType = dec.ReadUint8() - - if dec.ReadUint8() != 0 { - assetID := dec.ReadBytes(32) - if dec.Err() != nil { - return types.AssetDescriptorOperation{}, coreerr.E("DecodeAssetDescriptorOperation", "read asset id", dec.Err()) - } - copy(op.AssetID[:], assetID) - } - - if dec.ReadUint8() != 0 { - desc := &types.AssetDescriptorBase{} - desc.Ticker = decodeStringField(dec) - desc.FullName = decodeStringField(dec) - desc.TotalMaxSupply = dec.ReadUint64LE() - desc.CurrentSupply = dec.ReadUint64LE() - desc.DecimalPoint = dec.ReadUint8() - desc.MetaInfo = decodeStringField(dec) - ownerKey := dec.ReadBytes(32) - if dec.Err() != nil { - return types.AssetDescriptorOperation{}, coreerr.E("DecodeAssetDescriptorOperation", "read owner key", dec.Err()) - } - copy(desc.OwnerKey[:], ownerKey) - desc.Etc = readVariantVectorFixed(dec, 1) - if dec.Err() != nil { - return types.AssetDescriptorOperation{}, coreerr.E("DecodeAssetDescriptorOperation", "read descriptor etc", dec.Err()) - } - op.Descriptor = desc - } - - op.AmountToEmit = dec.ReadUint64LE() - op.AmountToBurn = dec.ReadUint64LE() - op.Etc = readVariantVectorFixed(dec, 1) - if dec.Err() != nil { - return types.AssetDescriptorOperation{}, coreerr.E("DecodeAssetDescriptorOperation", "read trailing etc", dec.Err()) - } + _, op := parseAssetDescriptorOperation(dec) if dec.Err() != nil { return types.AssetDescriptorOperation{}, coreerr.E("DecodeAssetDescriptorOperation", "decode asset descriptor operation", dec.Err()) @@ -99,3 +61,154 @@ func DecodeAssetDescriptorOperation(raw []byte) (types.AssetDescriptorOperation, return op, nil } + +// parseAssetDescriptorOperation is the single source of truth for both raw +// wire preservation and typed HF5 asset operation decoding. +func parseAssetDescriptorOperation(dec *Decoder) ([]byte, types.AssetDescriptorOperation) { + var raw []byte + var op types.AssetDescriptorOperation + + appendByte := func(v uint8) { + raw = append(raw, v) + } + appendBytes := func(v []byte) { + raw = append(raw, v...) + } + + op.Version = dec.ReadUint8() + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendByte(op.Version) + + op.OperationType = dec.ReadUint8() + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendByte(op.OperationType) + + assetMarker := dec.ReadUint8() + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendByte(assetMarker) + if assetMarker != 0 { + assetID := dec.ReadBytes(32) + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + copy(op.AssetID[:], assetID) + appendBytes(assetID) + } + + descMarker := dec.ReadUint8() + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendByte(descMarker) + if descMarker != 0 { + desc := &types.AssetDescriptorBase{} + + tickerRaw := readStringBlob(dec) + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + desc.Ticker = decodeStringBlob(tickerRaw) + appendBytes(tickerRaw) + + fullNameRaw := readStringBlob(dec) + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + desc.FullName = decodeStringBlob(fullNameRaw) + appendBytes(fullNameRaw) + + desc.TotalMaxSupply = dec.ReadUint64LE() + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendBytes(uint64LEBytes(desc.TotalMaxSupply)) + + desc.CurrentSupply = dec.ReadUint64LE() + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendBytes(uint64LEBytes(desc.CurrentSupply)) + + desc.DecimalPoint = dec.ReadUint8() + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendByte(desc.DecimalPoint) + + metaInfoRaw := readStringBlob(dec) + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + desc.MetaInfo = decodeStringBlob(metaInfoRaw) + appendBytes(metaInfoRaw) + + ownerKey := dec.ReadBytes(32) + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + copy(desc.OwnerKey[:], ownerKey) + appendBytes(ownerKey) + + desc.Etc = readVariantVectorFixed(dec, 1) + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendBytes(desc.Etc) + + op.Descriptor = desc + } + + op.AmountToEmit = dec.ReadUint64LE() + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendBytes(uint64LEBytes(op.AmountToEmit)) + + op.AmountToBurn = dec.ReadUint64LE() + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendBytes(uint64LEBytes(op.AmountToBurn)) + + op.Etc = readVariantVectorFixed(dec, 1) + if dec.Err() != nil { + return nil, types.AssetDescriptorOperation{} + } + appendBytes(op.Etc) + + return raw, op +} + +func decodeStringBlob(raw []byte) string { + return string(raw[varintPrefixLen(raw):]) +} + +func varintPrefixLen(raw []byte) int { + n := 0 + for n < len(raw) { + n++ + if raw[n-1] < 0x80 { + return n + } + } + return len(raw) +} + +func uint64LEBytes(v uint64) []byte { + return []byte{ + byte(v), + byte(v >> 8), + byte(v >> 16), + byte(v >> 24), + byte(v >> 32), + byte(v >> 40), + byte(v >> 48), + byte(v >> 56), + } +}