Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bcs]Add Serialized type and serialization methods for handling byte arrays #135

Merged
merged 2 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions bcs/deserializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,8 @@ func (des *Deserializer) setError(msg string, args ...any) {
}
des.err = fmt.Errorf(msg, args...)
}

// DeserializeBytes deserializes a byte array prefixed with a length
func (des *Deserializer) Serialized() *Serialized {
return &Serialized{des.ReadBytes()}
}
29 changes: 29 additions & 0 deletions bcs/serialized.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package bcs

// Serialized represents a serialized transaction argument
type Serialized struct {
Value []byte
}

// NewSerialized creates a new Serialized instance
func NewSerialized(value []byte) *Serialized {
return &Serialized{
Value: value,
}
}

// Serialize serializes the Serialized instance
func (s *Serialized) Serialized(serializer *Serializer) {
serializer.WriteBytes(s.Value)
}

// SerializeForEntryFunction serializes the Serialized instance for entry function
func (s *Serialized) SerializedForEntryFunction(serializer *Serializer) {
s.Serialized(serializer)
}

// SerializeForScriptFunction serializes the Serialized instance for script function
func (s *Serialized) SerializedForScriptFunction(serializer *Serializer) {
serializer.Uleb128(uint32(9))
s.Serialized(serializer)
}
Comment on lines +1 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what situation would this not work as a vector? It should be encoded as bytes right?

or is there something I'm missing

13 changes: 13 additions & 0 deletions bcs/serializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,16 @@ func SerializeOption[T any](ser *Serializer, input *T, serialize func(ser *Seria
SerializeSequenceWithFunction([]T{*input}, ser, serialize)
}
}

// Serialized wraps already serialized bytes with BCS serialization
// It prepends the byte array with its length (encoded as Uleb128) and writes the bytes
// Primarily used for handling nested serialization structures where bytes are already in BCS format
func (ser *Serializer) Serialized(s Serialized) {
ser.WriteBytes(s.Value)
}

func SerializeSerialized(input Serialized) ([]byte, error) {
return SerializeSingle(func(ser *Serializer) {
ser.Serialized(input)
})
}
107 changes: 107 additions & 0 deletions examples/script_transaction/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// fungible_asset is an example of how to create and transfer fungible assets
package main

import (
"fmt"

"github.com/aptos-labs/aptos-go-sdk"
"github.com/aptos-labs/aptos-go-sdk/bcs"
"github.com/aptos-labs/aptos-go-sdk/crypto"
)

const testEd25519PrivateKey = "ed25519-priv-0xc5338cd251c22daa8c9c9cc94f498cc8a5c7e1d2e75287a5dda91096fe64efa5"

const TransferScriptPayload = "0x008601a11ceb0b0700000a06010002030206050806070e1708252010451900000001010000010003060c05030d6170746f735f6163636f756e74087472616e736665720000000000000000000000000000000000000000000000000000000000000001166170746f733a3a7363726970745f636f6d706f7365720100000100050a000b010b0211000200020920000000000000000000000000000000000000000000000000000000000000000209086400000000000000"
const TransferAmount = uint64(1_000)
const FundAmount = uint64(100_000_000)

func example(networkConfig aptos.NetworkConfig) {
// Create a client for Aptos
client, err := aptos.NewClient(networkConfig)
if err != nil {
panic("Failed to create client:" + err.Error())
}

// Create a sender locally
key := crypto.Ed25519PrivateKey{}
err = key.FromHex(testEd25519PrivateKey)
if err != nil {
panic("Failed to decode Ed25519 private key:" + err.Error())
}
sender, err := aptos.NewAccountFromSigner(&key)
if err != nil {
panic("Failed to create sender:" + err.Error())
}

// Fund the sender with the faucet to create it on-chain
println("SENDER: ", sender.Address.String())
err = client.Fund(sender.Address, FundAmount)
if err != nil {
panic("Failed to fund sender:" + err.Error())
}
receiver := &aptos.AccountAddress{}

// Now run a script version
fmt.Printf("\n== Now running script version ==\n")
runScript(client, sender, receiver)

if err != nil {
panic("Failed to get store balance:" + err.Error())
}
// fmt.Printf("After Script: Receiver Before transfer: %d, after transfer: %d\n", receiverAfterBalance, receiverAfterAfterBalance)
}

func runScript(client *aptos.Client, alice *aptos.Account, bob *aptos.AccountAddress) {
scriptBytes, err := aptos.ParseHex(TransferScriptPayload)
if err != nil {
panic("Failed to parse script:" + err.Error())
}

var payload aptos.TransactionPayload
payload.UnmarshalBCS(bcs.NewDeserializer(scriptBytes))

// 1. Build transaction
rawTxn, err := client.BuildTransaction(alice.AccountAddress(), payload)

if err != nil {
panic("Failed to build transaction:" + err.Error())
}

// 2. Simulate transaction (optional)
// This is useful for understanding how much the transaction will cost
// and to ensure that the transaction is valid before sending it to the network
// This is optional, but recommended
simulationResult, err := client.SimulateTransaction(rawTxn, alice)
if err != nil {
panic("Failed to simulate transaction:" + err.Error())
}
fmt.Printf("\n=== Simulation ===\n")
fmt.Printf("Gas unit price: %d\n", simulationResult[0].GasUnitPrice)
fmt.Printf("Gas used: %d\n", simulationResult[0].GasUsed)
fmt.Printf("Total gas fee: %d\n", simulationResult[0].GasUsed*simulationResult[0].GasUnitPrice)
fmt.Printf("Status: %s\n", simulationResult[0].VmStatus)

// 3. Sign transaction
signedTxn, err := rawTxn.SignedTransaction(alice)
if err != nil {
panic("Failed to sign transaction:" + err.Error())
}

// 4. Submit transaction
submitResult, err := client.SubmitTransaction(signedTxn)
if err != nil {
panic("Failed to submit transaction:" + err.Error())
}
txnHash := submitResult.Hash

// 5. Wait for the transaction to complete
_, err = client.WaitForTransaction(txnHash)
if err != nil {
panic("Failed to wait for transaction:" + err.Error())
}
}

// main This example shows how to send a transaction with a script
func main() {
example(aptos.DevnetConfig)
}
27 changes: 18 additions & 9 deletions script.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,16 @@ func (s *Script) UnmarshalBCS(des *bcs.Deserializer) {
type ScriptArgumentVariant uint32

const (
ScriptArgumentU8 ScriptArgumentVariant = 0 // u8 type argument
ScriptArgumentU64 ScriptArgumentVariant = 1 // u64 type argument
ScriptArgumentU128 ScriptArgumentVariant = 2 // u128 type argument
ScriptArgumentAddress ScriptArgumentVariant = 3 // address type argument
ScriptArgumentU8Vector ScriptArgumentVariant = 4 // vector<u8> type argument
ScriptArgumentBool ScriptArgumentVariant = 5 // bool type argument
ScriptArgumentU16 ScriptArgumentVariant = 6 // u16 type argument
ScriptArgumentU32 ScriptArgumentVariant = 7 // u32 type argument
ScriptArgumentU256 ScriptArgumentVariant = 8 // u256 type argument
ScriptArgumentU8 ScriptArgumentVariant = 0 // u8 type argument
ScriptArgumentU64 ScriptArgumentVariant = 1 // u64 type argument
ScriptArgumentU128 ScriptArgumentVariant = 2 // u128 type argument
ScriptArgumentAddress ScriptArgumentVariant = 3 // address type argument
ScriptArgumentU8Vector ScriptArgumentVariant = 4 // vector<u8> type argument
ScriptArgumentBool ScriptArgumentVariant = 5 // bool type argument
ScriptArgumentU16 ScriptArgumentVariant = 6 // u16 type argument
ScriptArgumentU32 ScriptArgumentVariant = 7 // u32 type argument
ScriptArgumentU256 ScriptArgumentVariant = 8 // u256 type argument
ScriptArgumentSerialized ScriptArgumentVariant = 9 // Serialized type argument
)

// ScriptArgument a Move script argument, which encodes its type with it
Expand Down Expand Up @@ -125,6 +126,12 @@ func (sa *ScriptArgument) MarshalBCS(ser *bcs.Serializer) {
ser.SetError(fmt.Errorf("invalid input type (%T) for ScriptArgumentBool, must be bool", sa.Value))
}
ser.Bool(value)
case ScriptArgumentSerialized:
value, ok := (sa.Value).(*bcs.Serialized)
if !ok {
ser.SetError(fmt.Errorf("invalid input type (%T) for ScriptArgumentSerialized, must be *bcs.Serialized", sa.Value))
}
ser.Serialized(*value)
}
}

Expand All @@ -151,6 +158,8 @@ func (sa *ScriptArgument) UnmarshalBCS(des *bcs.Deserializer) {
sa.Value = des.ReadBytes()
case ScriptArgumentBool:
sa.Value = des.Bool()
case ScriptArgumentSerialized:
sa.Value = des.Serialized()
}
}

Expand Down