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
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,20 @@ Since this is just doing a syscall, all the languages behave the same. The only
difference is that the Assembly version *doesn't* set the return code to 0, and
lets the VM assume it worked.

* Transfer-Lamports: moves 5 lamports from a source account to a destination
* Transfer-Lamports: moves lamports from a source account to a destination, with
the amount given by a little-endian u64 in instruction data.

| Language | CU Usage |
| --- | --- |
| Rust | 464 |
| Zig | 43 |
| C | 103 |
| Assembly | 22 |
| Rust (pinocchio) | 23 |
| Rust | 459 |
| Zig | 44 |
| C | 104 |
| Assembly | 31 |
| Rust (pinocchio) | 32 |

This one starts to get interesting since it requires parsing the instruction
input. Since the assembly version knows exactly where to find everything, it can
be hyper-optimized. The C version is also very performant.

Zig's version should perform the same as C, but there are some inefficiencies that
are currently being fixed.
be hyper-optimized.

* CPI: allocates a PDA given by the seed "You pass butter" and a bump seed in
the instruction data. This requires a call to `create_program_address` to check
Expand Down
25 changes: 20 additions & 5 deletions transfer-lamports/asm/main.s
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,38 @@
entrypoint:
ldxdw r2, [r1 + 0] # get number of accounts
jne r2, 2, error # error if not 2 accounts

ldxb r2, [r1 + 8] # get first account
jne r2, 0xff, error # shouldn't be a duplicate, but check
# can check this, but isn't necessary
# jne r2, 0xff, error
ldxdw r2, [r1 + 8 + 8 + 32 + 32] # get source lamports
ldxdw r3, [r1 + 8 + 8 + 32 + 32 + 8] # get account data size
mov64 r4, r1
add64 r4, 8 + 8 + 32 + 32 + 8 + 8 + 10240 + 8 # calculate end of account data
add64 r4, r3
mov64 r5, r4 # check how much padding we need to add
and64 r5, 7 # clear high bits
jeq r5, 0, 1 # no low bits set, jump ahead
and64 r5, -8 # clear low bits
jeq r5, r4, 1 # no low bits set, jump ahead
add64 r4, 8 # add 8 for truncation if needed
and64 r4, -8 # clear low bits

ldxb r5, [r4 + 0] # get second account
jne r5, 0xff, error # we don't allow duplicates
ldxdw r5, [r4 + 8 + 32 + 32] # get destination lamports
sub64 r2, 5 # subtract lamports
add64 r5, 5 # add lamports
ldxdw r6, [r4 + 8 + 32 + 32 + 8] # get account data size
mov64 r7, r4
add64 r7, 8 + 32 + 32 + 8 + 8 + 10240 + 8 # calculate end of account data
add64 r7, r6
mov64 r8, r7 # check how much padding we need to add
and64 r8, -8 # clear low bits
jeq r8, r7, 1 # no low bits set, jump ahead
add64 r7, 8 # add 8 for truncation if low bits are set
ldxdw r8, [r7 + 0] # get instruction data size
jne r8, 0x08, error # need 8 bytes of instruction data
ldxdw r8, [r7 + 8] # get instruction data as little-endian u64

sub64 r2, r8 # subtract lamports
add64 r5, r8 # add lamports
stxdw [r1 + 8 + 8 + 32 + 32], r2 # write the new values back
stxdw [r4 + 8 + 32 + 32], r5
exit
Expand Down
5 changes: 3 additions & 2 deletions transfer-lamports/c/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ extern uint64_t entrypoint(const uint8_t *input) {
return ERROR_INVALID_ARGUMENT;
}

uint64_t transfer_amount = *(uint64_t *) params.data;
SolAccountInfo source_account = params.ka[0];
SolAccountInfo destination_account = params.ka[1];
*source_account.lamports -= 5;
*destination_account.lamports += 5;
*source_account.lamports -= transfer_amount;
*destination_account.lamports += transfer_amount;

return 0;
}
6 changes: 4 additions & 2 deletions transfer-lamports/pinocchio/src/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ fn process_instruction(mut context: InstructionContext) -> ProgramResult {
// accounts are different, so we can safely ignore the case when the account is
// duplicated.
if let MaybeAccount::Account(destination_info) = context.next_account_unchecked() {
let (instruction_data, _) = context.instruction_data_unchecked();
let transfer_amount = u64::from_le_bytes(instruction_data.try_into().unwrap());
// withdraw five lamports
*source_info.borrow_mut_lamports_unchecked() -= 5;
*source_info.borrow_mut_lamports_unchecked() -= transfer_amount;
// deposit five lamports
*destination_info.borrow_mut_lamports_unchecked() += 5;
*destination_info.borrow_mut_lamports_unchecked() += transfer_amount;
}
}

Expand Down
7 changes: 4 additions & 3 deletions transfer-lamports/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,21 @@ use solana_program::{
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
instruction_data: &[u8],
) -> ProgramResult {
// Create an iterator to safely reference accounts in the slice
let account_info_iter = &mut accounts.iter();
let transfer_amount = u64::from_le_bytes(instruction_data.try_into().unwrap());

// As part of the program specification the first account is the source
// account and the second is the destination account
let source_info = next_account_info(account_info_iter)?;
let destination_info = next_account_info(account_info_iter)?;

// Withdraw five lamports from the source
**source_info.try_borrow_mut_lamports()? -= 5;
**source_info.try_borrow_mut_lamports()? -= transfer_amount;
// Deposit five lamports into the destination
**destination_info.try_borrow_mut_lamports()? += 5;
**destination_info.try_borrow_mut_lamports()? += transfer_amount;

Ok(())
}
4 changes: 2 additions & 2 deletions transfer-lamports/tests/functional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async fn test_lamport_transfer() {
None,
);

let source_lamports = 5;
let source_lamports = 555_555;
let destination_lamports = 890_875;
program_test.add_account(
source_pubkey,
Expand All @@ -43,7 +43,7 @@ async fn test_lamport_transfer() {
let mut transaction = Transaction::new_with_payer(
&[Instruction::new_with_bincode(
program_id,
&(),
&source_lamports.to_le_bytes(),
vec![
AccountMeta::new(source_pubkey, false),
AccountMeta::new(destination_pubkey, false),
Expand Down
6 changes: 4 additions & 2 deletions transfer-lamports/zig/main.zig
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const std = @import("std");
const sol = @import("solana-program-sdk");

export fn entrypoint(input: [*]u8) u64 {
const context = sol.Context.load(input) catch return 1;
const source = context.accounts[0];
const destination = context.accounts[1];
source.lamports().* -= 5;
destination.lamports().* += 5;
const transfer_amount = std.mem.bytesToValue(u64, context.data[0..@sizeOf(u64)]);
source.lamports().* -= transfer_amount;
destination.lamports().* += transfer_amount;
return 0;
}