Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: fb-solana-${{ matrix.name }}
path: release/fb-solana-${{ steps.tag.outputs._tag }}-${{ matrix.name }}.*
path: release/fb-solana-${{ steps.tag.outputs._tag }}-${{ matrix.name }}.tar.gz
- name: Upload artifact spl-token
uses: actions/upload-artifact@v4
with:
name: fb-spl-token-${{ matrix.name }}
path: release/fb-spl-token-${{ steps.tag.outputs._tag }}-${{ matrix.name }}.*
path: release/fb-spl-token-${{ steps.tag.outputs._tag }}-${{ matrix.name }}.tar.gz
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v1
with:
Expand Down
115 changes: 115 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli-tests/keys.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
STAKE="CTjxRPQ6D3pkkkYU3VwLDv38r4vgCiVTWrC4A731QraS"
TOKEN22=UkQiTvTQ16q9PHAt2oWUWSsSrZnNN24v4whTWqapzMs
NONCE=H6BUq6pXsLi1D9sxBHFA2j9U4Nrh6LiPyShVBgZnKoTh
PROGRAM=JE4YApSuex77V3sfEL913uATBD3B65bBgPA6itfkrRun
3 changes: 3 additions & 0 deletions cli-tests/program.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
source ./cli-tests/keys.sh
cargo run -p fireblocks-solana-cli -- --config "${1:?}" program deploy --verbose --program-id ./cli-tests/program-memo.json
27 changes: 27 additions & 0 deletions crates/memo-pinocchio/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "p-memo"
version = "0.0.0"
description = "A pinocchio-based Memo program"
repository = "https://github.com/febo/p-memo"
license = "Apache-2.0"
edition = "2021"
readme = "./README.md"
publish = false

[lib]
crate-type = ["cdylib"]

[package.metadata.solana]
program-id = "PMemo11111111111111111111111111111111111111"

[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = ['cfg(target_os, values("solana"))']

[dependencies]
pinocchio = "0.9"
pinocchio-log = "0.5"

[dev-dependencies]
mollusk-svm = "0.7"
solana-sdk = { workspace = true }
55 changes: 55 additions & 0 deletions crates/memo-pinocchio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# `p-memo`

A `pinocchio`-based Memo program.

## Overview

`p-memo` is a reimplementation of the SPL Memo program using [`pinocchio`](https://github.com/anza-xyz/pinocchio). The program uses at most `~5%` of the compute units used by the current Memo program when signers are present; even when there are no signers, it needs only `~20%` of the current Memo program compute units. This efficiency is achieved by a combination of:
1. `pinocchio` "lazy" entrypoint
2. `sol_log_pubkey` syscall to log pubkey values
3. [`pinocchio-log`](https://crates.io/crates/pinocchio-log) to format the memo message

Since it uses the syscall to log pubkeys, the output of the program is slightly different while loging the same information:
```
1: Program PMemo11111111111111111111111111111111111111 invoke [1]
2: Program log: Signed by:
3: Program log: 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM
4: Program log: Memo (len 60):
5: Program log: why does spl memo use 36000 cus to print len 60 msg of ascii
6: Program PMemo11111111111111111111111111111111111111 consumed 537 of 1400000 compute units
7: Program PMemo11111111111111111111111111111111111111 success
```

Logging begins with entry into the program (`line 1`). Then there is a separate log to start the signers section (`line 2`); this is only present if there are signer accounts. After that there will be one line for each signer account (`line 3`), followed by the memo length and UTF-8 text (`line 4-5`). The program ends with the status of the instruction (`lines 6-7`).

## Performance

CU comsumption:

| \# signers | p-memo | SPL Memo |
| ---------- | ----------- | --------- |
| 0 | 313 (`15%`) | 2,022 |
| 1 | 537 (`4%`) | 13,525 |
| 2 | 654 (`3%`) | 25,111 |
| 3 | 771 (`2%`) | 36,406 |

> [!NOTE]
> Using Solana CLI `v2.2.15`.

## Building

To build the program from its directory:
```bash
cargo build-sbf
```

## Testing

To run the tests (after building the program):
```bash
SBF_OUT_DIR=../target/deploy cargo test
```

## License

The code is licensed under the [Apache License Version 2.0](LICENSE)
54 changes: 54 additions & 0 deletions crates/memo-pinocchio/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use pinocchio::{
entrypoint::{InstructionContext, MaybeAccount},
program_error::ProgramError,
syscalls::{sol_log_, sol_log_pubkey},
ProgramResult,
};
use pinocchio_log::log;

/// Process a memo instruction.
///
/// This function processes a memo instruction by logging the public keys of the signers
/// (if any) and the memo data itself, checking if the required signatures are present;
/// when any required signature is missing, it returns `ProgramError::MissingRequiredSignature`
/// error.
pub fn process_instruction(mut context: InstructionContext) -> ProgramResult {
let mut missing_required_signature = false;

// Validates signer accounts (if any).

if context.remaining() > 0 {
// Logs a message indicating that there are signers.
log!("Signed by:");

while context.remaining() > 0 {
// Duplicated accounts are implicitly checked since at least one of the
// "copies" must be a signer.
if let MaybeAccount::Account(account) = context.next_account()? {
if account.is_signer() {
// SAFETY: Use the syscall to log the public key of the account.
unsafe { sol_log_pubkey(account.key().as_ptr()) };
} else {
missing_required_signature = true;
}
}
}

if missing_required_signature {
return Err(ProgramError::MissingRequiredSignature);
}
}

// SAFETY: All accounts have been processed.
let instruction_data = unsafe { context.instruction_data_unchecked() };

// Logs the length of the memo message and its content.

log!("Memo (len {}):", instruction_data.len());
// SAFETY: The syscall will validate the UTF-8 encoding of the memo data.
unsafe {
sol_log_(instruction_data.as_ptr(), instruction_data.len() as u64);
}

Ok(())
}
23 changes: 23 additions & 0 deletions crates/memo-pinocchio/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! A pinocchio-based Memo (aka 'p-memo') program.
//!
//! 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.

#![no_std]

mod entrypoint;

use pinocchio::{lazy_program_entrypoint, no_allocator, nostd_panic_handler};

use crate::entrypoint::process_instruction;

// Process the input lazily.
lazy_program_entrypoint!(process_instruction);
// Disable the memory allocator.
no_allocator!();
// Use a `no_std` panic handler.
nostd_panic_handler!();
Loading
Loading