Add initial wasm bindings for `Instruction`, `SystemProgram` and `Transaction`

This commit is contained in:
Michael Vines 2021-10-18 08:39:21 -07:00
parent 03a956e8d9
commit a35df1cb02
10 changed files with 272 additions and 4 deletions

View File

@ -2,7 +2,7 @@
//! Defines a composable Instruction type and a memory-efficient CompiledInstruction.
use {
crate::{pubkey::Pubkey, sanitize::Sanitize, short_vec},
crate::{pubkey::Pubkey, sanitize::Sanitize, short_vec, wasm_bindgen},
bincode::serialize,
borsh::BorshSerialize,
serde::Serialize,
@ -240,13 +240,17 @@ pub enum InstructionError {
// conversions must also be added
}
#[wasm_bindgen]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Instruction {
/// Pubkey of the instruction processor that executes this instruction
#[wasm_bindgen(skip)]
pub program_id: Pubkey,
/// Metadata for what accounts should be passed to the instruction processor
#[wasm_bindgen(skip)]
pub accounts: Vec<AccountMeta>,
/// Opaque data passed to the instruction processor
#[wasm_bindgen(skip)]
pub data: Vec<u8>,
}

View File

@ -12,7 +12,7 @@ use {
serialize_utils::{
append_slice, append_u16, append_u8, read_pubkey, read_slice, read_u16, read_u8,
},
short_vec, system_instruction, system_program, sysvar,
short_vec, system_instruction, system_program, sysvar, wasm_bindgen,
},
lazy_static::lazy_static,
std::{collections::BTreeSet, convert::TryFrom, str::FromStr},
@ -168,15 +168,18 @@ fn get_program_ids(instructions: &[Instruction]) -> Vec<Pubkey> {
// NOTE: Serialization-related changes must be paired with the custom serialization
// for versioned messages in the `RemainingLegacyMessage` struct.
#[wasm_bindgen]
#[frozen_abi(digest = "2KnLEqfLcTBQqitE22Pp8JYkaqVVbAkGbCfdeHoyxcAU")]
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
#[serde(rename_all = "camelCase")]
pub struct Message {
/// The message header, identifying signed and read-only `account_keys`
/// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
#[wasm_bindgen(skip)]
pub header: MessageHeader,
/// All the account keys used by this transaction
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub account_keys: Vec<Pubkey>,
@ -185,6 +188,7 @@ pub struct Message {
/// Programs that will be executed in sequence and committed in one atomic transaction if all
/// succeed.
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub instructions: Vec<CompiledInstruction>,
}

View File

@ -1,5 +1,4 @@
#[allow(deprecated)]
use crate::sysvar::recent_blockhashes;
use {
crate::{
decode_error::DecodeError,
@ -7,7 +6,7 @@ use {
nonce,
pubkey::Pubkey,
system_program,
sysvar::rent,
sysvar::{recent_blockhashes, rent},
},
num_derive::{FromPrimitive, ToPrimitive},
thiserror::Error,

View File

@ -0,0 +1,28 @@
//! The `Instructions` struct is a workaround for the lack of Vec<T> support in wasm-bindgen
//! (ref: https://github.com/rustwasm/wasm-bindgen/issues/111)
#![cfg(target_arch = "wasm32")]
use {crate::instruction::Instruction, wasm_bindgen::prelude::*};
#[wasm_bindgen]
#[derive(Default)]
pub struct Instructions {
instructions: Vec<Instruction>,
}
#[wasm_bindgen]
impl Instructions {
#[wasm_bindgen(constructor)]
pub fn constructor() -> Instructions {
Instructions::default()
}
pub fn push(&mut self, instruction: Instruction) {
self.instructions.push(instruction);
}
}
impl From<Instructions> for Vec<Instruction> {
fn from(instructions: Instructions) -> Self {
instructions.instructions
}
}

View File

@ -3,7 +3,9 @@
use wasm_bindgen::prelude::*;
pub mod hash;
pub mod instructions;
pub mod pubkey;
pub mod system_instruction;
/// Initialize Javascript logging and panic handler
#[wasm_bindgen]

View File

@ -0,0 +1,112 @@
//! `SystemInstruction` Javascript interface
#![cfg(target_arch = "wasm32")]
#![allow(non_snake_case)]
use {
crate::{instruction::Instruction, pubkey::Pubkey, system_instruction::*},
wasm_bindgen::prelude::*,
};
#[wasm_bindgen]
impl SystemInstruction {
pub fn createAccount(
from_pubkey: &Pubkey,
to_pubkey: &Pubkey,
lamports: u64,
space: u64,
owner: &Pubkey,
) -> Instruction {
create_account(from_pubkey, to_pubkey, lamports, space, owner)
}
pub fn createAccountWithSeed(
from_pubkey: &Pubkey,
to_pubkey: &Pubkey,
base: &Pubkey,
seed: &str,
lamports: u64,
space: u64,
owner: &Pubkey,
) -> Instruction {
create_account_with_seed(from_pubkey, to_pubkey, base, seed, lamports, space, owner)
}
pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
assign(pubkey, owner)
}
pub fn assignWithSeed(
pubkey: &Pubkey,
base: &Pubkey,
seed: &str,
owner: &Pubkey,
) -> Instruction {
assign_with_seed(pubkey, base, seed, owner)
}
pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
transfer(from_pubkey, to_pubkey, lamports)
}
pub fn transferWithSeed(
from_pubkey: &Pubkey,
from_base: &Pubkey,
from_seed: String,
from_owner: &Pubkey,
to_pubkey: &Pubkey,
lamports: u64,
) -> Instruction {
transfer_with_seed(
from_pubkey,
from_base,
from_seed,
from_owner,
to_pubkey,
lamports,
)
}
pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction {
allocate(pubkey, space)
}
pub fn allocateWithSeed(
address: &Pubkey,
base: &Pubkey,
seed: &str,
space: u64,
owner: &Pubkey,
) -> Instruction {
allocate_with_seed(address, base, seed, space, owner)
}
pub fn createNonceAccount(
from_pubkey: &Pubkey,
nonce_pubkey: &Pubkey,
authority: &Pubkey,
lamports: u64,
) -> js_sys::Array {
let instructions = create_nonce_account(from_pubkey, nonce_pubkey, authority, lamports);
instructions.into_iter().map(JsValue::from).collect()
}
pub fn advanceNonceAccount(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
advance_nonce_account(nonce_pubkey, authorized_pubkey)
}
pub fn withdrawNonceAccount(
nonce_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
to_pubkey: &Pubkey,
lamports: u64,
) -> Instruction {
withdraw_nonce_account(nonce_pubkey, authorized_pubkey, to_pubkey, lamports)
}
pub fn authorizeNonceAccount(
nonce_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
new_authority: &Pubkey,
) -> Instruction {
authorize_nonce_account(nonce_pubkey, authorized_pubkey, new_authority)
}
}

View File

@ -15,6 +15,7 @@ use {
short_vec,
signature::{Signature, SignerError},
signers::Signers,
wasm_bindgen,
},
serde::Serialize,
solana_program::{system_instruction::SystemInstruction, system_program},
@ -38,6 +39,7 @@ pub enum TransactionVerificationMode {
pub type Result<T> = result::Result<T, TransactionError>;
/// An atomic transaction
#[wasm_bindgen]
#[frozen_abi(digest = "FZtncnS1Xk8ghHfKiXE5oGiUbw2wJhmfXQuNgQR3K6Mc")]
#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)]
pub struct Transaction {
@ -47,10 +49,12 @@ pub struct Transaction {
/// [`account_keys`]: Message::account_keys
///
// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
/// The message to sign.
#[wasm_bindgen(skip)]
pub message: Message,
}

View File

@ -2,3 +2,4 @@
#![cfg(target_arch = "wasm32")]
pub mod keypair;
pub mod transaction;

View File

@ -0,0 +1,58 @@
//! `Transaction` Javascript interface
#![cfg(target_arch = "wasm32")]
#![allow(non_snake_case)]
use {
crate::{
hash::Hash,
signer::keypair::Keypair,
{message::Message, transaction::Transaction},
},
solana_program::{
pubkey::Pubkey,
wasm::{display_to_jsvalue, instructions::Instructions},
},
wasm_bindgen::prelude::*,
};
#[wasm_bindgen]
impl Transaction {
/// Create a new `Transaction`
#[wasm_bindgen(constructor)]
pub fn constructor(instructions: Instructions, payer: Option<Pubkey>) -> Transaction {
let instructions: Vec<_> = instructions.into();
Transaction::new_with_payer(&instructions, payer.as_ref())
}
/// Return a message containing all data that should be signed.
#[wasm_bindgen(js_name = message)]
pub fn js_message(&self) -> Message {
self.message.clone()
}
/// Return the serialized message data to sign.
pub fn messageData(&self) -> Box<[u8]> {
self.message_data().into()
}
/// Verify the transaction
#[wasm_bindgen(js_name = verify)]
pub fn js_verify(&self) -> Result<(), JsValue> {
self.verify().map_err(display_to_jsvalue)
}
pub fn partialSign(&mut self, keypair: &Keypair, recent_blockhash: &Hash) {
self.partial_sign(&[keypair], *recent_blockhash);
}
pub fn isSigned(&self) -> bool {
self.is_signed()
}
pub fn toBytes(&self) -> Box<[u8]> {
bincode::serialize(self).unwrap().into()
}
pub fn fromBytes(bytes: &[u8]) -> Result<Transaction, JsValue> {
bincode::deserialize(bytes).map_err(display_to_jsvalue)
}
}

56
sdk/tests/transaction.mjs Normal file
View File

@ -0,0 +1,56 @@
import { expect } from "chai";
import {
init,
Pubkey,
Keypair,
Hash,
SystemInstruction,
Instructions,
Transaction,
} from "crate";
init();
describe("Transaction", function () {
it("SystemInstruction::Transfer", () => {
const payer = Keypair.fromBytes(
new Uint8Array([
241, 230, 222, 64, 184, 48, 232, 92, 156, 210, 229, 183, 154, 251, 5,
227, 98, 184, 34, 234, 39, 106, 62, 210, 166, 187, 31, 44, 40, 96, 24,
51, 252, 28, 2, 120, 234, 212, 139, 111, 96, 8, 168, 204, 34, 72, 199,
205, 117, 165, 82, 51, 32, 93, 211, 36, 239, 245, 139, 218, 99, 211,
207, 177,
])
);
const src = Keypair.fromBytes(
new Uint8Array([
172, 219, 139, 103, 154, 105, 92, 23, 227, 108, 174, 80, 215, 227, 62,
8, 66, 38, 151, 239, 148, 184, 180, 148, 149, 18, 106, 94, 73, 143, 27,
132, 193, 64, 199, 93, 222, 83, 172, 224, 116, 205, 54, 38, 191, 178,
149, 71, 65, 132, 46, 71, 126, 81, 63, 254, 21, 101, 90, 52, 67, 204,
128, 199,
])
);
const dst = new Pubkey("11111111111111111111111111111112");
const recent_blockhash = new Hash(
"EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k"
);
let instructions = new Instructions();
instructions.push(
SystemInstruction.transfer(src.pubkey(), dst, BigInt(123))
);
let transaction = new Transaction(instructions, payer.pubkey());
transaction.partialSign(payer, recent_blockhash);
transaction.partialSign(src, recent_blockhash);
expect(transaction.isSigned()).to.be.true;
transaction.verify();
expect(Buffer.from(transaction.toBytes()).toString("base64")).to.equal(
"AoZrVzP93eyp3vbl6CU9XQjQfm4Xp/7nSiBlsX/kJmfTQZsGTOrFnt6EUqHVte97fGZ71UAXDfLbR5B31OtRdgdab57BOU8mq0ztMutZAVBPtGJHVly8RPz4TYa+OFU7EIk3Wrv4WUMCb/NR+LxELLH+tQt5SrkvB7rCE2DniM8JAgABBPwcAnjq1ItvYAiozCJIx811pVIzIF3TJO/1i9pj08+xwUDHXd5TrOB0zTYmv7KVR0GELkd+UT/+FWVaNEPMgMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAwIBAgwCAAAAewAAAAAAAAA="
);
});
});