Skip to content

Commit abf1988

Browse files
CriesofCarrotsjoncinque
authored andcommitted
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
1 parent fe605bc commit abf1988

File tree

5 files changed

+355
-37
lines changed

5 files changed

+355
-37
lines changed

program/src/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "spl-memo"
3-
version = "2.0.1"
3+
version = "3.0.0"
44
description = "Solana Program Library Memo"
55
authors = ["Solana Maintainers <[email protected]>"]
66
repository = "https://github.com/solana-labs/solana-program-library"
@@ -9,10 +9,16 @@ edition = "2018"
99

1010
[features]
1111
no-entrypoint = []
12+
test-bpf = []
1213

1314
[dependencies]
1415
solana-program = "1.5.1"
1516

17+
[dev-dependencies]
18+
solana-program-test = "1.5.1"
19+
solana-sdk = "1.5.1"
20+
tokio = { version = "0.3", features = ["macros"]}
21+
1622
[lib]
1723
crate-type = ["cdylib", "lib"]
1824

program/src/entrypoint.rs

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,16 @@
11
//! Program entrypoint
22
3+
#![cfg(not(feature = "no-entrypoint"))]
4+
35
use solana_program::{
4-
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, program_error::ProgramError,
5-
pubkey::Pubkey,
6+
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
67
};
7-
use std::str::from_utf8;
88

99
entrypoint!(process_instruction);
1010
fn process_instruction(
11-
_program_id: &Pubkey,
12-
_accounts: &[AccountInfo],
11+
program_id: &Pubkey,
12+
accounts: &[AccountInfo],
1313
instruction_data: &[u8],
1414
) -> ProgramResult {
15-
from_utf8(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;
16-
Ok(())
17-
}
18-
19-
#[cfg(test)]
20-
mod tests {
21-
use super::*;
22-
use solana_program::{program_error::ProgramError, pubkey::Pubkey};
23-
24-
#[test]
25-
fn test_utf8_memo() {
26-
let program_id = Pubkey::new(&[0; 32]);
27-
28-
let string = b"letters and such";
29-
assert_eq!(Ok(()), process_instruction(&program_id, &[], string));
30-
31-
let emoji = "🐆".as_bytes();
32-
let bytes = [0xF0, 0x9F, 0x90, 0x86];
33-
assert_eq!(emoji, bytes);
34-
assert_eq!(Ok(()), process_instruction(&program_id, &[], &emoji));
35-
36-
let mut bad_utf8 = bytes;
37-
bad_utf8[3] = 0xFF; // Invalid UTF-8 byte
38-
assert_eq!(
39-
Err(ProgramError::InvalidInstructionData),
40-
process_instruction(&program_id, &[], &bad_utf8)
41-
);
42-
}
15+
crate::processor::process_instruction(program_id, accounts, instruction_data)
4316
}

program/src/lib.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
11
#![deny(missing_docs)]
22

3-
//! A simple program that accepts a string of encoded characters and verifies that it parses. Currently handles UTF-8.
3+
//! A program that accepts a string of encoded characters and verifies that it parses,
4+
//! while verifying and logging signers. Currently handles UTF-8 characters.
45
5-
#[cfg(not(feature = "no-entrypoint"))]
66
mod entrypoint;
7+
pub mod processor;
78

89
// Export current sdk types for downstream users building with a different sdk version
910
pub use solana_program;
11+
use solana_program::{
12+
instruction::{AccountMeta, Instruction},
13+
pubkey::Pubkey,
14+
};
1015

11-
solana_program::declare_id!("Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo");
16+
solana_program::declare_id!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
17+
18+
/// Build a memo instruction, possibly signed
19+
///
20+
/// Accounts expected by this instruction:
21+
///
22+
/// 0. ..0+N. `[signer]` Expected signers; if zero provided, instruction will be processed as a
23+
/// normal, unsigned spl-memo
24+
///
25+
pub fn build_memo(memo: &[u8], signer_pubkeys: &[&Pubkey]) -> Instruction {
26+
Instruction {
27+
program_id: id(),
28+
accounts: signer_pubkeys
29+
.iter()
30+
.map(|&pubkey| AccountMeta::new_readonly(*pubkey, true))
31+
.collect(),
32+
data: memo.to_vec(),
33+
}
34+
}

program/src/processor.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//! Program state processor
2+
3+
use solana_program::{
4+
account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
5+
pubkey::Pubkey,
6+
};
7+
use std::str::from_utf8;
8+
9+
/// Instruction processor
10+
pub fn process_instruction(
11+
_program_id: &Pubkey,
12+
accounts: &[AccountInfo],
13+
input: &[u8],
14+
) -> ProgramResult {
15+
let account_info_iter = &mut accounts.iter();
16+
let mut missing_required_signature = false;
17+
for account_info in account_info_iter {
18+
if let Some(address) = account_info.signer_key() {
19+
msg!("Signed by {:?}", address);
20+
} else {
21+
missing_required_signature = true;
22+
}
23+
}
24+
if missing_required_signature {
25+
return Err(ProgramError::MissingRequiredSignature);
26+
}
27+
28+
let memo = from_utf8(input).map_err(|err| {
29+
msg!("Invalid UTF-8, from byte {}", err.valid_up_to());
30+
ProgramError::InvalidInstructionData
31+
})?;
32+
msg!("Memo (len {}): {:?}", memo.len(), memo);
33+
34+
Ok(())
35+
}
36+
37+
#[cfg(test)]
38+
mod tests {
39+
use super::*;
40+
use solana_program::{
41+
account_info::IntoAccountInfo, program_error::ProgramError, pubkey::Pubkey,
42+
};
43+
use solana_sdk::account::Account;
44+
45+
#[test]
46+
fn test_utf8_memo() {
47+
let program_id = Pubkey::new(&[0; 32]);
48+
49+
let string = b"letters and such";
50+
assert_eq!(Ok(()), process_instruction(&program_id, &[], string));
51+
52+
let emoji = "🐆".as_bytes();
53+
let bytes = [0xF0, 0x9F, 0x90, 0x86];
54+
assert_eq!(emoji, bytes);
55+
assert_eq!(Ok(()), process_instruction(&program_id, &[], &emoji));
56+
57+
let mut bad_utf8 = bytes;
58+
bad_utf8[3] = 0xFF; // Invalid UTF-8 byte
59+
assert_eq!(
60+
Err(ProgramError::InvalidInstructionData),
61+
process_instruction(&program_id, &[], &bad_utf8)
62+
);
63+
}
64+
65+
#[test]
66+
fn test_signers() {
67+
let program_id = Pubkey::new(&[0; 32]);
68+
let memo = "🐆".as_bytes();
69+
70+
let pubkey0 = Pubkey::new_unique();
71+
let pubkey1 = Pubkey::new_unique();
72+
let pubkey2 = Pubkey::new_unique();
73+
let mut account0 = Account::default();
74+
let mut account1 = Account::default();
75+
let mut account2 = Account::default();
76+
77+
let signed_account_infos = vec![
78+
(&pubkey0, true, &mut account0).into_account_info(),
79+
(&pubkey1, true, &mut account1).into_account_info(),
80+
(&pubkey2, true, &mut account2).into_account_info(),
81+
];
82+
assert_eq!(
83+
Ok(()),
84+
process_instruction(&program_id, &signed_account_infos, memo)
85+
);
86+
87+
assert_eq!(Ok(()), process_instruction(&program_id, &[], memo));
88+
89+
let unsigned_account_infos = vec![
90+
(&pubkey0, false, &mut account0).into_account_info(),
91+
(&pubkey1, false, &mut account1).into_account_info(),
92+
(&pubkey2, false, &mut account2).into_account_info(),
93+
];
94+
assert_eq!(
95+
Err(ProgramError::MissingRequiredSignature),
96+
process_instruction(&program_id, &unsigned_account_infos, memo)
97+
);
98+
99+
let partially_signed_account_infos = vec![
100+
(&pubkey0, true, &mut account0).into_account_info(),
101+
(&pubkey1, false, &mut account1).into_account_info(),
102+
(&pubkey2, true, &mut account2).into_account_info(),
103+
];
104+
assert_eq!(
105+
Err(ProgramError::MissingRequiredSignature),
106+
process_instruction(&program_id, &partially_signed_account_infos, memo)
107+
);
108+
}
109+
}

0 commit comments

Comments
 (0)