mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-13 19:49:29 +00:00
refactor: use system faucet and vaults to supply accounts from genesis
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
7f6fffe6cb
commit
f3e807de3f
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -3959,6 +3959,7 @@ dependencies = [
|
|||||||
"token_core",
|
"token_core",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
|
"vault_core",
|
||||||
"wallet",
|
"wallet",
|
||||||
"wallet-ffi",
|
"wallet-ffi",
|
||||||
]
|
]
|
||||||
@ -7062,6 +7063,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"token_core",
|
"token_core",
|
||||||
"token_program",
|
"token_program",
|
||||||
|
"vault_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -8416,7 +8418,6 @@ name = "sequencer_core"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"authenticated_transfer_core",
|
|
||||||
"borsh",
|
"borsh",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -8440,6 +8441,7 @@ dependencies = [
|
|||||||
"testnet_initial_state",
|
"testnet_initial_state",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
|
"vault_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -10077,6 +10079,15 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vault_core"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"nssa_core",
|
||||||
|
"risc0-zkvm",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|||||||
@ -21,6 +21,7 @@ members = [
|
|||||||
"programs/associated_token_account/core",
|
"programs/associated_token_account/core",
|
||||||
"programs/associated_token_account",
|
"programs/associated_token_account",
|
||||||
"programs/authenticated_transfer/core",
|
"programs/authenticated_transfer/core",
|
||||||
|
"programs/vault/core",
|
||||||
"sequencer/core",
|
"sequencer/core",
|
||||||
"sequencer/service",
|
"sequencer/service",
|
||||||
"sequencer/service/protocol",
|
"sequencer/service/protocol",
|
||||||
@ -67,6 +68,7 @@ amm_program = { path = "programs/amm" }
|
|||||||
ata_core = { path = "programs/associated_token_account/core" }
|
ata_core = { path = "programs/associated_token_account/core" }
|
||||||
ata_program = { path = "programs/associated_token_account" }
|
ata_program = { path = "programs/associated_token_account" }
|
||||||
authenticated_transfer_core = { path = "programs/authenticated_transfer/core" }
|
authenticated_transfer_core = { path = "programs/authenticated_transfer/core" }
|
||||||
|
vault_core = { path = "programs/vault/core" }
|
||||||
test_program_methods = { path = "test_program_methods" }
|
test_program_methods = { path = "test_program_methods" }
|
||||||
testnet_initial_state = { path = "testnet_initial_state" }
|
testnet_initial_state = { path = "testnet_initial_state" }
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
artifacts/program_methods/vault.bin
Normal file
BIN
artifacts/program_methods/vault.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -67,7 +67,8 @@ impl NSSATransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Validates the transaction against the current state and returns the resulting diff
|
/// Validates the transaction against the current state and returns the resulting diff
|
||||||
/// without applying it. Rejects transactions that modify clock system accounts.
|
/// without applying it. Rejects transactions that modify clock system accounts and
|
||||||
|
/// rejects unsafe modifications of the system faucet account.
|
||||||
pub fn validate_on_state(
|
pub fn validate_on_state(
|
||||||
&self,
|
&self,
|
||||||
state: &V03State,
|
state: &V03State,
|
||||||
@ -98,6 +99,36 @@ impl NSSATransaction {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let faucet_account_id = nssa::SYSTEM_FAUCET_ACCOUNT_ID;
|
||||||
|
if let Some(post_faucet) = public_diff.get(&faucet_account_id) {
|
||||||
|
let pre_faucet = state.get_account_by_id(faucet_account_id);
|
||||||
|
|
||||||
|
let nssa::Account {
|
||||||
|
program_owner: post_program_owner,
|
||||||
|
data: post_data,
|
||||||
|
nonce: post_nonce,
|
||||||
|
balance: post_balance,
|
||||||
|
} = post_faucet;
|
||||||
|
|
||||||
|
let nssa::Account {
|
||||||
|
program_owner: pre_program_owner,
|
||||||
|
data: pre_data,
|
||||||
|
nonce: pre_nonce,
|
||||||
|
balance: pre_balance,
|
||||||
|
} = pre_faucet;
|
||||||
|
|
||||||
|
let faucet_change_is_allowed = *post_program_owner == pre_program_owner
|
||||||
|
&& *post_data == pre_data
|
||||||
|
&& *post_nonce == pre_nonce
|
||||||
|
&& *post_balance >= pre_balance;
|
||||||
|
|
||||||
|
if !faucet_change_is_allowed {
|
||||||
|
return Err(nssa::error::NssaError::InvalidInput(
|
||||||
|
"Transaction modifies system faucet account".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(diff)
|
Ok(diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,13 +18,13 @@
|
|||||||
"indexer_rpc_url": "ws://indexer_service:8779",
|
"indexer_rpc_url": "ws://indexer_service:8779",
|
||||||
"genesis": [
|
"genesis": [
|
||||||
{
|
{
|
||||||
"supply_public_account": {
|
"supply_account": {
|
||||||
"account_id": "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV",
|
"account_id": "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV",
|
||||||
"balance": 10000
|
"balance": 10000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"supply_public_account": {
|
"supply_account": {
|
||||||
"account_id": "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo",
|
"account_id": "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo",
|
||||||
"balance": 20000
|
"balance": 20000
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use common::{
|
|||||||
use log::info;
|
use log::info;
|
||||||
use logos_blockchain_core::{header::HeaderId, mantle::ops::channel::MsgId};
|
use logos_blockchain_core::{header::HeaderId, mantle::ops::channel::MsgId};
|
||||||
use logos_blockchain_zone_sdk::Slot;
|
use logos_blockchain_zone_sdk::Slot;
|
||||||
use nssa::{Account, AccountId, V03State, ValidatedStateDiff};
|
use nssa::{Account, AccountId, V03State};
|
||||||
use nssa_core::BlockId;
|
use nssa_core::BlockId;
|
||||||
use storage::indexer::RocksDBIO;
|
use storage::indexer::RocksDBIO;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
@ -160,12 +160,13 @@ impl IndexerStore {
|
|||||||
anyhow::bail!("Genesis block should contain only public transactions")
|
anyhow::bail!("Genesis block should contain only public transactions")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let state_diff = ValidatedStateDiff::from_public_genesis_transaction(
|
state_guard
|
||||||
genesis_tx,
|
.transition_from_public_transaction(
|
||||||
&state_guard,
|
genesis_tx,
|
||||||
)
|
block.header.block_id,
|
||||||
.context("Failed to create state diff from genesis transaction")?;
|
block.header.timestamp,
|
||||||
state_guard.apply_state_diff(state_diff);
|
)
|
||||||
|
.context("Failed to execute genesis public transaction")?;
|
||||||
} else {
|
} else {
|
||||||
transaction
|
transaction
|
||||||
.clone()
|
.clone()
|
||||||
@ -202,39 +203,11 @@ impl IndexerStore {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use common::{HashType, block::HashableBlockData};
|
use common::{HashType, block::HashableBlockData};
|
||||||
use nssa::{AccountId, CLOCK_01_PROGRAM_ACCOUNT_ID, PublicKey, PublicTransaction};
|
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
use testnet_initial_state::initial_pub_accounts_private_keys;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn acc1_sign_key() -> nssa::PrivateKey {
|
|
||||||
nssa::PrivateKey::try_new([1; 32]).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn acc2_sign_key() -> nssa::PrivateKey {
|
|
||||||
nssa::PrivateKey::try_new([2; 32]).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn acc1() -> AccountId {
|
|
||||||
AccountId::from(&PublicKey::new_from_private_key(&acc1_sign_key()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn acc2() -> AccountId {
|
|
||||||
AccountId::from(&PublicKey::new_from_private_key(&acc2_sign_key()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn genesis_mint_tx(account: AccountId, balance: u128) -> NSSATransaction {
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
nssa::program::Program::authenticated_transfer_program().id(),
|
|
||||||
vec![account, CLOCK_01_PROGRAM_ACCOUNT_ID],
|
|
||||||
vec![],
|
|
||||||
authenticated_transfer_core::Instruction::Mint { amount: balance },
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
|
||||||
PublicTransaction::new(message, witness_set).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn correct_startup() {
|
fn correct_startup() {
|
||||||
let home = tempdir().unwrap();
|
let home = tempdir().unwrap();
|
||||||
@ -252,19 +225,18 @@ mod tests {
|
|||||||
|
|
||||||
let storage = IndexerStore::open_db(home.as_ref()).unwrap();
|
let storage = IndexerStore::open_db(home.as_ref()).unwrap();
|
||||||
|
|
||||||
let from = acc1();
|
let initial_accounts = initial_pub_accounts_private_keys();
|
||||||
let to = acc2();
|
let from = initial_accounts[0].account_id;
|
||||||
let sign_key = acc1_sign_key();
|
let to = initial_accounts[1].account_id;
|
||||||
|
let sign_key = initial_accounts[0].pub_sign_key.clone();
|
||||||
|
|
||||||
// Submit genesis block
|
// Submit genesis block
|
||||||
let clock_tx = NSSATransaction::Public(clock_invocation(0));
|
let clock_tx = NSSATransaction::Public(clock_invocation(0));
|
||||||
let supply_from_tx = genesis_mint_tx(from, 10000);
|
|
||||||
let supply_to_tx = genesis_mint_tx(to, 20000);
|
|
||||||
let genesis_block_data = HashableBlockData {
|
let genesis_block_data = HashableBlockData {
|
||||||
block_id: 1,
|
block_id: 1,
|
||||||
prev_block_hash: HashType::default(),
|
prev_block_hash: HashType::default(),
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
transactions: vec![supply_from_tx, supply_to_tx, clock_tx],
|
transactions: vec![clock_tx],
|
||||||
};
|
};
|
||||||
let genesis_block = genesis_block_data.into_pending_block(
|
let genesis_block = genesis_block_data.into_pending_block(
|
||||||
&common::test_utils::sequencer_sign_key_for_testing(),
|
&common::test_utils::sequencer_sign_key_for_testing(),
|
||||||
@ -276,30 +248,29 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for i in 2..10 {
|
for i in 0..10 {
|
||||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||||
from,
|
from, i, to, 10, &sign_key,
|
||||||
i - 2,
|
|
||||||
to,
|
|
||||||
10,
|
|
||||||
&sign_key,
|
|
||||||
);
|
);
|
||||||
let block_id = u64::try_from(i).unwrap();
|
let block_id = u64::try_from(i + 1).unwrap();
|
||||||
|
|
||||||
let next_block = common::test_utils::produce_dummy_block(block_id, prev_hash, vec![tx]);
|
let next_block = common::test_utils::produce_dummy_block(block_id, prev_hash, vec![tx]);
|
||||||
prev_hash = Some(next_block.header.hash);
|
prev_hash = Some(next_block.header.hash);
|
||||||
|
|
||||||
storage
|
storage
|
||||||
.put_block(next_block, HeaderId::from([u8::try_from(i).unwrap(); 32]))
|
.put_block(
|
||||||
|
next_block,
|
||||||
|
HeaderId::from([u8::try_from(i + 1).unwrap(); 32]),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let acc1_val = storage.account_current_state(&acc1()).await.unwrap();
|
let acc1_val = storage.account_current_state(&from).await.unwrap();
|
||||||
let acc2_val = storage.account_current_state(&acc2()).await.unwrap();
|
let acc2_val = storage.account_current_state(&to).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(acc1_val.balance, 9920);
|
assert_eq!(acc1_val.balance, 9900);
|
||||||
assert_eq!(acc2_val.balance, 20080);
|
assert_eq!(acc2_val.balance, 20100);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -310,45 +281,45 @@ mod tests {
|
|||||||
|
|
||||||
let mut prev_hash = None;
|
let mut prev_hash = None;
|
||||||
|
|
||||||
let from = acc1();
|
let initial_accounts = initial_pub_accounts_private_keys();
|
||||||
let to = acc2();
|
let from = initial_accounts[0].account_id;
|
||||||
let sign_key = acc1_sign_key();
|
let to = initial_accounts[1].account_id;
|
||||||
|
let sign_key = initial_accounts[0].pub_sign_key.clone();
|
||||||
|
|
||||||
for i in 2..10 {
|
for i in 0..10 {
|
||||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||||
from,
|
from, i, to, 10, &sign_key,
|
||||||
i - 2,
|
|
||||||
to,
|
|
||||||
10,
|
|
||||||
&sign_key,
|
|
||||||
);
|
);
|
||||||
let block_id = u64::try_from(i).unwrap();
|
let block_id = u64::try_from(i + 1).unwrap();
|
||||||
|
|
||||||
let next_block = common::test_utils::produce_dummy_block(block_id, prev_hash, vec![tx]);
|
let next_block = common::test_utils::produce_dummy_block(block_id, prev_hash, vec![tx]);
|
||||||
prev_hash = Some(next_block.header.hash);
|
prev_hash = Some(next_block.header.hash);
|
||||||
|
|
||||||
storage
|
storage
|
||||||
.put_block(next_block, HeaderId::from([u8::try_from(i).unwrap(); 32]))
|
.put_block(
|
||||||
|
next_block,
|
||||||
|
HeaderId::from([u8::try_from(i + 1).unwrap(); 32]),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Genesis block: no transfers applied yet.
|
// Genesis block: no transfers applied yet.
|
||||||
let acc1_at_1 = storage.account_state_at_block(&acc1(), 1).unwrap();
|
let acc1_at_1 = storage.account_state_at_block(&from, 1).unwrap();
|
||||||
let acc2_at_1 = storage.account_state_at_block(&acc2(), 1).unwrap();
|
let acc2_at_1 = storage.account_state_at_block(&to, 1).unwrap();
|
||||||
assert_eq!(acc1_at_1.balance, 10000);
|
assert_eq!(acc1_at_1.balance, 9990);
|
||||||
assert_eq!(acc2_at_1.balance, 20000);
|
assert_eq!(acc2_at_1.balance, 20010);
|
||||||
|
|
||||||
// After block 5: 4 transfers of 10 applied (one each in blocks 2..=5).
|
// After block 5: 4 transfers of 10 applied (one each in blocks 2..=5).
|
||||||
let acc1_at_5 = storage.account_state_at_block(&acc1(), 5).unwrap();
|
let acc1_at_5 = storage.account_state_at_block(&from, 5).unwrap();
|
||||||
let acc2_at_5 = storage.account_state_at_block(&acc2(), 5).unwrap();
|
let acc2_at_5 = storage.account_state_at_block(&to, 5).unwrap();
|
||||||
assert_eq!(acc1_at_5.balance, 9960);
|
assert_eq!(acc1_at_5.balance, 9950);
|
||||||
assert_eq!(acc2_at_5.balance, 20040);
|
assert_eq!(acc2_at_5.balance, 20050);
|
||||||
|
|
||||||
// After final block 9: 8 transfers applied; should match current state.
|
// After final block 9: 8 transfers applied; should match current state.
|
||||||
let acc1_at_9 = storage.account_state_at_block(&acc1(), 9).unwrap();
|
let acc1_at_9 = storage.account_state_at_block(&from, 9).unwrap();
|
||||||
let acc2_at_9 = storage.account_state_at_block(&acc2(), 9).unwrap();
|
let acc2_at_9 = storage.account_state_at_block(&to, 9).unwrap();
|
||||||
assert_eq!(acc1_at_9.balance, 9920);
|
assert_eq!(acc1_at_9.balance, 9910);
|
||||||
assert_eq!(acc2_at_9.balance, 20080);
|
assert_eq!(acc2_at_9.balance, 20090);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ indexer_service.workspace = true
|
|||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
token_core.workspace = true
|
token_core.workspace = true
|
||||||
ata_core.workspace = true
|
ata_core.workspace = true
|
||||||
|
vault_core.workspace = true
|
||||||
indexer_service_rpc = { workspace = true, features = ["client"] }
|
indexer_service_rpc = { workspace = true, features = ["client"] }
|
||||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||||
jsonrpsee = { workspace = true, features = ["ws-client"] }
|
jsonrpsee = { workspace = true, features = ["ws-client"] }
|
||||||
|
|||||||
@ -3,14 +3,30 @@ use std::{net::SocketAddr, path::PathBuf, time::Duration};
|
|||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
use indexer_service::{ChannelId, ClientConfig, IndexerConfig};
|
use indexer_service::{ChannelId, ClientConfig, IndexerConfig};
|
||||||
|
use key_protocol::key_management::KeyChain;
|
||||||
use nssa::{AccountId, PrivateKey, PublicKey};
|
use nssa::{AccountId, PrivateKey, PublicKey};
|
||||||
|
use nssa_core::Identifier;
|
||||||
use sequencer_core::config::{BedrockConfig, GenesisTransaction, SequencerConfig};
|
use sequencer_core::config::{BedrockConfig, GenesisTransaction, SequencerConfig};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use wallet::config::WalletConfig;
|
use wallet::config::WalletConfig;
|
||||||
|
|
||||||
pub const INITIAL_PUBLIC_BALANCES_FOR_WALLET: [u128; 2] = [20_000, 40_000];
|
pub const INITIAL_PUBLIC_BALANCES_FOR_WALLET: [u128; 2] = [10_000, 20_000];
|
||||||
pub const INITIAL_PRIVATE_BALANCES_FOR_WALLET: [u128; 2] = [10_000, 20_000];
|
pub const INITIAL_PRIVATE_BALANCES_FOR_WALLET: [u128; 2] = [10_000, 20_000];
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct InitialPrivateAccountForWallet {
|
||||||
|
pub key_chain: KeyChain,
|
||||||
|
pub identifier: Identifier,
|
||||||
|
pub balance: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InitialPrivateAccountForWallet {
|
||||||
|
#[must_use]
|
||||||
|
pub fn account_id(&self) -> AccountId {
|
||||||
|
AccountId::from((&self.key_chain.nullifier_public_key, self.identifier))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sequencer config options available for custom changes in integration tests.
|
/// Sequencer config options available for custom changes in integration tests.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct SequencerPartialConfig {
|
pub struct SequencerPartialConfig {
|
||||||
@ -80,43 +96,58 @@ pub fn sequencer_config(
|
|||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn default_public_accounts_for_wallet() -> Vec<(PrivateKey, u128)> {
|
pub fn default_public_accounts_for_wallet() -> Vec<(PrivateKey, u128)> {
|
||||||
let mut first_private_key = PrivateKey::new_os_random();
|
let mut private_keys = vec![PrivateKey::new_os_random(), PrivateKey::new_os_random()];
|
||||||
let first_public_key = PublicKey::new_from_private_key(&first_private_key);
|
private_keys.sort_unstable_by_key(|private_key| {
|
||||||
let mut first_account_id = AccountId::from(&first_public_key);
|
AccountId::from(&PublicKey::new_from_private_key(private_key))
|
||||||
|
});
|
||||||
|
|
||||||
let mut second_private_key = PrivateKey::new_os_random();
|
private_keys
|
||||||
let second_public_key = PublicKey::new_from_private_key(&second_private_key);
|
.into_iter()
|
||||||
let mut second_account_id = AccountId::from(&second_public_key);
|
.zip(INITIAL_PUBLIC_BALANCES_FOR_WALLET)
|
||||||
|
.collect()
|
||||||
// Keep account ordering deterministic for tests that index into account lists.
|
|
||||||
if first_account_id > second_account_id {
|
|
||||||
std::mem::swap(&mut first_private_key, &mut second_private_key);
|
|
||||||
std::mem::swap(&mut first_account_id, &mut second_account_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![
|
|
||||||
(first_private_key, INITIAL_PUBLIC_BALANCES_FOR_WALLET[0]),
|
|
||||||
(second_private_key, INITIAL_PUBLIC_BALANCES_FOR_WALLET[1]),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn genesis_from_public_accounts(
|
pub fn default_private_accounts_for_wallet() -> Vec<InitialPrivateAccountForWallet> {
|
||||||
public_accounts: &[(PrivateKey, u128)],
|
let mut key_chains = vec![KeyChain::new_os_random(), KeyChain::new_os_random()];
|
||||||
) -> Vec<GenesisTransaction> {
|
key_chains.sort_unstable();
|
||||||
public_accounts
|
|
||||||
.iter()
|
key_chains
|
||||||
.map(|(private_key, balance)| {
|
.into_iter()
|
||||||
let public_key = PublicKey::new_from_private_key(private_key);
|
.zip(INITIAL_PRIVATE_BALANCES_FOR_WALLET)
|
||||||
let account_id = AccountId::from(&public_key);
|
.map(|(key_chain, balance)| InitialPrivateAccountForWallet {
|
||||||
GenesisTransaction::SupplyPublicAccount {
|
key_chain,
|
||||||
account_id,
|
identifier: 0,
|
||||||
balance: *balance,
|
balance,
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn genesis_from_accounts(
|
||||||
|
public_accounts: &[(PrivateKey, u128)],
|
||||||
|
private_accounts: &[InitialPrivateAccountForWallet],
|
||||||
|
) -> Vec<GenesisTransaction> {
|
||||||
|
let public_genesis = public_accounts.iter().map(|(private_key, balance)| {
|
||||||
|
let public_key = PublicKey::new_from_private_key(private_key);
|
||||||
|
let account_id = AccountId::from(&public_key);
|
||||||
|
GenesisTransaction::SupplyAccount {
|
||||||
|
account_id,
|
||||||
|
balance: *balance,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let private_genesis =
|
||||||
|
private_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|account| GenesisTransaction::SupplyAccount {
|
||||||
|
account_id: account.account_id(),
|
||||||
|
balance: account.balance,
|
||||||
|
});
|
||||||
|
|
||||||
|
public_genesis.chain(private_genesis).collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn wallet_config(sequencer_addr: SocketAddr) -> Result<WalletConfig> {
|
pub fn wallet_config(sequencer_addr: SocketAddr) -> Result<WalletConfig> {
|
||||||
Ok(WalletConfig {
|
Ok(WalletConfig {
|
||||||
sequencer_addr: addr_to_url(UrlProtocol::Http, sequencer_addr)
|
sequencer_addr: addr_to_url(UrlProtocol::Http, sequencer_addr)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ use crate::{
|
|||||||
indexer_client::IndexerClient,
|
indexer_client::IndexerClient,
|
||||||
setup::{
|
setup::{
|
||||||
setup_bedrock_node, setup_indexer, setup_private_accounts_with_initial_supply,
|
setup_bedrock_node, setup_indexer, setup_private_accounts_with_initial_supply,
|
||||||
setup_sequencer, setup_wallet,
|
setup_public_accounts_with_initial_supply, setup_sequencer, setup_wallet,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -225,7 +225,7 @@ impl TestContextBuilder {
|
|||||||
Self {
|
Self {
|
||||||
genesis_transactions: None,
|
genesis_transactions: None,
|
||||||
sequencer_partial_config: None,
|
sequencer_partial_config: None,
|
||||||
enable_indexer: false,
|
enable_indexer: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,19 +290,29 @@ impl TestContextBuilder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let initial_public_accounts = config::default_public_accounts_for_wallet();
|
let initial_public_accounts = config::default_public_accounts_for_wallet();
|
||||||
|
let initial_private_accounts = config::default_private_accounts_for_wallet();
|
||||||
let (sequencer_handle, temp_sequencer_dir) = setup_sequencer(
|
let (sequencer_handle, temp_sequencer_dir) = setup_sequencer(
|
||||||
sequencer_partial_config.unwrap_or_default(),
|
sequencer_partial_config.unwrap_or_default(),
|
||||||
bedrock_addr,
|
bedrock_addr,
|
||||||
genesis_transactions
|
genesis_transactions.unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| config::genesis_from_public_accounts(&initial_public_accounts)),
|
config::genesis_from_accounts(&initial_public_accounts, &initial_private_accounts)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to setup Sequencer")?;
|
.context("Failed to setup Sequencer")?;
|
||||||
|
|
||||||
let (mut wallet, temp_wallet_dir, wallet_password) =
|
let (mut wallet, temp_wallet_dir, wallet_password) = setup_wallet(
|
||||||
setup_wallet(sequencer_handle.addr(), &initial_public_accounts)
|
sequencer_handle.addr(),
|
||||||
.context("Failed to setup wallet")?;
|
&initial_public_accounts,
|
||||||
setup_private_accounts_with_initial_supply(&mut wallet)
|
&initial_private_accounts,
|
||||||
|
)
|
||||||
|
.context("Failed to setup wallet")?;
|
||||||
|
|
||||||
|
setup_public_accounts_with_initial_supply(&wallet, &initial_public_accounts)
|
||||||
|
.await
|
||||||
|
.context("Failed to initialize public accounts in wallet")?;
|
||||||
|
|
||||||
|
setup_private_accounts_with_initial_supply(&mut wallet, &initial_private_accounts)
|
||||||
.await
|
.await
|
||||||
.context("Failed to initialize private accounts in wallet")?;
|
.context("Failed to initialize private accounts in wallet")?;
|
||||||
|
|
||||||
|
|||||||
@ -1,27 +1,21 @@
|
|||||||
use std::{net::SocketAddr, path::PathBuf};
|
use std::{collections::HashMap, net::SocketAddr, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
|
use common::transaction::NSSATransaction;
|
||||||
use indexer_service::IndexerHandle;
|
use indexer_service::IndexerHandle;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use nssa::PrivateKey;
|
use nssa::{AccountId, PrivateKey, PublicKey, PublicTransaction, program::Program};
|
||||||
use sequencer_service::{GenesisTransaction, SequencerHandle};
|
use sequencer_service::{GenesisTransaction, SequencerHandle};
|
||||||
|
use sequencer_service_rpc::RpcClient as _;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use testcontainers::compose::DockerCompose;
|
use testcontainers::compose::DockerCompose;
|
||||||
use wallet::{
|
use wallet::{
|
||||||
WalletCore,
|
AccDecodeData::Decode, PrivacyPreservingAccount, WalletCore, config::WalletConfigOverrides,
|
||||||
cli::{
|
|
||||||
Command, SubcommandReturnValue,
|
|
||||||
account::{AccountSubcommand, NewSubcommand},
|
|
||||||
execute_subcommand,
|
|
||||||
programs::native_token_transfer::AuthTransferSubcommand,
|
|
||||||
},
|
|
||||||
config::WalletConfigOverrides,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BEDROCK_SERVICE_PORT, BEDROCK_SERVICE_WITH_OPEN_PORT,
|
BEDROCK_SERVICE_PORT, BEDROCK_SERVICE_WITH_OPEN_PORT,
|
||||||
config::{self, INITIAL_PRIVATE_BALANCES_FOR_WALLET},
|
config::{self, InitialPrivateAccountForWallet},
|
||||||
private_mention, public_mention,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn setup_bedrock_node() -> Result<(DockerCompose, SocketAddr)> {
|
pub async fn setup_bedrock_node() -> Result<(DockerCompose, SocketAddr)> {
|
||||||
@ -141,6 +135,7 @@ pub async fn setup_sequencer(
|
|||||||
pub fn setup_wallet(
|
pub fn setup_wallet(
|
||||||
sequencer_addr: SocketAddr,
|
sequencer_addr: SocketAddr,
|
||||||
initial_public_accounts: &[(PrivateKey, u128)],
|
initial_public_accounts: &[(PrivateKey, u128)],
|
||||||
|
initial_private_accounts: &[InitialPrivateAccountForWallet],
|
||||||
) -> Result<(WalletCore, TempDir, String)> {
|
) -> Result<(WalletCore, TempDir, String)> {
|
||||||
let config = config::wallet_config(sequencer_addr).context("Failed to create Wallet config")?;
|
let config = config::wallet_config(sequencer_addr).context("Failed to create Wallet config")?;
|
||||||
let config_serialized =
|
let config_serialized =
|
||||||
@ -172,6 +167,18 @@ pub fn setup_wallet(
|
|||||||
.add_imported_public_account(private_key.clone());
|
.add_imported_public_account(private_key.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for private_account in initial_private_accounts {
|
||||||
|
wallet
|
||||||
|
.storage_mut()
|
||||||
|
.key_chain_mut()
|
||||||
|
.add_imported_private_account(
|
||||||
|
private_account.key_chain.clone(),
|
||||||
|
None,
|
||||||
|
private_account.identifier,
|
||||||
|
nssa::Account::default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
wallet
|
wallet
|
||||||
.store_persistent_data()
|
.store_persistent_data()
|
||||||
.context("Failed to store wallet persistent data")?;
|
.context("Failed to store wallet persistent data")?;
|
||||||
@ -179,72 +186,142 @@ pub fn setup_wallet(
|
|||||||
Ok((wallet, temp_wallet_dir, wallet_password))
|
Ok((wallet, temp_wallet_dir, wallet_password))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn setup_private_accounts_with_initial_supply(wallet: &mut WalletCore) -> Result<()> {
|
pub async fn setup_public_accounts_with_initial_supply(
|
||||||
for _ in INITIAL_PRIVATE_BALANCES_FOR_WALLET {
|
wallet: &WalletCore,
|
||||||
let result = execute_subcommand(
|
initial_public_accounts: &[(PrivateKey, u128)],
|
||||||
|
) -> Result<()> {
|
||||||
|
for (private_key, amount) in initial_public_accounts {
|
||||||
|
claim_funds_from_vault(
|
||||||
wallet,
|
wallet,
|
||||||
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
|
AccountId::from(&PublicKey::new_from_private_key(private_key)),
|
||||||
cci: None,
|
*amount,
|
||||||
label: None,
|
|
||||||
})),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to create a private account")?;
|
.context("Failed to claim funds from vault into public account")?;
|
||||||
let SubcommandReturnValue::RegisterAccount { account_id: _ } = result else {
|
|
||||||
bail!("Expected RegisterAccount return value when creating private account");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let public_account_ids: Vec<_> = wallet
|
|
||||||
.storage()
|
|
||||||
.key_chain()
|
|
||||||
.public_account_ids()
|
|
||||||
.map(|(account_id, _idx)| account_id)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if public_account_ids.len() < INITIAL_PRIVATE_BALANCES_FOR_WALLET.len() {
|
|
||||||
bail!(
|
|
||||||
"Expected at least {} public accounts in wallet storage, found {}",
|
|
||||||
INITIAL_PRIVATE_BALANCES_FOR_WALLET.len(),
|
|
||||||
public_account_ids.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let private_account_ids: Vec<_> = wallet
|
|
||||||
.storage()
|
|
||||||
.key_chain()
|
|
||||||
.private_account_ids()
|
|
||||||
.map(|(account_id, _idx)| account_id)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for ((from, to), amount) in public_account_ids
|
|
||||||
.into_iter()
|
|
||||||
.zip(private_account_ids.into_iter())
|
|
||||||
.zip(INITIAL_PRIVATE_BALANCES_FOR_WALLET)
|
|
||||||
{
|
|
||||||
let result = execute_subcommand(
|
|
||||||
wallet,
|
|
||||||
Command::AuthTransfer(AuthTransferSubcommand::Send {
|
|
||||||
from: public_mention(from),
|
|
||||||
to: Some(private_mention(to)),
|
|
||||||
to_npk: None,
|
|
||||||
to_vpk: None,
|
|
||||||
to_identifier: None,
|
|
||||||
amount,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Failed to perform initial shielded transfer to private account")?;
|
|
||||||
|
|
||||||
if !matches!(
|
|
||||||
result,
|
|
||||||
SubcommandReturnValue::PrivacyPreservingTransfer { .. }
|
|
||||||
) {
|
|
||||||
bail!(
|
|
||||||
"Expected PrivacyPreservingTransfer return value when shielding initial private funds"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn setup_private_accounts_with_initial_supply(
|
||||||
|
wallet: &mut WalletCore,
|
||||||
|
initial_private_accounts: &[InitialPrivateAccountForWallet],
|
||||||
|
) -> Result<()> {
|
||||||
|
for private_account in initial_private_accounts {
|
||||||
|
claim_funds_from_vault_to_private(
|
||||||
|
wallet,
|
||||||
|
private_account.account_id(),
|
||||||
|
private_account.balance,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Failed to claim funds from vault into private account")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn claim_funds_from_vault(
|
||||||
|
wallet: &WalletCore,
|
||||||
|
owner_id: AccountId,
|
||||||
|
amount: u128,
|
||||||
|
) -> Result<()> {
|
||||||
|
let vault_program_id = Program::vault().id();
|
||||||
|
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
|
||||||
|
|
||||||
|
let nonces = wallet
|
||||||
|
.get_accounts_nonces(vec![owner_id])
|
||||||
|
.await
|
||||||
|
.context("Failed to fetch owner nonce")?;
|
||||||
|
|
||||||
|
let signing_key = wallet
|
||||||
|
.storage()
|
||||||
|
.key_chain()
|
||||||
|
.pub_account_signing_key(owner_id)
|
||||||
|
.with_context(|| format!("Missing signing key for public account {owner_id}"))?;
|
||||||
|
|
||||||
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
|
vault_program_id,
|
||||||
|
vec![owner_id, owner_vault_id],
|
||||||
|
nonces,
|
||||||
|
vault_core::Instruction::Claim { amount },
|
||||||
|
)
|
||||||
|
.context("Failed to build vault claim message")?;
|
||||||
|
|
||||||
|
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
||||||
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
let tx_hash = wallet
|
||||||
|
.sequencer_client
|
||||||
|
.send_transaction(NSSATransaction::Public(tx))
|
||||||
|
.await
|
||||||
|
.context("Failed to submit vault claim transaction")?;
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.poll_native_token_transfer(tx_hash)
|
||||||
|
.await
|
||||||
|
.context("Failed to confirm vault claim transaction")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn claim_funds_from_vault_to_private(
|
||||||
|
wallet: &mut WalletCore,
|
||||||
|
owner_id: AccountId,
|
||||||
|
amount: u128,
|
||||||
|
) -> Result<()> {
|
||||||
|
let Some(_) = wallet.storage().key_chain().private_account(owner_id) else {
|
||||||
|
bail!("Missing private account in wallet key chain for account {owner_id}");
|
||||||
|
};
|
||||||
|
|
||||||
|
let vault_program = Program::vault();
|
||||||
|
let vault_program_id = vault_program.id();
|
||||||
|
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
|
||||||
|
|
||||||
|
let instruction_data =
|
||||||
|
Program::serialize_instruction(vault_core::Instruction::Claim { amount })
|
||||||
|
.context("Failed to serialize vault private claim instruction")?;
|
||||||
|
|
||||||
|
let program_with_dependencies =
|
||||||
|
nssa::privacy_preserving_transaction::circuit::ProgramWithDependencies::new(
|
||||||
|
vault_program,
|
||||||
|
HashMap::from([(
|
||||||
|
Program::authenticated_transfer_program().id(),
|
||||||
|
Program::authenticated_transfer_program(),
|
||||||
|
)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (tx_hash, mut secrets) = wallet
|
||||||
|
.send_privacy_preserving_tx(
|
||||||
|
vec![
|
||||||
|
PrivacyPreservingAccount::PrivateOwned(owner_id),
|
||||||
|
PrivacyPreservingAccount::Public(owner_vault_id),
|
||||||
|
],
|
||||||
|
instruction_data,
|
||||||
|
&program_with_dependencies,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Failed to submit private vault claim transaction")?;
|
||||||
|
|
||||||
|
let secret = secrets
|
||||||
|
.pop()
|
||||||
|
.context("Expected one private output secret for vault claim")?;
|
||||||
|
|
||||||
|
let transfer_tx = wallet
|
||||||
|
.poll_native_token_transfer(tx_hash)
|
||||||
|
.await
|
||||||
|
.context("Failed to confirm private vault claim transaction")?;
|
||||||
|
|
||||||
|
let NSSATransaction::PrivacyPreserving(tx) = transfer_tx else {
|
||||||
|
bail!("Expected privacy preserving transaction result for private vault claim");
|
||||||
|
};
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.decode_insert_privacy_preserving_transaction_results(&tx, &[Decode(secret, owner_id)])
|
||||||
|
.context("Failed to decode private vault claim transaction")?;
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.store_persistent_data()
|
||||||
|
.context("Failed to store wallet data after private vault claim")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use common::transaction::NSSATransaction;
|
||||||
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, public_mention};
|
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, public_mention};
|
||||||
use log::info;
|
use log::info;
|
||||||
use nssa::program::Program;
|
use nssa::{SYSTEM_FAUCET_ACCOUNT_ID, program::Program, public_transaction};
|
||||||
use sequencer_service_rpc::RpcClient as _;
|
use sequencer_service_rpc::RpcClient as _;
|
||||||
use tokio::test;
|
use tokio::test;
|
||||||
use wallet::{
|
use wallet::{
|
||||||
@ -344,3 +345,90 @@ async fn successful_transfer_using_to_label() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> {
|
||||||
|
let ctx = TestContext::new().await?;
|
||||||
|
|
||||||
|
let recipient = ctx.existing_public_accounts()[0];
|
||||||
|
let recipient_balance_before = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account_balance(recipient)
|
||||||
|
.await?;
|
||||||
|
let faucet_balance_before = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account_balance(SYSTEM_FAUCET_ACCOUNT_ID)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let amount = 1_u128;
|
||||||
|
let message = public_transaction::Message::try_new(
|
||||||
|
Program::authenticated_transfer_program().id(),
|
||||||
|
vec![SYSTEM_FAUCET_ACCOUNT_ID, recipient],
|
||||||
|
vec![],
|
||||||
|
authenticated_transfer_core::Instruction::Transfer { amount },
|
||||||
|
)?;
|
||||||
|
let tx = nssa::PublicTransaction::new(
|
||||||
|
message,
|
||||||
|
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
||||||
|
);
|
||||||
|
let tx_hash = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.send_transaction(NSSATransaction::Public(tx))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
let recipient_balance_after = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account_balance(recipient)
|
||||||
|
.await?;
|
||||||
|
let faucet_balance_after = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account_balance(SYSTEM_FAUCET_ACCOUNT_ID)
|
||||||
|
.await?;
|
||||||
|
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
|
||||||
|
|
||||||
|
assert_eq!(recipient_balance_after, recipient_balance_before);
|
||||||
|
assert_eq!(faucet_balance_after, faucet_balance_before);
|
||||||
|
assert!(tx_on_chain.is_none());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn can_transfer_funds_to_system_faucet_account() -> Result<()> {
|
||||||
|
let mut ctx = TestContext::new().await?;
|
||||||
|
|
||||||
|
let sender = ctx.existing_public_accounts()[0];
|
||||||
|
let sender_balance_before = ctx.sequencer_client().get_account_balance(sender).await?;
|
||||||
|
let faucet_balance_before = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account_balance(SYSTEM_FAUCET_ACCOUNT_ID)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let amount = 100_u128;
|
||||||
|
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
|
||||||
|
from: public_mention(sender),
|
||||||
|
to: Some(public_mention(SYSTEM_FAUCET_ACCOUNT_ID)),
|
||||||
|
to_npk: None,
|
||||||
|
to_vpk: None,
|
||||||
|
to_identifier: Some(0),
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
let sender_balance_after = ctx.sequencer_client().get_account_balance(sender).await?;
|
||||||
|
let faucet_balance_after = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account_balance(SYSTEM_FAUCET_ACCOUNT_ID)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(sender_balance_after, sender_balance_before - amount);
|
||||||
|
assert_eq!(faucet_balance_after, faucet_balance_before + amount);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@ -125,9 +125,9 @@ fn indexer_test_run_ffi() -> Result<()> {
|
|||||||
|
|
||||||
let last_block_indexer_ffi = unsafe { *last_block_indexer_ffi_res.value };
|
let last_block_indexer_ffi = unsafe { *last_block_indexer_ffi_res.value };
|
||||||
|
|
||||||
info!("Last block on ind ffi now is {last_block_indexer_ffi}");
|
info!("Last block on indexer FFI now is {last_block_indexer_ffi}");
|
||||||
|
|
||||||
assert!(last_block_indexer_ffi > 1);
|
assert!(last_block_indexer_ffi > 0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -149,9 +149,9 @@ fn indexer_ffi_block_batching() -> Result<()> {
|
|||||||
|
|
||||||
let last_block_indexer = unsafe { *last_block_indexer_ffi_res.value };
|
let last_block_indexer = unsafe { *last_block_indexer_ffi_res.value };
|
||||||
|
|
||||||
info!("Last block on ind now is {last_block_indexer}");
|
info!("Last block on indexer FFI now is {last_block_indexer}");
|
||||||
|
|
||||||
assert!(last_block_indexer > 1);
|
assert!(last_block_indexer > 0);
|
||||||
|
|
||||||
let before_ffi = FfiOption::<u64>::from_none();
|
let before_ffi = FfiOption::<u64>::from_none();
|
||||||
let limit = 100;
|
let limit = 100;
|
||||||
|
|||||||
@ -97,7 +97,7 @@ impl TpsTestManager {
|
|||||||
fn generate_genesis(&self) -> Vec<GenesisTransaction> {
|
fn generate_genesis(&self) -> Vec<GenesisTransaction> {
|
||||||
self.public_keypairs
|
self.public_keypairs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, account_id)| GenesisTransaction::SupplyPublicAccount {
|
.map(|(_, account_id)| GenesisTransaction::SupplyAccount {
|
||||||
account_id: *account_id,
|
account_id: *account_id,
|
||||||
balance: 10,
|
balance: 10,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -25,6 +25,8 @@ pub mod program;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
pub const GENESIS_BLOCK_ID: BlockId = 1;
|
pub const GENESIS_BLOCK_ID: BlockId = 1;
|
||||||
|
pub const SYSTEM_FAUCET_ACCOUNT_ID: account::AccountId =
|
||||||
|
account::AccountId::new(*b"/LEZ/SystemFaucetAccount/0000000");
|
||||||
|
|
||||||
pub type BlockId = u64;
|
pub type BlockId = u64;
|
||||||
/// Unix timestamp in milliseconds.
|
/// Unix timestamp in milliseconds.
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
pub use nssa_core::{
|
pub use nssa_core::{
|
||||||
GENESIS_BLOCK_ID, SharedSecretKey,
|
GENESIS_BLOCK_ID, SYSTEM_FAUCET_ACCOUNT_ID, SharedSecretKey,
|
||||||
account::{Account, AccountId, Data},
|
account::{Account, AccountId, Data},
|
||||||
encryption::EphemeralPublicKey,
|
encryption::EphemeralPublicKey,
|
||||||
program::ProgramId,
|
program::ProgramId,
|
||||||
|
|||||||
@ -543,7 +543,11 @@ mod tests {
|
|||||||
let recipient = AccountWithMetadata::new(Account::default(), false, shared_account_id);
|
let recipient = AccountWithMetadata::new(Account::default(), false, shared_account_id);
|
||||||
|
|
||||||
let balance_to_move: u128 = 100;
|
let balance_to_move: u128 = 100;
|
||||||
let instruction = Program::serialize_instruction(balance_to_move).unwrap();
|
let instruction =
|
||||||
|
Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer {
|
||||||
|
amount: balance_to_move,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let result = execute_and_prove(
|
let result = execute_and_prove(
|
||||||
vec![sender, recipient],
|
vec![sender, recipient],
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use crate::{
|
|||||||
program_methods::{
|
program_methods::{
|
||||||
AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID,
|
AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID,
|
||||||
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, PINATA_ELF,
|
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, PINATA_ELF,
|
||||||
PINATA_ID, TOKEN_ELF, TOKEN_ID,
|
PINATA_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -148,6 +148,14 @@ impl Program {
|
|||||||
elf: ASSOCIATED_TOKEN_ACCOUNT_ELF.to_vec(),
|
elf: ASSOCIATED_TOKEN_ACCOUNT_ELF.to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn vault() -> Self {
|
||||||
|
Self {
|
||||||
|
id: VAULT_ID,
|
||||||
|
elf: VAULT_ELF.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
||||||
@ -179,7 +187,7 @@ mod tests {
|
|||||||
program_methods::{
|
program_methods::{
|
||||||
AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID,
|
AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID,
|
||||||
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, PINATA_ELF,
|
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, PINATA_ELF,
|
||||||
PINATA_ID, PINATA_TOKEN_ELF, PINATA_TOKEN_ID, TOKEN_ELF, TOKEN_ID,
|
PINATA_ID, PINATA_TOKEN_ELF, PINATA_TOKEN_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -482,12 +490,15 @@ mod tests {
|
|||||||
fn builtin_programs() {
|
fn builtin_programs() {
|
||||||
let auth_transfer_program = Program::authenticated_transfer_program();
|
let auth_transfer_program = Program::authenticated_transfer_program();
|
||||||
let token_program = Program::token();
|
let token_program = Program::token();
|
||||||
|
let vault_program = Program::vault();
|
||||||
let pinata_program = Program::pinata();
|
let pinata_program = Program::pinata();
|
||||||
|
|
||||||
assert_eq!(auth_transfer_program.id, AUTHENTICATED_TRANSFER_ID);
|
assert_eq!(auth_transfer_program.id, AUTHENTICATED_TRANSFER_ID);
|
||||||
assert_eq!(auth_transfer_program.elf, AUTHENTICATED_TRANSFER_ELF);
|
assert_eq!(auth_transfer_program.elf, AUTHENTICATED_TRANSFER_ELF);
|
||||||
assert_eq!(token_program.id, TOKEN_ID);
|
assert_eq!(token_program.id, TOKEN_ID);
|
||||||
assert_eq!(token_program.elf, TOKEN_ELF);
|
assert_eq!(token_program.elf, TOKEN_ELF);
|
||||||
|
assert_eq!(vault_program.id, VAULT_ID);
|
||||||
|
assert_eq!(vault_program.elf, VAULT_ELF);
|
||||||
assert_eq!(pinata_program.id, PINATA_ID);
|
assert_eq!(pinata_program.id, PINATA_ID);
|
||||||
assert_eq!(pinata_program.elf, PINATA_ELF);
|
assert_eq!(pinata_program.elf, PINATA_ELF);
|
||||||
}
|
}
|
||||||
@ -502,6 +513,7 @@ mod tests {
|
|||||||
(PINATA_ELF, PINATA_ID),
|
(PINATA_ELF, PINATA_ID),
|
||||||
(PINATA_TOKEN_ELF, PINATA_TOKEN_ID),
|
(PINATA_TOKEN_ELF, PINATA_TOKEN_ID),
|
||||||
(TOKEN_ELF, TOKEN_ID),
|
(TOKEN_ELF, TOKEN_ID),
|
||||||
|
(VAULT_ELF, VAULT_ID),
|
||||||
];
|
];
|
||||||
for (elf, expected_id) in cases {
|
for (elf, expected_id) in cases {
|
||||||
let program = Program::new(elf.to_vec()).unwrap();
|
let program = Program::new(elf.to_vec()).unwrap();
|
||||||
|
|||||||
@ -1,118 +0,0 @@
|
|||||||
use std::collections::{HashMap, VecDeque};
|
|
||||||
|
|
||||||
use log::debug;
|
|
||||||
use nssa_core::{
|
|
||||||
account::{Account, AccountId, AccountWithMetadata},
|
|
||||||
program::{ChainedCall, ProgramId, ProgramOutput},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{PublicTransaction, V03State, error::NssaError};
|
|
||||||
|
|
||||||
pub trait Validator {
|
|
||||||
fn validate_pre_execution(&mut self) -> Result<(), NssaError>;
|
|
||||||
|
|
||||||
fn on_chained_call(&mut self) -> Result<(), NssaError>;
|
|
||||||
|
|
||||||
fn validate_output(
|
|
||||||
&mut self,
|
|
||||||
state_diff: &HashMap<AccountId, Account>,
|
|
||||||
caller_program_id: Option<ProgramId>,
|
|
||||||
chained_call: &ChainedCall,
|
|
||||||
program_output: &ProgramOutput,
|
|
||||||
) -> Result<(), NssaError>;
|
|
||||||
|
|
||||||
fn validate_post_execution(
|
|
||||||
&mut self,
|
|
||||||
state_diff: &HashMap<AccountId, Account>,
|
|
||||||
) -> Result<(), NssaError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn execute(
|
|
||||||
mut validator: impl Validator,
|
|
||||||
tx: &PublicTransaction,
|
|
||||||
state: &V03State,
|
|
||||||
) -> Result<HashMap<AccountId, Account>, NssaError> {
|
|
||||||
validator.validate_pre_execution()?;
|
|
||||||
|
|
||||||
let message = tx.message();
|
|
||||||
let signer_account_ids = tx.signer_account_ids();
|
|
||||||
|
|
||||||
// Build pre_states for execution
|
|
||||||
let input_pre_states: Vec<_> = message
|
|
||||||
.account_ids
|
|
||||||
.iter()
|
|
||||||
.map(|account_id| {
|
|
||||||
AccountWithMetadata::new(
|
|
||||||
state.get_account_by_id(*account_id),
|
|
||||||
signer_account_ids.contains(account_id),
|
|
||||||
*account_id,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut state_diff: HashMap<AccountId, Account> = HashMap::new();
|
|
||||||
|
|
||||||
let initial_call = ChainedCall {
|
|
||||||
program_id: message.program_id,
|
|
||||||
instruction_data: message.instruction_data.clone(),
|
|
||||||
pre_states: input_pre_states,
|
|
||||||
pda_seeds: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut chained_calls = VecDeque::from_iter([(initial_call, None)]);
|
|
||||||
|
|
||||||
while let Some((chained_call, caller_program_id)) = chained_calls.pop_front() {
|
|
||||||
validator.on_chained_call()?;
|
|
||||||
|
|
||||||
// Check that the `program_id` corresponds to a deployed program
|
|
||||||
let Some(program) = state.programs().get(&chained_call.program_id) else {
|
|
||||||
return Err(NssaError::InvalidInput("Unknown program".into()));
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"Program {:?} pre_states: {:?}, instruction_data: {:?}",
|
|
||||||
chained_call.program_id, chained_call.pre_states, chained_call.instruction_data
|
|
||||||
);
|
|
||||||
let mut program_output = program.execute(
|
|
||||||
caller_program_id,
|
|
||||||
&chained_call.pre_states,
|
|
||||||
&chained_call.instruction_data,
|
|
||||||
)?;
|
|
||||||
debug!(
|
|
||||||
"Program {:?} output: {:?}",
|
|
||||||
chained_call.program_id, program_output
|
|
||||||
);
|
|
||||||
|
|
||||||
validator.validate_output(
|
|
||||||
&state_diff,
|
|
||||||
caller_program_id,
|
|
||||||
&chained_call,
|
|
||||||
&program_output,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
for post in program_output
|
|
||||||
.post_states
|
|
||||||
.iter_mut()
|
|
||||||
.filter(|post| post.required_claim().is_some())
|
|
||||||
{
|
|
||||||
post.account_mut().program_owner = chained_call.program_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the state diff
|
|
||||||
for (pre, post) in program_output
|
|
||||||
.pre_states
|
|
||||||
.iter()
|
|
||||||
.zip(program_output.post_states.iter())
|
|
||||||
{
|
|
||||||
state_diff.insert(pre.account_id, post.account().clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
for new_call in program_output.chained_calls.into_iter().rev() {
|
|
||||||
chained_calls.push_front((new_call, Some(chained_call.program_id)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validator.validate_post_execution(&state_diff)?;
|
|
||||||
|
|
||||||
Ok(state_diff)
|
|
||||||
}
|
|
||||||
@ -1,9 +1,7 @@
|
|||||||
pub use execution::{Validator, execute};
|
|
||||||
pub use message::Message;
|
pub use message::Message;
|
||||||
pub use transaction::PublicTransaction;
|
pub use transaction::PublicTransaction;
|
||||||
pub use witness_set::WitnessSet;
|
pub use witness_set::WitnessSet;
|
||||||
|
|
||||||
mod execution;
|
|
||||||
mod message;
|
mod message;
|
||||||
mod transaction;
|
mod transaction;
|
||||||
mod witness_set;
|
mod witness_set;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ pub use clock_core::{
|
|||||||
};
|
};
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
BlockId, Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
|
BlockId, Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
|
||||||
Timestamp,
|
SYSTEM_FAUCET_ACCOUNT_ID, Timestamp,
|
||||||
account::{Account, AccountId, Nonce},
|
account::{Account, AccountId, Nonce},
|
||||||
program::ProgramId,
|
program::ProgramId,
|
||||||
};
|
};
|
||||||
@ -124,8 +124,12 @@ pub struct V03State {
|
|||||||
|
|
||||||
impl Default for V03State {
|
impl Default for V03State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let faucet_account = system_faucet_account();
|
||||||
|
let mut public_state = HashMap::new();
|
||||||
|
public_state.insert(SYSTEM_FAUCET_ACCOUNT_ID, faucet_account);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
public_state: HashMap::new(),
|
public_state,
|
||||||
private_state: (CommitmentSet::with_capacity(32), NullifierSet::new()),
|
private_state: (CommitmentSet::with_capacity(32), NullifierSet::new()),
|
||||||
programs: HashMap::new(),
|
programs: HashMap::new(),
|
||||||
}
|
}
|
||||||
@ -145,7 +149,7 @@ impl V03State {
|
|||||||
genesis_timestamp: nssa_core::Timestamp,
|
genesis_timestamp: nssa_core::Timestamp,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let authenticated_transfer_program = Program::authenticated_transfer_program();
|
let authenticated_transfer_program = Program::authenticated_transfer_program();
|
||||||
let public_state = initial_data
|
let mut public_state: HashMap<_, _> = initial_data
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.map(|(account_id, balance)| {
|
.map(|(account_id, balance)| {
|
||||||
@ -157,6 +161,8 @@ impl V03State {
|
|||||||
(account_id, account)
|
(account_id, account)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
let faucet_account = system_faucet_account();
|
||||||
|
public_state.insert(SYSTEM_FAUCET_ACCOUNT_ID, faucet_account);
|
||||||
|
|
||||||
let mut commitment_set = CommitmentSet::with_capacity(32);
|
let mut commitment_set = CommitmentSet::with_capacity(32);
|
||||||
commitment_set.extend(&[DUMMY_COMMITMENT]);
|
commitment_set.extend(&[DUMMY_COMMITMENT]);
|
||||||
@ -180,6 +186,7 @@ impl V03State {
|
|||||||
this.insert_program(Program::token());
|
this.insert_program(Program::token());
|
||||||
this.insert_program(Program::amm());
|
this.insert_program(Program::amm());
|
||||||
this.insert_program(Program::ata());
|
this.insert_program(Program::ata());
|
||||||
|
this.insert_program(Program::vault());
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
@ -366,6 +373,14 @@ impl V03State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn system_faucet_account() -> Account {
|
||||||
|
Account {
|
||||||
|
program_owner: Program::authenticated_transfer_program().id(),
|
||||||
|
balance: u128::MAX,
|
||||||
|
..Account::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
#![expect(
|
#![expect(
|
||||||
@ -389,7 +404,7 @@ pub mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PublicKey, PublicTransaction, V03State,
|
PublicKey, PublicTransaction, SYSTEM_FAUCET_ACCOUNT_ID, V03State,
|
||||||
error::{InvalidProgramBehaviorError, NssaError},
|
error::{InvalidProgramBehaviorError, NssaError},
|
||||||
execute_and_prove,
|
execute_and_prove,
|
||||||
privacy_preserving_transaction::{
|
privacy_preserving_transaction::{
|
||||||
@ -403,7 +418,7 @@ pub mod tests {
|
|||||||
signature::PrivateKey,
|
signature::PrivateKey,
|
||||||
state::{
|
state::{
|
||||||
CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID,
|
CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID,
|
||||||
CLOCK_PROGRAM_ACCOUNT_IDS, MAX_NUMBER_CHAINED_CALLS,
|
CLOCK_PROGRAM_ACCOUNT_IDS, MAX_NUMBER_CHAINED_CALLS, system_faucet_account,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -597,6 +612,7 @@ pub mod tests {
|
|||||||
..Account::default()
|
..Account::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
this.insert(SYSTEM_FAUCET_ACCOUNT_ID, system_faucet_account());
|
||||||
for account_id in CLOCK_PROGRAM_ACCOUNT_IDS {
|
for account_id in CLOCK_PROGRAM_ACCOUNT_IDS {
|
||||||
this.insert(
|
this.insert(
|
||||||
account_id,
|
account_id,
|
||||||
@ -619,6 +635,7 @@ pub mod tests {
|
|||||||
this.insert(Program::token().id(), Program::token());
|
this.insert(Program::token().id(), Program::token());
|
||||||
this.insert(Program::amm().id(), Program::amm());
|
this.insert(Program::amm().id(), Program::amm());
|
||||||
this.insert(Program::ata().id(), Program::ata());
|
this.insert(Program::ata().id(), Program::ata());
|
||||||
|
this.insert(Program::vault().id(), Program::vault());
|
||||||
this
|
this
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
BlockId, Commitment, Nullifier, PrivacyPreservingCircuitOutput, Timestamp,
|
BlockId, Commitment, Nullifier, PrivacyPreservingCircuitOutput, Timestamp,
|
||||||
account::{Account, AccountId, AccountWithMetadata},
|
account::{Account, AccountId, AccountWithMetadata},
|
||||||
program::{
|
program::{
|
||||||
ChainedCall, Claim, DEFAULT_PROGRAM_ID, ProgramId, ProgramOutput,
|
ChainedCall, Claim, DEFAULT_PROGRAM_ID, compute_public_authorized_pdas, validate_execution,
|
||||||
compute_public_authorized_pdas, validate_execution,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,11 +45,237 @@ impl ValidatedStateDiff {
|
|||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
) -> Result<Self, NssaError> {
|
) -> Result<Self, NssaError> {
|
||||||
let validator = PublicTransactionValidator::new(tx, state, block_id, timestamp);
|
let message = tx.message();
|
||||||
let state_diff = crate::public_transaction::execute(validator, tx, state)?;
|
let witness_set = tx.witness_set();
|
||||||
|
|
||||||
|
// All account_ids must be different
|
||||||
|
ensure!(
|
||||||
|
message.account_ids.iter().collect::<HashSet<_>>().len() == message.account_ids.len(),
|
||||||
|
NssaError::InvalidInput("Duplicate account_ids found in message".into(),)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check exactly one nonce is provided for each signature
|
||||||
|
ensure!(
|
||||||
|
message.nonces.len() == witness_set.signatures_and_public_keys.len(),
|
||||||
|
NssaError::InvalidInput(
|
||||||
|
"Mismatch between number of nonces and signatures/public keys".into(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check the signatures are valid
|
||||||
|
ensure!(
|
||||||
|
witness_set.is_valid_for(message),
|
||||||
|
NssaError::InvalidInput("Invalid signature for given message and public key".into())
|
||||||
|
);
|
||||||
|
|
||||||
|
let signer_account_ids = tx.signer_account_ids();
|
||||||
|
// Check nonces corresponds to the current nonces on the public state.
|
||||||
|
for (account_id, nonce) in signer_account_ids.iter().zip(&message.nonces) {
|
||||||
|
let current_nonce = state.get_account_by_id(*account_id).nonce;
|
||||||
|
ensure!(
|
||||||
|
current_nonce == *nonce,
|
||||||
|
NssaError::InvalidInput("Nonce mismatch".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build pre_states for execution
|
||||||
|
let input_pre_states: Vec<_> = message
|
||||||
|
.account_ids
|
||||||
|
.iter()
|
||||||
|
.map(|account_id| {
|
||||||
|
AccountWithMetadata::new(
|
||||||
|
state.get_account_by_id(*account_id),
|
||||||
|
signer_account_ids.contains(account_id),
|
||||||
|
*account_id,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut state_diff: HashMap<AccountId, Account> = HashMap::new();
|
||||||
|
|
||||||
|
let initial_call = ChainedCall {
|
||||||
|
program_id: message.program_id,
|
||||||
|
instruction_data: message.instruction_data.clone(),
|
||||||
|
pre_states: input_pre_states,
|
||||||
|
pda_seeds: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut chained_calls = VecDeque::from_iter([(initial_call, None)]);
|
||||||
|
let mut chain_calls_counter = 0;
|
||||||
|
|
||||||
|
while let Some((chained_call, caller_program_id)) = chained_calls.pop_front() {
|
||||||
|
ensure!(
|
||||||
|
chain_calls_counter <= MAX_NUMBER_CHAINED_CALLS,
|
||||||
|
NssaError::MaxChainedCallsDepthExceeded
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that the `program_id` corresponds to a deployed program
|
||||||
|
let Some(program) = state.programs().get(&chained_call.program_id) else {
|
||||||
|
return Err(NssaError::InvalidInput("Unknown program".into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Program {:?} pre_states: {:?}, instruction_data: {:?}",
|
||||||
|
chained_call.program_id, chained_call.pre_states, chained_call.instruction_data
|
||||||
|
);
|
||||||
|
let mut program_output = program.execute(
|
||||||
|
caller_program_id,
|
||||||
|
&chained_call.pre_states,
|
||||||
|
&chained_call.instruction_data,
|
||||||
|
)?;
|
||||||
|
debug!(
|
||||||
|
"Program {:?} output: {:?}",
|
||||||
|
chained_call.program_id, program_output
|
||||||
|
);
|
||||||
|
|
||||||
|
let authorized_pdas =
|
||||||
|
compute_public_authorized_pdas(caller_program_id, &chained_call.pda_seeds);
|
||||||
|
|
||||||
|
let is_authorized = |account_id: &AccountId| {
|
||||||
|
signer_account_ids.contains(account_id) || authorized_pdas.contains(account_id)
|
||||||
|
};
|
||||||
|
|
||||||
|
for pre in &program_output.pre_states {
|
||||||
|
let account_id = pre.account_id;
|
||||||
|
// Check that the program output pre_states coincide with the values in the public
|
||||||
|
// state or with any modifications to those values during the chain of calls.
|
||||||
|
let expected_pre = state_diff
|
||||||
|
.get(&account_id)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| state.get_account_by_id(account_id));
|
||||||
|
ensure!(
|
||||||
|
pre.account == expected_pre,
|
||||||
|
InvalidProgramBehaviorError::InconsistentAccountPreState {
|
||||||
|
account_id,
|
||||||
|
expected: Box::new(expected_pre),
|
||||||
|
actual: Box::new(pre.account.clone())
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that authorization flags are consistent with the provided ones or
|
||||||
|
// authorized by program through the PDA mechanism
|
||||||
|
let expected_is_authorized = is_authorized(&account_id);
|
||||||
|
ensure!(
|
||||||
|
pre.is_authorized == expected_is_authorized,
|
||||||
|
InvalidProgramBehaviorError::InconsistentAccountAuthorization {
|
||||||
|
account_id,
|
||||||
|
expected_authorization: expected_is_authorized,
|
||||||
|
actual_authorization: pre.is_authorized
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the program output's self_program_id matches the expected program ID.
|
||||||
|
ensure!(
|
||||||
|
program_output.self_program_id == chained_call.program_id,
|
||||||
|
InvalidProgramBehaviorError::MismatchedProgramId {
|
||||||
|
expected: chained_call.program_id,
|
||||||
|
actual: program_output.self_program_id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify that the program output's caller_program_id matches the actual caller.
|
||||||
|
ensure!(
|
||||||
|
program_output.caller_program_id == caller_program_id,
|
||||||
|
InvalidProgramBehaviorError::MismatchedCallerProgramId {
|
||||||
|
expected: caller_program_id,
|
||||||
|
actual: program_output.caller_program_id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify execution corresponds to a well-behaved program.
|
||||||
|
// See the # Programs section for the definition of the `validate_execution` method.
|
||||||
|
validate_execution(
|
||||||
|
&program_output.pre_states,
|
||||||
|
&program_output.post_states,
|
||||||
|
chained_call.program_id,
|
||||||
|
)
|
||||||
|
.map_err(InvalidProgramBehaviorError::ExecutionValidationFailed)?;
|
||||||
|
|
||||||
|
// Verify validity window
|
||||||
|
ensure!(
|
||||||
|
program_output.block_validity_window.is_valid_for(block_id)
|
||||||
|
&& program_output
|
||||||
|
.timestamp_validity_window
|
||||||
|
.is_valid_for(timestamp),
|
||||||
|
NssaError::OutOfValidityWindow
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, post) in program_output.post_states.iter_mut().enumerate() {
|
||||||
|
let Some(claim) = post.required_claim() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let account_id = program_output.pre_states[i].account_id;
|
||||||
|
|
||||||
|
// The invoked program can only claim accounts with default program id.
|
||||||
|
ensure!(
|
||||||
|
post.account().program_owner == DEFAULT_PROGRAM_ID,
|
||||||
|
InvalidProgramBehaviorError::ClaimedNonDefaultAccount { account_id }
|
||||||
|
);
|
||||||
|
|
||||||
|
match claim {
|
||||||
|
Claim::Authorized => {
|
||||||
|
// The program can only claim accounts that were authorized by the signer.
|
||||||
|
ensure!(
|
||||||
|
is_authorized(&account_id),
|
||||||
|
InvalidProgramBehaviorError::ClaimedUnauthorizedAccount { account_id }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Claim::Pda(seed) => {
|
||||||
|
// The program can only claim accounts that correspond to the PDAs it is
|
||||||
|
// authorized to claim. The public-execution path only sees public
|
||||||
|
// accounts, so the public-PDA derivation is the correct formula here.
|
||||||
|
let pda = AccountId::for_public_pda(&chained_call.program_id, &seed);
|
||||||
|
ensure!(
|
||||||
|
account_id == pda,
|
||||||
|
InvalidProgramBehaviorError::MismatchedPdaClaim {
|
||||||
|
expected: pda,
|
||||||
|
actual: account_id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post.account_mut().program_owner = chained_call.program_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the state diff
|
||||||
|
for (pre, post) in program_output
|
||||||
|
.pre_states
|
||||||
|
.iter()
|
||||||
|
.zip(program_output.post_states.iter())
|
||||||
|
{
|
||||||
|
state_diff.insert(pre.account_id, post.account().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for new_call in program_output.chained_calls.into_iter().rev() {
|
||||||
|
chained_calls.push_front((new_call, Some(chained_call.program_id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
chain_calls_counter = chain_calls_counter
|
||||||
|
.checked_add(1)
|
||||||
|
.expect("we check the max depth at the beginning of the loop");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all modified uninitialized accounts where claimed
|
||||||
|
for (account_id, post) in state_diff.iter().filter_map(|(account_id, post)| {
|
||||||
|
let pre = state.get_account_by_id(*account_id);
|
||||||
|
if pre.program_owner != DEFAULT_PROGRAM_ID {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if pre == *post {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((*account_id, post))
|
||||||
|
}) {
|
||||||
|
ensure!(
|
||||||
|
post.program_owner != DEFAULT_PROGRAM_ID,
|
||||||
|
InvalidProgramBehaviorError::DefaultAccountModifiedWithoutClaim { account_id }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self(StateDiff {
|
Ok(Self(StateDiff {
|
||||||
signer_account_ids: tx.signer_account_ids(),
|
signer_account_ids,
|
||||||
public_diff: state_diff,
|
public_diff: state_diff,
|
||||||
new_commitments: vec![],
|
new_commitments: vec![],
|
||||||
new_nullifiers: vec![],
|
new_nullifiers: vec![],
|
||||||
@ -190,22 +416,6 @@ impl ValidatedStateDiff {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_public_genesis_transaction(
|
|
||||||
tx: &PublicTransaction,
|
|
||||||
state: &V03State,
|
|
||||||
) -> Result<Self, NssaError> {
|
|
||||||
let validator = GenesisPublicTransactionValidator;
|
|
||||||
let state_diff = crate::public_transaction::execute(validator, tx, state)?;
|
|
||||||
|
|
||||||
Ok(Self(StateDiff {
|
|
||||||
signer_account_ids: tx.signer_account_ids(),
|
|
||||||
public_diff: state_diff,
|
|
||||||
new_commitments: vec![],
|
|
||||||
new_nullifiers: vec![],
|
|
||||||
program: None,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the public account changes produced by this transaction.
|
/// Returns the public account changes produced by this transaction.
|
||||||
///
|
///
|
||||||
/// Used by callers (e.g. the sequencer) to inspect the diff before committing it, for example
|
/// Used by callers (e.g. the sequencer) to inspect the diff before committing it, for example
|
||||||
@ -220,256 +430,6 @@ impl ValidatedStateDiff {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PublicTransactionValidator<'tx, 'state> {
|
|
||||||
tx: &'tx PublicTransaction,
|
|
||||||
state: &'state V03State,
|
|
||||||
block_id: BlockId,
|
|
||||||
timestamp: Timestamp,
|
|
||||||
chain_calls_counter: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'tx, 'state> PublicTransactionValidator<'tx, 'state> {
|
|
||||||
pub const fn new(
|
|
||||||
tx: &'tx PublicTransaction,
|
|
||||||
state: &'state V03State,
|
|
||||||
block_id: BlockId,
|
|
||||||
timestamp: Timestamp,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
tx,
|
|
||||||
state,
|
|
||||||
block_id,
|
|
||||||
timestamp,
|
|
||||||
chain_calls_counter: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::public_transaction::Validator for PublicTransactionValidator<'_, '_> {
|
|
||||||
fn validate_pre_execution(&mut self) -> Result<(), NssaError> {
|
|
||||||
let message = self.tx.message();
|
|
||||||
let witness_set = self.tx.witness_set();
|
|
||||||
|
|
||||||
// All account_ids must be different
|
|
||||||
ensure!(
|
|
||||||
message.account_ids.iter().collect::<HashSet<_>>().len() == message.account_ids.len(),
|
|
||||||
NssaError::InvalidInput("Duplicate account_ids found in message".into(),)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check exactly one nonce is provided for each signature
|
|
||||||
ensure!(
|
|
||||||
message.nonces.len() == witness_set.signatures_and_public_keys.len(),
|
|
||||||
NssaError::InvalidInput(
|
|
||||||
"Mismatch between number of nonces and signatures/public keys".into(),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check the signatures are valid
|
|
||||||
ensure!(
|
|
||||||
witness_set.is_valid_for(message),
|
|
||||||
NssaError::InvalidInput("Invalid signature for given message and public key".into())
|
|
||||||
);
|
|
||||||
|
|
||||||
let signer_account_ids = self.tx.signer_account_ids();
|
|
||||||
// Check nonces corresponds to the current nonces on the public state.
|
|
||||||
for (account_id, nonce) in signer_account_ids.iter().zip(&message.nonces) {
|
|
||||||
let current_nonce = self.state.get_account_by_id(*account_id).nonce;
|
|
||||||
ensure!(
|
|
||||||
current_nonce == *nonce,
|
|
||||||
NssaError::InvalidInput("Nonce mismatch".into())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_chained_call(&mut self) -> Result<(), NssaError> {
|
|
||||||
self.chain_calls_counter = self
|
|
||||||
.chain_calls_counter
|
|
||||||
.checked_add(1)
|
|
||||||
.ok_or(NssaError::MaxChainedCallsDepthExceeded)?;
|
|
||||||
ensure!(
|
|
||||||
self.chain_calls_counter <= MAX_NUMBER_CHAINED_CALLS,
|
|
||||||
NssaError::MaxChainedCallsDepthExceeded
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_output(
|
|
||||||
&mut self,
|
|
||||||
state_diff: &HashMap<AccountId, Account>,
|
|
||||||
caller_program_id: Option<ProgramId>,
|
|
||||||
chained_call: &ChainedCall,
|
|
||||||
program_output: &ProgramOutput,
|
|
||||||
) -> Result<(), NssaError> {
|
|
||||||
let authorized_pdas =
|
|
||||||
compute_public_authorized_pdas(caller_program_id, &chained_call.pda_seeds);
|
|
||||||
|
|
||||||
let is_authorized = |account_id: &AccountId| {
|
|
||||||
self.tx.signer_account_ids().contains(account_id)
|
|
||||||
|| authorized_pdas.contains(account_id)
|
|
||||||
};
|
|
||||||
|
|
||||||
for pre in &program_output.pre_states {
|
|
||||||
let account_id = pre.account_id;
|
|
||||||
// Check that the program output pre_states coincide with the values in the public
|
|
||||||
// state or with any modifications to those values during the chain of calls.
|
|
||||||
let expected_pre = state_diff
|
|
||||||
.get(&account_id)
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| self.state.get_account_by_id(account_id));
|
|
||||||
ensure!(
|
|
||||||
pre.account == expected_pre,
|
|
||||||
InvalidProgramBehaviorError::InconsistentAccountPreState {
|
|
||||||
account_id,
|
|
||||||
expected: Box::new(expected_pre),
|
|
||||||
actual: Box::new(pre.account.clone())
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check that authorization flags are consistent with the provided ones or
|
|
||||||
// authorized by program through the PDA mechanism
|
|
||||||
let expected_is_authorized = is_authorized(&account_id);
|
|
||||||
ensure!(
|
|
||||||
pre.is_authorized == expected_is_authorized,
|
|
||||||
InvalidProgramBehaviorError::InconsistentAccountAuthorization {
|
|
||||||
account_id,
|
|
||||||
expected_authorization: expected_is_authorized,
|
|
||||||
actual_authorization: pre.is_authorized
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the program output's self_program_id matches the expected program ID.
|
|
||||||
ensure!(
|
|
||||||
program_output.self_program_id == chained_call.program_id,
|
|
||||||
InvalidProgramBehaviorError::MismatchedProgramId {
|
|
||||||
expected: chained_call.program_id,
|
|
||||||
actual: program_output.self_program_id
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify that the program output's caller_program_id matches the actual caller.
|
|
||||||
ensure!(
|
|
||||||
program_output.caller_program_id == caller_program_id,
|
|
||||||
InvalidProgramBehaviorError::MismatchedCallerProgramId {
|
|
||||||
expected: caller_program_id,
|
|
||||||
actual: program_output.caller_program_id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify execution corresponds to a well-behaved program.
|
|
||||||
// See the # Programs section for the definition of the `validate_execution` method.
|
|
||||||
validate_execution(
|
|
||||||
&program_output.pre_states,
|
|
||||||
&program_output.post_states,
|
|
||||||
chained_call.program_id,
|
|
||||||
)
|
|
||||||
.map_err(InvalidProgramBehaviorError::ExecutionValidationFailed)?;
|
|
||||||
|
|
||||||
// Verify validity window
|
|
||||||
ensure!(
|
|
||||||
program_output
|
|
||||||
.block_validity_window
|
|
||||||
.is_valid_for(self.block_id)
|
|
||||||
&& program_output
|
|
||||||
.timestamp_validity_window
|
|
||||||
.is_valid_for(self.timestamp),
|
|
||||||
NssaError::OutOfValidityWindow
|
|
||||||
);
|
|
||||||
|
|
||||||
for (i, post) in program_output.post_states.iter().enumerate() {
|
|
||||||
let Some(claim) = post.required_claim() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let account_id = program_output.pre_states[i].account_id;
|
|
||||||
|
|
||||||
// The invoked program can only claim accounts with default program id.
|
|
||||||
ensure!(
|
|
||||||
post.account().program_owner == DEFAULT_PROGRAM_ID,
|
|
||||||
InvalidProgramBehaviorError::ClaimedNonDefaultAccount { account_id }
|
|
||||||
);
|
|
||||||
|
|
||||||
match claim {
|
|
||||||
Claim::Authorized => {
|
|
||||||
// The program can only claim accounts that were authorized by the signer.
|
|
||||||
ensure!(
|
|
||||||
is_authorized(&account_id),
|
|
||||||
InvalidProgramBehaviorError::ClaimedUnauthorizedAccount { account_id }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Claim::Pda(seed) => {
|
|
||||||
// The program can only claim accounts that correspond to the PDAs it is
|
|
||||||
// authorized to claim.
|
|
||||||
let pda = AccountId::for_public_pda(&chained_call.program_id, &seed);
|
|
||||||
ensure!(
|
|
||||||
account_id == pda,
|
|
||||||
InvalidProgramBehaviorError::MismatchedPdaClaim {
|
|
||||||
expected: pda,
|
|
||||||
actual: account_id
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_post_execution(
|
|
||||||
&mut self,
|
|
||||||
state_diff: &HashMap<AccountId, Account>,
|
|
||||||
) -> Result<(), NssaError> {
|
|
||||||
// Check that all modified uninitialized accounts where claimed
|
|
||||||
for (account_id, post) in state_diff.iter().filter_map(|(account_id, post)| {
|
|
||||||
let pre = self.state.get_account_by_id(*account_id);
|
|
||||||
if pre.program_owner != DEFAULT_PROGRAM_ID {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if pre == *post {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some((*account_id, post))
|
|
||||||
}) {
|
|
||||||
ensure!(
|
|
||||||
post.program_owner != DEFAULT_PROGRAM_ID,
|
|
||||||
InvalidProgramBehaviorError::DefaultAccountModifiedWithoutClaim { account_id }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GenesisPublicTransactionValidator;
|
|
||||||
|
|
||||||
impl crate::public_transaction::Validator for GenesisPublicTransactionValidator {
|
|
||||||
fn validate_pre_execution(&mut self) -> Result<(), NssaError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_chained_call(&mut self) -> Result<(), NssaError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_output(
|
|
||||||
&mut self,
|
|
||||||
_state_diff: &HashMap<AccountId, Account>,
|
|
||||||
_caller_program_id: Option<ProgramId>,
|
|
||||||
_chained_call: &ChainedCall,
|
|
||||||
_program_output: &ProgramOutput,
|
|
||||||
) -> Result<(), NssaError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_post_execution(
|
|
||||||
&mut self,
|
|
||||||
_state_diff: &HashMap<AccountId, Account>,
|
|
||||||
) -> Result<(), NssaError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_privacy_preserving_circuit_proof_is_valid(
|
fn check_privacy_preserving_circuit_proof_is_valid(
|
||||||
proof: &Proof,
|
proof: &Proof,
|
||||||
public_pre_states: &[AccountWithMetadata],
|
public_pre_states: &[AccountWithMetadata],
|
||||||
|
|||||||
@ -17,5 +17,6 @@ amm_core.workspace = true
|
|||||||
amm_program.workspace = true
|
amm_program.workspace = true
|
||||||
ata_core.workspace = true
|
ata_core.workspace = true
|
||||||
ata_program.workspace = true
|
ata_program.workspace = true
|
||||||
|
vault_core.workspace = true
|
||||||
risc0-zkvm.workspace = true
|
risc0-zkvm.workspace = true
|
||||||
serde = { workspace = true, default-features = false }
|
serde = { workspace = true, default-features = false }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use authenticated_transfer_core::Instruction;
|
use authenticated_transfer_core::Instruction;
|
||||||
use clock_core::{CLOCK_01_PROGRAM_ACCOUNT_ID, ClockAccountData};
|
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
|
SYSTEM_FAUCET_ACCOUNT_ID,
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
program::{
|
program::{
|
||||||
AccountPostState, Claim, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
AccountPostState, Claim, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
@ -27,7 +27,12 @@ fn transfer(
|
|||||||
balance_to_move: u128,
|
balance_to_move: u128,
|
||||||
) -> Vec<AccountPostState> {
|
) -> Vec<AccountPostState> {
|
||||||
// Continue only if the sender has authorized this operation
|
// Continue only if the sender has authorized this operation
|
||||||
assert!(sender.is_authorized, "Sender must be authorized");
|
// or it's the system faucet account which is allowed without authorization as it may be used
|
||||||
|
// only by sequencer.
|
||||||
|
assert!(
|
||||||
|
sender.is_authorized || sender.account_id == SYSTEM_FAUCET_ACCOUNT_ID,
|
||||||
|
"Sender must be authorized"
|
||||||
|
);
|
||||||
|
|
||||||
// Create accounts post states, with updated balances
|
// Create accounts post states, with updated balances
|
||||||
let sender_post = {
|
let sender_post = {
|
||||||
@ -59,39 +64,6 @@ fn transfer(
|
|||||||
vec![sender_post, recipient_post]
|
vec![sender_post, recipient_post]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mints `balance` into a new account at genesis (`block_id` == 0).
|
|
||||||
///
|
|
||||||
/// Claims the target account and sets its balance in a single operation.
|
|
||||||
fn mint(
|
|
||||||
target: AccountWithMetadata,
|
|
||||||
clock: AccountWithMetadata,
|
|
||||||
balance: u128,
|
|
||||||
) -> Vec<AccountPostState> {
|
|
||||||
assert_eq!(
|
|
||||||
clock.account_id, CLOCK_01_PROGRAM_ACCOUNT_ID,
|
|
||||||
"Second account must be the clock account"
|
|
||||||
);
|
|
||||||
|
|
||||||
let clock_data = ClockAccountData::from_bytes(&clock.account.data.clone().into_inner());
|
|
||||||
assert_eq!(
|
|
||||||
clock_data.block_id, 0,
|
|
||||||
"Mint can only execute at genesis (block_id must be 0)"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
target.account == Account::default(),
|
|
||||||
"Target account must be uninitialized"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut target_post_account = target.account;
|
|
||||||
target_post_account.balance = balance;
|
|
||||||
let target_post = AccountPostState::new_claimed(target_post_account, Claim::Authorized);
|
|
||||||
|
|
||||||
let clock_post = AccountPostState::new(clock.account);
|
|
||||||
|
|
||||||
vec![target_post, clock_post]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A transfer of balance program.
|
/// A transfer of balance program.
|
||||||
/// To be used both in public and private contexts.
|
/// To be used both in public and private contexts.
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -119,11 +91,6 @@ fn main() {
|
|||||||
.expect("Transfer requires exactly 2 accounts");
|
.expect("Transfer requires exactly 2 accounts");
|
||||||
transfer(sender, recipient, balance_to_move)
|
transfer(sender, recipient, balance_to_move)
|
||||||
}
|
}
|
||||||
Instruction::Mint { amount: balance } => {
|
|
||||||
let [target, clock] = <[_; 2]>::try_from(pre_states.clone())
|
|
||||||
.expect("Mint requires exactly 2 accounts: target, clock");
|
|
||||||
mint(target, clock, balance)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ProgramOutput::new(
|
ProgramOutput::new(
|
||||||
|
|||||||
103
program_methods/guest/src/bin/vault.rs
Normal file
103
program_methods/guest/src/bin/vault.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use authenticated_transfer_core::Instruction as AuthTransferInstruction;
|
||||||
|
use nssa_core::program::{
|
||||||
|
AccountPostState, ChainedCall, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
|
};
|
||||||
|
use vault_core::Instruction;
|
||||||
|
|
||||||
|
fn unchanged_post_states(
|
||||||
|
pre_states: &[nssa_core::account::AccountWithMetadata],
|
||||||
|
) -> Vec<AccountPostState> {
|
||||||
|
pre_states
|
||||||
|
.iter()
|
||||||
|
.map(|pre_state| AccountPostState::new(pre_state.account.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let (
|
||||||
|
ProgramInput {
|
||||||
|
self_program_id,
|
||||||
|
caller_program_id,
|
||||||
|
pre_states,
|
||||||
|
instruction,
|
||||||
|
},
|
||||||
|
instruction_words,
|
||||||
|
) = read_nssa_inputs::<Instruction>();
|
||||||
|
|
||||||
|
let pre_states_clone = pre_states.clone();
|
||||||
|
let post_states = unchanged_post_states(&pre_states_clone);
|
||||||
|
|
||||||
|
let chained_calls = match instruction {
|
||||||
|
Instruction::Initialize => {
|
||||||
|
let [owner, owner_vault] = pre_states
|
||||||
|
.try_into()
|
||||||
|
.expect("Initialize requires exactly 2 accounts");
|
||||||
|
|
||||||
|
let seed = vault_core::compute_vault_seed(owner.account_id);
|
||||||
|
assert!(
|
||||||
|
owner.account.program_owner != nssa_core::program::DEFAULT_PROGRAM_ID,
|
||||||
|
"Owner account must be initialized"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut owner_vault_for_callee = owner_vault;
|
||||||
|
owner_vault_for_callee.is_authorized = true;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
ChainedCall::new(
|
||||||
|
owner.account.program_owner,
|
||||||
|
vec![owner_vault_for_callee],
|
||||||
|
&AuthTransferInstruction::Initialize,
|
||||||
|
)
|
||||||
|
.with_pda_seeds(vec![seed]),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Instruction::Transfer { amount } => {
|
||||||
|
let [sender, recipient, recipient_vault] = pre_states
|
||||||
|
.try_into()
|
||||||
|
.expect("Transfer requires exactly 3 accounts");
|
||||||
|
|
||||||
|
let seed = vault_core::compute_vault_seed(recipient.account_id);
|
||||||
|
|
||||||
|
let mut recipient_vault_for_callee = recipient_vault;
|
||||||
|
recipient_vault_for_callee.is_authorized = true;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
ChainedCall::new(
|
||||||
|
sender.account.program_owner,
|
||||||
|
vec![sender, recipient_vault_for_callee],
|
||||||
|
&AuthTransferInstruction::Transfer { amount },
|
||||||
|
)
|
||||||
|
.with_pda_seeds(vec![seed]),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Instruction::Claim { amount } => {
|
||||||
|
let [owner, owner_vault] = pre_states
|
||||||
|
.try_into()
|
||||||
|
.expect("Claim requires exactly 2 accounts");
|
||||||
|
|
||||||
|
let seed = vault_core::compute_vault_seed(owner.account_id);
|
||||||
|
|
||||||
|
let mut owner_vault_for_callee = owner_vault;
|
||||||
|
owner_vault_for_callee.is_authorized = true;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
ChainedCall::new(
|
||||||
|
owner_vault_for_callee.account.program_owner,
|
||||||
|
vec![owner_vault_for_callee, owner],
|
||||||
|
&AuthTransferInstruction::Transfer { amount },
|
||||||
|
)
|
||||||
|
.with_pda_seeds(vec![seed]),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ProgramOutput::new(
|
||||||
|
self_program_id,
|
||||||
|
caller_program_id,
|
||||||
|
instruction_words,
|
||||||
|
pre_states_clone,
|
||||||
|
post_states,
|
||||||
|
)
|
||||||
|
.with_chained_calls(chained_calls)
|
||||||
|
.write();
|
||||||
|
}
|
||||||
@ -14,16 +14,4 @@ pub enum Instruction {
|
|||||||
///
|
///
|
||||||
/// Required accounts: `[account_to_initialize]`.
|
/// Required accounts: `[account_to_initialize]`.
|
||||||
Initialize,
|
Initialize,
|
||||||
|
|
||||||
/// Mint `amount` into a new account at genesis (`block_id` == 0).
|
|
||||||
///
|
|
||||||
/// Claims the target account (sets `program_owner` to `authenticated_transfer` program id)
|
|
||||||
/// and sets its balance in a single operation.
|
|
||||||
///
|
|
||||||
/// Required accounts: `[target_account, clock_account]`.
|
|
||||||
///
|
|
||||||
/// Panics if:
|
|
||||||
/// - `target_account` is not in the default (uninitialized) state
|
|
||||||
/// - clock's `block_id` is not 0
|
|
||||||
Mint { amount: u128 },
|
|
||||||
}
|
}
|
||||||
|
|||||||
13
programs/vault/core/Cargo.toml
Normal file
13
programs/vault/core/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "vault_core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nssa_core.workspace = true
|
||||||
|
serde = { workspace = true, default-features = false }
|
||||||
|
risc0-zkvm.workspace = true
|
||||||
57
programs/vault/core/src/lib.rs
Normal file
57
programs/vault/core/src/lib.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
pub use nssa_core::program::PdaSeed;
|
||||||
|
use nssa_core::{account::AccountId, program::ProgramId};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
const VAULT_SEED_DOMAIN_SEPARATOR: &[u8] = b"/LEZ/v0.3/VaultSeed/00000000000/";
|
||||||
|
|
||||||
|
const _: () = assert!(
|
||||||
|
VAULT_SEED_DOMAIN_SEPARATOR.len() == 32,
|
||||||
|
"Domain separator must be exactly 32 bytes long"
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum Instruction {
|
||||||
|
/// Initializes a vault account for owner.
|
||||||
|
///
|
||||||
|
/// Required accounts (2):
|
||||||
|
/// - Owner account
|
||||||
|
/// - Owner vault PDA account
|
||||||
|
Initialize,
|
||||||
|
|
||||||
|
/// Transfers native tokens from sender to recipient's vault.
|
||||||
|
///
|
||||||
|
/// Required accounts (3):
|
||||||
|
/// - Sender account
|
||||||
|
/// - Recipient account
|
||||||
|
/// - Recipient vault PDA account
|
||||||
|
Transfer { amount: u128 },
|
||||||
|
|
||||||
|
/// Claims native tokens from owner's vault into owner's account.
|
||||||
|
///
|
||||||
|
/// Required accounts (2):
|
||||||
|
/// - Owner account
|
||||||
|
/// - Owner vault PDA account
|
||||||
|
Claim { amount: u128 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn compute_vault_seed(owner_id: AccountId) -> PdaSeed {
|
||||||
|
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||||
|
|
||||||
|
let mut bytes = [0_u8; 64];
|
||||||
|
bytes[..32].copy_from_slice(VAULT_SEED_DOMAIN_SEPARATOR);
|
||||||
|
bytes[32..64].copy_from_slice(&owner_id.to_bytes());
|
||||||
|
|
||||||
|
PdaSeed::new(
|
||||||
|
Impl::hash_bytes(&bytes)
|
||||||
|
.as_bytes()
|
||||||
|
.try_into()
|
||||||
|
.expect("Hash output must be exactly 32 bytes long"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn compute_vault_account_id(vault_program_id: ProgramId, owner_id: AccountId) -> AccountId {
|
||||||
|
let seed = compute_vault_seed(owner_id);
|
||||||
|
AccountId::for_public_pda(&vault_program_id, &seed)
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@ storage.workspace = true
|
|||||||
mempool.workspace = true
|
mempool.workspace = true
|
||||||
logos-blockchain-zone-sdk.workspace = true
|
logos-blockchain-zone-sdk.workspace = true
|
||||||
testnet_initial_state.workspace = true
|
testnet_initial_state.workspace = true
|
||||||
authenticated_transfer_core.workspace = true
|
vault_core.workspace = true
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|||||||
@ -18,7 +18,7 @@ use url::Url;
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum GenesisTransaction {
|
pub enum GenesisTransaction {
|
||||||
SupplyPublicAccount {
|
SupplyAccount {
|
||||||
account_id: AccountId,
|
account_id: AccountId,
|
||||||
balance: u128,
|
balance: u128,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -12,9 +12,7 @@ use logos_blockchain_key_management_system_service::keys::{ED25519_SECRET_KEY_SI
|
|||||||
use mempool::{MemPool, MemPoolHandle};
|
use mempool::{MemPool, MemPoolHandle};
|
||||||
#[cfg(feature = "mock")]
|
#[cfg(feature = "mock")]
|
||||||
pub use mock::SequencerCoreWithMockClients;
|
pub use mock::SequencerCoreWithMockClients;
|
||||||
use nssa::{
|
use nssa::{AccountId, PublicTransaction, program::Program, public_transaction::Message};
|
||||||
AccountId, PublicTransaction, ValidatedStateDiff, program::Program, public_transaction::Message,
|
|
||||||
};
|
|
||||||
use nssa_core::GENESIS_BLOCK_ID;
|
use nssa_core::GENESIS_BLOCK_ID;
|
||||||
pub use storage::error::DbError;
|
pub use storage::error::DbError;
|
||||||
|
|
||||||
@ -363,49 +361,48 @@ fn build_genesis_state(config: &SequencerConfig) -> (nssa::V03State, Vec<NSSATra
|
|||||||
#[cfg(feature = "testnet")]
|
#[cfg(feature = "testnet")]
|
||||||
let mut state = testnet_initial_state::initial_state_testnet();
|
let mut state = testnet_initial_state::initial_state_testnet();
|
||||||
|
|
||||||
let mut genesis_txs = Vec::new();
|
let genesis_txs = config
|
||||||
|
.genesis
|
||||||
for genesis_tx in &config.genesis {
|
.iter()
|
||||||
let (tx, diff) = match genesis_tx {
|
.map(|genesis_tx| match genesis_tx {
|
||||||
GenesisTransaction::SupplyPublicAccount {
|
GenesisTransaction::SupplyAccount {
|
||||||
account_id,
|
account_id,
|
||||||
balance,
|
balance,
|
||||||
} => build_supply_public_account_genesis_transaction(&state, account_id, *balance),
|
} => build_supply_account_genesis_transaction(account_id, *balance),
|
||||||
};
|
})
|
||||||
state.apply_state_diff(diff);
|
.chain(std::iter::once(clock_invocation(0)))
|
||||||
genesis_txs.push(tx);
|
.inspect(|tx| {
|
||||||
}
|
state
|
||||||
|
.transition_from_public_transaction(tx, GENESIS_BLOCK_ID, 0)
|
||||||
let clock_tx = clock_invocation(0);
|
.expect("Failed to execute genesis transaction");
|
||||||
let diff = ValidatedStateDiff::from_public_transaction(&clock_tx, &state, GENESIS_BLOCK_ID, 0)
|
})
|
||||||
.expect("Failed to execute clock transaction for genesis block");
|
.map(NSSATransaction::Public)
|
||||||
state.apply_state_diff(diff);
|
.collect();
|
||||||
genesis_txs.push(clock_tx.into());
|
|
||||||
|
|
||||||
(state, genesis_txs)
|
(state, genesis_txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_supply_public_account_genesis_transaction(
|
fn build_supply_account_genesis_transaction(
|
||||||
state: &nssa::V03State,
|
|
||||||
account_id: &AccountId,
|
account_id: &AccountId,
|
||||||
balance: u128,
|
balance: u128,
|
||||||
) -> (NSSATransaction, ValidatedStateDiff) {
|
) -> PublicTransaction {
|
||||||
let authenticated_transfer_id = Program::authenticated_transfer_program().id();
|
let vault_program_id = Program::vault().id();
|
||||||
|
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, *account_id);
|
||||||
|
|
||||||
let message = Message::try_new(
|
let message = Message::try_new(
|
||||||
authenticated_transfer_id,
|
vault_program_id,
|
||||||
vec![*account_id, nssa::CLOCK_01_PROGRAM_ACCOUNT_ID],
|
vec![
|
||||||
|
nssa::SYSTEM_FAUCET_ACCOUNT_ID,
|
||||||
|
*account_id,
|
||||||
|
recipient_vault_id,
|
||||||
|
],
|
||||||
vec![],
|
vec![],
|
||||||
authenticated_transfer_core::Instruction::Mint { amount: balance },
|
vault_core::Instruction::Transfer { amount: balance },
|
||||||
)
|
)
|
||||||
.expect("Failed to serialize genesis mint instruction");
|
.expect("Failed to serialize genesis transfer instruction");
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = nssa::public_transaction::WitnessSet::from_raw_parts(vec![]);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
PublicTransaction::new(message, witness_set)
|
||||||
let diff = ValidatedStateDiff::from_public_genesis_transaction(&tx, state)
|
|
||||||
.expect("Failed to execute genesis mint public transaction");
|
|
||||||
|
|
||||||
(tx.into(), diff)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load signing key from file or generate a new one if it doesn't exist.
|
/// Load signing key from file or generate a new one if it doesn't exist.
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use common::{
|
|||||||
block::Block,
|
block::Block,
|
||||||
transaction::{NSSATransaction, clock_invocation},
|
transaction::{NSSATransaction, clock_invocation},
|
||||||
};
|
};
|
||||||
use nssa::{GENESIS_BLOCK_ID, V03State, ValidatedStateDiff};
|
use nssa::{GENESIS_BLOCK_ID, V03State};
|
||||||
use rocksdb::{
|
use rocksdb::{
|
||||||
BoundColumnFamily, ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options,
|
BoundColumnFamily, ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options,
|
||||||
};
|
};
|
||||||
@ -189,16 +189,17 @@ impl RocksDBIO {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let state_diff = ValidatedStateDiff::from_public_genesis_transaction(
|
breakpoint
|
||||||
&genesis_tx,
|
.transition_from_public_transaction(
|
||||||
&breakpoint,
|
&genesis_tx,
|
||||||
)
|
block.header.block_id,
|
||||||
.map_err(|err| {
|
block.header.timestamp,
|
||||||
DbError::db_interaction_error(format!(
|
)
|
||||||
"Failed to create state diff from genesis transaction with err {err:?}"
|
.map_err(|err| {
|
||||||
))
|
DbError::db_interaction_error(format!(
|
||||||
})?;
|
"genesis transaction execution failed with err {err:?}"
|
||||||
breakpoint.apply_state_diff(state_diff);
|
))
|
||||||
|
})?;
|
||||||
} else {
|
} else {
|
||||||
transaction
|
transaction
|
||||||
.transaction_stateless_check()
|
.transaction_stateless_check()
|
||||||
|
|||||||
@ -56,9 +56,12 @@ fn main() {
|
|||||||
// private PDA (seed, npk) binding when pda_seeds match the private PDA derivation.
|
// private PDA (seed, npk) binding when pda_seeds match the private PDA derivation.
|
||||||
let mut auth_pda_pre = pda_pre;
|
let mut auth_pda_pre = pda_pre;
|
||||||
auth_pda_pre.is_authorized = true;
|
auth_pda_pre.is_authorized = true;
|
||||||
let auth_call =
|
let auth_call = ChainedCall::new(
|
||||||
ChainedCall::new(auth_transfer_id, vec![auth_pda_pre, recipient_pre], &amount)
|
auth_transfer_id,
|
||||||
.with_pda_seeds(vec![pda_seed]);
|
vec![auth_pda_pre, recipient_pre],
|
||||||
|
&authenticated_transfer_core::Instruction::Transfer { amount },
|
||||||
|
)
|
||||||
|
.with_pda_seeds(vec![pda_seed]);
|
||||||
|
|
||||||
ProgramOutput::new(
|
ProgramOutput::new(
|
||||||
self_program_id,
|
self_program_id,
|
||||||
@ -81,8 +84,12 @@ fn main() {
|
|||||||
// to authorize the PDA. authenticated_transfer will claim it with Claim::Authorized.
|
// to authorize the PDA. authenticated_transfer will claim it with Claim::Authorized.
|
||||||
let mut auth_pda_pre = pda_pre;
|
let mut auth_pda_pre = pda_pre;
|
||||||
auth_pda_pre.is_authorized = true;
|
auth_pda_pre.is_authorized = true;
|
||||||
let auth_call = ChainedCall::new(auth_transfer_id, vec![auth_pda_pre], &0_u128)
|
let auth_call = ChainedCall::new(
|
||||||
.with_pda_seeds(vec![pda_seed]);
|
auth_transfer_id,
|
||||||
|
vec![auth_pda_pre],
|
||||||
|
&authenticated_transfer_core::Instruction::Initialize,
|
||||||
|
)
|
||||||
|
.with_pda_seeds(vec![pda_seed]);
|
||||||
|
|
||||||
ProgramOutput::new(
|
ProgramOutput::new(
|
||||||
self_program_id,
|
self_program_id,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user