diff --git a/README.md b/README.md index 4097c46..bd2e4a3 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/transfer-lamports/asm/main.s b/transfer-lamports/asm/main.s index 793d2ee..cdba678 100644 --- a/transfer-lamports/asm/main.s +++ b/transfer-lamports/asm/main.s @@ -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 diff --git a/transfer-lamports/c/src/main.c b/transfer-lamports/c/src/main.c index 0800b9c..f87d7be 100644 --- a/transfer-lamports/c/src/main.c +++ b/transfer-lamports/c/src/main.c @@ -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; } diff --git a/transfer-lamports/pinocchio/src/entrypoint.rs b/transfer-lamports/pinocchio/src/entrypoint.rs index 91b20cf..660296d 100644 --- a/transfer-lamports/pinocchio/src/entrypoint.rs +++ b/transfer-lamports/pinocchio/src/entrypoint.rs @@ -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; } } diff --git a/transfer-lamports/src/processor.rs b/transfer-lamports/src/processor.rs index 5c8bb94..8c9e8f3 100644 --- a/transfer-lamports/src/processor.rs +++ b/transfer-lamports/src/processor.rs @@ -11,10 +11,11 @@ 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 @@ -22,9 +23,9 @@ pub fn process_instruction( 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(()) } diff --git a/transfer-lamports/tests/functional.rs b/transfer-lamports/tests/functional.rs index a090436..ed2e947 100644 --- a/transfer-lamports/tests/functional.rs +++ b/transfer-lamports/tests/functional.rs @@ -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, @@ -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), diff --git a/transfer-lamports/zig/main.zig b/transfer-lamports/zig/main.zig index 95a465a..6903a42 100644 --- a/transfer-lamports/zig/main.zig +++ b/transfer-lamports/zig/main.zig @@ -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; }