From 4fe5f52d4b9709f10fe7f0ecfed4d3d6e0c7d0ec Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Mon, 10 Mar 2025 17:10:46 +0800 Subject: [PATCH 1/2] Add Serialized type and serialization methods for handling byte arrays --- bcs/deserializer.go | 5 ++ bcs/serialized.go | 29 ++++++++ bcs/serializer.go | 13 ++++ examples/script_transaction/main.go | 107 ++++++++++++++++++++++++++++ script.go | 29 +++++--- 5 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 bcs/serialized.go create mode 100644 examples/script_transaction/main.go diff --git a/bcs/deserializer.go b/bcs/deserializer.go index 34e57ff..95e6eba 100644 --- a/bcs/deserializer.go +++ b/bcs/deserializer.go @@ -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()} +} diff --git a/bcs/serialized.go b/bcs/serialized.go new file mode 100644 index 0000000..45484dd --- /dev/null +++ b/bcs/serialized.go @@ -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) +} diff --git a/bcs/serializer.go b/bcs/serializer.go index e923e8c..e58c645 100644 --- a/bcs/serializer.go +++ b/bcs/serializer.go @@ -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) + }) +} \ No newline at end of file diff --git a/examples/script_transaction/main.go b/examples/script_transaction/main.go new file mode 100644 index 0000000..90eba90 --- /dev/null +++ b/examples/script_transaction/main.go @@ -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) +} diff --git a/script.go b/script.go index 3f8238b..4395d42 100644 --- a/script.go +++ b/script.go @@ -2,8 +2,8 @@ package aptos import ( "fmt" - "github.com/aptos-labs/aptos-go-sdk/bcs" "math/big" + "github.com/aptos-labs/aptos-go-sdk/bcs" ) //region Script @@ -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 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 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 @@ -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) } } @@ -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() } } From 3d29de486e0d41a09015db041efbb1ed7893ecc2 Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Mon, 10 Mar 2025 17:21:26 +0800 Subject: [PATCH 2/2] Fix formatting and add missing newline in serializer.go; clean up whitespace in main.go and script.go --- bcs/serializer.go | 2 +- examples/script_transaction/main.go | 4 ++-- script.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bcs/serializer.go b/bcs/serializer.go index e58c645..56d49f9 100644 --- a/bcs/serializer.go +++ b/bcs/serializer.go @@ -364,4 +364,4 @@ func SerializeSerialized(input Serialized) ([]byte, error) { return SerializeSingle(func(ser *Serializer) { ser.Serialized(input) }) -} \ No newline at end of file +} diff --git a/examples/script_transaction/main.go b/examples/script_transaction/main.go index 90eba90..d9a6147 100644 --- a/examples/script_transaction/main.go +++ b/examples/script_transaction/main.go @@ -8,6 +8,7 @@ import ( "github.com/aptos-labs/aptos-go-sdk/bcs" "github.com/aptos-labs/aptos-go-sdk/crypto" ) + const testEd25519PrivateKey = "ed25519-priv-0xc5338cd251c22daa8c9c9cc94f498cc8a5c7e1d2e75287a5dda91096fe64efa5" const TransferScriptPayload = "0x008601a11ceb0b0700000a06010002030206050806070e1708252010451900000001010000010003060c05030d6170746f735f6163636f756e74087472616e736665720000000000000000000000000000000000000000000000000000000000000001166170746f733a3a7363726970745f636f6d706f7365720100000100050a000b010b0211000200020920000000000000000000000000000000000000000000000000000000000000000209086400000000000000" @@ -59,9 +60,8 @@ func runScript(client *aptos.Client, alice *aptos.Account, bob *aptos.AccountAdd var payload aptos.TransactionPayload payload.UnmarshalBCS(bcs.NewDeserializer(scriptBytes)) - // 1. Build transaction - rawTxn, err := client.BuildTransaction(alice.AccountAddress(), payload); + rawTxn, err := client.BuildTransaction(alice.AccountAddress(), payload) if err != nil { panic("Failed to build transaction:" + err.Error()) diff --git a/script.go b/script.go index 4395d42..de8543c 100644 --- a/script.go +++ b/script.go @@ -2,8 +2,8 @@ package aptos import ( "fmt" - "math/big" "github.com/aptos-labs/aptos-go-sdk/bcs" + "math/big" ) //region Script