diff --git a/example/Transactions/transaction_example.gd b/example/Transactions/transaction_example.gd index 788bb77a..e82e2197 100644 --- a/example/Transactions/transaction_example.gd +++ b/example/Transactions/transaction_example.gd @@ -4,7 +4,7 @@ extends VBoxContainer @onready var payer: Keypair = Keypair.new_from_file("res://payer.json") const LAMPORTS_PER_SOL = 1000000000 -const TOTAL_CASES := 11 +const TOTAL_CASES := 12 var passed_test_mask := 0 @@ -185,6 +185,44 @@ func blockhash_before_instruction(): PASS(10) +func transaction_from_bytes(): + var receiver: Pubkey = Pubkey.new_from_string("78GVwUb8ojcJVrEVkwCU5tfUKTfJuiazRrysGwgjqsif") + var tx = Transaction.new() + + add_child(tx) + + # A transaction can be sent after three steps: + # Set the payer. + # Add instruction(s). + # Set latest blockhash. + + tx.set_payer(payer) + + var ix: Instruction = SystemProgram.transfer(payer, receiver, LAMPORTS_PER_SOL / 10) + tx.add_instruction(ix) + + tx.update_latest_blockhash() + + tx.sign() + var original = tx.serialize() + var reconstructed = Transaction.new_from_bytes(tx.serialize()).serialize() + + assert(original == reconstructed) + + # Signers are not stored in the bytes so need to set them. + tx.set_signers([payer]) + tx.send() + # On success transaction response signal will contain results. + # Connect it to avoid errors in you application. + var response = await tx.transaction_response_received + + assert(response.has("result")) + var signature = response["result"] + assert(typeof(signature) == TYPE_STRING) + + PASS(11) + + func _ready(): # Use a local cluster for unlimited Solana airdrops. # SolanaClient defaults to devnet cluster URL if not specified. @@ -199,6 +237,7 @@ func _ready(): transaction_with_confirmation_1() transaction_with_confirmation_2() blockhash_before_instruction() + transaction_from_bytes() func _on_timeout_timeout(): diff --git a/src/instruction.cpp b/src/instruction.cpp index 82a132ec..c98fa5ed 100644 --- a/src/instruction.cpp +++ b/src/instruction.cpp @@ -93,14 +93,21 @@ CompiledInstruction::CompiledInstruction(){ } int CompiledInstruction::create_from_bytes(const PackedByteArray& bytes){ + const unsigned int MINIMUM_COMPILED_INSTRUCTION_SIZE = 3; + ERR_FAIL_COND_V_EDMSG(bytes.size() < MINIMUM_COMPILED_INSTRUCTION_SIZE, 0, "Invalid compiled instruction."); + int cursor = 0; program_id_index = bytes[cursor++]; const unsigned int account_size = bytes[cursor++]; + + ERR_FAIL_COND_V_EDMSG(bytes.size() < MINIMUM_COMPILED_INSTRUCTION_SIZE + account_size, 0, "Invalid compiled instruction."); accounts = bytes.slice(cursor, cursor + account_size); cursor += account_size; const unsigned int data_size = bytes[cursor++]; + + ERR_FAIL_COND_V_EDMSG(bytes.size() < MINIMUM_COMPILED_INSTRUCTION_SIZE + account_size + data_size, 0, "Invalid compiled instruction."); data = bytes.slice(cursor, cursor + data_size); return cursor + data_size; diff --git a/src/instructions/mpl_candy_machine.cpp b/src/instructions/mpl_candy_machine.cpp index 64ae254a..dc297076 100644 --- a/src/instructions/mpl_candy_machine.cpp +++ b/src/instructions/mpl_candy_machine.cpp @@ -1660,6 +1660,7 @@ Variant MplCandyGuard::mint( Instruction *result = memnew(Instruction); PackedByteArray data = MplCandyMachine::mint2_discriminator(); + data.append_array(Object::cast_to(candy_guard_acl)->get_group(label).serialize_mint_settings()); data.append_array(serialize_label(label)); @@ -1721,7 +1722,7 @@ Variant MplCandyGuard::mint( TypedArray mint_arg_accounts = Object::cast_to(candy_guard_acl)->get_group(label).get_mint_arg_accounts(receiver); for(unsigned int i = 0; i < mint_arg_accounts.size(); i++){ - result->append_meta(mint_arg_accounts[i]); + result->append_meta(*memnew(AccountMeta(mint_arg_accounts[i]))); } return result; diff --git a/src/message.cpp b/src/message.cpp index f404da31..27e24ed5 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -159,12 +159,22 @@ Message::Message(TypedArray instructions, Variant &payer, uint32_t Message::Message(const PackedByteArray& bytes){ int cursor = 0; + + // blockhash + number of accounts + compiled instruction size + unsigned int minimum_remaining_size = 32 + 1 + 1; + ERR_FAIL_COND_EDMSG(bytes.size() < minimum_remaining_size, "Invalid accounts size"); + num_required_signatures = bytes[cursor++]; + + ERR_FAIL_COND_EDMSG(num_required_signatures > 127, "V0 transactions are not yet supported."); + num_readonly_signed_accounts = bytes[cursor++]; num_readonly_unsigned_accounts = bytes[cursor++]; uint8_t account_size = bytes[cursor++]; + ERR_FAIL_COND_EDMSG(bytes.size() < minimum_remaining_size + account_size * 32, "Invalid accounts size"); + for(unsigned int i = 0; i < account_size; i++){ account_keys.append(Pubkey::new_from_bytes(bytes.slice(cursor, cursor + 32))); cursor += 32; @@ -177,6 +187,8 @@ Message::Message(const PackedByteArray& bytes){ for(int i = 0; i < compiled_instructions_size; i++){ CompiledInstruction *new_instruction = memnew(CompiledInstruction); int consumed_bytes = new_instruction->create_from_bytes(bytes.slice(cursor)); + ERR_FAIL_COND(consumed_bytes == 0); + compiled_instructions.append(new_instruction); cursor += consumed_bytes; } diff --git a/src/transaction.cpp b/src/transaction.cpp index d511bc4d..23f5de6f 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -310,6 +310,10 @@ Transaction::Transaction() { } Transaction::Transaction(const PackedByteArray& bytes){ + const unsigned int MINIMUM_MESSAGE_SIZE = 32 + 4 + 1; + const unsigned int MINIMUM_TRANSACTION_SIZE = MINIMUM_MESSAGE_SIZE + 1; + ERR_FAIL_COND_EDMSG(bytes.size() < MINIMUM_TRANSACTION_SIZE, "Invalid transaction size"); + send_client = memnew(SolanaClient); blockhash_client = memnew(SolanaClient); subscribe_client = memnew(SolanaClient); @@ -320,13 +324,16 @@ Transaction::Transaction(const PackedByteArray& bytes){ subscribe_client->set_async_override(true); int cursor = 0; + const unsigned int signer_size = bytes[cursor++]; + ERR_FAIL_COND_EDMSG(bytes.size() < MINIMUM_MESSAGE_SIZE + 1 + signer_size * 64, "Invalid message size."); for(unsigned int i = 0; i < signer_size; i++){ const PackedByteArray signature_bytes = bytes.slice(cursor, cursor + 64); if(! are_all_bytes_zeroes(signature_bytes)){ ready_signature_amount += 1; } signatures.append(bytes.slice(cursor, cursor + 64)); + cursor += 64; }