Add initial signed-memo program (#1135)
* Initial s-memo * Populate readme * Add signed-memo to spl docs * Log less, fail faster * Replace and bump memo * Update memo id * Add memo prefix and len * Add test that demonstrates compute bounds * Add logging and compute to memo docs
This commit is contained in:
parent
1c4753e9a3
commit
190e664dd1
|
@ -3554,7 +3554,7 @@ dependencies = [
|
|||
"solana-sdk",
|
||||
"solana-stake-program",
|
||||
"solana-vote-program",
|
||||
"spl-memo 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"spl-memo 2.0.1",
|
||||
"spl-token 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror",
|
||||
]
|
||||
|
@ -3712,17 +3712,20 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "spl-memo"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb2b771f6146dec14ef5fbf498f9374652c54badc3befc8c40c1d426dd45d720"
|
||||
dependencies = [
|
||||
"solana-program",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-memo"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb2b771f6146dec14ef5fbf498f9374652c54badc3befc8c40c1d426dd45d720"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"solana-program",
|
||||
"solana-program-test",
|
||||
"solana-sdk",
|
||||
"tokio 0.3.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4062,7 +4065,7 @@ name = "test-client"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"solana-sdk",
|
||||
"spl-memo 2.0.1",
|
||||
"spl-memo 3.0.0",
|
||||
"spl-token 3.0.1",
|
||||
"spl-token-swap",
|
||||
]
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
title: Memo Program
|
||||
---
|
||||
|
||||
A simple program that validates a string of UTF-8 encoded characters. It can be
|
||||
used to record a string on-chain, stored in the instruction data of a successful
|
||||
transaction.
|
||||
The Memo program is a simple program that validates a string of UTF-8 encoded
|
||||
characters and verifies that any accounts provided are signers of the
|
||||
transaction. The program also logs the memo, as well as any verified signer
|
||||
addresses, to the transaction log, so that anyone can easily observe memos and
|
||||
know they were approved by zero or more addresses by inspecting the transaction
|
||||
log from a trusted provider.
|
||||
|
||||
## Background
|
||||
|
||||
|
@ -22,9 +25,54 @@ The Memo Program's source is available on
|
|||
## Interface
|
||||
|
||||
The on-chain Memo Program is written in Rust and available on crates.io as
|
||||
[spl-memo](https://crates.io/crates/spl-memo).
|
||||
[spl-memo](https://crates.io/crates/spl-memo) and
|
||||
[docs.rs](https://docs.rs/spl-memo).
|
||||
|
||||
## Operational overview
|
||||
The crate provides a `build_memo()` method to easily create a properly
|
||||
constructed Instruction.
|
||||
|
||||
The Memo program attempts to UTF-8 decode the instruction data; if successfully
|
||||
decoded, the instruction is successful.
|
||||
## Operational Notes
|
||||
|
||||
If zero accounts are provided to the signed-memo instruction, the program
|
||||
succeeds when the memo is valid UTF-8, and logs the memo to the transaction log.
|
||||
|
||||
If one or more accounts are provided to the signed-memo instruction, all must be
|
||||
valid signers of the transaction for the instruction to succeed.
|
||||
|
||||
### Logs
|
||||
|
||||
This section details expected log output for memo instructions.
|
||||
|
||||
Logging begins with entry into the program:
|
||||
`Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr invoke [1]`
|
||||
|
||||
The program will include a separate log for each verified signer:
|
||||
`Program log: Signed by <BASE_58_ADDRESS>`
|
||||
|
||||
Then the program logs the memo length and UTF-8 text:
|
||||
`Program log: Memo (len 4): "🐆"`
|
||||
|
||||
If UTF-8 parsing fails, the program will log the failure point:
|
||||
`Program log: Invalid UTF-8, from byte 4`
|
||||
|
||||
Logging ends with the status of the instruction, one of:
|
||||
`Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr success`
|
||||
`Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr failed: missing required signature for instruction`
|
||||
`Program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr failed: invalid instruction data`
|
||||
|
||||
For more information about exposing program logs on a node, head to the
|
||||
[developer
|
||||
docs](https://docs.solana.com/developing/deployed-programs/debugging#logging)
|
||||
|
||||
### Compute Limits
|
||||
|
||||
Like all programs, the Memo Program is subject to the cluster's [compute
|
||||
budget](https://docs.solana.com/developing/programming-model/runtime#compute-budget).
|
||||
In Memo, compute is used for parsing UTF-8, verifying signers, and logging,
|
||||
limiting the memo length and number of signers that can be processed
|
||||
successfully in a single instruction. The longer or more complex the UTF-8 memo,
|
||||
the fewer signers can be supported, and vice versa.
|
||||
|
||||
As of v1.5.1, an unsigned instruction can support single-byte UTF-8 of up to 566
|
||||
bytes. An instruction with a simple memo of 32 bytes can support up to 12
|
||||
signers.
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Memo Program
|
||||
|
||||
A simple program that validates a string of UTF-8 encoded characters. It can be
|
||||
used to record a string on-chain, stored in the instruction data of a successful
|
||||
transaction.
|
||||
A simple program that validates a string of UTF-8 encoded characters and logs it
|
||||
in the transaction log. The program also verifies that any accounts provided are
|
||||
signers of the transaction, and if so, logs their addresses. It can be used to
|
||||
record a string on-chain, stored in the instruction data of a successful
|
||||
transaction, and optionally verify the originator.
|
||||
|
||||
Full documentation is available at https://spl.solana.com/memo
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "spl-memo"
|
||||
version = "2.0.1"
|
||||
version = "3.0.0"
|
||||
description = "Solana Program Library Memo"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana-program-library"
|
||||
|
@ -9,10 +9,16 @@ edition = "2018"
|
|||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
solana-program = "1.5.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "1.5.1"
|
||||
solana-sdk = "1.5.1"
|
||||
tokio = { version = "0.3", features = ["macros"]}
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo
|
||||
MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
cd "$(dirname "$0")"
|
||||
cargo fmt -- --check
|
||||
cargo clippy
|
||||
cargo build
|
||||
cargo build-bpf
|
||||
|
||||
if [[ $1 = -v ]]; then
|
||||
export RUST_LOG=solana=debug
|
||||
fi
|
||||
|
||||
cargo test
|
||||
cargo test-bpf
|
|
@ -1,43 +1,16 @@
|
|||
//! Program entrypoint
|
||||
|
||||
#![cfg(not(feature = "no-entrypoint"))]
|
||||
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
|
||||
};
|
||||
use std::str::from_utf8;
|
||||
|
||||
entrypoint!(process_instruction);
|
||||
fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
_accounts: &[AccountInfo],
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
instruction_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
from_utf8(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_program::{program_error::ProgramError, pubkey::Pubkey};
|
||||
|
||||
#[test]
|
||||
fn test_utf8_memo() {
|
||||
let program_id = Pubkey::new(&[0; 32]);
|
||||
|
||||
let string = b"letters and such";
|
||||
assert_eq!(Ok(()), process_instruction(&program_id, &[], string));
|
||||
|
||||
let emoji = "🐆".as_bytes();
|
||||
let bytes = [0xF0, 0x9F, 0x90, 0x86];
|
||||
assert_eq!(emoji, bytes);
|
||||
assert_eq!(Ok(()), process_instruction(&program_id, &[], &emoji));
|
||||
|
||||
let mut bad_utf8 = bytes;
|
||||
bad_utf8[3] = 0xFF; // Invalid UTF-8 byte
|
||||
assert_eq!(
|
||||
Err(ProgramError::InvalidInstructionData),
|
||||
process_instruction(&program_id, &[], &bad_utf8)
|
||||
);
|
||||
}
|
||||
crate::processor::process_instruction(program_id, accounts, instruction_data)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,34 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
//! A simple program that accepts a string of encoded characters and verifies that it parses. Currently handles UTF-8.
|
||||
//! A program that accepts a string of encoded characters and verifies that it parses,
|
||||
//! while verifying and logging signers. Currently handles UTF-8 characters.
|
||||
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
mod entrypoint;
|
||||
pub mod processor;
|
||||
|
||||
// Export current sdk types for downstream users building with a different sdk version
|
||||
pub use solana_program;
|
||||
use solana_program::{
|
||||
instruction::{AccountMeta, Instruction},
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
solana_program::declare_id!("Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo");
|
||||
solana_program::declare_id!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
|
||||
|
||||
/// Build a memo instruction, possibly signed
|
||||
///
|
||||
/// Accounts expected by this instruction:
|
||||
///
|
||||
/// 0. ..0+N. `[signer]` Expected signers; if zero provided, instruction will be processed as a
|
||||
/// normal, unsigned spl-memo
|
||||
///
|
||||
pub fn build_memo(memo: &[u8], signer_pubkeys: &[&Pubkey]) -> Instruction {
|
||||
Instruction {
|
||||
program_id: id(),
|
||||
accounts: signer_pubkeys
|
||||
.iter()
|
||||
.map(|&pubkey| AccountMeta::new_readonly(*pubkey, true))
|
||||
.collect(),
|
||||
data: memo.to_vec(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
//! Program state processor
|
||||
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
use std::str::from_utf8;
|
||||
|
||||
/// Instruction processor
|
||||
pub fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
input: &[u8],
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
let mut missing_required_signature = false;
|
||||
for account_info in account_info_iter {
|
||||
if let Some(address) = account_info.signer_key() {
|
||||
msg!("Signed by {:?}", address);
|
||||
} else {
|
||||
missing_required_signature = true;
|
||||
}
|
||||
}
|
||||
if missing_required_signature {
|
||||
return Err(ProgramError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
let memo = from_utf8(input).map_err(|err| {
|
||||
msg!("Invalid UTF-8, from byte {}", err.valid_up_to());
|
||||
ProgramError::InvalidInstructionData
|
||||
})?;
|
||||
msg!("Memo (len {}): {:?}", memo.len(), memo);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_program::{
|
||||
account_info::IntoAccountInfo, program_error::ProgramError, pubkey::Pubkey,
|
||||
};
|
||||
use solana_sdk::account::Account;
|
||||
|
||||
#[test]
|
||||
fn test_utf8_memo() {
|
||||
let program_id = Pubkey::new(&[0; 32]);
|
||||
|
||||
let string = b"letters and such";
|
||||
assert_eq!(Ok(()), process_instruction(&program_id, &[], string));
|
||||
|
||||
let emoji = "🐆".as_bytes();
|
||||
let bytes = [0xF0, 0x9F, 0x90, 0x86];
|
||||
assert_eq!(emoji, bytes);
|
||||
assert_eq!(Ok(()), process_instruction(&program_id, &[], &emoji));
|
||||
|
||||
let mut bad_utf8 = bytes;
|
||||
bad_utf8[3] = 0xFF; // Invalid UTF-8 byte
|
||||
assert_eq!(
|
||||
Err(ProgramError::InvalidInstructionData),
|
||||
process_instruction(&program_id, &[], &bad_utf8)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signers() {
|
||||
let program_id = Pubkey::new(&[0; 32]);
|
||||
let memo = "🐆".as_bytes();
|
||||
|
||||
let pubkey0 = Pubkey::new_unique();
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
let mut account0 = Account::default();
|
||||
let mut account1 = Account::default();
|
||||
let mut account2 = Account::default();
|
||||
|
||||
let signed_account_infos = vec![
|
||||
(&pubkey0, true, &mut account0).into_account_info(),
|
||||
(&pubkey1, true, &mut account1).into_account_info(),
|
||||
(&pubkey2, true, &mut account2).into_account_info(),
|
||||
];
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
process_instruction(&program_id, &signed_account_infos, memo)
|
||||
);
|
||||
|
||||
assert_eq!(Ok(()), process_instruction(&program_id, &[], memo));
|
||||
|
||||
let unsigned_account_infos = vec![
|
||||
(&pubkey0, false, &mut account0).into_account_info(),
|
||||
(&pubkey1, false, &mut account1).into_account_info(),
|
||||
(&pubkey2, false, &mut account2).into_account_info(),
|
||||
];
|
||||
assert_eq!(
|
||||
Err(ProgramError::MissingRequiredSignature),
|
||||
process_instruction(&program_id, &unsigned_account_infos, memo)
|
||||
);
|
||||
|
||||
let partially_signed_account_infos = vec![
|
||||
(&pubkey0, true, &mut account0).into_account_info(),
|
||||
(&pubkey1, false, &mut account1).into_account_info(),
|
||||
(&pubkey2, true, &mut account2).into_account_info(),
|
||||
];
|
||||
assert_eq!(
|
||||
Err(ProgramError::MissingRequiredSignature),
|
||||
process_instruction(&program_id, &partially_signed_account_infos, memo)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use solana_program::{
|
||||
instruction::{AccountMeta, Instruction, InstructionError},
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::{
|
||||
signature::{Keypair, Signer},
|
||||
transaction::{Transaction, TransactionError},
|
||||
};
|
||||
use spl_memo::*;
|
||||
|
||||
fn program_test() -> ProgramTest {
|
||||
ProgramTest::new("spl_memo", id(), processor!(processor::process_instruction))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_memo_signing() {
|
||||
let memo = "🐆".as_bytes();
|
||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||
|
||||
let keypairs = vec![Keypair::new(), Keypair::new(), Keypair::new()];
|
||||
let pubkeys: Vec<Pubkey> = keypairs.iter().map(|keypair| keypair.pubkey()).collect();
|
||||
|
||||
// Test complete signing
|
||||
let signer_key_refs: Vec<&Pubkey> = pubkeys.iter().collect();
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(&[build_memo(memo, &signer_key_refs)], Some(&payer.pubkey()));
|
||||
let mut signers = vec![&payer];
|
||||
for keypair in keypairs.iter() {
|
||||
signers.push(keypair);
|
||||
}
|
||||
transaction.sign(&signers, recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
// Test unsigned memo
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(&[build_memo(memo, &[])], Some(&payer.pubkey()));
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
// Demonstrate success on signature provided, regardless of specific memo AccountMeta
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[Instruction {
|
||||
program_id: id(),
|
||||
accounts: vec![
|
||||
AccountMeta::new_readonly(keypairs[0].pubkey(), true),
|
||||
AccountMeta::new_readonly(keypairs[1].pubkey(), true),
|
||||
AccountMeta::new_readonly(payer.pubkey(), false),
|
||||
],
|
||||
data: memo.to_vec(),
|
||||
}],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
transaction.sign(&[&payer, &keypairs[0], &keypairs[1]], recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
// Test missing signer(s)
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[Instruction {
|
||||
program_id: id(),
|
||||
accounts: vec![
|
||||
AccountMeta::new_readonly(keypairs[0].pubkey(), true),
|
||||
AccountMeta::new_readonly(keypairs[1].pubkey(), false),
|
||||
AccountMeta::new_readonly(keypairs[2].pubkey(), true),
|
||||
],
|
||||
data: memo.to_vec(),
|
||||
}],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
transaction.sign(&[&payer, &keypairs[0], &keypairs[2]], recent_blockhash);
|
||||
assert_eq!(
|
||||
banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
|
||||
);
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[Instruction {
|
||||
program_id: id(),
|
||||
accounts: vec![
|
||||
AccountMeta::new_readonly(keypairs[0].pubkey(), false),
|
||||
AccountMeta::new_readonly(keypairs[1].pubkey(), false),
|
||||
AccountMeta::new_readonly(keypairs[2].pubkey(), false),
|
||||
],
|
||||
data: memo.to_vec(),
|
||||
}],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
assert_eq!(
|
||||
banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
|
||||
);
|
||||
|
||||
// Test invalid utf-8; demonstrate log
|
||||
let invalid_utf8 = [0xF0, 0x9F, 0x90, 0x86, 0xF0, 0x9F, 0xFF, 0x86];
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(&[build_memo(&invalid_utf8, &[])], Some(&payer.pubkey()));
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
assert_eq!(
|
||||
banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_memo_compute_limits() {
|
||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||
|
||||
// Test memo length
|
||||
let mut memo = vec![];
|
||||
for _ in 0..1000 {
|
||||
let mut vec = vec![0x53, 0x4F, 0x4C];
|
||||
memo.append(&mut vec);
|
||||
}
|
||||
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(&[build_memo(&memo[..566], &[])], Some(&payer.pubkey()));
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(&[build_memo(&memo[..567], &[])], Some(&payer.pubkey()));
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
assert_eq!(
|
||||
banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
||||
);
|
||||
|
||||
let mut memo = vec![];
|
||||
for _ in 0..100 {
|
||||
let mut vec = vec![0xE2, 0x97, 0x8E];
|
||||
memo.append(&mut vec);
|
||||
}
|
||||
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(&[build_memo(&memo[..60], &[])], Some(&payer.pubkey()));
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(&[build_memo(&memo[..63], &[])], Some(&payer.pubkey()));
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
assert_eq!(
|
||||
banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
||||
);
|
||||
|
||||
// Test num signers with 32-byte memo
|
||||
let memo = Pubkey::new_unique().to_bytes();
|
||||
let mut keypairs = vec![];
|
||||
for _ in 0..20 {
|
||||
keypairs.push(Keypair::new());
|
||||
}
|
||||
let pubkeys: Vec<Pubkey> = keypairs.iter().map(|keypair| keypair.pubkey()).collect();
|
||||
let signer_key_refs: Vec<&Pubkey> = pubkeys.iter().collect();
|
||||
|
||||
let mut signers = vec![&payer];
|
||||
for keypair in keypairs[..12].iter() {
|
||||
signers.push(keypair);
|
||||
}
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[build_memo(&memo, &signer_key_refs[..12])],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
transaction.sign(&signers, recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
let mut signers = vec![&payer];
|
||||
for keypair in keypairs[..13].iter() {
|
||||
signers.push(keypair);
|
||||
}
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[build_memo(&memo, &signer_key_refs[..13])],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
transaction.sign(&signers, recent_blockhash);
|
||||
assert_eq!(
|
||||
banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue