mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-05 23:03:06 +00:00
Merge pull request #214 from vacp2p/schouhy/fix-claiming-mechanism-on-chained-calls
Fix claiming mechanism on chained calls
This commit is contained in:
commit
09116830c9
Binary file not shown.
@ -20,11 +20,59 @@ pub struct ChainedCall {
|
|||||||
pub pre_states: Vec<AccountWithMetadata>,
|
pub pre_states: Vec<AccountWithMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the final state of an `Account` after a program execution.
|
||||||
|
/// A post state may optionally request that the executing program
|
||||||
|
/// becomes the owner of the account (a “claim”). This is used to signal
|
||||||
|
/// that the program intends to take ownership of the account.
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||||
|
pub struct AccountPostState {
|
||||||
|
account: Account,
|
||||||
|
claim: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountPostState {
|
||||||
|
/// Creates a post state without a claim request.
|
||||||
|
/// The executing program is not requesting ownership of the account.
|
||||||
|
pub fn new(account: Account) -> Self {
|
||||||
|
Self {
|
||||||
|
account,
|
||||||
|
claim: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a post state that requests ownership of the account.
|
||||||
|
/// This indicates that the executing program intends to claim the
|
||||||
|
/// account as its own and is allowed to mutate it.
|
||||||
|
pub fn new_claimed(account: Account) -> Self {
|
||||||
|
Self {
|
||||||
|
account,
|
||||||
|
claim: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this post state requests that the account
|
||||||
|
/// be claimed (owned) by the executing program.
|
||||||
|
pub fn requires_claim(&self) -> bool {
|
||||||
|
self.claim
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the underlying account
|
||||||
|
pub fn account(&self) -> &Account {
|
||||||
|
&self.account
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the underlying account
|
||||||
|
pub fn account_mut(&mut self) -> &mut Account {
|
||||||
|
&mut self.account
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||||
pub struct ProgramOutput {
|
pub struct ProgramOutput {
|
||||||
pub pre_states: Vec<AccountWithMetadata>,
|
pub pre_states: Vec<AccountWithMetadata>,
|
||||||
pub post_states: Vec<Account>,
|
pub post_states: Vec<AccountPostState>,
|
||||||
pub chained_calls: Vec<ChainedCall>,
|
pub chained_calls: Vec<ChainedCall>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +86,10 @@ pub fn read_nssa_inputs<T: DeserializeOwned>() -> ProgramInput<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_nssa_outputs(pre_states: Vec<AccountWithMetadata>, post_states: Vec<Account>) {
|
pub fn write_nssa_outputs(
|
||||||
|
pre_states: Vec<AccountWithMetadata>,
|
||||||
|
post_states: Vec<AccountPostState>,
|
||||||
|
) {
|
||||||
let output = ProgramOutput {
|
let output = ProgramOutput {
|
||||||
pre_states,
|
pre_states,
|
||||||
post_states,
|
post_states,
|
||||||
@ -49,7 +100,7 @@ pub fn write_nssa_outputs(pre_states: Vec<AccountWithMetadata>, post_states: Vec
|
|||||||
|
|
||||||
pub fn write_nssa_outputs_with_chained_call(
|
pub fn write_nssa_outputs_with_chained_call(
|
||||||
pre_states: Vec<AccountWithMetadata>,
|
pre_states: Vec<AccountWithMetadata>,
|
||||||
post_states: Vec<Account>,
|
post_states: Vec<AccountPostState>,
|
||||||
chained_calls: Vec<ChainedCall>,
|
chained_calls: Vec<ChainedCall>,
|
||||||
) {
|
) {
|
||||||
let output = ProgramOutput {
|
let output = ProgramOutput {
|
||||||
@ -68,7 +119,7 @@ pub fn write_nssa_outputs_with_chained_call(
|
|||||||
/// - `executing_program_id`: The identifier of the program that was executed.
|
/// - `executing_program_id`: The identifier of the program that was executed.
|
||||||
pub fn validate_execution(
|
pub fn validate_execution(
|
||||||
pre_states: &[AccountWithMetadata],
|
pre_states: &[AccountWithMetadata],
|
||||||
post_states: &[Account],
|
post_states: &[AccountPostState],
|
||||||
executing_program_id: ProgramId,
|
executing_program_id: ProgramId,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// 1. Lengths must match
|
// 1. Lengths must match
|
||||||
@ -78,25 +129,27 @@ pub fn validate_execution(
|
|||||||
|
|
||||||
for (pre, post) in pre_states.iter().zip(post_states) {
|
for (pre, post) in pre_states.iter().zip(post_states) {
|
||||||
// 2. Nonce must remain unchanged
|
// 2. Nonce must remain unchanged
|
||||||
if pre.account.nonce != post.nonce {
|
if pre.account.nonce != post.account.nonce {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Program ownership changes are not allowed
|
// 3. Program ownership changes are not allowed
|
||||||
if pre.account.program_owner != post.program_owner {
|
if pre.account.program_owner != post.account.program_owner {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let account_program_owner = pre.account.program_owner;
|
let account_program_owner = pre.account.program_owner;
|
||||||
|
|
||||||
// 4. Decreasing balance only allowed if owned by executing program
|
// 4. Decreasing balance only allowed if owned by executing program
|
||||||
if post.balance < pre.account.balance && account_program_owner != executing_program_id {
|
if post.account.balance < pre.account.balance
|
||||||
|
&& account_program_owner != executing_program_id
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Data changes only allowed if owned by executing program or if account pre state has
|
// 5. Data changes only allowed if owned by executing program or if account pre state has
|
||||||
// default values
|
// default values
|
||||||
if pre.account.data != post.data
|
if pre.account.data != post.account.data
|
||||||
&& pre.account != Account::default()
|
&& pre.account != Account::default()
|
||||||
&& account_program_owner != executing_program_id
|
&& account_program_owner != executing_program_id
|
||||||
{
|
{
|
||||||
@ -105,17 +158,67 @@ pub fn validate_execution(
|
|||||||
|
|
||||||
// 6. If a post state has default program owner, the pre state must have been a default
|
// 6. If a post state has default program owner, the pre state must have been a default
|
||||||
// account
|
// account
|
||||||
if post.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() {
|
if post.account.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Total balance is preserved
|
// 7. Total balance is preserved
|
||||||
let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum();
|
let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum();
|
||||||
let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum();
|
let total_balance_post_states: u128 = post_states.iter().map(|post| post.account.balance).sum();
|
||||||
if total_balance_pre_states != total_balance_post_states {
|
if total_balance_pre_states != total_balance_post_states {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_post_state_new_with_claim_constructor() {
|
||||||
|
let account = Account {
|
||||||
|
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
balance: 1337,
|
||||||
|
data: vec![0xde, 0xad, 0xbe, 0xef],
|
||||||
|
nonce: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let account_post_state = AccountPostState::new_claimed(account.clone());
|
||||||
|
|
||||||
|
assert_eq!(account, account_post_state.account);
|
||||||
|
assert!(account_post_state.requires_claim());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_post_state_new_without_claim_constructor() {
|
||||||
|
let account = Account {
|
||||||
|
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
balance: 1337,
|
||||||
|
data: vec![0xde, 0xad, 0xbe, 0xef],
|
||||||
|
nonce: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let account_post_state = AccountPostState::new(account.clone());
|
||||||
|
|
||||||
|
assert_eq!(account, account_post_state.account);
|
||||||
|
assert!(!account_post_state.requires_claim());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_post_state_account_getter() {
|
||||||
|
let mut account = Account {
|
||||||
|
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
balance: 1337,
|
||||||
|
data: vec![0xde, 0xad, 0xbe, 0xef],
|
||||||
|
nonce: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut account_post_state = AccountPostState::new(account.clone());
|
||||||
|
|
||||||
|
assert_eq!(account_post_state.account(), &account);
|
||||||
|
assert_eq!(account_post_state.account_mut(), &mut account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,16 +1,17 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
program::{ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
program::{
|
||||||
|
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initializes a default account under the ownership of this program.
|
/// Initializes a default account under the ownership of this program.
|
||||||
/// This is achieved by a noop.
|
|
||||||
fn initialize_account(pre_state: AccountWithMetadata) {
|
fn initialize_account(pre_state: AccountWithMetadata) {
|
||||||
let account_to_claim = pre_state.account.clone();
|
let account_to_claim = AccountPostState::new_claimed(pre_state.account.clone());
|
||||||
let is_authorized = pre_state.is_authorized;
|
let is_authorized = pre_state.is_authorized;
|
||||||
|
|
||||||
// Continue only if the account to claim has default values
|
// Continue only if the account to claim has default values
|
||||||
if account_to_claim != Account::default() {
|
if account_to_claim.account() != &Account::default() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,10 +37,25 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create accounts post states, with updated balances
|
// Create accounts post states, with updated balances
|
||||||
let mut sender_post = sender.account.clone();
|
let sender_post = {
|
||||||
let mut recipient_post = recipient.account.clone();
|
// Modify sender's balance
|
||||||
sender_post.balance -= balance_to_move;
|
let mut sender_post_account = sender.account.clone();
|
||||||
recipient_post.balance += balance_to_move;
|
sender_post_account.balance -= balance_to_move;
|
||||||
|
AccountPostState::new(sender_post_account)
|
||||||
|
};
|
||||||
|
|
||||||
|
let recipient_post = {
|
||||||
|
// Modify recipient's balance
|
||||||
|
let mut recipient_post_account = recipient.account.clone();
|
||||||
|
recipient_post_account.balance += balance_to_move;
|
||||||
|
|
||||||
|
// Claim recipient account if it has default program owner
|
||||||
|
if recipient_post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||||
|
AccountPostState::new_claimed(recipient_post_account)
|
||||||
|
} else {
|
||||||
|
AccountPostState::new(recipient_post_account)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
write_nssa_outputs(vec![sender, recipient], vec![sender_post, recipient_post]);
|
write_nssa_outputs(vec![sender, recipient], vec![sender_post, recipient_post]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||||
use risc0_zkvm::sha::{Impl, Sha256};
|
use risc0_zkvm::sha::{Impl, Sha256};
|
||||||
|
|
||||||
const PRIZE: u128 = 150;
|
const PRIZE: u128 = 150;
|
||||||
@ -66,5 +66,11 @@ fn main() {
|
|||||||
pinata_post.data = data.next_data().to_vec();
|
pinata_post.data = data.next_data().to_vec();
|
||||||
winner_post.balance += PRIZE;
|
winner_post.balance += PRIZE;
|
||||||
|
|
||||||
write_nssa_outputs(vec![pinata, winner], vec![pinata_post, winner_post]);
|
write_nssa_outputs(
|
||||||
|
vec![pinata, winner],
|
||||||
|
vec![
|
||||||
|
AccountPostState::new(pinata_post),
|
||||||
|
AccountPostState::new(winner_post),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,7 +70,7 @@ fn main() {
|
|||||||
// Public account
|
// Public account
|
||||||
public_pre_states.push(pre_states[i].clone());
|
public_pre_states.push(pre_states[i].clone());
|
||||||
|
|
||||||
let mut post = post_states[i].clone();
|
let mut post = post_states[i].account().clone();
|
||||||
if pre_states[i].is_authorized {
|
if pre_states[i].is_authorized {
|
||||||
post.nonce += 1;
|
post.nonce += 1;
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update post-state with new nonce
|
// Update post-state with new nonce
|
||||||
let mut post_with_updated_values = post_states[i].clone();
|
let mut post_with_updated_values = post_states[i].account().clone();
|
||||||
post_with_updated_values.nonce = *new_nonce;
|
post_with_updated_values.nonce = *new_nonce;
|
||||||
|
|
||||||
if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID {
|
if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountId, AccountWithMetadata, Data},
|
account::{Account, AccountId, AccountWithMetadata, Data},
|
||||||
program::{ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
program::{
|
||||||
|
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// The token program has three functions:
|
// The token program has three functions:
|
||||||
@ -112,7 +114,7 @@ impl TokenHolding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec<Account> {
|
fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec<AccountPostState> {
|
||||||
if pre_states.len() != 2 {
|
if pre_states.len() != 2 {
|
||||||
panic!("Invalid number of input accounts");
|
panic!("Invalid number of input accounts");
|
||||||
}
|
}
|
||||||
@ -148,12 +150,19 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec<Ac
|
|||||||
let sender_post = {
|
let sender_post = {
|
||||||
let mut this = sender.account.clone();
|
let mut this = sender.account.clone();
|
||||||
this.data = sender_holding.into_data();
|
this.data = sender_holding.into_data();
|
||||||
this
|
AccountPostState::new(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
let recipient_post = {
|
let recipient_post = {
|
||||||
let mut this = recipient.account.clone();
|
let mut this = recipient.account.clone();
|
||||||
this.data = recipient_holding.into_data();
|
this.data = recipient_holding.into_data();
|
||||||
this
|
|
||||||
|
// Claim the recipient account if it has default program owner
|
||||||
|
if this.program_owner == DEFAULT_PROGRAM_ID {
|
||||||
|
AccountPostState::new_claimed(this)
|
||||||
|
} else {
|
||||||
|
AccountPostState::new(this)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
vec![sender_post, recipient_post]
|
vec![sender_post, recipient_post]
|
||||||
@ -163,7 +172,7 @@ fn new_definition(
|
|||||||
pre_states: &[AccountWithMetadata],
|
pre_states: &[AccountWithMetadata],
|
||||||
name: [u8; 6],
|
name: [u8; 6],
|
||||||
total_supply: u128,
|
total_supply: u128,
|
||||||
) -> Vec<Account> {
|
) -> Vec<AccountPostState> {
|
||||||
if pre_states.len() != 2 {
|
if pre_states.len() != 2 {
|
||||||
panic!("Invalid number of input accounts");
|
panic!("Invalid number of input accounts");
|
||||||
}
|
}
|
||||||
@ -196,10 +205,13 @@ fn new_definition(
|
|||||||
let mut holding_target_account_post = holding_target_account.account.clone();
|
let mut holding_target_account_post = holding_target_account.account.clone();
|
||||||
holding_target_account_post.data = token_holding.into_data();
|
holding_target_account_post.data = token_holding.into_data();
|
||||||
|
|
||||||
vec![definition_target_account_post, holding_target_account_post]
|
vec![
|
||||||
|
AccountPostState::new_claimed(definition_target_account_post),
|
||||||
|
AccountPostState::new_claimed(holding_target_account_post),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<Account> {
|
fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<AccountPostState> {
|
||||||
if pre_states.len() != 2 {
|
if pre_states.len() != 2 {
|
||||||
panic!("Invalid number of accounts");
|
panic!("Invalid number of accounts");
|
||||||
}
|
}
|
||||||
@ -220,10 +232,13 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<Account> {
|
|||||||
let holding_values = TokenHolding::new(&definition.account_id);
|
let holding_values = TokenHolding::new(&definition.account_id);
|
||||||
|
|
||||||
let definition_post = definition.account.clone();
|
let definition_post = definition.account.clone();
|
||||||
let mut account_to_initialize_post = account_to_initialize.account.clone();
|
let mut account_to_initialize = account_to_initialize.account.clone();
|
||||||
account_to_initialize_post.data = holding_values.into_data();
|
account_to_initialize.data = holding_values.into_data();
|
||||||
|
|
||||||
vec![definition_post, account_to_initialize_post]
|
vec![
|
||||||
|
AccountPostState::new(definition_post),
|
||||||
|
AccountPostState::new_claimed(account_to_initialize),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Instruction = [u8; 23];
|
type Instruction = [u8; 23];
|
||||||
@ -387,14 +402,14 @@ mod tests {
|
|||||||
let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10);
|
let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10);
|
||||||
let [definition_account, holding_account] = post_states.try_into().ok().unwrap();
|
let [definition_account, holding_account] = post_states.try_into().ok().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
definition_account.data,
|
definition_account.account().data,
|
||||||
vec![
|
vec![
|
||||||
0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0
|
0, 0
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
holding_account.data,
|
holding_account.account().data,
|
||||||
vec![
|
vec![
|
||||||
1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||||
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
@ -619,14 +634,14 @@ mod tests {
|
|||||||
let post_states = transfer(&pre_states, 11);
|
let post_states = transfer(&pre_states, 11);
|
||||||
let [sender_post, recipient_post] = post_states.try_into().ok().unwrap();
|
let [sender_post, recipient_post] = post_states.try_into().ok().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sender_post.data,
|
sender_post.account().data,
|
||||||
vec![
|
vec![
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recipient_post.data,
|
recipient_post.account().data,
|
||||||
vec![
|
vec![
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
@ -657,9 +672,9 @@ mod tests {
|
|||||||
];
|
];
|
||||||
let post_states = initialize_account(&pre_states);
|
let post_states = initialize_account(&pre_states);
|
||||||
let [definition, holding] = post_states.try_into().ok().unwrap();
|
let [definition, holding] = post_states.try_into().ok().unwrap();
|
||||||
assert_eq!(definition.data, pre_states[0].account.data);
|
assert_eq!(definition.account().data, pre_states[0].account.data);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
holding.data,
|
holding.account().data,
|
||||||
vec![
|
vec![
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
|||||||
@ -207,6 +207,15 @@ mod tests {
|
|||||||
elf: CHAIN_CALLER_ELF.to_vec(),
|
elf: CHAIN_CALLER_ELF.to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn claimer() -> Self {
|
||||||
|
use test_program_methods::{CLAIMER_ELF, CLAIMER_ID};
|
||||||
|
|
||||||
|
Program {
|
||||||
|
id: CLAIMER_ID,
|
||||||
|
elf: CLAIMER_ELF.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -239,8 +248,8 @@ mod tests {
|
|||||||
|
|
||||||
let [sender_post, recipient_post] = program_output.post_states.try_into().unwrap();
|
let [sender_post, recipient_post] = program_output.post_states.try_into().unwrap();
|
||||||
|
|
||||||
assert_eq!(sender_post, expected_sender_post);
|
assert_eq!(sender_post.account(), &expected_sender_post);
|
||||||
assert_eq!(recipient_post, expected_recipient_post);
|
assert_eq!(recipient_post.account(), &expected_recipient_post);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -153,10 +153,16 @@ impl PublicTransaction {
|
|||||||
return Err(NssaError::InvalidProgramBehavior);
|
return Err(NssaError::InvalidProgramBehavior);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The invoked program claims the accounts with default program id.
|
for post in program_output
|
||||||
for post in program_output.post_states.iter_mut() {
|
.post_states
|
||||||
if post.program_owner == DEFAULT_PROGRAM_ID {
|
.iter_mut()
|
||||||
post.program_owner = chained_call.program_id;
|
.filter(|post| post.requires_claim())
|
||||||
|
{
|
||||||
|
// The invoked program can only claim accounts with default program id.
|
||||||
|
if post.account().program_owner == DEFAULT_PROGRAM_ID {
|
||||||
|
post.account_mut().program_owner = chained_call.program_id;
|
||||||
|
} else {
|
||||||
|
return Err(NssaError::InvalidProgramBehavior);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +172,7 @@ impl PublicTransaction {
|
|||||||
.iter()
|
.iter()
|
||||||
.zip(program_output.post_states.iter())
|
.zip(program_output.post_states.iter())
|
||||||
{
|
{
|
||||||
state_diff.insert(pre.account_id, post.clone());
|
state_diff.insert(pre.account_id, post.account().clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
for new_call in program_output.chained_calls.into_iter().rev() {
|
for new_call in program_output.chained_calls.into_iter().rev() {
|
||||||
|
|||||||
@ -477,6 +477,7 @@ pub mod tests {
|
|||||||
self.insert_program(Program::minter());
|
self.insert_program(Program::minter());
|
||||||
self.insert_program(Program::burner());
|
self.insert_program(Program::burner());
|
||||||
self.insert_program(Program::chain_caller());
|
self.insert_program(Program::chain_caller());
|
||||||
|
self.insert_program(Program::claimer());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2162,4 +2163,82 @@ pub mod tests {
|
|||||||
Err(NssaError::MaxChainedCallsDepthExceeded)
|
Err(NssaError::MaxChainedCallsDepthExceeded)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_claiming_mechanism_within_chain_call() {
|
||||||
|
// This test calls the authenticated transfer program through the chain_caller program.
|
||||||
|
// The transfer is made from an initialized sender to an uninitialized recipient. And
|
||||||
|
// it is expected that the recipient account is claimed by the authenticated transfer
|
||||||
|
// program and not the chained_caller program.
|
||||||
|
let chain_caller = Program::chain_caller();
|
||||||
|
let auth_transfer = Program::authenticated_transfer_program();
|
||||||
|
let key = PrivateKey::try_new([1; 32]).unwrap();
|
||||||
|
let account_id = AccountId::from(&PublicKey::new_from_private_key(&key));
|
||||||
|
let initial_balance = 100;
|
||||||
|
let initial_data = [(account_id, initial_balance)];
|
||||||
|
let mut state =
|
||||||
|
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
|
||||||
|
let from = account_id;
|
||||||
|
let from_key = key;
|
||||||
|
let to = AccountId::new([2; 32]);
|
||||||
|
let amount: u128 = 37;
|
||||||
|
|
||||||
|
// Check the recipient is an uninitialized account
|
||||||
|
assert_eq!(state.get_account_by_id(&to), Account::default());
|
||||||
|
|
||||||
|
let expected_to_post = Account {
|
||||||
|
// The expected program owner is the authenticated transfer program
|
||||||
|
program_owner: auth_transfer.id(),
|
||||||
|
balance: amount,
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// The transaction executes the chain_caller program, which internally calls the
|
||||||
|
// authenticated_transfer program
|
||||||
|
let instruction: (u128, ProgramId, u32) =
|
||||||
|
(amount, Program::authenticated_transfer_program().id(), 1);
|
||||||
|
let message = public_transaction::Message::try_new(
|
||||||
|
chain_caller.id(),
|
||||||
|
vec![to, from], // The chain_caller program permutes the account order in the chain
|
||||||
|
// call
|
||||||
|
vec![0],
|
||||||
|
instruction,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
||||||
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
state.transition_from_public_transaction(&tx).unwrap();
|
||||||
|
|
||||||
|
let from_post = state.get_account_by_id(&from);
|
||||||
|
let to_post = state.get_account_by_id(&to);
|
||||||
|
assert_eq!(from_post.balance, initial_balance - amount);
|
||||||
|
assert_eq!(to_post, expected_to_post);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_claiming_mechanism_cannot_claim_initialied_accounts() {
|
||||||
|
let claimer = Program::claimer();
|
||||||
|
let mut state = V02State::new_with_genesis_accounts(&[], &[]).with_test_programs();
|
||||||
|
let account_id = AccountId::new([2; 32]);
|
||||||
|
|
||||||
|
// Insert an account with non-default program owner
|
||||||
|
state.force_insert_account(
|
||||||
|
account_id,
|
||||||
|
Account {
|
||||||
|
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let message =
|
||||||
|
public_transaction::Message::try_new(claimer.id(), vec![account_id], vec![], ())
|
||||||
|
.unwrap();
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
let result = state.transition_from_public_transaction(&tx);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||||
|
|
||||||
type Instruction = u128;
|
type Instruction = u128;
|
||||||
|
|
||||||
@ -17,5 +17,5 @@ fn main() {
|
|||||||
let mut account_post = account_pre.clone();
|
let mut account_post = account_pre.clone();
|
||||||
account_post.balance -= balance_to_burn;
|
account_post.balance -= balance_to_burn;
|
||||||
|
|
||||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{
|
||||||
ChainedCall, ProgramId, ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call,
|
AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs,
|
||||||
|
write_nssa_outputs_with_chained_call,
|
||||||
};
|
};
|
||||||
use risc0_zkvm::serde::to_vec;
|
use risc0_zkvm::serde::to_vec;
|
||||||
|
|
||||||
@ -37,7 +38,10 @@ fn main() {
|
|||||||
|
|
||||||
write_nssa_outputs_with_chained_call(
|
write_nssa_outputs_with_chained_call(
|
||||||
vec![sender_pre.clone(), receiver_pre.clone()],
|
vec![sender_pre.clone(), receiver_pre.clone()],
|
||||||
vec![sender_pre.account, receiver_pre.account],
|
vec![
|
||||||
|
AccountPostState::new(sender_pre.account),
|
||||||
|
AccountPostState::new(receiver_pre.account),
|
||||||
|
],
|
||||||
chained_call,
|
chained_call,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
19
nssa/test_program_methods/guest/src/bin/claimer.rs
Normal file
19
nssa/test_program_methods/guest/src/bin/claimer.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||||
|
|
||||||
|
type Instruction = ();
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let ProgramInput {
|
||||||
|
pre_states,
|
||||||
|
instruction: _,
|
||||||
|
} = read_nssa_inputs::<Instruction>();
|
||||||
|
|
||||||
|
let [pre] = match pre_states.try_into() {
|
||||||
|
Ok(array) => array,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let account_post = AccountPostState::new_claimed(pre.account.clone());
|
||||||
|
|
||||||
|
write_nssa_outputs(vec![pre], vec![account_post]);
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -14,5 +14,5 @@ fn main() {
|
|||||||
let mut account_post = account_pre.clone();
|
let mut account_post = account_pre.clone();
|
||||||
account_post.data.push(0);
|
account_post.data.push(0);
|
||||||
|
|
||||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::Account,
|
account::Account,
|
||||||
program::{read_nssa_inputs, write_nssa_outputs, ProgramInput},
|
program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
||||||
};
|
};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
@ -15,5 +15,11 @@ fn main() {
|
|||||||
|
|
||||||
let account_pre = pre.account.clone();
|
let account_pre = pre.account.clone();
|
||||||
|
|
||||||
write_nssa_outputs(vec![pre], vec![account_pre, Account::default()]);
|
write_nssa_outputs(
|
||||||
|
vec![pre],
|
||||||
|
vec![
|
||||||
|
AccountPostState::new(account_pre),
|
||||||
|
AccountPostState::new(Account::default()),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -14,5 +14,5 @@ fn main() {
|
|||||||
let mut account_post = account_pre.clone();
|
let mut account_post = account_pre.clone();
|
||||||
account_post.balance += 1;
|
account_post.balance += 1;
|
||||||
|
|
||||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -12,5 +12,5 @@ fn main() {
|
|||||||
|
|
||||||
let account_pre1 = pre1.account.clone();
|
let account_pre1 = pre1.account.clone();
|
||||||
|
|
||||||
write_nssa_outputs(vec![pre1, pre2], vec![account_pre1]);
|
write_nssa_outputs(vec![pre1, pre2], vec![AccountPostState::new(account_pre1)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -14,5 +14,5 @@ fn main() {
|
|||||||
let mut account_post = account_pre.clone();
|
let mut account_post = account_pre.clone();
|
||||||
account_post.nonce += 1;
|
account_post.nonce += 1;
|
||||||
|
|
||||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -14,5 +14,5 @@ fn main() {
|
|||||||
let mut account_post = account_pre.clone();
|
let mut account_post = account_pre.clone();
|
||||||
account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7];
|
account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7];
|
||||||
|
|
||||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||||
|
|
||||||
type Instruction = u128;
|
type Instruction = u128;
|
||||||
|
|
||||||
@ -20,6 +20,9 @@ fn main() {
|
|||||||
|
|
||||||
write_nssa_outputs(
|
write_nssa_outputs(
|
||||||
vec![sender_pre, receiver_pre],
|
vec![sender_pre, receiver_pre],
|
||||||
vec![sender_post, receiver_post],
|
vec![
|
||||||
|
AccountPostState::new(sender_post),
|
||||||
|
AccountPostState::new(receiver_post),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user