mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-25 03:11:21 +00:00
refactor!(artifacts): keep lee and lez artifacts separated
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "test_programs"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
lee.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
risc0-build.workspace = true
|
||||
|
||||
[package.metadata.risc0]
|
||||
methods = ["guest"]
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
risc0_build::embed_methods();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "test_program_guests"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
lee_core.workspace = true
|
||||
authenticated_transfer_core.workspace = true
|
||||
clock_core.workspace = true
|
||||
faucet_core.workspace = true
|
||||
|
||||
risc0-zkvm.workspace = true
|
||||
@@ -0,0 +1,71 @@
|
||||
use authenticated_transfer_core::Instruction as AuthTransferInstruction;
|
||||
use lee_core::program::{
|
||||
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs,
|
||||
};
|
||||
use risc0_zkvm::serde::to_vec;
|
||||
|
||||
type Instruction = (u128, ProgramId, u32, Option<PdaSeed>);
|
||||
|
||||
/// A program that calls another program `num_chain_calls` times.
|
||||
/// It permutes the order of the input accounts on the subsequent call
|
||||
/// The `ProgramId` in the instruction must be the `program_id` of the authenticated transfers
|
||||
/// program.
|
||||
fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
pre_states,
|
||||
instruction: (balance, auth_transfer_id, num_chain_calls, pda_seed),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_lee_inputs::<Instruction>();
|
||||
|
||||
let Ok([recipient_pre, sender_pre]) = <[_; 2]>::try_from(pre_states) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let instruction_data = to_vec(&AuthTransferInstruction::Transfer { amount: balance }).unwrap();
|
||||
|
||||
let mut running_recipient_pre = recipient_pre.clone();
|
||||
let mut running_sender_pre = sender_pre.clone();
|
||||
|
||||
if pda_seed.is_some() {
|
||||
running_sender_pre.is_authorized = true;
|
||||
}
|
||||
|
||||
let mut chained_calls = Vec::new();
|
||||
for _i in 0..num_chain_calls {
|
||||
let new_chained_call = ChainedCall {
|
||||
program_id: auth_transfer_id,
|
||||
instruction_data: instruction_data.clone(),
|
||||
pre_states: vec![running_sender_pre.clone(), running_recipient_pre.clone()], /* <- Account order permutation here */
|
||||
pda_seeds: pda_seed.iter().copied().collect(),
|
||||
};
|
||||
chained_calls.push(new_chained_call);
|
||||
|
||||
running_sender_pre.account.balance =
|
||||
match running_sender_pre.account.balance.checked_sub(balance) {
|
||||
Some(new_balance) => new_balance,
|
||||
None => return,
|
||||
};
|
||||
running_recipient_pre.account.balance =
|
||||
match running_recipient_pre.account.balance.checked_add(balance) {
|
||||
Some(new_balance) => new_balance,
|
||||
None => return,
|
||||
};
|
||||
}
|
||||
|
||||
ProgramOutput::new(
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
instruction_words,
|
||||
vec![sender_pre.clone(), recipient_pre.clone()],
|
||||
vec![
|
||||
AccountPostState::new(sender_pre.account),
|
||||
AccountPostState::new(recipient_pre.account),
|
||||
],
|
||||
)
|
||||
.with_chained_calls(chained_calls)
|
||||
.write();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use lee_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs};
|
||||
|
||||
type Instruction = ();
|
||||
|
||||
fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
pre_states,
|
||||
instruction: (),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_lee_inputs::<Instruction>();
|
||||
|
||||
let Ok([pre]) = <[_; 1]>::try_from(pre_states) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let account_post = AccountPostState::new_claimed(pre.account.clone(), Claim::Authorized);
|
||||
|
||||
ProgramOutput::new(
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
instruction_words,
|
||||
vec![pre],
|
||||
vec![account_post],
|
||||
)
|
||||
.write();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use lee_core::{
|
||||
Timestamp,
|
||||
program::{
|
||||
AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs,
|
||||
},
|
||||
};
|
||||
use risc0_zkvm::serde::to_vec;
|
||||
|
||||
type Instruction = (ProgramId, Timestamp); // (clock_program_id, timestamp)
|
||||
|
||||
/// A program that chain-calls the clock program with the clock accounts it received as pre-states.
|
||||
/// Used in tests to verify that user transactions cannot modify clock accounts, even indirectly
|
||||
/// via chain calls.
|
||||
fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
pre_states,
|
||||
instruction: (clock_program_id, timestamp),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_lee_inputs::<Instruction>();
|
||||
|
||||
let post_states: Vec<_> = pre_states
|
||||
.iter()
|
||||
.map(|pre| AccountPostState::new(pre.account.clone()))
|
||||
.collect();
|
||||
|
||||
let chained_call = ChainedCall {
|
||||
program_id: clock_program_id,
|
||||
instruction_data: to_vec(×tamp).unwrap(),
|
||||
pre_states: pre_states.clone(),
|
||||
pda_seeds: vec![],
|
||||
};
|
||||
|
||||
ProgramOutput::new(
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
instruction_words,
|
||||
pre_states,
|
||||
post_states,
|
||||
)
|
||||
.with_chained_calls(vec![chained_call])
|
||||
.write();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use lee_core::{
|
||||
account::AccountId,
|
||||
program::{
|
||||
AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs,
|
||||
},
|
||||
};
|
||||
use risc0_zkvm::serde::to_vec;
|
||||
|
||||
type Instruction = (ProgramId, ProgramId, AccountId, u128);
|
||||
// (faucet_program_id, vault_program_id, recipient_id, amount)
|
||||
|
||||
fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
pre_states,
|
||||
instruction: (faucet_program_id, vault_program_id, recipient_id, amount),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_lee_inputs::<Instruction>();
|
||||
|
||||
let post_states: Vec<_> = pre_states
|
||||
.iter()
|
||||
.map(|pre| AccountPostState::new(pre.account.clone()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(pre_states.len(), 2);
|
||||
let [faucet_pre, vault_pda_pre] = [pre_states[0].clone(), pre_states[1].clone()];
|
||||
|
||||
let chained_calls = vec![ChainedCall {
|
||||
program_id: faucet_program_id,
|
||||
instruction_data: to_vec(&faucet_core::Instruction::GenesisTransferVault {
|
||||
vault_program_id,
|
||||
recipient_id,
|
||||
amount,
|
||||
})
|
||||
.unwrap(),
|
||||
pre_states: vec![faucet_pre, vault_pda_pre],
|
||||
pda_seeds: vec![],
|
||||
}];
|
||||
|
||||
ProgramOutput::new(
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
instruction_words,
|
||||
pre_states,
|
||||
post_states,
|
||||
)
|
||||
.with_chained_calls(chained_calls)
|
||||
.write();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use lee_core::program::{
|
||||
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs,
|
||||
};
|
||||
use risc0_zkvm::serde::to_vec;
|
||||
|
||||
/// Proxy for spending from a private PDA via `auth_transfer`.
|
||||
///
|
||||
/// `pre_states = [pda (authorized), recipient]`. Debits the PDA and credits the recipient.
|
||||
/// The PDA-to-npk binding is established via `pda_seeds` in the chained call to `auth_transfer`.
|
||||
type Instruction = (PdaSeed, u128, ProgramId);
|
||||
|
||||
fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
pre_states,
|
||||
instruction: (seed, amount, auth_transfer_id),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_lee_inputs::<Instruction>();
|
||||
|
||||
let Ok([first, second]) = <[_; 2]>::try_from(pre_states) else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert!(first.is_authorized, "first pre_state must be authorized");
|
||||
|
||||
let first_post = AccountPostState::new(first.account.clone());
|
||||
let second_post = AccountPostState::new(second.account.clone());
|
||||
|
||||
let chained_call = ChainedCall {
|
||||
program_id: auth_transfer_id,
|
||||
instruction_data: to_vec(&authenticated_transfer_core::Instruction::Transfer { amount })
|
||||
.unwrap(),
|
||||
pre_states: vec![first.clone(), second.clone()],
|
||||
pda_seeds: vec![seed],
|
||||
};
|
||||
|
||||
ProgramOutput::new(
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
instruction_words,
|
||||
vec![first, second],
|
||||
vec![first_post, second_post],
|
||||
)
|
||||
.with_chained_calls(vec![chained_call])
|
||||
.write();
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
//! Cooldown-based pinata program.
|
||||
//!
|
||||
//! A Piñata program that uses the on-chain clock to prevent abuse.
|
||||
//! After each prize claim the program records the current timestamp; the next claim is only
|
||||
//! allowed once a configurable cooldown period has elapsed.
|
||||
//!
|
||||
//! Expected pre-states (in order):
|
||||
//! 0 - pinata account (authorized, owned by this program)
|
||||
//! 1 - winner account
|
||||
//! 2 - clock account `CLOCK_01`.
|
||||
//!
|
||||
//! Pinata account data layout (24 bytes):
|
||||
//! [prize: u64 LE | `cooldown_ms`: u64 LE | `last_claim_timestamp`: u64 LE].
|
||||
|
||||
use clock_core::{CLOCK_01_PROGRAM_ACCOUNT_ID, ClockAccountData};
|
||||
use lee_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs};
|
||||
|
||||
type Instruction = ();
|
||||
|
||||
struct PinataState {
|
||||
prize: u128,
|
||||
cooldown_ms: u64,
|
||||
last_claim_timestamp: u64,
|
||||
}
|
||||
|
||||
impl PinataState {
|
||||
fn from_bytes(bytes: &[u8]) -> Self {
|
||||
assert!(bytes.len() >= 32, "Pinata account data too short");
|
||||
let prize = u128::from_le_bytes(bytes[..16].try_into().unwrap());
|
||||
let cooldown_ms = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
|
||||
let last_claim_timestamp = u64::from_le_bytes(bytes[24..32].try_into().unwrap());
|
||||
Self {
|
||||
prize,
|
||||
cooldown_ms,
|
||||
last_claim_timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut buf = Vec::with_capacity(32);
|
||||
buf.extend_from_slice(&self.prize.to_le_bytes());
|
||||
buf.extend_from_slice(&self.cooldown_ms.to_le_bytes());
|
||||
buf.extend_from_slice(&self.last_claim_timestamp.to_le_bytes());
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
pre_states,
|
||||
instruction: (),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_lee_inputs::<Instruction>();
|
||||
|
||||
let Ok([pinata, winner, clock_pre]) = <[_; 3]>::try_from(pre_states) else {
|
||||
panic!("Expected exactly 3 input accounts: pinata, winner, clock");
|
||||
};
|
||||
|
||||
// Check the clock account is the system clock account
|
||||
assert_eq!(clock_pre.account_id, CLOCK_01_PROGRAM_ACCOUNT_ID);
|
||||
|
||||
let clock_data = ClockAccountData::from_bytes(&clock_pre.account.data.clone().into_inner());
|
||||
let current_timestamp = clock_data.timestamp;
|
||||
|
||||
let pinata_state = PinataState::from_bytes(&pinata.account.data.clone().into_inner());
|
||||
|
||||
// Enforce cooldown: the elapsed time since the last claim must exceed the cooldown period.
|
||||
let elapsed = current_timestamp.saturating_sub(pinata_state.last_claim_timestamp);
|
||||
assert!(
|
||||
elapsed >= pinata_state.cooldown_ms,
|
||||
"Cooldown not elapsed: {elapsed}ms since last claim, need {}ms",
|
||||
pinata_state.cooldown_ms,
|
||||
);
|
||||
|
||||
let mut pinata_post = pinata.account.clone();
|
||||
let mut winner_post = winner.account.clone();
|
||||
|
||||
pinata_post.balance = pinata_post
|
||||
.balance
|
||||
.checked_sub(pinata_state.prize)
|
||||
.expect("Not enough balance in the pinata");
|
||||
winner_post.balance = winner_post
|
||||
.balance
|
||||
.checked_add(pinata_state.prize)
|
||||
.expect("Overflow when adding prize to winner");
|
||||
|
||||
// Update the last claim timestamp.
|
||||
let updated_state = PinataState {
|
||||
last_claim_timestamp: current_timestamp,
|
||||
..pinata_state
|
||||
};
|
||||
pinata_post.data = updated_state
|
||||
.to_bytes()
|
||||
.try_into()
|
||||
.expect("Pinata state should fit in account data");
|
||||
|
||||
// Clock account is read-only.
|
||||
let clock_post = clock_pre.account.clone();
|
||||
|
||||
ProgramOutput::new(
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
instruction_words,
|
||||
vec![pinata, winner, clock_pre],
|
||||
vec![
|
||||
AccountPostState::new_claimed_if_default(pinata_post, Claim::Authorized),
|
||||
AccountPostState::new(winner_post),
|
||||
AccountPostState::new(clock_post),
|
||||
],
|
||||
)
|
||||
.write();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//! Time-locked transfer program.
|
||||
//!
|
||||
//! Demonstrates how a program can include a clock account among its inputs and use the on-chain
|
||||
//! timestamp in its logic. The transfer only executes when the clock timestamp is at or past a
|
||||
//! caller-supplied deadline; otherwise the program panics.
|
||||
//!
|
||||
//! Expected pre-states (in order):
|
||||
//! 0 - sender account (authorized)
|
||||
//! 1 - receiver account
|
||||
//! 2 - clock account (read-only, e.g. `CLOCK_01`).
|
||||
|
||||
use clock_core::{CLOCK_01_PROGRAM_ACCOUNT_ID, ClockAccountData};
|
||||
use lee_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_lee_inputs};
|
||||
|
||||
/// (`amount`, `deadline_timestamp`).
|
||||
type Instruction = (u128, u64);
|
||||
|
||||
fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
pre_states,
|
||||
instruction: (amount, deadline),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_lee_inputs::<Instruction>();
|
||||
|
||||
let Ok([sender_pre, receiver_pre, clock_pre]) = <[_; 3]>::try_from(pre_states) else {
|
||||
panic!("Expected exactly 3 input accounts: sender, receiver, clock");
|
||||
};
|
||||
|
||||
// Check the clock account is the system clock account
|
||||
assert_eq!(clock_pre.account_id, CLOCK_01_PROGRAM_ACCOUNT_ID);
|
||||
|
||||
// Read the current timestamp from the clock account.
|
||||
let clock_data = ClockAccountData::from_bytes(&clock_pre.account.data.clone().into_inner());
|
||||
|
||||
assert!(
|
||||
clock_data.timestamp >= deadline,
|
||||
"Transfer is time-locked until timestamp {deadline}, current is {}",
|
||||
clock_data.timestamp,
|
||||
);
|
||||
|
||||
let mut sender_post = sender_pre.account.clone();
|
||||
let mut receiver_post = receiver_pre.account.clone();
|
||||
|
||||
sender_post.balance = sender_post
|
||||
.balance
|
||||
.checked_sub(amount)
|
||||
.expect("Insufficient balance");
|
||||
receiver_post.balance = receiver_post
|
||||
.balance
|
||||
.checked_add(amount)
|
||||
.expect("Balance overflow");
|
||||
|
||||
// Clock account is read-only: post state equals pre state.
|
||||
let clock_post = clock_pre.account.clone();
|
||||
|
||||
ProgramOutput::new(
|
||||
self_program_id,
|
||||
caller_program_id,
|
||||
instruction_words,
|
||||
vec![sender_pre, receiver_pre, clock_pre],
|
||||
vec![
|
||||
AccountPostState::new(sender_post),
|
||||
AccountPostState::new(receiver_post),
|
||||
AccountPostState::new(clock_post),
|
||||
],
|
||||
)
|
||||
.write();
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#![expect(
|
||||
clippy::no_effect_underscore_binding,
|
||||
reason = "This way we can remove warnings about unused path constants"
|
||||
)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use lee::program::Program;
|
||||
|
||||
mod guests {
|
||||
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn chain_caller() -> Program {
|
||||
use guests::{CHAIN_CALLER_ELF, CHAIN_CALLER_ID, CHAIN_CALLER_PATH};
|
||||
|
||||
let _unused = CHAIN_CALLER_PATH;
|
||||
|
||||
Program::new_unchecked(CHAIN_CALLER_ID, Cow::Borrowed(CHAIN_CALLER_ELF))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn claimer() -> Program {
|
||||
use guests::{CLAIMER_ELF, CLAIMER_ID, CLAIMER_PATH};
|
||||
|
||||
let _unused = CLAIMER_PATH;
|
||||
|
||||
Program::new_unchecked(CLAIMER_ID, Cow::Borrowed(CLAIMER_ELF))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn pda_spend_proxy() -> Program {
|
||||
use guests::{PDA_SPEND_PROXY_ELF, PDA_SPEND_PROXY_ID, PDA_SPEND_PROXY_PATH};
|
||||
|
||||
let _unused = PDA_SPEND_PROXY_PATH;
|
||||
|
||||
Program::new_unchecked(PDA_SPEND_PROXY_ID, Cow::Borrowed(PDA_SPEND_PROXY_ELF))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn time_locked_transfer() -> Program {
|
||||
use guests::{TIME_LOCKED_TRANSFER_ELF, TIME_LOCKED_TRANSFER_ID, TIME_LOCKED_TRANSFER_PATH};
|
||||
|
||||
let _unused = TIME_LOCKED_TRANSFER_PATH;
|
||||
|
||||
Program::new_unchecked(
|
||||
TIME_LOCKED_TRANSFER_ID,
|
||||
Cow::Borrowed(TIME_LOCKED_TRANSFER_ELF),
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn pinata_cooldown() -> Program {
|
||||
use guests::{PINATA_COOLDOWN_ELF, PINATA_COOLDOWN_ID, PINATA_COOLDOWN_PATH};
|
||||
|
||||
let _unused = PINATA_COOLDOWN_PATH;
|
||||
|
||||
Program::new_unchecked(PINATA_COOLDOWN_ID, Cow::Borrowed(PINATA_COOLDOWN_ELF))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn faucet_chain_caller() -> Program {
|
||||
use guests::{FAUCET_CHAIN_CALLER_ELF, FAUCET_CHAIN_CALLER_ID, FAUCET_CHAIN_CALLER_PATH};
|
||||
|
||||
let _unused = FAUCET_CHAIN_CALLER_PATH;
|
||||
|
||||
Program::new_unchecked(
|
||||
FAUCET_CHAIN_CALLER_ID,
|
||||
Cow::Borrowed(FAUCET_CHAIN_CALLER_ELF),
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn clock_chain_caller() -> Program {
|
||||
use guests::{CLOCK_CHAIN_CALLER_ELF, CLOCK_CHAIN_CALLER_ID, CLOCK_CHAIN_CALLER_PATH};
|
||||
|
||||
let _unused = CLOCK_CHAIN_CALLER_PATH;
|
||||
|
||||
Program::new_unchecked(CLOCK_CHAIN_CALLER_ID, Cow::Borrowed(CLOCK_CHAIN_CALLER_ELF))
|
||||
}
|
||||
Reference in New Issue
Block a user