mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-03-18 18:03:07 +00:00
feat: add pedantic clippy lints
This commit is contained in:
parent
756f2f4135
commit
efe8393ba0
14
Cargo.toml
14
Cargo.toml
@ -146,4 +146,16 @@ codegen-units = 1
|
||||
warnings = "deny"
|
||||
|
||||
[workspace.lints]
|
||||
clippy.all = "deny"
|
||||
clippy.all = { level = "deny", priority = -1 }
|
||||
clippy.pedantic = { level = "deny", priority = -1 }
|
||||
|
||||
# Reason: documenting every function returning Result is too verbose and doesn't add much value when you have good error types.
|
||||
clippy.missing-errors-doc = "allow"
|
||||
# Reason: most of the panics are internal and not part of the public API, so documenting them is not necessary.
|
||||
clippy.missing-panics-doc = "allow"
|
||||
# Reason: this isn't always bad and actually works well for our financial and cryptography code.
|
||||
clippy.similar-names = "allow"
|
||||
# Reason: this lint is too strict and hard to fix.
|
||||
clippy.too-many-lines = "allow"
|
||||
# Reason: std hasher is fine for us in public functions
|
||||
clippy.implicit-hasher = "allow"
|
||||
|
||||
@ -96,9 +96,14 @@ impl BedrockClient {
|
||||
}
|
||||
|
||||
fn backoff_strategy(&self) -> impl Iterator<Item = Duration> {
|
||||
tokio_retry::strategy::FibonacciBackoff::from_millis(
|
||||
self.backoff.start_delay.as_millis() as u64
|
||||
)
|
||||
.take(self.backoff.max_retries)
|
||||
let start_delay_millis = self
|
||||
.backoff
|
||||
.start_delay
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("Start delay must be less than u64::MAX milliseconds");
|
||||
|
||||
tokio_retry::strategy::FibonacciBackoff::from_millis(start_delay_millis)
|
||||
.take(self.backoff.max_retries)
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +69,7 @@ pub struct HashableBlockData {
|
||||
}
|
||||
|
||||
impl HashableBlockData {
|
||||
#[must_use]
|
||||
pub fn into_pending_block(
|
||||
self,
|
||||
signing_key: &nssa::PrivateKey,
|
||||
@ -93,6 +94,7 @@ impl HashableBlockData {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn block_hash(&self) -> BlockHash {
|
||||
OwnHasher::hash(&borsh::to_vec(&self).unwrap())
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ impl FromStr for BasicAuth {
|
||||
|
||||
Ok(Self {
|
||||
username: username.to_string(),
|
||||
password: password.map(|p| p.to_string()),
|
||||
password: password.map(std::string::ToString::to_string),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,6 +49,7 @@ impl RpcError {
|
||||
/// A generic constructor.
|
||||
///
|
||||
/// Mostly for completeness, doesn't do anything but filling in the corresponding fields.
|
||||
#[must_use]
|
||||
pub fn new(code: i64, message: String, data: Option<Value>) -> Self {
|
||||
RpcError {
|
||||
code,
|
||||
@ -82,6 +83,7 @@ impl RpcError {
|
||||
}
|
||||
|
||||
/// Create a parse error.
|
||||
#[must_use]
|
||||
pub fn parse_error(e: String) -> Self {
|
||||
RpcError {
|
||||
code: -32_700,
|
||||
@ -93,12 +95,14 @@ impl RpcError {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn serialization_error(e: &str) -> Self {
|
||||
RpcError::new_internal_error(Some(Value::String(e.to_owned())), e)
|
||||
}
|
||||
|
||||
/// Helper method to define extract `INTERNAL_ERROR` in separate `RpcErrorKind`
|
||||
/// Returns `HANDLER_ERROR` if the error is not internal one
|
||||
#[must_use]
|
||||
pub fn new_internal_or_handler_error(error_data: Option<Value>, error_struct: Value) -> Self {
|
||||
if error_struct["name"] == "INTERNAL_ERROR" {
|
||||
let error_message = match error_struct["info"].get("error_message") {
|
||||
@ -111,6 +115,7 @@ impl RpcError {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new_internal_error(error_data: Option<Value>, info: &str) -> Self {
|
||||
RpcError {
|
||||
code: -32_000,
|
||||
@ -133,6 +138,7 @@ impl RpcError {
|
||||
}
|
||||
|
||||
/// Create a method not found error.
|
||||
#[must_use]
|
||||
pub fn method_not_found(method: String) -> Self {
|
||||
RpcError {
|
||||
code: -32_601,
|
||||
|
||||
@ -32,7 +32,7 @@ impl<'de> serde::Deserialize<'de> for Version {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct VersionVisitor;
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
impl<'de> Visitor<'de> for VersionVisitor {
|
||||
impl Visitor<'_> for VersionVisitor {
|
||||
type Value = Version;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
|
||||
@ -62,6 +62,7 @@ pub struct Request {
|
||||
}
|
||||
|
||||
impl Request {
|
||||
#[must_use]
|
||||
pub fn from_payload_version_2_0(method: String, payload: serde_json::Value) -> Self {
|
||||
Self {
|
||||
jsonrpc: Version,
|
||||
@ -75,6 +76,7 @@ impl Request {
|
||||
/// Answer the request with a (positive) reply.
|
||||
///
|
||||
/// The ID is taken from the request.
|
||||
#[must_use]
|
||||
pub fn reply(&self, reply: Value) -> Message {
|
||||
Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
@ -84,6 +86,7 @@ impl Request {
|
||||
}
|
||||
|
||||
/// Answer the request with an error.
|
||||
#[must_use]
|
||||
pub fn error(&self, error: RpcError) -> Message {
|
||||
Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
@ -212,6 +215,7 @@ impl Message {
|
||||
/// A constructor for a request.
|
||||
///
|
||||
/// The ID is auto-set to dontcare.
|
||||
#[must_use]
|
||||
pub fn request(method: String, params: Value) -> Self {
|
||||
let id = Value::from("dontcare");
|
||||
Message::Request(Request {
|
||||
@ -223,6 +227,7 @@ impl Message {
|
||||
}
|
||||
|
||||
/// Create a top-level error (without an ID).
|
||||
#[must_use]
|
||||
pub fn error(error: RpcError) -> Self {
|
||||
Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
@ -232,6 +237,7 @@ impl Message {
|
||||
}
|
||||
|
||||
/// A constructor for a notification.
|
||||
#[must_use]
|
||||
pub fn notification(method: String, params: Value) -> Self {
|
||||
Message::Notification(Notification {
|
||||
jsonrpc: Version,
|
||||
@ -241,6 +247,7 @@ impl Message {
|
||||
}
|
||||
|
||||
/// A constructor for a response.
|
||||
#[must_use]
|
||||
pub fn response(id: Value, result: Result<Value, RpcError>) -> Self {
|
||||
Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
@ -250,6 +257,7 @@ impl Message {
|
||||
}
|
||||
|
||||
/// Returns id or Null if there is no id.
|
||||
#[must_use]
|
||||
pub fn id(&self) -> Value {
|
||||
match self {
|
||||
Message::Request(req) => req.id.clone(),
|
||||
@ -276,6 +284,7 @@ impl Broken {
|
||||
///
|
||||
/// The error message for these things are specified in the RFC, so this just creates an error
|
||||
/// with the right values.
|
||||
#[must_use]
|
||||
pub fn reply(&self) -> Message {
|
||||
match *self {
|
||||
Broken::Unmatched(_) => Message::error(RpcError::parse_error(
|
||||
@ -343,7 +352,6 @@ mod tests {
|
||||
/// But since serialization doesn't have to produce the exact same result (order, spaces, …),
|
||||
/// we then serialize and deserialize the thing again and check it matches.
|
||||
#[test]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn message_serde() {
|
||||
// A helper for running one message test
|
||||
fn one(input: &str, expected: &Message) {
|
||||
@ -491,10 +499,10 @@ mod tests {
|
||||
// Something completely different
|
||||
one(r#"{"x": [1, 2, 3]}"#);
|
||||
|
||||
match from_str(r#"{]"#) {
|
||||
match from_str(r"{]") {
|
||||
Err(Broken::SyntaxError(_)) => (),
|
||||
other => panic!("Something unexpected: {other:?}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Test some non-trivial aspects of the constructors
|
||||
@ -503,7 +511,7 @@ mod tests {
|
||||
/// Most of it is related to the ids.
|
||||
#[test]
|
||||
#[allow(clippy::panic)]
|
||||
#[ignore]
|
||||
#[ignore = "Not a full coverage test"]
|
||||
fn constructors() {
|
||||
let msg1 = Message::request("call".to_owned(), json!([1, 2, 3]));
|
||||
let msg2 = Message::request("call".to_owned(), json!([1, 2, 3]));
|
||||
|
||||
@ -39,6 +39,7 @@ impl Default for RpcConfig {
|
||||
}
|
||||
|
||||
impl RpcConfig {
|
||||
#[must_use]
|
||||
pub fn new(addr: &str) -> Self {
|
||||
RpcConfig {
|
||||
addr: addr.to_owned(),
|
||||
@ -46,6 +47,7 @@ impl RpcConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_port(port: u16) -> Self {
|
||||
RpcConfig {
|
||||
addr: format!("0.0.0.0:{port}"),
|
||||
|
||||
@ -30,6 +30,14 @@ use crate::{
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct SequencerRpcResponse {
|
||||
jsonrpc: String,
|
||||
result: serde_json::Value,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SequencerClient {
|
||||
pub client: reqwest::Client,
|
||||
@ -86,14 +94,6 @@ impl SequencerClient {
|
||||
})
|
||||
.await?;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct SequencerRpcResponse {
|
||||
pub jsonrpc: String,
|
||||
pub result: serde_json::Value,
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
if let Ok(response) = serde_json::from_value::<SequencerRpcResponse>(response_vall.clone())
|
||||
{
|
||||
Ok(response.result)
|
||||
|
||||
@ -8,6 +8,7 @@ use crate::{
|
||||
|
||||
// Helpers
|
||||
|
||||
#[must_use]
|
||||
pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
|
||||
nssa::PrivateKey::try_new([37; 32]).unwrap()
|
||||
}
|
||||
@ -21,6 +22,7 @@ pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
|
||||
/// `prev_hash` - hash of previous block, provide None for genesis
|
||||
///
|
||||
/// `transactions` - vector of `EncodedTransaction` objects
|
||||
#[must_use]
|
||||
pub fn produce_dummy_block(
|
||||
id: u64,
|
||||
prev_hash: Option<HashType>,
|
||||
@ -36,6 +38,7 @@ pub fn produce_dummy_block(
|
||||
block_data.into_pending_block(&sequencer_sign_key_for_testing(), [0; 32])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn produce_dummy_empty_transaction() -> NSSATransaction {
|
||||
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
||||
let account_ids = vec![];
|
||||
@ -56,12 +59,13 @@ pub fn produce_dummy_empty_transaction() -> NSSATransaction {
|
||||
NSSATransaction::Public(nssa_tx)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn create_transaction_native_token_transfer(
|
||||
from: AccountId,
|
||||
nonce: u128,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
signing_key: nssa::PrivateKey,
|
||||
signing_key: &nssa::PrivateKey,
|
||||
) -> NSSATransaction {
|
||||
let account_ids = vec![from, to];
|
||||
let nonces = vec![nonce];
|
||||
@ -73,7 +77,7 @@ pub fn create_transaction_native_token_transfer(
|
||||
balance_to_move,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]);
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
||||
|
||||
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ pub enum NSSATransaction {
|
||||
}
|
||||
|
||||
impl NSSATransaction {
|
||||
#[must_use]
|
||||
pub fn hash(&self) -> HashType {
|
||||
HashType(match self {
|
||||
NSSATransaction::Public(tx) => tx.hash(),
|
||||
@ -21,6 +22,7 @@ impl NSSATransaction {
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
|
||||
match self {
|
||||
NSSATransaction::ProgramDeployment(tx) => tx.affected_public_account_ids(),
|
||||
|
||||
@ -36,9 +36,7 @@ fn main() {
|
||||
// Fail if the input account is not authorized
|
||||
// The `is_authorized` field will be correctly populated or verified by the system if
|
||||
// authorization is provided.
|
||||
if !pre_state.is_authorized {
|
||||
panic!("Missing required authorization");
|
||||
}
|
||||
assert!(pre_state.is_authorized, "Missing required authorization");
|
||||
// ####
|
||||
|
||||
// Construct the post state account values
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata},
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
program::{
|
||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
},
|
||||
@ -35,12 +35,12 @@ fn build_post_state(post_account: Account) -> AccountPostState {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(pre_state: AccountWithMetadata, greeting: Vec<u8>) -> AccountPostState {
|
||||
fn write(pre_state: AccountWithMetadata, greeting: &[u8]) -> AccountPostState {
|
||||
// Construct the post state account values
|
||||
let post_account = {
|
||||
let mut this = pre_state.account.clone();
|
||||
let mut this = pre_state.account;
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&greeting);
|
||||
bytes.extend_from_slice(greeting);
|
||||
this.data = bytes
|
||||
.try_into()
|
||||
.expect("Data should fit within the allowed limits");
|
||||
@ -50,21 +50,18 @@ fn write(pre_state: AccountWithMetadata, greeting: Vec<u8>) -> AccountPostState
|
||||
build_post_state(post_account)
|
||||
}
|
||||
|
||||
fn move_data(
|
||||
from_pre: &AccountWithMetadata,
|
||||
to_pre: &AccountWithMetadata,
|
||||
) -> Vec<AccountPostState> {
|
||||
fn move_data(from_pre: AccountWithMetadata, to_pre: AccountWithMetadata) -> Vec<AccountPostState> {
|
||||
// Construct the post state account values
|
||||
let from_data: Vec<u8> = from_pre.account.data.clone().into();
|
||||
|
||||
let from_post = {
|
||||
let mut this = from_pre.account.clone();
|
||||
this.data = Default::default();
|
||||
let mut this = from_pre.account;
|
||||
this.data = Data::default();
|
||||
build_post_state(this)
|
||||
};
|
||||
|
||||
let to_post = {
|
||||
let mut this = to_pre.account.clone();
|
||||
let mut this = to_pre.account;
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&from_data);
|
||||
this.data = bytes
|
||||
@ -88,11 +85,11 @@ fn main() {
|
||||
|
||||
let post_states = match (pre_states.as_slice(), function_id, data.len()) {
|
||||
([account_pre], WRITE_FUNCTION_ID, _) => {
|
||||
let post = write(account_pre.clone(), data);
|
||||
let post = write(account_pre.clone(), &data);
|
||||
vec![post]
|
||||
}
|
||||
([account_from_pre, account_to_pre], MOVE_DATA_FUNCTION_ID, 0) => {
|
||||
move_data(account_from_pre, account_to_pre)
|
||||
move_data(account_from_pre.clone(), account_to_pre.clone())
|
||||
}
|
||||
_ => panic!("invalid params"),
|
||||
};
|
||||
|
||||
@ -29,7 +29,7 @@ fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: _,
|
||||
instruction: (),
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<()>();
|
||||
|
||||
@ -34,7 +34,7 @@ fn main() {
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: _,
|
||||
instruction: (),
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<()>();
|
||||
|
||||
@ -148,5 +148,5 @@ async fn main() {
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ pub async fn get_account(account_id: AccountId) -> Result<Account, ServerFnError
|
||||
client
|
||||
.get_account(account_id)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Search for a block, transaction, or account by query string
|
||||
@ -80,7 +80,7 @@ pub async fn get_block_by_id(block_id: BlockId) -> Result<Block, ServerFnError>
|
||||
client
|
||||
.get_block_by_id(block_id)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Get latest block ID
|
||||
@ -91,7 +91,7 @@ pub async fn get_latest_block_id() -> Result<BlockId, ServerFnError> {
|
||||
client
|
||||
.get_last_finalized_block_id()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Get block by hash
|
||||
@ -102,7 +102,7 @@ pub async fn get_block_by_hash(block_hash: HashType) -> Result<Block, ServerFnEr
|
||||
client
|
||||
.get_block_by_hash(block_hash)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Get transaction by hash
|
||||
@ -113,33 +113,33 @@ pub async fn get_transaction(tx_hash: HashType) -> Result<Transaction, ServerFnE
|
||||
client
|
||||
.get_transaction(tx_hash)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Get blocks with pagination
|
||||
#[server]
|
||||
pub async fn get_blocks(before: Option<u64>, limit: u32) -> Result<Vec<Block>, ServerFnError> {
|
||||
pub async fn get_blocks(before: Option<BlockId>, limit: u64) -> Result<Vec<Block>, ServerFnError> {
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
client
|
||||
.get_blocks(before, limit)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Get transactions by account
|
||||
#[server]
|
||||
pub async fn get_transactions_by_account(
|
||||
account_id: AccountId,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
offset: u64,
|
||||
limit: u64,
|
||||
) -> Result<Vec<Transaction>, ServerFnError> {
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
client
|
||||
.get_transactions_by_account(account_id, limit, offset)
|
||||
.get_transactions_by_account(account_id, offset, limit)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Create the RPC client for the indexer service (server-side only)
|
||||
|
||||
@ -13,6 +13,10 @@ fn transaction_type_info(tx: &Transaction) -> (&'static str, &'static str) {
|
||||
|
||||
/// Transaction preview component
|
||||
#[component]
|
||||
#[expect(
|
||||
clippy::needless_pass_by_value,
|
||||
reason = "Leptos component props are passed by value by framework convention"
|
||||
)]
|
||||
pub fn TransactionPreview(transaction: Transaction) -> impl IntoView {
|
||||
let hash = transaction.hash();
|
||||
let hash_str = hash.to_string();
|
||||
|
||||
@ -3,7 +3,10 @@
|
||||
/// Format timestamp to human-readable string
|
||||
pub fn format_timestamp(timestamp: u64) -> String {
|
||||
let seconds = timestamp / 1000;
|
||||
let datetime = chrono::DateTime::from_timestamp(seconds as i64, 0)
|
||||
.unwrap_or_else(|| chrono::DateTime::from_timestamp(0, 0).unwrap());
|
||||
let datetime = chrono::DateTime::from_timestamp(
|
||||
i64::try_from(seconds).expect("Timestamp out of range"),
|
||||
0,
|
||||
)
|
||||
.unwrap_or_else(|| chrono::DateTime::from_timestamp(0, 0).unwrap());
|
||||
datetime.format("%Y-%m-%d %H:%M:%S UTC").to_string()
|
||||
}
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
#![expect(
|
||||
clippy::must_use_candidate,
|
||||
reason = "Warns on code generated by leptos macros"
|
||||
)]
|
||||
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::{Meta, Stylesheet, Title, provide_meta_context};
|
||||
use leptos_router::{
|
||||
|
||||
@ -8,8 +8,6 @@ async fn main() {
|
||||
use leptos_axum::{LeptosRoutes, generate_route_list};
|
||||
use leptos_meta::MetaTags;
|
||||
|
||||
env_logger::init();
|
||||
|
||||
/// LEZ Block Explorer Server CLI arguments.
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
@ -19,6 +17,8 @@ async fn main() {
|
||||
indexer_rpc_url: url::Url,
|
||||
}
|
||||
|
||||
env_logger::init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
let conf = get_configuration(None).unwrap();
|
||||
|
||||
@ -10,11 +10,11 @@ use crate::{api, components::TransactionPreview};
|
||||
#[component]
|
||||
pub fn AccountPage() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let (tx_offset, set_tx_offset) = signal(0u32);
|
||||
let (tx_offset, set_tx_offset) = signal(0u64);
|
||||
let (all_transactions, set_all_transactions) = signal(Vec::new());
|
||||
let (is_loading, set_is_loading) = signal(false);
|
||||
let (has_more, set_has_more) = signal(true);
|
||||
let tx_limit = 10u32;
|
||||
let tx_limit = 10u64;
|
||||
|
||||
// Parse account ID from URL params
|
||||
let account_id = move || {
|
||||
@ -35,7 +35,7 @@ pub fn AccountPage() -> impl IntoView {
|
||||
// Load initial transactions
|
||||
let transactions_resource = Resource::new(account_id, move |acc_id_opt| async move {
|
||||
match acc_id_opt {
|
||||
Some(acc_id) => api::get_transactions_by_account(acc_id, tx_limit, 0).await,
|
||||
Some(acc_id) => api::get_transactions_by_account(acc_id, 0, tx_limit).await,
|
||||
None => Err(leptos::prelude::ServerFnError::ServerError(
|
||||
"Invalid account ID".to_string(),
|
||||
)),
|
||||
@ -46,7 +46,9 @@ pub fn AccountPage() -> impl IntoView {
|
||||
Effect::new(move || {
|
||||
if let Some(Ok(txs)) = transactions_resource.get() {
|
||||
set_all_transactions.set(txs.clone());
|
||||
set_has_more.set(txs.len() as u32 == tx_limit);
|
||||
set_has_more.set(
|
||||
u64::try_from(txs.len()).expect("Transaction count should fit in u64") == tx_limit,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -61,14 +63,15 @@ pub fn AccountPage() -> impl IntoView {
|
||||
set_tx_offset.set(current_offset);
|
||||
|
||||
leptos::task::spawn_local(async move {
|
||||
match api::get_transactions_by_account(acc_id, tx_limit, current_offset).await {
|
||||
match api::get_transactions_by_account(acc_id, current_offset, tx_limit).await {
|
||||
Ok(new_txs) => {
|
||||
let txs_count = new_txs.len() as u32;
|
||||
let txs_count =
|
||||
u64::try_from(new_txs.len()).expect("Transaction count should fit in u64");
|
||||
set_all_transactions.update(|txs| txs.extend(new_txs));
|
||||
set_has_more.set(txs_count == tx_limit);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to load more transactions: {}", e);
|
||||
log::error!("Failed to load more transactions: {e}");
|
||||
}
|
||||
}
|
||||
set_is_loading.set(false);
|
||||
@ -123,7 +126,7 @@ pub fn AccountPage() -> impl IntoView {
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">"Data:"</span>
|
||||
<span class="info-value">{format!("{} bytes", data_len)}</span>
|
||||
<span class="info-value">{format!("{data_len} bytes")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -190,7 +193,7 @@ pub fn AccountPage() -> impl IntoView {
|
||||
Err(e) => {
|
||||
view! {
|
||||
<div class="error">
|
||||
{format!("Failed to load transactions: {}", e)}
|
||||
{format!("Failed to load transactions: {e}")}
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
@ -208,7 +211,7 @@ pub fn AccountPage() -> impl IntoView {
|
||||
view! {
|
||||
<div class="error-page">
|
||||
<h1>"Error"</h1>
|
||||
<p>{format!("Failed to load account: {}", e)}</p>
|
||||
<p>{format!("Failed to load account: {e}")}</p>
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
|
||||
@ -144,7 +144,7 @@ pub fn BlockPage() -> impl IntoView {
|
||||
view! {
|
||||
<div class="error-page">
|
||||
<h1>"Error"</h1>
|
||||
<p>{format!("Failed to load block: {}", e)}</p>
|
||||
<p>{format!("Failed to load block: {e}")}</p>
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::hooks::{use_navigate, use_query_map};
|
||||
use leptos_router::{
|
||||
NavigateOptions,
|
||||
hooks::{use_navigate, use_query_map},
|
||||
};
|
||||
use web_sys::SubmitEvent;
|
||||
|
||||
use crate::{
|
||||
@ -7,7 +10,7 @@ use crate::{
|
||||
components::{AccountPreview, BlockPreview, TransactionPreview},
|
||||
};
|
||||
|
||||
const RECENT_BLOCKS_LIMIT: u32 = 10;
|
||||
const RECENT_BLOCKS_LIMIT: u64 = 10;
|
||||
|
||||
/// Main page component
|
||||
#[component]
|
||||
@ -33,7 +36,7 @@ pub fn MainPage() -> impl IntoView {
|
||||
match api::search(query).await {
|
||||
Ok(result) => Some(result),
|
||||
Err(e) => {
|
||||
log::error!("Search error: {}", e);
|
||||
log::error!("Search error: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
@ -48,7 +51,7 @@ pub fn MainPage() -> impl IntoView {
|
||||
// Load recent blocks on mount
|
||||
let recent_blocks_resource = Resource::new(
|
||||
|| (),
|
||||
|_| async { api::get_blocks(None, RECENT_BLOCKS_LIMIT).await },
|
||||
|()| async { api::get_blocks(None, RECENT_BLOCKS_LIMIT).await },
|
||||
);
|
||||
|
||||
// Update all_blocks when initial load completes
|
||||
@ -57,8 +60,11 @@ pub fn MainPage() -> impl IntoView {
|
||||
let oldest_id = blocks.last().map(|b| b.header.block_id);
|
||||
set_all_blocks.set(blocks.clone());
|
||||
set_oldest_loaded_block_id.set(oldest_id);
|
||||
set_has_more_blocks
|
||||
.set(blocks.len() as u32 == RECENT_BLOCKS_LIMIT && oldest_id.unwrap_or(0) > 1);
|
||||
set_has_more_blocks.set(
|
||||
u64::try_from(blocks.len()).expect("usize should fit in u64")
|
||||
== RECENT_BLOCKS_LIMIT
|
||||
&& oldest_id.unwrap_or(0) > 1,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -75,7 +81,8 @@ pub fn MainPage() -> impl IntoView {
|
||||
leptos::task::spawn_local(async move {
|
||||
match api::get_blocks(before_id, RECENT_BLOCKS_LIMIT).await {
|
||||
Ok(new_blocks) => {
|
||||
let blocks_count = new_blocks.len() as u32;
|
||||
let blocks_count =
|
||||
u64::try_from(new_blocks.len()).expect("usize should fit in u64");
|
||||
let new_oldest_id = new_blocks.last().map(|b| b.header.block_id);
|
||||
set_all_blocks.update(|blocks| blocks.extend(new_blocks));
|
||||
set_oldest_loaded_block_id.set(new_oldest_id);
|
||||
@ -83,7 +90,7 @@ pub fn MainPage() -> impl IntoView {
|
||||
.set(blocks_count == RECENT_BLOCKS_LIMIT && new_oldest_id.unwrap_or(0) > 1);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to load more blocks: {}", e);
|
||||
log::error!("Failed to load more blocks: {e}");
|
||||
}
|
||||
}
|
||||
set_is_loading_blocks.set(false);
|
||||
@ -95,13 +102,13 @@ pub fn MainPage() -> impl IntoView {
|
||||
ev.prevent_default();
|
||||
let query = search_query.get();
|
||||
if query.is_empty() {
|
||||
navigate("?", Default::default());
|
||||
navigate("?", NavigateOptions::default());
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(
|
||||
&format!("?q={}", urlencoding::encode(&query)),
|
||||
Default::default(),
|
||||
NavigateOptions::default(),
|
||||
);
|
||||
};
|
||||
|
||||
@ -142,78 +149,78 @@ pub fn MainPage() -> impl IntoView {
|
||||
view! {
|
||||
<div class="search-results">
|
||||
<h2>"Search Results"</h2>
|
||||
{if !has_results {
|
||||
view! { <div class="not-found">"No results found"</div> }
|
||||
.into_any()
|
||||
} else {
|
||||
view! {
|
||||
<div class="results-container">
|
||||
{if !blocks.is_empty() {
|
||||
view! {
|
||||
<div class="results-section">
|
||||
<h3>"Blocks"</h3>
|
||||
<div class="results-list">
|
||||
{blocks
|
||||
.into_iter()
|
||||
.map(|block| {
|
||||
view! { <BlockPreview block=block /> }
|
||||
})
|
||||
.collect::<Vec<_>>()}
|
||||
{if has_results {
|
||||
view! {
|
||||
<div class="results-container">
|
||||
{if blocks.is_empty() {
|
||||
().into_any()
|
||||
} else {
|
||||
view! {
|
||||
<div class="results-section">
|
||||
<h3>"Blocks"</h3>
|
||||
<div class="results-list">
|
||||
{blocks
|
||||
.into_iter()
|
||||
.map(|block| {
|
||||
view! { <BlockPreview block=block /> }
|
||||
})
|
||||
.collect::<Vec<_>>()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
().into_any()
|
||||
}}
|
||||
}
|
||||
.into_any()
|
||||
}}
|
||||
|
||||
{if !transactions.is_empty() {
|
||||
view! {
|
||||
<div class="results-section">
|
||||
<h3>"Transactions"</h3>
|
||||
<div class="results-list">
|
||||
{transactions
|
||||
.into_iter()
|
||||
.map(|tx| {
|
||||
view! { <TransactionPreview transaction=tx /> }
|
||||
})
|
||||
.collect::<Vec<_>>()}
|
||||
{if transactions.is_empty() {
|
||||
().into_any()
|
||||
} else {
|
||||
view! {
|
||||
<div class="results-section">
|
||||
<h3>"Transactions"</h3>
|
||||
<div class="results-list">
|
||||
{transactions
|
||||
.into_iter()
|
||||
.map(|tx| {
|
||||
view! { <TransactionPreview transaction=tx /> }
|
||||
})
|
||||
.collect::<Vec<_>>()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
().into_any()
|
||||
}}
|
||||
}
|
||||
.into_any()
|
||||
}}
|
||||
|
||||
{if !accounts.is_empty() {
|
||||
view! {
|
||||
<div class="results-section">
|
||||
<h3>"Accounts"</h3>
|
||||
<div class="results-list">
|
||||
{accounts
|
||||
.into_iter()
|
||||
.map(|(id, account)| {
|
||||
view! {
|
||||
<AccountPreview
|
||||
account_id=id
|
||||
account=account
|
||||
/>
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()}
|
||||
{if accounts.is_empty() {
|
||||
().into_any()
|
||||
} else {
|
||||
view! {
|
||||
<div class="results-section">
|
||||
<h3>"Accounts"</h3>
|
||||
<div class="results-list">
|
||||
{accounts
|
||||
.into_iter()
|
||||
.map(|(id, account)| {
|
||||
view! {
|
||||
<AccountPreview
|
||||
account_id=id
|
||||
account=account
|
||||
/>
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
().into_any()
|
||||
}}
|
||||
}
|
||||
.into_any()
|
||||
}}
|
||||
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
view! { <div class="not-found">"No results found"</div> }
|
||||
.into_any()
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
@ -274,7 +281,7 @@ pub fn MainPage() -> impl IntoView {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
view! { <div class="error">{format!("Error: {}", e)}</div> }
|
||||
view! { <div class="error">{format!("Error: {e}")}</div> }
|
||||
.into_any()
|
||||
}
|
||||
})
|
||||
|
||||
@ -105,7 +105,7 @@ pub fn TransactionPage() -> impl IntoView {
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">"Proof Size:"</span>
|
||||
<span class="info-value">{format!("{} bytes", proof_len)}</span>
|
||||
<span class="info-value">{format!("{proof_len} bytes")}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">"Signatures:"</span>
|
||||
@ -212,7 +212,7 @@ pub fn TransactionPage() -> impl IntoView {
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">"Proof Size:"</span>
|
||||
<span class="info-value">{format!("{} bytes", proof_len)}</span>
|
||||
<span class="info-value">{format!("{proof_len} bytes")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -284,7 +284,7 @@ pub fn TransactionPage() -> impl IntoView {
|
||||
<div class="info-row">
|
||||
<span class="info-label">"Bytecode Size:"</span>
|
||||
<span class="info-value">
|
||||
{format!("{} bytes", bytecode_len)}
|
||||
{format!("{bytecode_len} bytes")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -302,7 +302,7 @@ pub fn TransactionPage() -> impl IntoView {
|
||||
view! {
|
||||
<div class="error-page">
|
||||
<h1>"Error"</h1>
|
||||
<p>{format!("Failed to load transaction: {}", e)}</p>
|
||||
<p>{format!("Failed to load transaction: {e}")}</p>
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
|
||||
@ -3,7 +3,7 @@ use std::{path::Path, sync::Arc};
|
||||
use anyhow::Result;
|
||||
use bedrock_client::HeaderId;
|
||||
use common::{
|
||||
block::{BedrockStatus, Block},
|
||||
block::{BedrockStatus, Block, BlockId},
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
use nssa::{Account, AccountId, V02State};
|
||||
@ -50,7 +50,7 @@ impl IndexerStore {
|
||||
Ok(self.dbio.get_block(id)?)
|
||||
}
|
||||
|
||||
pub fn get_block_batch(&self, before: Option<u64>, limit: u64) -> Result<Vec<Block>> {
|
||||
pub fn get_block_batch(&self, before: Option<BlockId>, limit: u64) -> Result<Vec<Block>> {
|
||||
Ok(self.dbio.get_block_batch(before, limit)?)
|
||||
}
|
||||
|
||||
@ -79,12 +79,14 @@ impl IndexerStore {
|
||||
Ok(self.dbio.get_acc_transactions(acc_id, offset, limit)?)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn genesis_id(&self) -> u64 {
|
||||
self.dbio
|
||||
.get_meta_first_block_in_db()
|
||||
.expect("Must be set at the DB startup")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn last_block(&self) -> u64 {
|
||||
self.dbio
|
||||
.get_meta_last_block_in_db()
|
||||
|
||||
@ -43,11 +43,16 @@ pub struct IndexerConfig {
|
||||
|
||||
impl IndexerConfig {
|
||||
pub fn from_path(config_path: &Path) -> Result<IndexerConfig> {
|
||||
let file = File::open(config_path)
|
||||
.with_context(|| format!("Failed to open indexer config at {config_path:?}"))?;
|
||||
let file = File::open(config_path).with_context(|| {
|
||||
format!("Failed to open indexer config at {}", config_path.display())
|
||||
})?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
serde_json::from_reader(reader)
|
||||
.with_context(|| format!("Failed to parse indexer config at {config_path:?}"))
|
||||
serde_json::from_reader(reader).with_context(|| {
|
||||
format!(
|
||||
"Failed to parse indexer config at {}",
|
||||
config_path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,43 +98,40 @@ impl IndexerCore {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn subscribe_parse_block_stream(&self) -> impl futures::Stream<Item = Result<Block>> {
|
||||
pub fn subscribe_parse_block_stream(&self) -> impl futures::Stream<Item = Result<Block>> {
|
||||
async_stream::stream! {
|
||||
info!("Searching for initial header");
|
||||
|
||||
let last_l1_lib_header = self.store.last_observed_l1_lib_header()?;
|
||||
|
||||
let mut prev_last_l1_lib_header = match last_l1_lib_header {
|
||||
Some(last_l1_lib_header) => {
|
||||
info!("Last l1 lib header found: {last_l1_lib_header}");
|
||||
last_l1_lib_header
|
||||
},
|
||||
None => {
|
||||
info!("Last l1 lib header not found in DB");
|
||||
info!("Searching for the start of a channel");
|
||||
let mut prev_last_l1_lib_header = if let Some(last_l1_lib_header) = last_l1_lib_header {
|
||||
info!("Last l1 lib header found: {last_l1_lib_header}");
|
||||
last_l1_lib_header
|
||||
} else {
|
||||
info!("Last l1 lib header not found in DB");
|
||||
info!("Searching for the start of a channel");
|
||||
|
||||
let BackfillData {
|
||||
block_data: start_buff,
|
||||
curr_fin_l1_lib_header: last_l1_lib_header,
|
||||
} = self.search_for_channel_start().await?;
|
||||
let BackfillData {
|
||||
block_data: start_buff,
|
||||
curr_fin_l1_lib_header: last_l1_lib_header,
|
||||
} = self.search_for_channel_start().await?;
|
||||
|
||||
for BackfillBlockData {
|
||||
l2_blocks: l2_block_vec,
|
||||
l1_header,
|
||||
} in start_buff {
|
||||
let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect();
|
||||
l2_blocks_parsed_ids.sort();
|
||||
info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids);
|
||||
for BackfillBlockData {
|
||||
l2_blocks: l2_block_vec,
|
||||
l1_header,
|
||||
} in start_buff {
|
||||
let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect();
|
||||
l2_blocks_parsed_ids.sort_unstable();
|
||||
info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids);
|
||||
|
||||
for l2_block in l2_block_vec {
|
||||
self.store.put_block(l2_block.clone(), l1_header)?;
|
||||
for l2_block in l2_block_vec {
|
||||
self.store.put_block(l2_block.clone(), l1_header)?;
|
||||
|
||||
yield Ok(l2_block);
|
||||
}
|
||||
yield Ok(l2_block);
|
||||
}
|
||||
}
|
||||
|
||||
last_l1_lib_header
|
||||
},
|
||||
last_l1_lib_header
|
||||
};
|
||||
|
||||
info!("Searching for initial header finished");
|
||||
@ -157,7 +154,7 @@ impl IndexerCore {
|
||||
l1_header: header,
|
||||
} in buff {
|
||||
let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect();
|
||||
l2_blocks_parsed_ids.sort();
|
||||
l2_blocks_parsed_ids.sort_unstable();
|
||||
info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids);
|
||||
|
||||
for l2_block in l2_block_vec {
|
||||
@ -177,14 +174,14 @@ impl IndexerCore {
|
||||
async fn get_next_lib(&self, prev_lib: HeaderId) -> Result<HeaderId> {
|
||||
loop {
|
||||
let next_lib = self.get_lib().await?;
|
||||
if next_lib != prev_lib {
|
||||
break Ok(next_lib);
|
||||
} else {
|
||||
if next_lib == prev_lib {
|
||||
info!(
|
||||
"Wait {:?} to not spam the node",
|
||||
self.config.consensus_info_polling_interval
|
||||
);
|
||||
tokio::time::sleep(self.config.consensus_info_polling_interval).await;
|
||||
} else {
|
||||
break Ok(next_lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -204,15 +201,13 @@ impl IndexerCore {
|
||||
let mut cycle_header = curr_last_l1_lib_header;
|
||||
|
||||
loop {
|
||||
let cycle_block =
|
||||
if let Some(block) = self.bedrock_client.get_block_by_id(cycle_header).await? {
|
||||
block
|
||||
} else {
|
||||
// First run can reach root easily
|
||||
// so here we are optimistic about L1
|
||||
// failing to get parent.
|
||||
break;
|
||||
};
|
||||
let Some(cycle_block) = self.bedrock_client.get_block_by_id(cycle_header).await?
|
||||
else {
|
||||
// First run can reach root easily
|
||||
// so here we are optimistic about L1
|
||||
// failing to get parent.
|
||||
break;
|
||||
};
|
||||
|
||||
// It would be better to have id, but block does not have it, so slot will do.
|
||||
info!(
|
||||
@ -289,10 +284,9 @@ impl IndexerCore {
|
||||
|
||||
if cycle_block.header().id() == last_fin_l1_lib_header {
|
||||
break;
|
||||
} else {
|
||||
// Step back to parent
|
||||
cycle_header = cycle_block.header().parent();
|
||||
}
|
||||
// Step back to parent
|
||||
cycle_header = cycle_block.header().parent();
|
||||
|
||||
// It would be better to have id, but block does not have it, so slot will do.
|
||||
info!(
|
||||
@ -335,7 +329,7 @@ fn parse_block_owned(
|
||||
}) if channel_id == decoded_channel_id => {
|
||||
borsh::from_slice::<Block>(inscription)
|
||||
.inspect_err(|err| {
|
||||
error!("Failed to deserialize our inscription with err: {err:#?}")
|
||||
error!("Failed to deserialize our inscription with err: {err:#?}");
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
//! Conversions between indexer_service_protocol types and nssa/nssa_core types
|
||||
//! Conversions between `indexer_service_protocol` types and `nssa/nssa_core` types
|
||||
|
||||
use crate::*;
|
||||
use crate::{
|
||||
Account, AccountId, BedrockStatus, Block, BlockBody, BlockHeader, Ciphertext, Commitment,
|
||||
CommitmentSetDigest, Data, EncryptedAccountData, EphemeralPublicKey, HashType, MantleMsgId,
|
||||
Nullifier, PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage,
|
||||
ProgramDeploymentTransaction, ProgramId, Proof, PublicKey, PublicMessage, PublicTransaction,
|
||||
Signature, Transaction, WitnessSet,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Account-related conversions
|
||||
|
||||
@ -141,6 +141,7 @@ pub enum Transaction {
|
||||
|
||||
impl Transaction {
|
||||
/// Get the hash of the transaction
|
||||
#[must_use]
|
||||
pub fn hash(&self) -> &self::HashType {
|
||||
match self {
|
||||
Transaction::Public(tx) => &tx.hash,
|
||||
@ -308,7 +309,7 @@ mod base64 {
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub mod arr {
|
||||
use super::*;
|
||||
use super::{Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S: Serializer>(v: &[u8], s: S) -> Result<S::Ok, S::Error> {
|
||||
super::serialize(v, s)
|
||||
|
||||
@ -44,16 +44,16 @@ pub trait Rpc {
|
||||
#[method(name = "getBlocks")]
|
||||
async fn get_blocks(
|
||||
&self,
|
||||
before: Option<u64>,
|
||||
limit: u32,
|
||||
before: Option<BlockId>,
|
||||
limit: u64,
|
||||
) -> Result<Vec<Block>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getTransactionsByAccount")]
|
||||
async fn get_transactions_by_account(
|
||||
&self,
|
||||
account_id: AccountId,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
offset: u64,
|
||||
limit: u64,
|
||||
) -> Result<Vec<Transaction>, ErrorObjectOwned>;
|
||||
|
||||
// ToDo: expand healthcheck response into some kind of report
|
||||
|
||||
@ -23,6 +23,7 @@ impl IndexerHandle {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn addr(&self) -> SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
@ -33,9 +34,14 @@ impl IndexerHandle {
|
||||
.take()
|
||||
.expect("Indexer server handle is set");
|
||||
|
||||
handle.stopped().await
|
||||
handle.stopped().await;
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::redundant_closure_for_method_calls,
|
||||
reason = "Clippy suggested path jsonrpsee::jsonrpsee_server::ServerHandle is not accessible"
|
||||
)]
|
||||
#[must_use]
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
self.server_handle
|
||||
.as_ref()
|
||||
|
||||
@ -26,10 +26,10 @@ async fn main() -> Result<()> {
|
||||
let indexer_handle = indexer_service::run_server(config, port).await?;
|
||||
|
||||
tokio::select! {
|
||||
_ = cancellation_token.cancelled() => {
|
||||
() = cancellation_token.cancelled() => {
|
||||
info!("Shutting down server...");
|
||||
}
|
||||
_ = indexer_handle.stopped() => {
|
||||
() = indexer_handle.stopped() => {
|
||||
error!("Server stopped unexpectedly");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
#![expect(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_lossless,
|
||||
reason = "Mock service uses intentional casts and format patterns for test data generation"
|
||||
)]
|
||||
use std::collections::HashMap;
|
||||
|
||||
use indexer_service_protocol::{
|
||||
@ -9,7 +14,7 @@ use indexer_service_protocol::{
|
||||
};
|
||||
use jsonrpsee::{core::SubscriptionResult, types::ErrorObjectOwned};
|
||||
|
||||
/// A mock implementation of the IndexerService RPC for testing purposes.
|
||||
/// A mock implementation of the `IndexerService` RPC for testing purposes.
|
||||
pub struct MockIndexerService {
|
||||
blocks: Vec<Block>,
|
||||
accounts: HashMap<AccountId, Account>,
|
||||
@ -17,6 +22,7 @@ pub struct MockIndexerService {
|
||||
}
|
||||
|
||||
impl MockIndexerService {
|
||||
#[must_use]
|
||||
pub fn new_with_mock_blocks() -> Self {
|
||||
let mut blocks = Vec::new();
|
||||
let mut accounts = HashMap::new();
|
||||
@ -136,7 +142,7 @@ impl MockIndexerService {
|
||||
block_id,
|
||||
prev_block_hash: prev_hash,
|
||||
hash: block_hash,
|
||||
timestamp: 1704067200000 + (block_id * 12000), // ~12 seconds per block
|
||||
timestamp: 1_704_067_200_000 + (block_id * 12_000), // ~12 seconds per block
|
||||
signature: Signature([0u8; 64]),
|
||||
},
|
||||
body: BlockBody {
|
||||
@ -197,7 +203,7 @@ impl indexer_service_rpc::RpcServer for MockIndexerService {
|
||||
.ok_or_else(|| {
|
||||
ErrorObjectOwned::owned(
|
||||
-32001,
|
||||
format!("Block with ID {} not found", block_id),
|
||||
format!("Block with ID {block_id} not found"),
|
||||
None::<()>,
|
||||
)
|
||||
})
|
||||
@ -227,15 +233,18 @@ impl indexer_service_rpc::RpcServer for MockIndexerService {
|
||||
|
||||
async fn get_blocks(
|
||||
&self,
|
||||
before: Option<u64>,
|
||||
limit: u32,
|
||||
before: Option<BlockId>,
|
||||
limit: u64,
|
||||
) -> Result<Vec<Block>, ErrorObjectOwned> {
|
||||
let start_id = before.map_or_else(|| self.blocks.len() as u64, |id| id.saturating_sub(1));
|
||||
let start_id = before.map_or_else(
|
||||
|| self.blocks.len(),
|
||||
|id| usize::try_from(id.saturating_sub(1)).expect("u64 should fit in usize"),
|
||||
);
|
||||
|
||||
let result = (1..=start_id)
|
||||
.rev()
|
||||
.take(limit as usize)
|
||||
.map_while(|block_id| self.blocks.get(block_id as usize - 1).cloned())
|
||||
.map_while(|block_id| self.blocks.get(block_id - 1).cloned())
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
@ -244,8 +253,8 @@ impl indexer_service_rpc::RpcServer for MockIndexerService {
|
||||
async fn get_transactions_by_account(
|
||||
&self,
|
||||
account_id: AccountId,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
offset: u64,
|
||||
limit: u64,
|
||||
) -> Result<Vec<Transaction>, ErrorObjectOwned> {
|
||||
let mut account_txs: Vec<_> = self
|
||||
.transactions
|
||||
|
||||
@ -90,19 +90,19 @@ impl indexer_service_rpc::RpcServer for IndexerService {
|
||||
|
||||
async fn get_blocks(
|
||||
&self,
|
||||
before: Option<u64>,
|
||||
limit: u32,
|
||||
before: Option<BlockId>,
|
||||
limit: u64,
|
||||
) -> Result<Vec<Block>, ErrorObjectOwned> {
|
||||
let blocks = self
|
||||
.indexer
|
||||
.store
|
||||
.get_block_batch(before, limit as u64)
|
||||
.get_block_batch(before, limit)
|
||||
.map_err(db_error)?;
|
||||
|
||||
let mut block_res = vec![];
|
||||
|
||||
for block in blocks {
|
||||
block_res.push(block.into())
|
||||
block_res.push(block.into());
|
||||
}
|
||||
|
||||
Ok(block_res)
|
||||
@ -111,19 +111,19 @@ impl indexer_service_rpc::RpcServer for IndexerService {
|
||||
async fn get_transactions_by_account(
|
||||
&self,
|
||||
account_id: AccountId,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
offset: u64,
|
||||
limit: u64,
|
||||
) -> Result<Vec<Transaction>, ErrorObjectOwned> {
|
||||
let transactions = self
|
||||
.indexer
|
||||
.store
|
||||
.get_transactions_by_account(account_id.value, offset as u64, limit as u64)
|
||||
.get_transactions_by_account(account_id.value, offset, limit)
|
||||
.map_err(db_error)?;
|
||||
|
||||
let mut tx_res = vec![];
|
||||
|
||||
for tx in transactions {
|
||||
tx_res.push(tx.into())
|
||||
tx_res.push(tx.into());
|
||||
}
|
||||
|
||||
Ok(tx_res)
|
||||
@ -177,8 +177,8 @@ impl SubscriptionService {
|
||||
}
|
||||
}
|
||||
|
||||
bail!(err);
|
||||
};
|
||||
bail!(err)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -190,7 +190,7 @@ impl SubscriptionService {
|
||||
let handle = tokio::spawn(async move {
|
||||
let mut subscribers = Vec::new();
|
||||
|
||||
let mut block_stream = pin!(indexer.subscribe_parse_block_stream().await);
|
||||
let mut block_stream = pin!(indexer.subscribe_parse_block_stream());
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@ -273,6 +273,7 @@ impl<T> Drop for Subscription<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn not_yet_implemented_error() -> ErrorObjectOwned {
|
||||
ErrorObject::owned(
|
||||
ErrorCode::InternalError.code(),
|
||||
@ -281,6 +282,10 @@ pub fn not_yet_implemented_error() -> ErrorObjectOwned {
|
||||
)
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::needless_pass_by_value,
|
||||
reason = "Error is consumed to extract details for error response"
|
||||
)]
|
||||
fn db_error(err: anyhow::Error) -> ErrorObjectOwned {
|
||||
ErrorObjectOwned::owned(
|
||||
ErrorCode::InternalError.code(),
|
||||
|
||||
@ -38,6 +38,7 @@ pub fn indexer_config(
|
||||
}
|
||||
|
||||
/// Sequencer config options available for custom changes in integration tests.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SequencerPartialConfig {
|
||||
pub max_num_tx_in_block: usize,
|
||||
pub max_block_size: ByteSize,
|
||||
@ -122,6 +123,7 @@ pub struct InitialData {
|
||||
}
|
||||
|
||||
impl InitialData {
|
||||
#[must_use]
|
||||
pub fn with_two_public_and_two_private_initialized_accounts() -> Self {
|
||||
let mut public_alice_private_key = PrivateKey::new_os_random();
|
||||
let mut public_alice_public_key =
|
||||
@ -231,6 +233,7 @@ impl InitialData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum UrlProtocol {
|
||||
Http,
|
||||
Ws,
|
||||
|
||||
@ -52,6 +52,7 @@ impl TestContext {
|
||||
Self::builder().build().await
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn builder() -> TestContextBuilder {
|
||||
TestContextBuilder::new()
|
||||
}
|
||||
@ -120,6 +121,10 @@ impl TestContext {
|
||||
// Setting port to 0 to avoid conflicts between parallel tests, actual port will be retrieved after container is up
|
||||
.with_env("PORT", "0");
|
||||
|
||||
#[expect(
|
||||
clippy::items_after_statements,
|
||||
reason = "This is more readable is this function used just after its definition"
|
||||
)]
|
||||
async fn up_and_retrieve_port(compose: &mut DockerCompose) -> Result<u16> {
|
||||
compose
|
||||
.up()
|
||||
@ -181,7 +186,10 @@ impl TestContext {
|
||||
let temp_indexer_dir =
|
||||
tempfile::tempdir().context("Failed to create temp dir for indexer home")?;
|
||||
|
||||
debug!("Using temp indexer home at {:?}", temp_indexer_dir.path());
|
||||
debug!(
|
||||
"Using temp indexer home at {}",
|
||||
temp_indexer_dir.path().display()
|
||||
);
|
||||
|
||||
let indexer_config = config::indexer_config(
|
||||
bedrock_addr,
|
||||
@ -206,8 +214,8 @@ impl TestContext {
|
||||
tempfile::tempdir().context("Failed to create temp dir for sequencer home")?;
|
||||
|
||||
debug!(
|
||||
"Using temp sequencer home at {:?}",
|
||||
temp_sequencer_dir.path()
|
||||
"Using temp sequencer home at {}",
|
||||
temp_sequencer_dir.path().display()
|
||||
);
|
||||
|
||||
let config = config::sequencer_config(
|
||||
@ -260,10 +268,12 @@ impl TestContext {
|
||||
}
|
||||
|
||||
/// Get reference to the wallet.
|
||||
#[must_use]
|
||||
pub fn wallet(&self) -> &WalletCore {
|
||||
&self.wallet
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn wallet_password(&self) -> &str {
|
||||
&self.wallet_password
|
||||
}
|
||||
@ -274,16 +284,19 @@ impl TestContext {
|
||||
}
|
||||
|
||||
/// Get reference to the sequencer client.
|
||||
#[must_use]
|
||||
pub fn sequencer_client(&self) -> &SequencerClient {
|
||||
&self.sequencer_client
|
||||
}
|
||||
|
||||
/// Get reference to the indexer client.
|
||||
#[must_use]
|
||||
pub fn indexer_client(&self) -> &IndexerClient {
|
||||
&self.indexer_client
|
||||
}
|
||||
|
||||
/// Get existing public account IDs in the wallet.
|
||||
#[must_use]
|
||||
pub fn existing_public_accounts(&self) -> Vec<AccountId> {
|
||||
self.wallet
|
||||
.storage()
|
||||
@ -293,6 +306,7 @@ impl TestContext {
|
||||
}
|
||||
|
||||
/// Get existing private account IDs in the wallet.
|
||||
#[must_use]
|
||||
pub fn existing_private_accounts(&self) -> Vec<AccountId> {
|
||||
self.wallet
|
||||
.storage()
|
||||
@ -386,11 +400,13 @@ impl TestContextBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_initial_data(mut self, initial_data: config::InitialData) -> Self {
|
||||
self.initial_data = Some(initial_data);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_sequencer_partial_config(
|
||||
mut self,
|
||||
sequencer_partial_config: config::SequencerPartialConfig,
|
||||
@ -419,14 +435,16 @@ impl Drop for BlockingTestContext {
|
||||
if let Some(ctx) = ctx.take() {
|
||||
drop(ctx);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn format_public_account_id(account_id: AccountId) -> String {
|
||||
format!("Public/{account_id}")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn format_private_account_id(account_id: AccountId) -> String {
|
||||
format!("Private/{account_id}")
|
||||
}
|
||||
|
||||
@ -45,9 +45,8 @@ async fn new_public_account_with_label() -> Result<()> {
|
||||
let result = execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
// Extract the account_id from the result
|
||||
let account_id = match result {
|
||||
wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } => account_id,
|
||||
_ => panic!("Expected RegisterAccount return value"),
|
||||
let wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } = result else {
|
||||
panic!("Expected RegisterAccount return value")
|
||||
};
|
||||
|
||||
// Verify the label was stored
|
||||
@ -78,9 +77,9 @@ async fn new_private_account_with_label() -> Result<()> {
|
||||
let result = execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
// Extract the account_id from the result
|
||||
let account_id = match result {
|
||||
wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } => account_id,
|
||||
_ => panic!("Expected RegisterAccount return value"),
|
||||
|
||||
let wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } = result else {
|
||||
panic!("Expected RegisterAccount return value")
|
||||
};
|
||||
|
||||
// Verify the label was stored
|
||||
@ -110,9 +109,9 @@ async fn new_public_account_without_label() -> Result<()> {
|
||||
let result = execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
// Extract the account_id from the result
|
||||
let account_id = match result {
|
||||
wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } => account_id,
|
||||
_ => panic!("Expected RegisterAccount return value"),
|
||||
|
||||
let wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } = result else {
|
||||
panic!("Expected RegisterAccount return value")
|
||||
};
|
||||
|
||||
// Verify no label was stored
|
||||
|
||||
@ -86,7 +86,7 @@ async fn private_transfer_to_foreign_account() -> Result<()> {
|
||||
assert_eq!(tx.message.new_commitments[0], new_commitment1);
|
||||
|
||||
assert_eq!(tx.message.new_commitments.len(), 2);
|
||||
for commitment in tx.message.new_commitments.into_iter() {
|
||||
for commitment in tx.message.new_commitments {
|
||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||
}
|
||||
|
||||
@ -198,7 +198,7 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
|
||||
assert_eq!(tx.message.new_commitments[0], new_commitment1);
|
||||
|
||||
assert_eq!(tx.message.new_commitments.len(), 2);
|
||||
for commitment in tx.message.new_commitments.into_iter() {
|
||||
for commitment in tx.message.new_commitments {
|
||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||
}
|
||||
|
||||
@ -353,7 +353,7 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
|
||||
|
||||
// Verify commitments are in state
|
||||
assert_eq!(tx.message.new_commitments.len(), 2);
|
||||
for commitment in tx.message.new_commitments.into_iter() {
|
||||
for commitment in tx.message.new_commitments {
|
||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||
}
|
||||
|
||||
|
||||
@ -112,7 +112,7 @@ async fn failed_transfer_with_insufficient_balance() -> Result<()> {
|
||||
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
amount: 1000000,
|
||||
amount: 1_000_000,
|
||||
});
|
||||
|
||||
let failed_send = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await;
|
||||
|
||||
@ -38,13 +38,12 @@ async fn reject_oversized_transaction() -> Result<()> {
|
||||
);
|
||||
|
||||
let err = result.unwrap_err();
|
||||
let err_str = format!("{:?}", err);
|
||||
let err_str = format!("{err:?}");
|
||||
|
||||
// Check if the error contains information about transaction being too large
|
||||
assert!(
|
||||
err_str.contains("TransactionTooLarge") || err_str.contains("too large"),
|
||||
"Expected TransactionTooLarge error, got: {}",
|
||||
err_str
|
||||
"Expected TransactionTooLarge error, got: {err_str}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -23,7 +23,7 @@ async fn modify_config_field() -> Result<()> {
|
||||
// Return how it was at the beginning
|
||||
let command = Command::Config(ConfigSubcommand::Set {
|
||||
key: "seq_poll_timeout".to_string(),
|
||||
value: format!("{:?}", old_seq_poll_timeout),
|
||||
value: format!("{old_seq_poll_timeout:?}"),
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ use tokio::test;
|
||||
use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand};
|
||||
|
||||
/// Timeout in milliseconds to reliably await for block finalization
|
||||
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 600000;
|
||||
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 600_000;
|
||||
|
||||
#[test]
|
||||
async fn indexer_test_run() -> Result<()> {
|
||||
|
||||
@ -87,7 +87,7 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
|
||||
assert_eq!(tx.message.new_commitments[0], new_commitment1);
|
||||
|
||||
assert_eq!(tx.message.new_commitments.len(), 2);
|
||||
for commitment in tx.message.new_commitments.into_iter() {
|
||||
for commitment in tx.message.new_commitments {
|
||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||
}
|
||||
|
||||
|
||||
@ -56,16 +56,17 @@ pub async fn tps_test() -> Result<()> {
|
||||
|
||||
for (i, tx_hash) in tx_hashes.iter().enumerate() {
|
||||
loop {
|
||||
if now.elapsed().as_millis() > target_time.as_millis() {
|
||||
panic!("TPS test failed by timeout");
|
||||
}
|
||||
assert!(
|
||||
now.elapsed().as_millis() <= target_time.as_millis(),
|
||||
"TPS test failed by timeout"
|
||||
);
|
||||
|
||||
let tx_obj = ctx
|
||||
.sequencer_client()
|
||||
.get_transaction_by_hash(*tx_hash)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
log::warn!("Failed to get transaction by hash {tx_hash} with error: {err:#?}")
|
||||
log::warn!("Failed to get transaction by hash {tx_hash} with error: {err:#?}");
|
||||
});
|
||||
|
||||
if let Ok(tx_obj) = tx_obj
|
||||
@ -119,6 +120,10 @@ impl TpsTestManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::cast_precision_loss,
|
||||
reason = "This is just for testing purposes, we don't care about precision loss here"
|
||||
)]
|
||||
pub(crate) fn target_time(&self) -> Duration {
|
||||
let number_transactions = (self.public_keypairs.len() - 1) as u64;
|
||||
Duration::from_secs_f64(number_transactions as f64 / self.target_tps as f64)
|
||||
@ -165,7 +170,7 @@ impl TpsTestManager {
|
||||
let key_chain = KeyChain::new_os_random();
|
||||
let account = Account {
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
nonce: 0xdead_beef,
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
data: Data::default(),
|
||||
};
|
||||
@ -200,7 +205,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
let sender_pre = AccountWithMetadata::new(
|
||||
Account {
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
nonce: 0xdead_beef,
|
||||
program_owner: program.id(),
|
||||
data: Data::default(),
|
||||
},
|
||||
@ -234,7 +239,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
vec![sender_pre, recipient_pre],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(sender_npk.clone(), sender_ss),
|
||||
(recipient_npk.clone(), recipient_ss),
|
||||
|
||||
@ -152,7 +152,7 @@ unsafe extern "C" {
|
||||
fn new_wallet_ffi_with_test_context_config(
|
||||
ctx: &BlockingTestContext,
|
||||
home: &Path,
|
||||
) -> *mut WalletHandle {
|
||||
) -> Result<*mut WalletHandle> {
|
||||
let config_path = home.join("wallet_config.json");
|
||||
let storage_path = home.join("storage.json");
|
||||
let mut config = ctx.ctx().wallet().config().to_owned();
|
||||
@ -163,75 +163,73 @@ fn new_wallet_ffi_with_test_context_config(
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&config_path)
|
||||
.unwrap();
|
||||
.open(&config_path)?;
|
||||
|
||||
let config_with_overrides_serialized = serde_json::to_vec_pretty(&config).unwrap();
|
||||
let config_with_overrides_serialized = serde_json::to_vec_pretty(&config)?;
|
||||
|
||||
file.write_all(&config_with_overrides_serialized).unwrap();
|
||||
file.write_all(&config_with_overrides_serialized)?;
|
||||
|
||||
let config_path = CString::new(config_path.to_str().unwrap()).unwrap();
|
||||
let storage_path = CString::new(storage_path.to_str().unwrap()).unwrap();
|
||||
let password = CString::new(ctx.ctx().wallet_password()).unwrap();
|
||||
let config_path = CString::new(config_path.to_str().unwrap())?;
|
||||
let storage_path = CString::new(storage_path.to_str().unwrap())?;
|
||||
let password = CString::new(ctx.ctx().wallet_password())?;
|
||||
|
||||
unsafe {
|
||||
Ok(unsafe {
|
||||
wallet_ffi_create_new(
|
||||
config_path.as_ptr(),
|
||||
storage_path.as_ptr(),
|
||||
password.as_ptr(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn new_wallet_ffi_with_default_config(password: &str) -> *mut WalletHandle {
|
||||
let tempdir = tempdir().unwrap();
|
||||
fn new_wallet_ffi_with_default_config(password: &str) -> Result<*mut WalletHandle> {
|
||||
let tempdir = tempdir()?;
|
||||
let config_path = tempdir.path().join("wallet_config.json");
|
||||
let storage_path = tempdir.path().join("storage.json");
|
||||
let config_path_c = CString::new(config_path.to_str().unwrap()).unwrap();
|
||||
let storage_path_c = CString::new(storage_path.to_str().unwrap()).unwrap();
|
||||
let password = CString::new(password).unwrap();
|
||||
let config_path_c = CString::new(config_path.to_str().unwrap())?;
|
||||
let storage_path_c = CString::new(storage_path.to_str().unwrap())?;
|
||||
let password = CString::new(password)?;
|
||||
|
||||
unsafe {
|
||||
Ok(unsafe {
|
||||
wallet_ffi_create_new(
|
||||
config_path_c.as_ptr(),
|
||||
storage_path_c.as_ptr(),
|
||||
password.as_ptr(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn new_wallet_rust_with_default_config(password: &str) -> WalletCore {
|
||||
let tempdir = tempdir().unwrap();
|
||||
fn new_wallet_rust_with_default_config(password: &str) -> Result<WalletCore> {
|
||||
let tempdir = tempdir()?;
|
||||
let config_path = tempdir.path().join("wallet_config.json");
|
||||
let storage_path = tempdir.path().join("storage.json");
|
||||
|
||||
WalletCore::new_init_storage(
|
||||
config_path.to_path_buf(),
|
||||
storage_path.to_path_buf(),
|
||||
config_path.clone(),
|
||||
storage_path.clone(),
|
||||
None,
|
||||
password.to_string(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn load_existing_ffi_wallet(home: &Path) -> *mut WalletHandle {
|
||||
fn load_existing_ffi_wallet(home: &Path) -> Result<*mut WalletHandle> {
|
||||
let config_path = home.join("wallet_config.json");
|
||||
let storage_path = home.join("storage.json");
|
||||
let config_path = CString::new(config_path.to_str().unwrap()).unwrap();
|
||||
let storage_path = CString::new(storage_path.to_str().unwrap()).unwrap();
|
||||
let config_path = CString::new(config_path.to_str().unwrap())?;
|
||||
let storage_path = CString::new(storage_path.to_str().unwrap())?;
|
||||
|
||||
unsafe { wallet_ffi_open(config_path.as_ptr(), storage_path.as_ptr()) }
|
||||
Ok(unsafe { wallet_ffi_open(config_path.as_ptr(), storage_path.as_ptr()) })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_create_public_accounts() {
|
||||
fn test_wallet_ffi_create_public_accounts() -> Result<()> {
|
||||
let password = "password_for_tests";
|
||||
let n_accounts = 10;
|
||||
// First `n_accounts` public accounts created with Rust wallet
|
||||
let new_public_account_ids_rust = {
|
||||
let mut account_ids = Vec::new();
|
||||
|
||||
let mut wallet_rust = new_wallet_rust_with_default_config(password);
|
||||
let mut wallet_rust = new_wallet_rust_with_default_config(password)?;
|
||||
for _ in 0..n_accounts {
|
||||
let account_id = wallet_rust.create_new_account_public(None).0;
|
||||
account_ids.push(*account_id.value());
|
||||
@ -243,13 +241,10 @@ fn test_wallet_ffi_create_public_accounts() {
|
||||
let new_public_account_ids_ffi = unsafe {
|
||||
let mut account_ids = Vec::new();
|
||||
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password);
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password)?;
|
||||
for _ in 0..n_accounts {
|
||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
wallet_ffi_create_account_public(
|
||||
wallet_ffi_handle,
|
||||
(&mut out_account_id) as *mut FfiBytes32,
|
||||
);
|
||||
wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut out_account_id);
|
||||
account_ids.push(out_account_id.data);
|
||||
}
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
@ -257,17 +252,19 @@ fn test_wallet_ffi_create_public_accounts() {
|
||||
};
|
||||
|
||||
assert_eq!(new_public_account_ids_ffi, new_public_account_ids_rust);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_create_private_accounts() {
|
||||
fn test_wallet_ffi_create_private_accounts() -> Result<()> {
|
||||
let password = "password_for_tests";
|
||||
let n_accounts = 10;
|
||||
// First `n_accounts` private accounts created with Rust wallet
|
||||
let new_private_account_ids_rust = {
|
||||
let mut account_ids = Vec::new();
|
||||
|
||||
let mut wallet_rust = new_wallet_rust_with_default_config(password);
|
||||
let mut wallet_rust = new_wallet_rust_with_default_config(password)?;
|
||||
for _ in 0..n_accounts {
|
||||
let account_id = wallet_rust.create_new_account_private(None).0;
|
||||
account_ids.push(*account_id.value());
|
||||
@ -279,56 +276,52 @@ fn test_wallet_ffi_create_private_accounts() {
|
||||
let new_private_account_ids_ffi = unsafe {
|
||||
let mut account_ids = Vec::new();
|
||||
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password);
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password)?;
|
||||
for _ in 0..n_accounts {
|
||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
wallet_ffi_create_account_private(
|
||||
wallet_ffi_handle,
|
||||
(&mut out_account_id) as *mut FfiBytes32,
|
||||
);
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
||||
account_ids.push(out_account_id.data);
|
||||
}
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
account_ids
|
||||
};
|
||||
|
||||
assert_eq!(new_private_account_ids_ffi, new_private_account_ids_rust)
|
||||
assert_eq!(new_private_account_ids_ffi, new_private_account_ids_rust);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn test_wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let mut out_private_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let home = tempfile::tempdir()?;
|
||||
|
||||
// Create a private account with the wallet FFI and save it
|
||||
unsafe {
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
wallet_ffi_create_account_private(
|
||||
wallet_ffi_handle,
|
||||
(&mut out_private_account_id) as *mut FfiBytes32,
|
||||
);
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_private_account_id);
|
||||
|
||||
wallet_ffi_save(wallet_ffi_handle);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
let private_account_keys = unsafe {
|
||||
let wallet_ffi_handle = load_existing_ffi_wallet(home.path());
|
||||
let wallet_ffi_handle = load_existing_ffi_wallet(home.path())?;
|
||||
|
||||
let mut private_account = FfiAccount::default();
|
||||
|
||||
let result = wallet_ffi_get_account_private(
|
||||
wallet_ffi_handle,
|
||||
(&out_private_account_id) as *const FfiBytes32,
|
||||
(&mut private_account) as *mut FfiAccount,
|
||||
&raw const out_private_account_id,
|
||||
&raw mut private_account,
|
||||
);
|
||||
assert_eq!(result, error::WalletFfiError::Success);
|
||||
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
let result = wallet_ffi_get_private_account_keys(
|
||||
wallet_ffi_handle,
|
||||
(&out_private_account_id) as *const FfiBytes32,
|
||||
(&mut out_keys) as *mut FfiPrivateAccountKeys,
|
||||
&raw const out_private_account_id,
|
||||
&raw mut out_keys,
|
||||
);
|
||||
assert_eq!(result, error::WalletFfiError::Success);
|
||||
|
||||
@ -346,17 +339,17 @@ fn test_wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_list_accounts() {
|
||||
fn test_wallet_ffi_list_accounts() -> Result<()> {
|
||||
let password = "password_for_tests";
|
||||
|
||||
// Create the wallet FFI
|
||||
let wallet_ffi_handle = unsafe {
|
||||
let handle = new_wallet_ffi_with_default_config(password);
|
||||
let handle = new_wallet_ffi_with_default_config(password)?;
|
||||
// Create 5 public accounts and 5 private accounts
|
||||
for _ in 0..5 {
|
||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
wallet_ffi_create_account_public(handle, (&mut out_account_id) as *mut FfiBytes32);
|
||||
wallet_ffi_create_account_private(handle, (&mut out_account_id) as *mut FfiBytes32);
|
||||
wallet_ffi_create_account_public(handle, &raw mut out_account_id);
|
||||
wallet_ffi_create_account_private(handle, &raw mut out_account_id);
|
||||
}
|
||||
|
||||
handle
|
||||
@ -364,7 +357,7 @@ fn test_wallet_ffi_list_accounts() {
|
||||
|
||||
// Create the wallet Rust
|
||||
let wallet_rust = {
|
||||
let mut wallet = new_wallet_rust_with_default_config(password);
|
||||
let mut wallet = new_wallet_rust_with_default_config(password)?;
|
||||
// Create 5 public accounts and 5 private accounts
|
||||
for _ in 0..5 {
|
||||
wallet.create_new_account_public(None);
|
||||
@ -376,7 +369,7 @@ fn test_wallet_ffi_list_accounts() {
|
||||
// Get the account list with FFI method
|
||||
let mut wallet_ffi_account_list = unsafe {
|
||||
let mut out_list = FfiAccountList::default();
|
||||
wallet_ffi_list_accounts(wallet_ffi_handle, (&mut out_list) as *mut FfiAccountList);
|
||||
wallet_ffi_list_accounts(wallet_ffi_handle, &raw mut out_list);
|
||||
out_list
|
||||
};
|
||||
|
||||
@ -400,7 +393,7 @@ fn test_wallet_ffi_list_accounts() {
|
||||
assert_eq!(
|
||||
wallet_rust_account_ids
|
||||
.iter()
|
||||
.map(|id| id.value())
|
||||
.map(nssa::AccountId::value)
|
||||
.collect::<HashSet<_>>(),
|
||||
wallet_ffi_account_list_slice
|
||||
.iter()
|
||||
@ -409,7 +402,7 @@ fn test_wallet_ffi_list_accounts() {
|
||||
);
|
||||
|
||||
// Assert `is_pub` flag is correct in the FFI result
|
||||
for entry in wallet_ffi_account_list_slice.iter() {
|
||||
for entry in wallet_ffi_account_list_slice {
|
||||
let account_id = AccountId::new(entry.account_id.data);
|
||||
let is_pub_default_in_rust_wallet = wallet_rust
|
||||
.storage()
|
||||
@ -429,27 +422,30 @@ fn test_wallet_ffi_list_accounts() {
|
||||
}
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_account_list((&mut wallet_ffi_account_list) as *mut FfiAccountList);
|
||||
wallet_ffi_free_account_list(&raw mut wallet_ffi_account_list);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_get_balance_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
let balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let ffi_account_id = FfiBytes32::from(&account_id);
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
(&ffi_account_id) as *const FfiBytes32,
|
||||
&raw const ffi_account_id,
|
||||
true,
|
||||
(&mut out_balance) as *mut [u8; 16],
|
||||
);
|
||||
&raw mut out_balance,
|
||||
)
|
||||
.unwrap();
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
assert_eq!(balance, 10000);
|
||||
@ -467,17 +463,18 @@ fn test_wallet_ffi_get_balance_public() -> Result<()> {
|
||||
fn test_wallet_ffi_get_account_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let mut out_account = FfiAccount::default();
|
||||
|
||||
let account: Account = unsafe {
|
||||
let ffi_account_id = FfiBytes32::from(&account_id);
|
||||
let _result = wallet_ffi_get_account_public(
|
||||
wallet_ffi_get_account_public(
|
||||
wallet_ffi_handle,
|
||||
(&ffi_account_id) as *const FfiBytes32,
|
||||
(&mut out_account) as *mut FfiAccount,
|
||||
);
|
||||
&raw const ffi_account_id,
|
||||
&raw mut out_account,
|
||||
)
|
||||
.unwrap();
|
||||
(&out_account).try_into().unwrap()
|
||||
};
|
||||
|
||||
@ -490,7 +487,7 @@ fn test_wallet_ffi_get_account_public() -> Result<()> {
|
||||
assert_eq!(account.nonce, 0);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_account_data((&mut out_account) as *mut FfiAccount);
|
||||
wallet_ffi_free_account_data(&raw mut out_account);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
@ -503,17 +500,18 @@ fn test_wallet_ffi_get_account_public() -> Result<()> {
|
||||
fn test_wallet_ffi_get_account_private() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let account_id: AccountId = ctx.ctx().existing_private_accounts()[0];
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let mut out_account = FfiAccount::default();
|
||||
|
||||
let account: Account = unsafe {
|
||||
let ffi_account_id = FfiBytes32::from(&account_id);
|
||||
let _result = wallet_ffi_get_account_private(
|
||||
wallet_ffi_get_account_private(
|
||||
wallet_ffi_handle,
|
||||
(&ffi_account_id) as *const FfiBytes32,
|
||||
(&mut out_account) as *mut FfiAccount,
|
||||
);
|
||||
&raw const ffi_account_id,
|
||||
&raw mut out_account,
|
||||
)
|
||||
.unwrap();
|
||||
(&out_account).try_into().unwrap()
|
||||
};
|
||||
|
||||
@ -526,7 +524,7 @@ fn test_wallet_ffi_get_account_private() -> Result<()> {
|
||||
assert_eq!(account.nonce, 0);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_account_data((&mut out_account) as *mut FfiAccount);
|
||||
wallet_ffi_free_account_data(&raw mut out_account);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
@ -539,17 +537,18 @@ fn test_wallet_ffi_get_account_private() -> Result<()> {
|
||||
fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let mut out_key = FfiPublicAccountKey::default();
|
||||
|
||||
let key: PublicKey = unsafe {
|
||||
let ffi_account_id = FfiBytes32::from(&account_id);
|
||||
let _result = wallet_ffi_get_public_account_key(
|
||||
wallet_ffi_get_public_account_key(
|
||||
wallet_ffi_handle,
|
||||
(&ffi_account_id) as *const FfiBytes32,
|
||||
(&mut out_key) as *mut FfiPublicAccountKey,
|
||||
);
|
||||
&raw const ffi_account_id,
|
||||
&raw mut out_key,
|
||||
)
|
||||
.unwrap();
|
||||
(&out_key).try_into().unwrap()
|
||||
};
|
||||
|
||||
@ -577,17 +576,18 @@ fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
|
||||
fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let account_id: AccountId = ctx.ctx().existing_private_accounts()[0];
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let mut keys = FfiPrivateAccountKeys::default();
|
||||
|
||||
unsafe {
|
||||
let ffi_account_id = FfiBytes32::from(&account_id);
|
||||
let _result = wallet_ffi_get_private_account_keys(
|
||||
wallet_ffi_get_private_account_keys(
|
||||
wallet_ffi_handle,
|
||||
(&ffi_account_id) as *const FfiBytes32,
|
||||
(&mut keys) as *mut FfiPrivateAccountKeys,
|
||||
);
|
||||
&raw const ffi_account_id,
|
||||
&raw mut keys,
|
||||
)
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let key_chain = &ctx
|
||||
@ -606,7 +606,7 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
|
||||
assert_eq!(&keys.vpk().unwrap(), expected_vpk);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_private_account_keys((&mut keys) as *mut FfiPrivateAccountKeys);
|
||||
wallet_ffi_free_private_account_keys(&raw mut keys);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
@ -616,66 +616,65 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_account_id_to_base58() {
|
||||
fn test_wallet_ffi_account_id_to_base58() -> Result<()> {
|
||||
let private_key = PrivateKey::new_os_random();
|
||||
let public_key = PublicKey::new_from_private_key(&private_key);
|
||||
let account_id = AccountId::from(&public_key);
|
||||
let ffi_bytes: FfiBytes32 = (&account_id).into();
|
||||
let ptr = unsafe { wallet_ffi_account_id_to_base58((&ffi_bytes) as *const FfiBytes32) };
|
||||
let ptr = unsafe { wallet_ffi_account_id_to_base58(&raw const ffi_bytes) };
|
||||
|
||||
let ffi_result = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||
let ffi_result = unsafe { CStr::from_ptr(ptr).to_str()? };
|
||||
|
||||
assert_eq!(account_id.to_string(), ffi_result);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_string(ptr);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_base58_to_account_id() {
|
||||
fn test_wallet_ffi_base58_to_account_id() -> Result<()> {
|
||||
let private_key = PrivateKey::new_os_random();
|
||||
let public_key = PublicKey::new_from_private_key(&private_key);
|
||||
let account_id = AccountId::from(&public_key);
|
||||
let account_id_str = account_id.to_string();
|
||||
let account_id_c_str = CString::new(account_id_str.clone()).unwrap();
|
||||
let account_id_c_str = CString::new(account_id_str.clone())?;
|
||||
let account_id: AccountId = unsafe {
|
||||
let mut out_account_id_bytes = FfiBytes32::default();
|
||||
wallet_ffi_account_id_from_base58(
|
||||
account_id_c_str.as_ptr(),
|
||||
(&mut out_account_id_bytes) as *mut FfiBytes32,
|
||||
);
|
||||
wallet_ffi_account_id_from_base58(account_id_c_str.as_ptr(), &raw mut out_account_id_bytes);
|
||||
out_account_id_bytes.into()
|
||||
};
|
||||
|
||||
let expected_account_id = account_id_str.parse().unwrap();
|
||||
let expected_account_id = account_id_str.parse()?;
|
||||
|
||||
assert_eq!(account_id, expected_account_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new().unwrap();
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
// Create a new uninitialized public account
|
||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
unsafe {
|
||||
wallet_ffi_create_account_public(
|
||||
wallet_ffi_handle,
|
||||
(&mut out_account_id) as *mut FfiBytes32,
|
||||
);
|
||||
wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut out_account_id);
|
||||
}
|
||||
|
||||
// Check its program owner is the default program id
|
||||
let account: Account = unsafe {
|
||||
let mut out_account = FfiAccount::default();
|
||||
let _result = wallet_ffi_get_account_public(
|
||||
wallet_ffi_get_account_public(
|
||||
wallet_ffi_handle,
|
||||
(&out_account_id) as *const FfiBytes32,
|
||||
(&mut out_account) as *mut FfiAccount,
|
||||
);
|
||||
&raw const out_account_id,
|
||||
&raw mut out_account,
|
||||
)
|
||||
.unwrap();
|
||||
(&out_account).try_into().unwrap()
|
||||
};
|
||||
assert_eq!(account.program_owner, DEFAULT_PROGRAM_ID);
|
||||
@ -685,8 +684,8 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
||||
unsafe {
|
||||
wallet_ffi_register_public_account(
|
||||
wallet_ffi_handle,
|
||||
(&out_account_id) as *const FfiBytes32,
|
||||
(&mut transfer_result) as *mut FfiTransferResult,
|
||||
&raw const out_account_id,
|
||||
&raw mut transfer_result,
|
||||
);
|
||||
}
|
||||
|
||||
@ -696,11 +695,12 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
||||
// Check that the program owner is now the authenticated transfer program
|
||||
let account: Account = unsafe {
|
||||
let mut out_account = FfiAccount::default();
|
||||
let _result = wallet_ffi_get_account_public(
|
||||
wallet_ffi_get_account_public(
|
||||
wallet_ffi_handle,
|
||||
(&out_account_id) as *const FfiBytes32,
|
||||
(&mut out_account) as *mut FfiAccount,
|
||||
);
|
||||
&raw const out_account_id,
|
||||
&raw mut out_account,
|
||||
)
|
||||
.unwrap();
|
||||
(&out_account).try_into().unwrap()
|
||||
};
|
||||
assert_eq!(
|
||||
@ -709,7 +709,7 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
||||
);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
@ -718,17 +718,14 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new().unwrap();
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
// Create a new uninitialized public account
|
||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
unsafe {
|
||||
wallet_ffi_create_account_private(
|
||||
wallet_ffi_handle,
|
||||
(&mut out_account_id) as *mut FfiBytes32,
|
||||
);
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
||||
}
|
||||
|
||||
// Check its program owner is the default program id
|
||||
@ -736,8 +733,8 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||
let mut out_account = FfiAccount::default();
|
||||
wallet_ffi_get_account_private(
|
||||
wallet_ffi_handle,
|
||||
(&out_account_id) as *const FfiBytes32,
|
||||
(&mut out_account) as *mut FfiAccount,
|
||||
&raw const out_account_id,
|
||||
&raw mut out_account,
|
||||
);
|
||||
(&out_account).try_into().unwrap()
|
||||
};
|
||||
@ -748,8 +745,8 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||
unsafe {
|
||||
wallet_ffi_register_private_account(
|
||||
wallet_ffi_handle,
|
||||
(&out_account_id) as *const FfiBytes32,
|
||||
(&mut transfer_result) as *mut FfiTransferResult,
|
||||
&raw const out_account_id,
|
||||
&raw mut transfer_result,
|
||||
);
|
||||
}
|
||||
|
||||
@ -759,18 +756,19 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, (&mut current_height) as *mut u64);
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height);
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
|
||||
};
|
||||
|
||||
// Check that the program owner is now the authenticated transfer program
|
||||
let account: Account = unsafe {
|
||||
let mut out_account = FfiAccount::default();
|
||||
let _result = wallet_ffi_get_account_private(
|
||||
wallet_ffi_get_account_private(
|
||||
wallet_ffi_handle,
|
||||
(&out_account_id) as *const FfiBytes32,
|
||||
(&mut out_account) as *mut FfiAccount,
|
||||
);
|
||||
&raw const out_account_id,
|
||||
&raw mut out_account,
|
||||
)
|
||||
.unwrap();
|
||||
(&out_account).try_into().unwrap()
|
||||
};
|
||||
assert_eq!(
|
||||
@ -779,7 +777,7 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||
);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
@ -788,9 +786,9 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_transfer_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new().unwrap();
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into();
|
||||
let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[1]).into();
|
||||
let amount: [u8; 16] = 100u128.to_le_bytes();
|
||||
@ -799,10 +797,10 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
|
||||
unsafe {
|
||||
wallet_ffi_transfer_public(
|
||||
wallet_ffi_handle,
|
||||
(&from) as *const FfiBytes32,
|
||||
(&to) as *const FfiBytes32,
|
||||
(&amount) as *const [u8; 16],
|
||||
(&mut transfer_result) as *mut FfiTransferResult,
|
||||
&raw const from,
|
||||
&raw const to,
|
||||
&raw const amount,
|
||||
&raw mut transfer_result,
|
||||
);
|
||||
}
|
||||
|
||||
@ -811,23 +809,20 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
|
||||
|
||||
let from_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
(&from) as *const FfiBytes32,
|
||||
&raw const from,
|
||||
true,
|
||||
(&mut out_balance) as *mut [u8; 16],
|
||||
);
|
||||
&raw mut out_balance,
|
||||
)
|
||||
.unwrap();
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let to_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
(&to) as *const FfiBytes32,
|
||||
true,
|
||||
(&mut out_balance) as *mut [u8; 16],
|
||||
);
|
||||
wallet_ffi_get_balance(wallet_ffi_handle, &raw const to, true, &raw mut out_balance)
|
||||
.unwrap();
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
@ -835,7 +830,7 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
|
||||
assert_eq!(to_balance, 20100);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
@ -844,21 +839,18 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new().unwrap();
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into();
|
||||
let (to, to_keys) = unsafe {
|
||||
let mut out_account_id = FfiBytes32::default();
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_account_private(
|
||||
wallet_ffi_handle,
|
||||
(&mut out_account_id) as *mut FfiBytes32,
|
||||
);
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
||||
wallet_ffi_get_private_account_keys(
|
||||
wallet_ffi_handle,
|
||||
(&out_account_id) as *const FfiBytes32,
|
||||
(&mut out_keys) as *mut FfiPrivateAccountKeys,
|
||||
&raw const out_account_id,
|
||||
&raw mut out_keys,
|
||||
);
|
||||
(out_account_id, out_keys)
|
||||
};
|
||||
@ -868,10 +860,10 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
unsafe {
|
||||
wallet_ffi_transfer_shielded(
|
||||
wallet_ffi_handle,
|
||||
(&from) as *const FfiBytes32,
|
||||
(&to_keys) as *const FfiPrivateAccountKeys,
|
||||
(&amount) as *const [u8; 16],
|
||||
(&mut transfer_result) as *mut FfiTransferResult,
|
||||
&raw const from,
|
||||
&raw const to_keys,
|
||||
&raw const amount,
|
||||
&raw mut transfer_result,
|
||||
);
|
||||
}
|
||||
|
||||
@ -881,18 +873,19 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, (&mut current_height) as *mut u64);
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height);
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
|
||||
};
|
||||
|
||||
let from_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
(&from) as *const FfiBytes32,
|
||||
&raw const from,
|
||||
true,
|
||||
(&mut out_balance) as *mut [u8; 16],
|
||||
);
|
||||
&raw mut out_balance,
|
||||
)
|
||||
.unwrap();
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
@ -900,9 +893,9 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
(&to) as *const FfiBytes32,
|
||||
&raw const to,
|
||||
false,
|
||||
(&mut out_balance) as *mut [u8; 16],
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
@ -911,7 +904,7 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
assert_eq!(to_balance, 100);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
@ -920,9 +913,9 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new().unwrap();
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = (&ctx.ctx().existing_private_accounts()[0]).into();
|
||||
let to = FfiBytes32::from_bytes([37; 32]);
|
||||
let amount: [u8; 16] = 100u128.to_le_bytes();
|
||||
@ -931,10 +924,10 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
||||
unsafe {
|
||||
wallet_ffi_transfer_deshielded(
|
||||
wallet_ffi_handle,
|
||||
(&from) as *const FfiBytes32,
|
||||
(&to) as *const FfiBytes32,
|
||||
(&amount) as *const [u8; 16],
|
||||
(&mut transfer_result) as *mut FfiTransferResult,
|
||||
&raw const from,
|
||||
&raw const to,
|
||||
&raw const amount,
|
||||
&raw mut transfer_result,
|
||||
);
|
||||
}
|
||||
|
||||
@ -944,7 +937,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, (&mut current_height) as *mut u64);
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height);
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
|
||||
};
|
||||
|
||||
@ -952,21 +945,17 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
(&from) as *const FfiBytes32,
|
||||
&raw const from,
|
||||
false,
|
||||
(&mut out_balance) as *mut [u8; 16],
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let to_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
(&to) as *const FfiBytes32,
|
||||
true,
|
||||
(&mut out_balance) as *mut [u8; 16],
|
||||
);
|
||||
let _result =
|
||||
wallet_ffi_get_balance(wallet_ffi_handle, &raw const to, true, &raw mut out_balance);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
@ -974,7 +963,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
||||
assert_eq!(to_balance, 100);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
@ -983,22 +972,19 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new().unwrap();
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
let from: FfiBytes32 = (&ctx.ctx().existing_private_accounts()[0]).into();
|
||||
let (to, to_keys) = unsafe {
|
||||
let mut out_account_id = FfiBytes32::default();
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_account_private(
|
||||
wallet_ffi_handle,
|
||||
(&mut out_account_id) as *mut FfiBytes32,
|
||||
);
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
||||
wallet_ffi_get_private_account_keys(
|
||||
wallet_ffi_handle,
|
||||
(&out_account_id) as *const FfiBytes32,
|
||||
(&mut out_keys) as *mut FfiPrivateAccountKeys,
|
||||
&raw const out_account_id,
|
||||
&raw mut out_keys,
|
||||
);
|
||||
(out_account_id, out_keys)
|
||||
};
|
||||
@ -1009,10 +995,10 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
unsafe {
|
||||
wallet_ffi_transfer_private(
|
||||
wallet_ffi_handle,
|
||||
(&from) as *const FfiBytes32,
|
||||
(&to_keys) as *const FfiPrivateAccountKeys,
|
||||
(&amount) as *const [u8; 16],
|
||||
(&mut transfer_result) as *mut FfiTransferResult,
|
||||
&raw const from,
|
||||
&raw const to_keys,
|
||||
&raw const amount,
|
||||
&raw mut transfer_result,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1022,7 +1008,7 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, (&mut current_height) as *mut u64);
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height);
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
|
||||
};
|
||||
|
||||
@ -1030,9 +1016,9 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
(&from) as *const FfiBytes32,
|
||||
&raw const from,
|
||||
false,
|
||||
(&mut out_balance) as *mut [u8; 16],
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
@ -1041,9 +1027,9 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
(&to) as *const FfiBytes32,
|
||||
&raw const to,
|
||||
false,
|
||||
(&mut out_balance) as *mut [u8; 16],
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
@ -1052,7 +1038,7 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
assert_eq!(to_balance, 100);
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ pub struct EphemeralKeyHolder {
|
||||
ephemeral_secret_key: EphemeralSecretKey,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn produce_one_sided_shared_secret_receiver(
|
||||
vpk: &ViewingPublicKey,
|
||||
) -> (SharedSecretKey, EphemeralPublicKey) {
|
||||
@ -24,6 +25,7 @@ pub fn produce_one_sided_shared_secret_receiver(
|
||||
}
|
||||
|
||||
impl EphemeralKeyHolder {
|
||||
#[must_use]
|
||||
pub fn new(receiver_nullifier_public_key: &NullifierPublicKey) -> Self {
|
||||
let mut nonce_bytes = [0; 16];
|
||||
OsRng.fill_bytes(&mut nonce_bytes);
|
||||
@ -36,10 +38,12 @@ impl EphemeralKeyHolder {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn generate_ephemeral_public_key(&self) -> EphemeralPublicKey {
|
||||
EphemeralPublicKey::from_scalar(self.ephemeral_secret_key)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn calculate_shared_secret_sender(
|
||||
&self,
|
||||
receiver_viewing_public_key: &ViewingPublicKey,
|
||||
|
||||
@ -28,7 +28,7 @@ impl FromStr for ChainIndex {
|
||||
|
||||
let uprooted_substring = s.strip_prefix("/").unwrap();
|
||||
|
||||
let splitted_chain: Vec<&str> = uprooted_substring.split("/").collect();
|
||||
let splitted_chain: Vec<&str> = uprooted_substring.split('/').collect();
|
||||
let mut res = vec![];
|
||||
|
||||
for split_ch in splitted_chain {
|
||||
@ -47,7 +47,7 @@ impl Display for ChainIndex {
|
||||
write!(f, "{cci}/")?;
|
||||
}
|
||||
if let Some(last) = self.0.last() {
|
||||
write!(f, "{}", last)?;
|
||||
write!(f, "{last}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -60,28 +60,33 @@ impl Default for ChainIndex {
|
||||
}
|
||||
|
||||
impl ChainIndex {
|
||||
#[must_use]
|
||||
pub fn root() -> Self {
|
||||
ChainIndex::default()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn chain(&self) -> &[u32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn index(&self) -> Option<u32> {
|
||||
self.chain().last().copied()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn next_in_line(&self) -> ChainIndex {
|
||||
let mut chain = self.0.clone();
|
||||
// ToDo: Add overflow check
|
||||
if let Some(last_p) = chain.last_mut() {
|
||||
*last_p += 1
|
||||
*last_p += 1;
|
||||
}
|
||||
|
||||
ChainIndex(chain)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn previous_in_line(&self) -> Option<ChainIndex> {
|
||||
let mut chain = self.0.clone();
|
||||
if let Some(last_p) = chain.last_mut() {
|
||||
@ -91,6 +96,7 @@ impl ChainIndex {
|
||||
Some(ChainIndex(chain))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn parent(&self) -> Option<ChainIndex> {
|
||||
if self.0.is_empty() {
|
||||
None
|
||||
@ -99,6 +105,7 @@ impl ChainIndex {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn nth_child(&self, child_id: u32) -> ChainIndex {
|
||||
let mut chain = self.0.clone();
|
||||
chain.push(child_id);
|
||||
@ -106,6 +113,7 @@ impl ChainIndex {
|
||||
ChainIndex(chain)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn depth(&self) -> u32 {
|
||||
self.0.iter().map(|cci| cci + 1).sum()
|
||||
}
|
||||
@ -124,7 +132,7 @@ impl ChainIndex {
|
||||
.iter()
|
||||
.permutations(self.0.len())
|
||||
.unique()
|
||||
.map(|item| ChainIndex(item.into_iter().cloned().collect()))
|
||||
.map(|item| ChainIndex(item.into_iter().copied().collect()))
|
||||
}
|
||||
|
||||
pub fn chain_ids_at_depth(depth: usize) -> impl Iterator<Item = ChainIndex> {
|
||||
@ -227,7 +235,7 @@ mod tests {
|
||||
|
||||
let prev_chain_id = chain_id.previous_in_line().unwrap();
|
||||
|
||||
assert_eq!(prev_chain_id, ChainIndex(vec![1, 7, 2]))
|
||||
assert_eq!(prev_chain_id, ChainIndex(vec![1, 7, 2]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -236,7 +244,7 @@ mod tests {
|
||||
|
||||
let prev_chain_id = chain_id.previous_in_line();
|
||||
|
||||
assert_eq!(prev_chain_id, None)
|
||||
assert_eq!(prev_chain_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -245,7 +253,7 @@ mod tests {
|
||||
|
||||
let parent_chain_id = chain_id.parent().unwrap();
|
||||
|
||||
assert_eq!(parent_chain_id, ChainIndex(vec![1, 7]))
|
||||
assert_eq!(parent_chain_id, ChainIndex(vec![1, 7]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -254,7 +262,7 @@ mod tests {
|
||||
|
||||
let parent_chain_id = chain_id.parent();
|
||||
|
||||
assert_eq!(parent_chain_id, None)
|
||||
assert_eq!(parent_chain_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -263,7 +271,7 @@ mod tests {
|
||||
|
||||
let parent_chain_id = chain_id.parent().unwrap();
|
||||
|
||||
assert_eq!(parent_chain_id, ChainIndex::root())
|
||||
assert_eq!(parent_chain_id, ChainIndex::root());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -272,7 +280,7 @@ mod tests {
|
||||
|
||||
let collapsed = chain_id.collapse_back().unwrap();
|
||||
|
||||
assert_eq!(collapsed, ChainIndex(vec![3]))
|
||||
assert_eq!(collapsed, ChainIndex(vec![3]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -281,7 +289,7 @@ mod tests {
|
||||
|
||||
let collapsed = chain_id.collapse_back();
|
||||
|
||||
assert_eq!(collapsed, None)
|
||||
assert_eq!(collapsed, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -290,7 +298,7 @@ mod tests {
|
||||
|
||||
let collapsed = chain_id.collapse_back();
|
||||
|
||||
assert_eq!(collapsed, None)
|
||||
assert_eq!(collapsed, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -16,21 +16,18 @@ impl ChildKeysPublic {
|
||||
fn compute_hash_value(&self, cci: u32) -> [u8; 64] {
|
||||
let mut hash_input = vec![];
|
||||
|
||||
match ((2u32).pow(31)).cmp(&cci) {
|
||||
if 2u32.pow(31) > cci {
|
||||
// Non-harden
|
||||
std::cmp::Ordering::Greater => {
|
||||
hash_input.extend_from_slice(self.cpk.value());
|
||||
hash_input.extend_from_slice(&cci.to_le_bytes());
|
||||
hash_input.extend_from_slice(self.cpk.value());
|
||||
hash_input.extend_from_slice(&cci.to_le_bytes());
|
||||
|
||||
hmac_sha512::HMAC::mac(hash_input, self.ccc)
|
||||
}
|
||||
hmac_sha512::HMAC::mac(hash_input, self.ccc)
|
||||
} else {
|
||||
// Harden
|
||||
_ => {
|
||||
hash_input.extend_from_slice(self.csk.value());
|
||||
hash_input.extend_from_slice(&(cci).to_le_bytes());
|
||||
hash_input.extend_from_slice(self.csk.value());
|
||||
hash_input.extend_from_slice(&(cci).to_le_bytes());
|
||||
|
||||
hmac_sha512::HMAC::mac(hash_input, self.ccc)
|
||||
}
|
||||
hmac_sha512::HMAC::mac(hash_input, self.ccc)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,9 +65,10 @@ impl KeyNode for ChildKeysPublic {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if secp256k1::constants::CURVE_ORDER < *csk.value() {
|
||||
panic!("Secret key cannot exceed curve order");
|
||||
}
|
||||
assert!(
|
||||
secp256k1::constants::CURVE_ORDER >= *csk.value(),
|
||||
"Secret key cannot exceed curve order"
|
||||
);
|
||||
|
||||
let ccc = *hash_value
|
||||
.last_chunk::<32>()
|
||||
|
||||
@ -32,6 +32,7 @@ pub type KeyTreePublic = KeyTree<ChildKeysPublic>;
|
||||
pub type KeyTreePrivate = KeyTree<ChildKeysPrivate>;
|
||||
|
||||
impl<N: KeyNode> KeyTree<N> {
|
||||
#[must_use]
|
||||
pub fn new(seed: &SeedHolder) -> Self {
|
||||
let seed_fit: [u8; 64] = seed
|
||||
.seed
|
||||
@ -63,6 +64,7 @@ impl<N: KeyNode> KeyTree<N> {
|
||||
|
||||
// ToDo: Add function to create a tree from list of nodes with consistency check.
|
||||
|
||||
#[must_use]
|
||||
pub fn find_next_last_child_of_id(&self, parent_id: &ChainIndex) -> Option<u32> {
|
||||
if !self.key_map.contains_key(parent_id) {
|
||||
return None;
|
||||
@ -87,14 +89,14 @@ impl<N: KeyNode> KeyTree<N> {
|
||||
match (&rightmost_ref, &rightmost_ref_next) {
|
||||
(Some(_), Some(_)) => {
|
||||
left_border = right;
|
||||
right = (right + right_border) / 2;
|
||||
right = u32::midpoint(right, right_border);
|
||||
}
|
||||
(Some(_), None) => {
|
||||
break Some(right + 1);
|
||||
}
|
||||
(None, None) => {
|
||||
right_border = right;
|
||||
right = (left_border + right) / 2;
|
||||
right = u32::midpoint(left_border, right);
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
unreachable!();
|
||||
@ -152,6 +154,7 @@ impl<N: KeyNode> KeyTree<N> {
|
||||
self.fill_node(&self.find_next_slot_layered())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_node(&self, account_id: nssa::AccountId) -> Option<&N> {
|
||||
self.account_id_map
|
||||
.get(&account_id)
|
||||
|
||||
@ -4,6 +4,7 @@ pub trait KeyNode {
|
||||
fn root(seed: [u8; 64]) -> Self;
|
||||
|
||||
/// `cci`'s child of node
|
||||
#[must_use]
|
||||
fn nth_child(&self, cci: u32) -> Self;
|
||||
|
||||
fn chain_code(&self) -> &[u8; 32];
|
||||
|
||||
@ -21,6 +21,7 @@ pub struct KeyChain {
|
||||
}
|
||||
|
||||
impl KeyChain {
|
||||
#[must_use]
|
||||
pub fn new_os_random() -> Self {
|
||||
// Currently dropping SeedHolder at the end of initialization.
|
||||
// Now entirely sure if we need it in the future.
|
||||
@ -40,6 +41,7 @@ impl KeyChain {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new_mnemonic(passphrase: String) -> Self {
|
||||
// Currently dropping SeedHolder at the end of initialization.
|
||||
// Not entirely sure if we need it in the future.
|
||||
@ -59,14 +61,15 @@ impl KeyChain {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn calculate_shared_secret_receiver(
|
||||
&self,
|
||||
ephemeral_public_key_sender: EphemeralPublicKey,
|
||||
ephemeral_public_key_sender: &EphemeralPublicKey,
|
||||
index: Option<u32>,
|
||||
) -> SharedSecretKey {
|
||||
SharedSecretKey::new(
|
||||
&self.secret_spending_key.generate_viewing_secret_key(index),
|
||||
&ephemeral_public_key_sender,
|
||||
ephemeral_public_key_sender,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -106,7 +109,7 @@ mod tests {
|
||||
|
||||
// Calculate shared secret
|
||||
let _shared_secret = account_id_key_holder
|
||||
.calculate_shared_secret_receiver(ephemeral_public_key_sender, None);
|
||||
.calculate_shared_secret_receiver(&ephemeral_public_key_sender, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -184,7 +187,7 @@ mod tests {
|
||||
|
||||
let key_sender = eph_key_holder.calculate_shared_secret_sender(&keys.viewing_public_key);
|
||||
let key_receiver = keys.calculate_shared_secret_receiver(
|
||||
eph_key_holder.generate_ephemeral_public_key(),
|
||||
&eph_key_holder.generate_ephemeral_public_key(),
|
||||
Some(2),
|
||||
);
|
||||
|
||||
|
||||
@ -25,14 +25,15 @@ pub struct SecretSpendingKey(pub(crate) [u8; 32]);
|
||||
pub type ViewingSecretKey = Scalar;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// Private key holder. Produces public keys. Can produce account_id. Can produce shared secret for
|
||||
/// recepient.
|
||||
/// Private key holder. Produces public keys. Can produce `account_id`. Can produce shared secret
|
||||
/// for recepient.
|
||||
pub struct PrivateKeyHolder {
|
||||
pub nullifier_secret_key: NullifierSecretKey,
|
||||
pub(crate) viewing_secret_key: ViewingSecretKey,
|
||||
}
|
||||
|
||||
impl SeedHolder {
|
||||
#[must_use]
|
||||
pub fn new_os_random() -> Self {
|
||||
let mut enthopy_bytes: [u8; 32] = [0; 32];
|
||||
OsRng.fill_bytes(&mut enthopy_bytes);
|
||||
@ -46,6 +47,7 @@ impl SeedHolder {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new_mnemonic(passphrase: String) -> Self {
|
||||
let mnemonic = Mnemonic::from_entropy(&NSSA_ENTROPY_BYTES)
|
||||
.expect("Enthropy must be a multiple of 32 bytes");
|
||||
@ -56,6 +58,7 @@ impl SeedHolder {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn generate_secret_spending_key_hash(&self) -> HashType {
|
||||
let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed");
|
||||
|
||||
@ -67,22 +70,24 @@ impl SeedHolder {
|
||||
HashType(*hash.first_chunk::<32>().unwrap())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn produce_top_secret_key_holder(&self) -> SecretSpendingKey {
|
||||
SecretSpendingKey(self.generate_secret_spending_key_hash().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretSpendingKey {
|
||||
#[must_use]
|
||||
pub fn generate_nullifier_secret_key(&self, index: Option<u32>) -> NullifierSecretKey {
|
||||
const PREFIX: &[u8; 8] = b"LEE/keys";
|
||||
const SUFFIX_1: &[u8; 1] = &[1];
|
||||
const SUFFIX_2: &[u8; 19] = &[0; 19];
|
||||
|
||||
let index = match index {
|
||||
None => 0u32,
|
||||
_ => index.expect("Expect a valid u32"),
|
||||
};
|
||||
|
||||
const PREFIX: &[u8; 8] = b"LEE/keys";
|
||||
const SUFFIX_1: &[u8; 1] = &[1];
|
||||
const SUFFIX_2: &[u8; 19] = &[0; 19];
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(PREFIX);
|
||||
hasher.update(self.0);
|
||||
@ -93,14 +98,16 @@ impl SecretSpendingKey {
|
||||
<NullifierSecretKey>::from(hasher.finalize_fixed())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn generate_viewing_secret_key(&self, index: Option<u32>) -> ViewingSecretKey {
|
||||
const PREFIX: &[u8; 8] = b"LEE/keys";
|
||||
const SUFFIX_1: &[u8; 1] = &[2];
|
||||
const SUFFIX_2: &[u8; 19] = &[0; 19];
|
||||
|
||||
let index = match index {
|
||||
None => 0u32,
|
||||
_ => index.expect("Expect a valid u32"),
|
||||
};
|
||||
const PREFIX: &[u8; 8] = b"LEE/keys";
|
||||
const SUFFIX_1: &[u8; 1] = &[2];
|
||||
const SUFFIX_2: &[u8; 19] = &[0; 19];
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(PREFIX);
|
||||
@ -112,6 +119,7 @@ impl SecretSpendingKey {
|
||||
hasher.finalize_fixed().into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn produce_private_key_holder(&self, index: Option<u32>) -> PrivateKeyHolder {
|
||||
PrivateKeyHolder {
|
||||
nullifier_secret_key: self.generate_nullifier_secret_key(index),
|
||||
@ -121,10 +129,12 @@ impl SecretSpendingKey {
|
||||
}
|
||||
|
||||
impl PrivateKeyHolder {
|
||||
#[must_use]
|
||||
pub fn generate_nullifier_public_key(&self) -> NullifierPublicKey {
|
||||
(&self.nullifier_secret_key).into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn generate_viewing_public_key(&self) -> ViewingPublicKey {
|
||||
ViewingPublicKey::from_scalar(self.viewing_secret_key)
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ impl NSSAUserData {
|
||||
let expected_account_id =
|
||||
nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(key));
|
||||
if &expected_account_id != account_id {
|
||||
println!("{}, {}", expected_account_id, account_id);
|
||||
println!("{expected_account_id}, {account_id}");
|
||||
check_res = false;
|
||||
}
|
||||
}
|
||||
@ -48,7 +48,7 @@ impl NSSAUserData {
|
||||
for (account_id, (key, _)) in accounts_keys_map {
|
||||
let expected_account_id = nssa::AccountId::from(&key.nullifer_public_key);
|
||||
if expected_account_id != *account_id {
|
||||
println!("{}, {}", expected_account_id, account_id);
|
||||
println!("{expected_account_id}, {account_id}");
|
||||
check_res = false;
|
||||
}
|
||||
}
|
||||
@ -86,7 +86,7 @@ impl NSSAUserData {
|
||||
|
||||
/// Generated new private key for public transaction signatures
|
||||
///
|
||||
/// Returns the account_id of new account
|
||||
/// Returns the `account_id` of new account
|
||||
pub fn generate_new_public_transaction_private_key(
|
||||
&mut self,
|
||||
parent_cci: Option<ChainIndex>,
|
||||
@ -119,7 +119,7 @@ impl NSSAUserData {
|
||||
|
||||
/// Generated new private key for privacy preserving transactions
|
||||
///
|
||||
/// Returns the account_id of new account
|
||||
/// Returns the `account_id` of new account
|
||||
pub fn generate_new_privacy_preserving_transaction_key_chain(
|
||||
&mut self,
|
||||
parent_cci: Option<ChainIndex>,
|
||||
|
||||
@ -6,6 +6,7 @@ pub struct MemPool<T> {
|
||||
}
|
||||
|
||||
impl<T> MemPool<T> {
|
||||
#[must_use]
|
||||
pub fn new(max_size: usize) -> (Self, MemPoolHandle<T>) {
|
||||
let (sender, receiver) = tokio::sync::mpsc::channel(max_size);
|
||||
|
||||
@ -17,6 +18,7 @@ impl<T> MemPool<T> {
|
||||
(mem_pool, sender)
|
||||
}
|
||||
|
||||
/// Pop an item from the mempool first checking the front buffer (LIFO) then the channel (FIFO).
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
use tokio::sync::mpsc::error::TryRecvError;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use std::{env, fs, path::PathBuf};
|
||||
use std::{env, fmt::Write as _, fs, path::PathBuf};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
|
||||
@ -15,7 +15,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if bins.is_empty() {
|
||||
return Err(format!("No .bin files found in {:?}", program_methods_dir).into());
|
||||
return Err(format!("No .bin files found in {}", program_methods_dir.display()).into());
|
||||
}
|
||||
|
||||
fs::create_dir_all(&mod_dir)?;
|
||||
@ -25,14 +25,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let name = path.file_stem().unwrap().to_string_lossy();
|
||||
let bytecode = fs::read(&path)?;
|
||||
let image_id: [u32; 8] = risc0_binfmt::compute_image_id(&bytecode)?.into();
|
||||
src.push_str(&format!(
|
||||
write!(
|
||||
src,
|
||||
"pub const {}_ELF: &[u8] = include_bytes!(r#\"{}\"#);\n\
|
||||
#[expect(clippy::unreadable_literal, reason = \"Generated image IDs from risc0 are cryptographic hashes represented as u32 arrays\")]\n\
|
||||
pub const {}_ID: [u32; 8] = {:?};\n",
|
||||
name.to_uppercase(),
|
||||
path.display(),
|
||||
name.to_uppercase(),
|
||||
image_id
|
||||
));
|
||||
)?;
|
||||
}
|
||||
fs::write(&mod_file, src)?;
|
||||
println!("cargo:warning=Generated module at {}", mod_file.display());
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
use std::{
|
||||
fmt::{Display, Write as _},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use base58::{FromBase58, ToBase58};
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
@ -25,12 +28,14 @@ pub struct Account {
|
||||
|
||||
impl std::fmt::Debug for Account {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let program_owner_hex: String = self
|
||||
let program_owner_hex = self
|
||||
.program_owner
|
||||
.iter()
|
||||
.flat_map(|n| n.to_le_bytes())
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect();
|
||||
.fold(String::new(), |mut acc, bytes| {
|
||||
write!(acc, "{bytes:02x}").expect("writing to string should not fail");
|
||||
acc
|
||||
});
|
||||
f.debug_struct("Account")
|
||||
.field("program_owner", &program_owner_hex)
|
||||
.field("balance", &self.balance)
|
||||
@ -82,14 +87,17 @@ impl std::fmt::Debug for AccountId {
|
||||
}
|
||||
|
||||
impl AccountId {
|
||||
#[must_use]
|
||||
pub fn new(value: [u8; 32]) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn value(&self) -> &[u8; 32] {
|
||||
&self.value
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_value(self) -> [u8; 32] {
|
||||
self.value
|
||||
}
|
||||
@ -172,7 +180,7 @@ mod tests {
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
nonce: 0xdeadbeef,
|
||||
nonce: 0xdead_beef,
|
||||
};
|
||||
let fingerprint = AccountId::new([8; 32]);
|
||||
let new_acc_with_metadata = AccountWithMetadata::new(account.clone(), true, fingerprint);
|
||||
|
||||
@ -10,10 +10,12 @@ pub const DATA_MAX_LENGTH: ByteSize = ByteSize::kib(100);
|
||||
pub struct Data(Vec<u8>);
|
||||
|
||||
impl Data {
|
||||
#[must_use]
|
||||
pub fn into_inner(self) -> Vec<u8> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Reads data from a cursor.
|
||||
#[cfg(feature = "host")]
|
||||
pub fn from_cursor(
|
||||
cursor: &mut std::io::Cursor<&[u8]>,
|
||||
@ -23,7 +25,9 @@ impl Data {
|
||||
let mut u32_bytes = [0u8; 4];
|
||||
cursor.read_exact(&mut u32_bytes)?;
|
||||
let data_length = u32::from_le_bytes(u32_bytes);
|
||||
if data_length as usize > DATA_MAX_LENGTH.as_u64() as usize {
|
||||
if data_length as usize
|
||||
> usize::try_from(DATA_MAX_LENGTH.as_u64()).expect("DATA_MAX_LENGTH fits in usize")
|
||||
{
|
||||
return Err(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidData, DataTooBigError).into(),
|
||||
);
|
||||
@ -49,7 +53,9 @@ impl TryFrom<Vec<u8>> for Data {
|
||||
type Error = DataTooBigError;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
if value.len() > DATA_MAX_LENGTH.as_u64() as usize {
|
||||
if value.len()
|
||||
> usize::try_from(DATA_MAX_LENGTH.as_u64()).expect("DATA_MAX_LENGTH fits in usize")
|
||||
{
|
||||
Err(DataTooBigError)
|
||||
} else {
|
||||
Ok(Self(value))
|
||||
@ -98,13 +104,17 @@ impl<'de> Deserialize<'de> for Data {
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut vec = Vec::with_capacity(
|
||||
seq.size_hint()
|
||||
.unwrap_or(0)
|
||||
.min(DATA_MAX_LENGTH.as_u64() as usize),
|
||||
seq.size_hint().unwrap_or(0).min(
|
||||
usize::try_from(DATA_MAX_LENGTH.as_u64())
|
||||
.expect("DATA_MAX_LENGTH fits in usize"),
|
||||
),
|
||||
);
|
||||
|
||||
while let Some(value) = seq.next_element()? {
|
||||
if vec.len() >= DATA_MAX_LENGTH.as_u64() as usize {
|
||||
if vec.len()
|
||||
>= usize::try_from(DATA_MAX_LENGTH.as_u64())
|
||||
.expect("DATA_MAX_LENGTH fits in usize")
|
||||
{
|
||||
return Err(serde::de::Error::custom(DataTooBigError));
|
||||
}
|
||||
vec.push(value);
|
||||
@ -125,10 +135,15 @@ impl BorshDeserialize for Data {
|
||||
let len = u32::deserialize_reader(reader)?;
|
||||
match len {
|
||||
0 => Ok(Self::default()),
|
||||
len if len as usize > DATA_MAX_LENGTH.as_u64() as usize => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
DataTooBigError,
|
||||
)),
|
||||
len if len as usize
|
||||
> usize::try_from(DATA_MAX_LENGTH.as_u64())
|
||||
.expect("DATA_MAX_LENGTH fits in usize") =>
|
||||
{
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
DataTooBigError,
|
||||
))
|
||||
}
|
||||
len => {
|
||||
let vec_bytes = u8::vec_from_reader(len, reader)?
|
||||
.expect("can't be None in current borsh crate implementation");
|
||||
@ -144,21 +159,35 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_data_max_length_allowed() {
|
||||
let max_vec = vec![0u8; DATA_MAX_LENGTH.as_u64() as usize];
|
||||
let max_vec = vec![
|
||||
0u8;
|
||||
usize::try_from(DATA_MAX_LENGTH.as_u64())
|
||||
.expect("DATA_MAX_LENGTH fits in usize")
|
||||
];
|
||||
let result = Data::try_from(max_vec);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_too_big_error() {
|
||||
let big_vec = vec![0u8; DATA_MAX_LENGTH.as_u64() as usize + 1];
|
||||
let big_vec = vec![
|
||||
0u8;
|
||||
usize::try_from(DATA_MAX_LENGTH.as_u64())
|
||||
.expect("DATA_MAX_LENGTH fits in usize")
|
||||
+ 1
|
||||
];
|
||||
let result = Data::try_from(big_vec);
|
||||
assert!(matches!(result, Err(DataTooBigError)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_borsh_deserialize_exceeding_limit_error() {
|
||||
let too_big_data = vec![0u8; DATA_MAX_LENGTH.as_u64() as usize + 1];
|
||||
let too_big_data = vec![
|
||||
0u8;
|
||||
usize::try_from(DATA_MAX_LENGTH.as_u64())
|
||||
.expect("DATA_MAX_LENGTH fits in usize")
|
||||
+ 1
|
||||
];
|
||||
let mut serialized = Vec::new();
|
||||
<_ as BorshSerialize>::serialize(&too_big_data, &mut serialized).unwrap();
|
||||
|
||||
@ -168,7 +197,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_json_deserialize_exceeding_limit_error() {
|
||||
let data = vec![0u8; DATA_MAX_LENGTH.as_u64() as usize + 1];
|
||||
let data = vec![
|
||||
0u8;
|
||||
usize::try_from(DATA_MAX_LENGTH.as_u64())
|
||||
.expect("DATA_MAX_LENGTH fits in usize")
|
||||
+ 1
|
||||
];
|
||||
let json = serde_json::to_string(&data).unwrap();
|
||||
|
||||
let result: Result<Data, _> = serde_json::from_str(&json);
|
||||
|
||||
@ -42,6 +42,8 @@ pub struct PrivacyPreservingCircuitOutput {
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
impl PrivacyPreservingCircuitOutput {
|
||||
/// Serializes the circuit output to a byte vector.
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
bytemuck::cast_slice(&risc0_zkvm::serde::to_vec(&self).unwrap()).to_vec()
|
||||
}
|
||||
@ -65,9 +67,9 @@ mod tests {
|
||||
AccountWithMetadata::new(
|
||||
Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 12345678901234567890,
|
||||
balance: 12_345_678_901_234_567_890,
|
||||
data: b"test data".to_vec().try_into().unwrap(),
|
||||
nonce: 18446744073709551614,
|
||||
nonce: 18_446_744_073_709_551_614,
|
||||
},
|
||||
true,
|
||||
AccountId::new([0; 32]),
|
||||
@ -75,9 +77,9 @@ mod tests {
|
||||
AccountWithMetadata::new(
|
||||
Account {
|
||||
program_owner: [9, 9, 9, 8, 8, 8, 7, 7],
|
||||
balance: 123123123456456567112,
|
||||
balance: 123_123_123_456_456_567_112,
|
||||
data: b"test data".to_vec().try_into().unwrap(),
|
||||
nonce: 9999999999999999999999,
|
||||
nonce: 9_999_999_999_999_999_999_999,
|
||||
},
|
||||
false,
|
||||
AccountId::new([1; 32]),
|
||||
@ -87,7 +89,7 @@ mod tests {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 100,
|
||||
data: b"post state data".to_vec().try_into().unwrap(),
|
||||
nonce: 18446744073709551615,
|
||||
nonce: 18_446_744_073_709_551_615,
|
||||
}],
|
||||
ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])],
|
||||
new_commitments: vec![Commitment::new(
|
||||
|
||||
@ -14,7 +14,12 @@ pub struct Commitment(pub(super) [u8; 32]);
|
||||
#[cfg(any(feature = "host", test))]
|
||||
impl std::fmt::Debug for Commitment {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let hex: String = self.0.iter().map(|b| format!("{b:02x}")).collect();
|
||||
use std::fmt::Write as _;
|
||||
|
||||
let hex: String = self.0.iter().fold(String::new(), |mut acc, b| {
|
||||
write!(acc, "{b:02x}").expect("writing to string should not fail");
|
||||
acc
|
||||
});
|
||||
write!(f, "Commitment({hex})")
|
||||
}
|
||||
}
|
||||
@ -45,7 +50,8 @@ pub const DUMMY_COMMITMENT_HASH: [u8; 32] = [
|
||||
|
||||
impl Commitment {
|
||||
/// Generates the commitment to a private account owned by user for npk:
|
||||
/// SHA256(npk || program_owner || balance || nonce || SHA256(data))
|
||||
/// SHA256(npk || `program_owner` || balance || nonce || SHA256(data))
|
||||
#[must_use]
|
||||
pub fn new(npk: &NullifierPublicKey, account: &Account) -> Self {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(&npk.to_byte_array());
|
||||
@ -73,6 +79,7 @@ pub type CommitmentSetDigest = [u8; 32];
|
||||
pub type MembershipProof = (usize, Vec<[u8; 32]>);
|
||||
|
||||
/// Computes the resulting digest for the given membership proof and corresponding commitment
|
||||
#[must_use]
|
||||
pub fn compute_digest_for_path(
|
||||
commitment: &Commitment,
|
||||
proof: &MembershipProof,
|
||||
|
||||
@ -17,6 +17,8 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Account {
|
||||
/// Serializes the account to bytes.
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
for word in &self.program_owner {
|
||||
@ -24,12 +26,13 @@ impl Account {
|
||||
}
|
||||
bytes.extend_from_slice(&self.balance.to_le_bytes());
|
||||
bytes.extend_from_slice(&self.nonce.to_le_bytes());
|
||||
let data_length: u32 = self.data.len() as u32;
|
||||
let data_length: u32 = u32::try_from(self.data.len()).expect("data length fits in u32");
|
||||
bytes.extend_from_slice(&data_length.to_le_bytes());
|
||||
bytes.extend_from_slice(self.data.as_ref());
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Deserializes an account from a cursor.
|
||||
#[cfg(feature = "host")]
|
||||
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
|
||||
use crate::account::data::Data;
|
||||
@ -65,15 +68,18 @@ impl Account {
|
||||
}
|
||||
|
||||
impl Commitment {
|
||||
#[must_use]
|
||||
pub fn to_byte_array(&self) -> [u8; 32] {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
#[must_use]
|
||||
pub fn from_byte_array(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
/// Deserializes a commitment from a cursor.
|
||||
#[cfg(feature = "host")]
|
||||
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
|
||||
let mut bytes = [0u8; 32];
|
||||
@ -83,6 +89,7 @@ impl Commitment {
|
||||
}
|
||||
|
||||
impl NullifierPublicKey {
|
||||
#[must_use]
|
||||
pub fn to_byte_array(&self) -> [u8; 32] {
|
||||
self.0
|
||||
}
|
||||
@ -90,15 +97,18 @@ impl NullifierPublicKey {
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
impl Nullifier {
|
||||
#[must_use]
|
||||
pub fn to_byte_array(&self) -> [u8; 32] {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
#[must_use]
|
||||
pub fn from_byte_array(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
/// Deserializes a nullifier from a cursor.
|
||||
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
|
||||
let mut bytes = [0u8; 32];
|
||||
cursor.read_exact(&mut bytes)?;
|
||||
@ -107,9 +117,12 @@ impl Nullifier {
|
||||
}
|
||||
|
||||
impl Ciphertext {
|
||||
/// Serializes the ciphertext to bytes.
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
let ciphertext_length: u32 = self.0.len() as u32;
|
||||
let ciphertext_length: u32 =
|
||||
u32::try_from(self.0.len()).expect("ciphertext length fits in u32");
|
||||
bytes.extend_from_slice(&ciphertext_length.to_le_bytes());
|
||||
bytes.extend_from_slice(&self.0);
|
||||
|
||||
@ -117,16 +130,19 @@ impl Ciphertext {
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
#[must_use]
|
||||
pub fn into_inner(self) -> Vec<u8> {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
#[must_use]
|
||||
pub fn from_inner(inner: Vec<u8>) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
/// Deserializes ciphertext from a cursor.
|
||||
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
|
||||
let mut u32_bytes = [0; 4];
|
||||
|
||||
@ -141,10 +157,13 @@ impl Ciphertext {
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
impl Secp256k1Point {
|
||||
/// Converts the point to bytes.
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> [u8; 33] {
|
||||
self.0.clone().try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Deserializes a secp256k1 point from a cursor.
|
||||
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
|
||||
let mut value = vec![0; 33];
|
||||
cursor.read_exact(&mut value)?;
|
||||
@ -153,6 +172,7 @@ impl Secp256k1Point {
|
||||
}
|
||||
|
||||
impl AccountId {
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
*self.value()
|
||||
}
|
||||
@ -166,7 +186,7 @@ mod tests {
|
||||
fn test_enconding() {
|
||||
let account = Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 123456789012345678901234567890123456,
|
||||
balance: 123_456_789_012_345_678_901_234_567_890_123_456,
|
||||
nonce: 42,
|
||||
data: b"hola mundo".to_vec().try_into().unwrap(),
|
||||
};
|
||||
@ -227,7 +247,7 @@ mod tests {
|
||||
fn test_account_to_bytes_roundtrip() {
|
||||
let account = Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 123456789012345678901234567890123456,
|
||||
balance: 123_456_789_012_345_678_901_234_567_890_123_456,
|
||||
nonce: 42,
|
||||
data: b"hola mundo".to_vec().try_into().unwrap(),
|
||||
};
|
||||
|
||||
@ -28,19 +28,25 @@ pub struct Ciphertext(pub(crate) Vec<u8>);
|
||||
#[cfg(any(feature = "host", test))]
|
||||
impl std::fmt::Debug for Ciphertext {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let hex: String = self.0.iter().map(|b| format!("{b:02x}")).collect();
|
||||
use std::fmt::Write as _;
|
||||
|
||||
let hex: String = self.0.iter().fold(String::new(), |mut acc, b| {
|
||||
write!(acc, "{b:02x}").expect("writing to string should not fail");
|
||||
acc
|
||||
});
|
||||
write!(f, "Ciphertext({hex})")
|
||||
}
|
||||
}
|
||||
|
||||
impl EncryptionScheme {
|
||||
#[must_use]
|
||||
pub fn encrypt(
|
||||
account: &Account,
|
||||
shared_secret: &SharedSecretKey,
|
||||
commitment: &Commitment,
|
||||
output_index: u32,
|
||||
) -> Ciphertext {
|
||||
let mut buffer = account.to_bytes().to_vec();
|
||||
let mut buffer = account.to_bytes().clone();
|
||||
Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index);
|
||||
Ciphertext(buffer)
|
||||
}
|
||||
@ -72,6 +78,7 @@ impl EncryptionScheme {
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
#[must_use]
|
||||
pub fn decrypt(
|
||||
ciphertext: &Ciphertext,
|
||||
shared_secret: &SharedSecretKey,
|
||||
@ -79,7 +86,7 @@ impl EncryptionScheme {
|
||||
output_index: u32,
|
||||
) -> Option<Account> {
|
||||
use std::io::Cursor;
|
||||
let mut buffer = ciphertext.0.to_owned();
|
||||
let mut buffer = ciphertext.0.clone();
|
||||
Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index);
|
||||
|
||||
let mut cursor = Cursor::new(buffer.as_slice());
|
||||
@ -87,12 +94,12 @@ impl EncryptionScheme {
|
||||
.inspect_err(|err| {
|
||||
println!(
|
||||
"Failed to decode {ciphertext:?} \n
|
||||
with secret {:?} ,\n
|
||||
with secret {:?} ,\n
|
||||
commitment {commitment:?} ,\n
|
||||
and output_index {output_index} ,\n
|
||||
with error {err:?}",
|
||||
shared_secret.0
|
||||
)
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use k256::{
|
||||
AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint,
|
||||
@ -15,12 +17,16 @@ pub struct Secp256k1Point(pub Vec<u8>);
|
||||
|
||||
impl std::fmt::Debug for Secp256k1Point {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let hex: String = self.0.iter().map(|b| format!("{b:02x}")).collect();
|
||||
let hex: String = self.0.iter().fold(String::new(), |mut acc, b| {
|
||||
write!(acc, "{b:02x}").expect("writing to string should not fail");
|
||||
acc
|
||||
});
|
||||
write!(f, "Secp256k1Point({hex})")
|
||||
}
|
||||
}
|
||||
|
||||
impl Secp256k1Point {
|
||||
#[must_use]
|
||||
pub fn from_scalar(value: Scalar) -> Secp256k1Point {
|
||||
let x_bytes: FieldBytes = value.into();
|
||||
let x = k256::Scalar::from_repr(x_bytes).unwrap();
|
||||
@ -43,6 +49,8 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey {
|
||||
}
|
||||
|
||||
impl SharedSecretKey {
|
||||
/// Creates a new shared secret key from a scalar and a point.
|
||||
#[must_use]
|
||||
pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self {
|
||||
let scalar = k256::Scalar::from_repr((*scalar).into()).unwrap();
|
||||
let point: [u8; 33] = point.0.clone().try_into().unwrap();
|
||||
|
||||
@ -28,10 +28,10 @@ impl AsRef<[u8]> for NullifierPublicKey {
|
||||
|
||||
impl From<&NullifierSecretKey> for NullifierPublicKey {
|
||||
fn from(value: &NullifierSecretKey) -> Self {
|
||||
let mut bytes = Vec::new();
|
||||
const PREFIX: &[u8; 8] = b"LEE/keys";
|
||||
const SUFFIX_1: &[u8; 1] = &[7];
|
||||
const SUFFIX_2: &[u8; 23] = &[0; 23];
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(PREFIX);
|
||||
bytes.extend_from_slice(value);
|
||||
bytes.extend_from_slice(SUFFIX_1);
|
||||
@ -52,12 +52,19 @@ pub struct Nullifier(pub(super) [u8; 32]);
|
||||
#[cfg(any(feature = "host", test))]
|
||||
impl std::fmt::Debug for Nullifier {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let hex: String = self.0.iter().map(|b| format!("{b:02x}")).collect();
|
||||
use std::fmt::Write as _;
|
||||
|
||||
let hex: String = self.0.iter().fold(String::new(), |mut acc, b| {
|
||||
write!(acc, "{b:02x}").expect("writing to string should not fail");
|
||||
acc
|
||||
});
|
||||
write!(f, "Nullifier({hex})")
|
||||
}
|
||||
}
|
||||
|
||||
impl Nullifier {
|
||||
/// Computes a nullifier for an account update.
|
||||
#[must_use]
|
||||
pub fn for_account_update(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self {
|
||||
const UPDATE_PREFIX: &[u8; 32] = b"/NSSA/v0.2/Nullifier/Update/\x00\x00\x00\x00";
|
||||
let mut bytes = UPDATE_PREFIX.to_vec();
|
||||
@ -66,6 +73,8 @@ impl Nullifier {
|
||||
Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap())
|
||||
}
|
||||
|
||||
/// Computes a nullifier for an account initialization.
|
||||
#[must_use]
|
||||
pub fn for_account_initialization(npk: &NullifierPublicKey) -> Self {
|
||||
const INIT_PREFIX: &[u8; 32] = b"/NSSA/v0.2/Nullifier/Initialize/";
|
||||
let mut bytes = INIT_PREFIX.to_vec();
|
||||
|
||||
@ -15,7 +15,7 @@ pub struct ProgramInput<T> {
|
||||
pub instruction: T,
|
||||
}
|
||||
|
||||
/// A 32-byte seed used to compute a *Program-Derived AccountId* (PDA).
|
||||
/// A 32-byte seed used to compute a *Program-Derived `AccountId`* (PDA).
|
||||
///
|
||||
/// Each program can derive up to `2^256` unique account IDs by choosing different
|
||||
/// seeds. PDAs allow programs to control namespaced account identifiers without
|
||||
@ -24,11 +24,13 @@ pub struct ProgramInput<T> {
|
||||
pub struct PdaSeed([u8; 32]);
|
||||
|
||||
impl PdaSeed {
|
||||
#[must_use]
|
||||
pub const fn new(value: [u8; 32]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compute_authorized_pdas(
|
||||
caller_program_id: Option<ProgramId>,
|
||||
pda_seeds: &[PdaSeed],
|
||||
@ -90,6 +92,7 @@ impl ChainedCall {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_pda_seeds(mut self, pda_seeds: Vec<PdaSeed>) -> Self {
|
||||
self.pda_seeds = pda_seeds;
|
||||
self
|
||||
@ -110,6 +113,7 @@ pub struct AccountPostState {
|
||||
impl AccountPostState {
|
||||
/// Creates a post state without a claim request.
|
||||
/// The executing program is not requesting ownership of the account.
|
||||
#[must_use]
|
||||
pub fn new(account: Account) -> Self {
|
||||
Self {
|
||||
account,
|
||||
@ -120,6 +124,7 @@ impl AccountPostState {
|
||||
/// Creates a post state that requests ownership of the account.
|
||||
/// This indicates that the executing program intends to claim the
|
||||
/// account as its own and is allowed to mutate it.
|
||||
#[must_use]
|
||||
pub fn new_claimed(account: Account) -> Self {
|
||||
Self {
|
||||
account,
|
||||
@ -129,6 +134,7 @@ impl AccountPostState {
|
||||
|
||||
/// Creates a post state that requests ownership of the account
|
||||
/// if the account's program owner is the default program ID.
|
||||
#[must_use]
|
||||
pub fn new_claimed_if_default(account: Account) -> Self {
|
||||
let claim = account.program_owner == DEFAULT_PROGRAM_ID;
|
||||
Self { account, claim }
|
||||
@ -136,11 +142,13 @@ impl AccountPostState {
|
||||
|
||||
/// Returns `true` if this post state requests that the account
|
||||
/// be claimed (owned) by the executing program.
|
||||
#[must_use]
|
||||
pub fn requires_claim(&self) -> bool {
|
||||
self.claim
|
||||
}
|
||||
|
||||
/// Returns the underlying account
|
||||
#[must_use]
|
||||
pub fn account(&self) -> &Account {
|
||||
&self.account
|
||||
}
|
||||
@ -151,6 +159,7 @@ impl AccountPostState {
|
||||
}
|
||||
|
||||
/// Consumes the post state and returns the underlying account
|
||||
#[must_use]
|
||||
pub fn into_account(self) -> Account {
|
||||
self.account
|
||||
}
|
||||
@ -167,6 +176,8 @@ pub struct ProgramOutput {
|
||||
pub chained_calls: Vec<ChainedCall>,
|
||||
}
|
||||
|
||||
/// Reads the NSSA inputs from the guest environment.
|
||||
#[must_use]
|
||||
pub fn read_nssa_inputs<T: DeserializeOwned>() -> (ProgramInput<T>, InstructionData) {
|
||||
let pre_states: Vec<AccountWithMetadata> = env::read();
|
||||
let instruction_words: InstructionData = env::read();
|
||||
@ -215,6 +226,7 @@ pub fn write_nssa_outputs_with_chained_call(
|
||||
/// - `pre_states`: The list of input accounts, each annotated with authorization metadata.
|
||||
/// - `post_states`: The list of resulting accounts after executing the program logic.
|
||||
/// - `executing_program_id`: The identifier of the program that was executed.
|
||||
#[must_use]
|
||||
pub fn validate_execution(
|
||||
pre_states: &[AccountWithMetadata],
|
||||
post_states: &[AccountPostState],
|
||||
|
||||
@ -4,6 +4,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Message {
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
borsh::to_vec(&self).expect("Autoderived borsh serialization failure")
|
||||
}
|
||||
@ -14,6 +15,7 @@ impl Message {
|
||||
}
|
||||
|
||||
impl PrivacyPreservingTransaction {
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
borsh::to_vec(&self).expect("Autoderived borsh serialization failure")
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::{ProgramDeploymentTransaction, error::NssaError};
|
||||
|
||||
impl ProgramDeploymentTransaction {
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
borsh::to_vec(&self).expect("Autoderived borsh serialization failure")
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ impl Message {
|
||||
}
|
||||
|
||||
impl PublicTransaction {
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
borsh::to_vec(&self).expect("Autoderived borsh serialization failure")
|
||||
}
|
||||
|
||||
@ -64,12 +64,12 @@ impl MerkleTree {
|
||||
let capacity = capacity.next_power_of_two();
|
||||
let total_depth = capacity.trailing_zeros() as usize;
|
||||
|
||||
let nodes = default_values::DEFAULT_VALUES[..(total_depth + 1)]
|
||||
let nodes = default_values::DEFAULT_VALUES[..=total_depth]
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.flat_map(|(level, default_value)| std::iter::repeat_n(default_value, 1 << level))
|
||||
.cloned()
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
@ -164,7 +164,7 @@ mod tests {
|
||||
impl MerkleTree {
|
||||
pub fn new(values: &[Value]) -> Self {
|
||||
let mut this = Self::with_capacity(values.len());
|
||||
for value in values.iter().cloned() {
|
||||
for value in values.iter().copied() {
|
||||
this.insert(value);
|
||||
}
|
||||
this
|
||||
@ -201,7 +201,7 @@ mod tests {
|
||||
hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de");
|
||||
assert_eq!(tree.root(), expected_root);
|
||||
assert_eq!(tree.capacity, 4);
|
||||
assert_eq!(tree.length, 4)
|
||||
assert_eq!(tree.length, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -283,15 +283,15 @@ mod tests {
|
||||
assert_eq!(tree.length, 0);
|
||||
assert_eq!(tree.nodes.len(), 15);
|
||||
for i in 7..15 {
|
||||
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[0])
|
||||
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[0]);
|
||||
}
|
||||
for i in 3..7 {
|
||||
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[1])
|
||||
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[1]);
|
||||
}
|
||||
for i in 1..3 {
|
||||
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[2])
|
||||
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[2]);
|
||||
}
|
||||
assert_eq!(*tree.get_node(0), default_values::DEFAULT_VALUES[3])
|
||||
assert_eq!(*tree.get_node(0), default_values::DEFAULT_VALUES[3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -21,10 +21,12 @@ use crate::{
|
||||
pub struct Proof(pub(crate) Vec<u8>);
|
||||
|
||||
impl Proof {
|
||||
#[must_use]
|
||||
pub fn into_inner(self) -> Vec<u8> {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_inner(inner: Vec<u8>) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
@ -38,6 +40,7 @@ pub struct ProgramWithDependencies {
|
||||
}
|
||||
|
||||
impl ProgramWithDependencies {
|
||||
#[must_use]
|
||||
pub fn new(program: Program, dependencies: HashMap<ProgramId, Program>) -> Self {
|
||||
Self {
|
||||
program,
|
||||
@ -74,7 +77,7 @@ pub fn execute_and_prove(
|
||||
|
||||
let initial_call = ChainedCall {
|
||||
program_id: program.id(),
|
||||
instruction_data: instruction_data.clone(),
|
||||
instruction_data,
|
||||
pre_states,
|
||||
pda_seeds: vec![],
|
||||
};
|
||||
@ -217,7 +220,7 @@ mod tests {
|
||||
let expected_recipient_post = Account {
|
||||
program_owner: program.id(),
|
||||
balance: balance_to_move,
|
||||
nonce: 0xdeadbeef,
|
||||
nonce: 0xdead_beef,
|
||||
data: Data::default(),
|
||||
};
|
||||
|
||||
@ -230,7 +233,7 @@ mod tests {
|
||||
vec![sender, recipient],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![0, 2],
|
||||
vec![0xdeadbeef],
|
||||
vec![0xdead_beef],
|
||||
vec![(recipient_keys.npk(), shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
@ -267,7 +270,7 @@ mod tests {
|
||||
let sender_pre = AccountWithMetadata::new(
|
||||
Account {
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
nonce: 0xdead_beef,
|
||||
program_owner: program.id(),
|
||||
data: Data::default(),
|
||||
},
|
||||
@ -302,13 +305,13 @@ mod tests {
|
||||
let expected_private_account_1 = Account {
|
||||
program_owner: program.id(),
|
||||
balance: 100 - balance_to_move,
|
||||
nonce: 0xdeadbeef1,
|
||||
nonce: 0xdead_beef1,
|
||||
..Default::default()
|
||||
};
|
||||
let expected_private_account_2 = Account {
|
||||
program_owner: program.id(),
|
||||
balance: balance_to_move,
|
||||
nonce: 0xdeadbeef2,
|
||||
nonce: 0xdead_beef2,
|
||||
..Default::default()
|
||||
};
|
||||
let expected_new_commitments = vec![
|
||||
@ -326,7 +329,7 @@ mod tests {
|
||||
vec![sender_pre.clone(), recipient],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(sender_keys.npk(), shared_secret_1),
|
||||
(recipient_keys.npk(), shared_secret_2),
|
||||
|
||||
@ -20,8 +20,8 @@ pub struct EncryptedAccountData {
|
||||
impl EncryptedAccountData {
|
||||
fn new(
|
||||
ciphertext: Ciphertext,
|
||||
npk: NullifierPublicKey,
|
||||
vpk: ViewingPublicKey,
|
||||
npk: &NullifierPublicKey,
|
||||
vpk: &ViewingPublicKey,
|
||||
epk: EphemeralPublicKey,
|
||||
) -> Self {
|
||||
let view_tag = Self::compute_view_tag(npk, vpk);
|
||||
@ -33,7 +33,8 @@ impl EncryptedAccountData {
|
||||
}
|
||||
|
||||
/// Computes the tag as the first byte of SHA256("/NSSA/v0.2/ViewTag/" || Npk || vpk)
|
||||
pub fn compute_view_tag(npk: NullifierPublicKey, vpk: ViewingPublicKey) -> ViewTag {
|
||||
#[must_use]
|
||||
pub fn compute_view_tag(npk: &NullifierPublicKey, vpk: &ViewingPublicKey) -> ViewTag {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"/NSSA/v0.2/ViewTag/");
|
||||
hasher.update(npk.to_byte_array());
|
||||
@ -98,7 +99,7 @@ impl Message {
|
||||
.into_iter()
|
||||
.zip(public_keys)
|
||||
.map(|(ciphertext, (npk, vpk, epk))| {
|
||||
EncryptedAccountData::new(ciphertext, npk, vpk, epk)
|
||||
EncryptedAccountData::new(ciphertext, &npk, &vpk, epk)
|
||||
})
|
||||
.collect();
|
||||
Ok(Self {
|
||||
@ -126,6 +127,7 @@ pub mod tests {
|
||||
privacy_preserving_transaction::message::{EncryptedAccountData, Message},
|
||||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn message_for_tests() -> Message {
|
||||
let account1 = Account::default();
|
||||
let account2 = Account::default();
|
||||
@ -173,7 +175,7 @@ pub mod tests {
|
||||
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||
let ciphertext = EncryptionScheme::encrypt(&account, &shared_secret, &commitment, 2);
|
||||
let encrypted_account_data =
|
||||
EncryptedAccountData::new(ciphertext.clone(), npk.clone(), vpk.clone(), epk.clone());
|
||||
EncryptedAccountData::new(ciphertext.clone(), &npk, &vpk, epk.clone());
|
||||
|
||||
let expected_view_tag = {
|
||||
let mut hasher = Sha256::new();
|
||||
@ -188,7 +190,7 @@ pub mod tests {
|
||||
assert_eq!(encrypted_account_data.epk, epk);
|
||||
assert_eq!(
|
||||
encrypted_account_data.view_tag,
|
||||
EncryptedAccountData::compute_view_tag(npk, vpk)
|
||||
EncryptedAccountData::compute_view_tag(&npk, &vpk)
|
||||
);
|
||||
assert_eq!(encrypted_account_data.view_tag, expected_view_tag);
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ pub struct PrivacyPreservingTransaction {
|
||||
}
|
||||
|
||||
impl PrivacyPreservingTransaction {
|
||||
#[must_use]
|
||||
pub fn new(message: Message, witness_set: WitnessSet) -> Self {
|
||||
Self {
|
||||
message,
|
||||
@ -119,19 +120,22 @@ impl PrivacyPreservingTransaction {
|
||||
Ok(message
|
||||
.public_account_ids
|
||||
.iter()
|
||||
.cloned()
|
||||
.copied()
|
||||
.zip(message.public_post_states.clone())
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn message(&self) -> &Message {
|
||||
&self.message
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn witness_set(&self) -> &WitnessSet {
|
||||
&self.witness_set
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
let bytes = self.to_bytes();
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
@ -147,6 +151,7 @@ impl PrivacyPreservingTransaction {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
|
||||
let mut acc_set = self
|
||||
.signer_account_ids()
|
||||
|
||||
@ -12,6 +12,7 @@ pub struct WitnessSet {
|
||||
}
|
||||
|
||||
impl WitnessSet {
|
||||
#[must_use]
|
||||
pub fn for_message(message: &Message, proof: Proof, private_keys: &[&PrivateKey]) -> Self {
|
||||
let message_bytes = message.to_bytes();
|
||||
let signatures_and_public_keys = private_keys
|
||||
@ -24,11 +25,12 @@ impl WitnessSet {
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
proof,
|
||||
signatures_and_public_keys,
|
||||
proof,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn signatures_are_valid_for(&self, message: &Message) -> bool {
|
||||
let message_bytes = message.to_bytes();
|
||||
for (signature, public_key) in self.signatures_and_public_keys() {
|
||||
@ -39,18 +41,22 @@ impl WitnessSet {
|
||||
true
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn signatures_and_public_keys(&self) -> &[(Signature, PublicKey)] {
|
||||
&self.signatures_and_public_keys
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn proof(&self) -> &Proof {
|
||||
&self.proof
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_raw_parts(self) -> (Vec<(Signature, PublicKey)>, Proof) {
|
||||
(self.signatures_and_public_keys, self.proof)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_raw_parts(
|
||||
signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
||||
proof: Proof,
|
||||
|
||||
@ -32,10 +32,12 @@ impl Program {
|
||||
Ok(Self { elf: bytecode, id })
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn id(&self) -> ProgramId {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn elf(&self) -> &[u8] {
|
||||
&self.elf
|
||||
}
|
||||
@ -85,18 +87,21 @@ impl Program {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn authenticated_transfer_program() -> Self {
|
||||
// This unwrap won't panic since the `AUTHENTICATED_TRANSFER_ELF` comes from risc0 build of
|
||||
// `program_methods`
|
||||
Self::new(AUTHENTICATED_TRANSFER_ELF.to_vec()).unwrap()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn token() -> Self {
|
||||
// This unwrap won't panic since the `TOKEN_ELF` comes from risc0 build of
|
||||
// `program_methods`
|
||||
Self::new(TOKEN_ELF.to_vec()).unwrap()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn amm() -> Self {
|
||||
Self::new(AMM_ELF.to_vec()).expect("The AMM program must be a valid Risc0 program")
|
||||
}
|
||||
@ -104,12 +109,14 @@ impl Program {
|
||||
|
||||
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
||||
impl Program {
|
||||
#[must_use]
|
||||
pub fn pinata() -> Self {
|
||||
// This unwrap won't panic since the `PINATA_ELF` comes from risc0 build of
|
||||
// `program_methods`
|
||||
Self::new(PINATA_ELF.to_vec()).unwrap()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn pinata_token() -> Self {
|
||||
use crate::program_methods::PINATA_TOKEN_ELF;
|
||||
Self::new(PINATA_TOKEN_ELF.to_vec()).expect("Piñata program must be a valid R0BF file")
|
||||
@ -130,6 +137,7 @@ mod tests {
|
||||
|
||||
impl Program {
|
||||
/// A program that changes the nonce of an account
|
||||
#[must_use]
|
||||
pub fn nonce_changer_program() -> Self {
|
||||
use test_program_methods::{NONCE_CHANGER_ELF, NONCE_CHANGER_ID};
|
||||
|
||||
@ -140,6 +148,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A program that produces more output accounts than the inputs it received
|
||||
#[must_use]
|
||||
pub fn extra_output_program() -> Self {
|
||||
use test_program_methods::{EXTRA_OUTPUT_ELF, EXTRA_OUTPUT_ID};
|
||||
|
||||
@ -150,6 +159,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A program that produces less output accounts than the inputs it received
|
||||
#[must_use]
|
||||
pub fn missing_output_program() -> Self {
|
||||
use test_program_methods::{MISSING_OUTPUT_ELF, MISSING_OUTPUT_ID};
|
||||
|
||||
@ -160,6 +170,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A program that changes the program owner of an account to [0, 1, 2, 3, 4, 5, 6, 7]
|
||||
#[must_use]
|
||||
pub fn program_owner_changer() -> Self {
|
||||
use test_program_methods::{PROGRAM_OWNER_CHANGER_ELF, PROGRAM_OWNER_CHANGER_ID};
|
||||
|
||||
@ -170,6 +181,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A program that transfers balance without caring about authorizations
|
||||
#[must_use]
|
||||
pub fn simple_balance_transfer() -> Self {
|
||||
use test_program_methods::{SIMPLE_BALANCE_TRANSFER_ELF, SIMPLE_BALANCE_TRANSFER_ID};
|
||||
|
||||
@ -180,6 +192,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A program that modifies the data of an account
|
||||
#[must_use]
|
||||
pub fn data_changer() -> Self {
|
||||
use test_program_methods::{DATA_CHANGER_ELF, DATA_CHANGER_ID};
|
||||
|
||||
@ -190,6 +203,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A program that mints balance
|
||||
#[must_use]
|
||||
pub fn minter() -> Self {
|
||||
use test_program_methods::{MINTER_ELF, MINTER_ID};
|
||||
|
||||
@ -200,6 +214,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A program that burns balance
|
||||
#[must_use]
|
||||
pub fn burner() -> Self {
|
||||
use test_program_methods::{BURNER_ELF, BURNER_ID};
|
||||
|
||||
@ -209,6 +224,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn chain_caller() -> Self {
|
||||
use test_program_methods::{CHAIN_CALLER_ELF, CHAIN_CALLER_ID};
|
||||
|
||||
@ -218,6 +234,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn claimer() -> Self {
|
||||
use test_program_methods::{CLAIMER_ELF, CLAIMER_ID};
|
||||
|
||||
@ -227,6 +244,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn changer_claimer() -> Self {
|
||||
use test_program_methods::{CHANGER_CLAIMER_ELF, CHANGER_CLAIMER_ID};
|
||||
|
||||
@ -236,6 +254,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn noop() -> Self {
|
||||
use test_program_methods::{NOOP_ELF, NOOP_ID};
|
||||
|
||||
@ -245,6 +264,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn malicious_authorization_changer() -> Self {
|
||||
use test_program_methods::{
|
||||
MALICIOUS_AUTHORIZATION_CHANGER_ELF, MALICIOUS_AUTHORIZATION_CHANGER_ID,
|
||||
@ -256,6 +276,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn modified_transfer_program() -> Self {
|
||||
use test_program_methods::MODIFIED_TRANSFER_ELF;
|
||||
// This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of
|
||||
@ -267,11 +288,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_program_execution() {
|
||||
let program = Program::simple_balance_transfer();
|
||||
let balance_to_move: u128 = 11223344556677;
|
||||
let balance_to_move: u128 = 11_223_344_556_677;
|
||||
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
|
||||
let sender = AccountWithMetadata::new(
|
||||
Account {
|
||||
balance: 77665544332211,
|
||||
balance: 77_665_544_332_211,
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
@ -281,7 +302,7 @@ mod tests {
|
||||
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||
|
||||
let expected_sender_post = Account {
|
||||
balance: 77665544332211 - balance_to_move,
|
||||
balance: 77_665_544_332_211 - balance_to_move,
|
||||
..Account::default()
|
||||
};
|
||||
let expected_recipient_post = Account {
|
||||
|
||||
@ -6,10 +6,12 @@ pub struct Message {
|
||||
}
|
||||
|
||||
impl Message {
|
||||
#[must_use]
|
||||
pub fn new(bytecode: Vec<u8>) -> Self {
|
||||
Self { bytecode }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_bytecode(self) -> Vec<u8> {
|
||||
self.bytecode
|
||||
}
|
||||
|
||||
@ -12,10 +12,12 @@ pub struct ProgramDeploymentTransaction {
|
||||
}
|
||||
|
||||
impl ProgramDeploymentTransaction {
|
||||
#[must_use]
|
||||
pub fn new(message: Message) -> Self {
|
||||
Self { message }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_message(self) -> Message {
|
||||
self.message
|
||||
}
|
||||
@ -33,6 +35,7 @@ impl ProgramDeploymentTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
let bytes = self.to_bytes();
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
@ -40,6 +43,7 @@ impl ProgramDeploymentTransaction {
|
||||
hasher.finalize_fixed().into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
@ -49,6 +49,7 @@ impl Message {
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new_preserialized(
|
||||
program_id: ProgramId,
|
||||
account_ids: Vec<AccountId>,
|
||||
|
||||
@ -22,6 +22,7 @@ pub struct PublicTransaction {
|
||||
}
|
||||
|
||||
impl PublicTransaction {
|
||||
#[must_use]
|
||||
pub fn new(message: Message, witness_set: WitnessSet) -> Self {
|
||||
Self {
|
||||
message,
|
||||
@ -29,10 +30,12 @@ impl PublicTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn message(&self) -> &Message {
|
||||
&self.message
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn witness_set(&self) -> &WitnessSet {
|
||||
&self.witness_set
|
||||
}
|
||||
@ -45,6 +48,7 @@ impl PublicTransaction {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
|
||||
let mut acc_set = self
|
||||
.signer_account_ids()
|
||||
@ -55,6 +59,7 @@ impl PublicTransaction {
|
||||
acc_set.into_iter().collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
let bytes = self.to_bytes();
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
@ -353,7 +358,7 @@ pub mod tests {
|
||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -373,7 +378,7 @@ pub mod tests {
|
||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -394,7 +399,7 @@ pub mod tests {
|
||||
witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -414,7 +419,7 @@ pub mod tests {
|
||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -423,13 +428,13 @@ pub mod tests {
|
||||
let state = state_for_tests();
|
||||
let nonces = vec![0, 0];
|
||||
let instruction = 1337;
|
||||
let unknown_program_id = [0xdeadbeef; 8];
|
||||
let unknown_program_id = [0xdead_beef; 8];
|
||||
let message =
|
||||
Message::try_new(unknown_program_id, vec![addr1, addr2], nonces, instruction).unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
|
||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ pub struct WitnessSet {
|
||||
}
|
||||
|
||||
impl WitnessSet {
|
||||
#[must_use]
|
||||
pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self {
|
||||
let message_bytes = message.to_bytes();
|
||||
let signatures_and_public_keys = private_keys
|
||||
@ -24,6 +25,7 @@ impl WitnessSet {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_valid_for(&self, message: &Message) -> bool {
|
||||
let message_bytes = message.to_bytes();
|
||||
for (signature, public_key) in self.signatures_and_public_keys() {
|
||||
@ -34,14 +36,17 @@ impl WitnessSet {
|
||||
true
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn signatures_and_public_keys(&self) -> &[(Signature, PublicKey)] {
|
||||
&self.signatures_and_public_keys
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_raw_parts(self) -> Vec<(Signature, PublicKey)> {
|
||||
self.signatures_and_public_keys
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_raw_parts(signatures_and_public_keys: Vec<(Signature, PublicKey)>) -> Self {
|
||||
Self {
|
||||
signatures_and_public_keys,
|
||||
|
||||
@ -14,7 +14,7 @@ pub struct TestVector {
|
||||
}
|
||||
|
||||
/// Test vectors from
|
||||
/// https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
|
||||
/// <https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv>
|
||||
//
|
||||
pub fn test_vectors() -> Vec<TestVector> {
|
||||
vec![
|
||||
|
||||
@ -18,6 +18,7 @@ impl std::fmt::Debug for Signature {
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
#[must_use]
|
||||
pub fn new(key: &PrivateKey, message: &[u8]) -> Self {
|
||||
let mut aux_random = [0u8; 32];
|
||||
OsRng.fill_bytes(&mut aux_random);
|
||||
@ -39,6 +40,7 @@ impl Signature {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_valid_for(&self, bytes: &[u8], public_key: &PublicKey) -> bool {
|
||||
let pk = secp256k1::XOnlyPublicKey::from_byte_array(*public_key.value()).unwrap();
|
||||
let secp = secp256k1::Secp256k1::new();
|
||||
|
||||
@ -9,14 +9,14 @@ use crate::error::NssaError;
|
||||
pub struct PrivateKey([u8; 32]);
|
||||
|
||||
impl PrivateKey {
|
||||
#[must_use]
|
||||
pub fn new_os_random() -> Self {
|
||||
let mut rng = OsRng;
|
||||
|
||||
loop {
|
||||
match Self::try_new(rng.r#gen()) {
|
||||
Ok(key) => break key,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if let Ok(key) = Self::try_new(rng.r#gen()) {
|
||||
break key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ impl PrivateKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn value(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ impl BorshDeserialize for PublicKey {
|
||||
}
|
||||
|
||||
impl PublicKey {
|
||||
#[must_use]
|
||||
pub fn new_from_private_key(key: &PrivateKey) -> Self {
|
||||
let value = {
|
||||
let secret_key = secp256k1::SecretKey::from_byte_array(*key.value()).unwrap();
|
||||
@ -47,6 +48,7 @@ impl PublicKey {
|
||||
Ok(Self(value))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn value(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
@ -52,8 +52,8 @@ impl CommitmentSet {
|
||||
}
|
||||
|
||||
/// Initializes an empty `CommitmentSet` with a given capacity.
|
||||
/// If the capacity is not a power_of_two, then capacity is taken
|
||||
/// to be the next power_of_two.
|
||||
/// If the capacity is not a `power_of_two`, then capacity is taken
|
||||
/// to be the next `power_of_two`.
|
||||
pub(crate) fn with_capacity(capacity: usize) -> CommitmentSet {
|
||||
Self {
|
||||
merkle_tree: MerkleTree::with_capacity(capacity),
|
||||
@ -114,6 +114,7 @@ pub struct V02State {
|
||||
}
|
||||
|
||||
impl V02State {
|
||||
#[must_use]
|
||||
pub fn new_with_genesis_accounts(
|
||||
initial_data: &[(AccountId, u128)],
|
||||
initial_commitments: &[nssa_core::Commitment],
|
||||
@ -159,7 +160,7 @@ impl V02State {
|
||||
) -> Result<(), NssaError> {
|
||||
let state_diff = tx.validate_and_produce_public_state_diff(self)?;
|
||||
|
||||
for (account_id, post) in state_diff.into_iter() {
|
||||
for (account_id, post) in state_diff {
|
||||
let current_account = self.get_account_by_id_mut(account_id);
|
||||
|
||||
*current_account = post;
|
||||
@ -195,7 +196,7 @@ impl V02State {
|
||||
self.private_state.1.extend(new_nullifiers);
|
||||
|
||||
// 4. Update public accounts
|
||||
for (account_id, post) in public_state_diff.into_iter() {
|
||||
for (account_id, post) in public_state_diff {
|
||||
let current_account = self.get_account_by_id_mut(account_id);
|
||||
*current_account = post;
|
||||
}
|
||||
@ -222,6 +223,7 @@ impl V02State {
|
||||
self.public_state.entry(account_id).or_default()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_account_by_id(&self, account_id: AccountId) -> Account {
|
||||
self.public_state
|
||||
.get(&account_id)
|
||||
@ -229,6 +231,7 @@ impl V02State {
|
||||
.unwrap_or(Account::default())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_proof_for_commitment(&self, commitment: &Commitment) -> Option<MembershipProof> {
|
||||
self.private_state.0.get_proof_for(commitment)
|
||||
}
|
||||
@ -237,6 +240,7 @@ impl V02State {
|
||||
&self.programs
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn commitment_set_digest(&self) -> CommitmentSetDigest {
|
||||
self.private_state.0.digest()
|
||||
}
|
||||
@ -245,7 +249,7 @@ impl V02State {
|
||||
&self,
|
||||
new_commitments: &[Commitment],
|
||||
) -> Result<(), NssaError> {
|
||||
for commitment in new_commitments.iter() {
|
||||
for commitment in new_commitments {
|
||||
if self.private_state.0.contains(commitment) {
|
||||
return Err(NssaError::InvalidInput(
|
||||
"Commitment already seen".to_string(),
|
||||
@ -259,7 +263,7 @@ impl V02State {
|
||||
&self,
|
||||
new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
|
||||
) -> Result<(), NssaError> {
|
||||
for (nullifier, digest) in new_nullifiers.iter() {
|
||||
for (nullifier, digest) in new_nullifiers {
|
||||
if self.private_state.1.contains(nullifier) {
|
||||
return Err(NssaError::InvalidInput(
|
||||
"Nullifier already seen".to_string(),
|
||||
@ -284,7 +288,7 @@ impl V02State {
|
||||
account_id,
|
||||
Account {
|
||||
program_owner: Program::pinata().id(),
|
||||
balance: 1500000,
|
||||
balance: 1_500_000,
|
||||
// Difficulty: 3
|
||||
data: vec![3; 33].try_into().expect("should fit"),
|
||||
nonce: 0,
|
||||
@ -339,7 +343,7 @@ pub mod tests {
|
||||
|
||||
fn transfer_transaction(
|
||||
from: AccountId,
|
||||
from_key: PrivateKey,
|
||||
from_key: &PrivateKey,
|
||||
nonce: u128,
|
||||
to: AccountId,
|
||||
balance: u128,
|
||||
@ -349,7 +353,7 @@ pub mod tests {
|
||||
let program_id = Program::authenticated_transfer_program().id();
|
||||
let message =
|
||||
public_transaction::Message::try_new(program_id, account_ids, nonces, balance).unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[from_key]);
|
||||
PublicTransaction::new(message, witness_set)
|
||||
}
|
||||
|
||||
@ -454,7 +458,7 @@ pub mod tests {
|
||||
assert_eq!(state.get_account_by_id(to), Account::default());
|
||||
let balance_to_move = 5;
|
||||
|
||||
let tx = transfer_transaction(from, key, 0, to, balance_to_move);
|
||||
let tx = transfer_transaction(from, &key, 0, to, balance_to_move);
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
assert_eq!(state.get_account_by_id(from).balance, 95);
|
||||
@ -475,7 +479,7 @@ pub mod tests {
|
||||
let balance_to_move = 101;
|
||||
assert!(state.get_account_by_id(from).balance < balance_to_move);
|
||||
|
||||
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
|
||||
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
|
||||
let result = state.transition_from_public_transaction(&tx);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
|
||||
@ -499,7 +503,7 @@ pub mod tests {
|
||||
assert_ne!(state.get_account_by_id(to), Account::default());
|
||||
let balance_to_move = 8;
|
||||
|
||||
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
|
||||
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
assert_eq!(state.get_account_by_id(from).balance, 192);
|
||||
@ -519,10 +523,10 @@ pub mod tests {
|
||||
let account_id3 = AccountId::new([3; 32]);
|
||||
let balance_to_move = 5;
|
||||
|
||||
let tx = transfer_transaction(account_id1, key1, 0, account_id2, balance_to_move);
|
||||
let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move);
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
let balance_to_move = 3;
|
||||
let tx = transfer_transaction(account_id2, key2, 0, account_id3, balance_to_move);
|
||||
let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move);
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
assert_eq!(state.get_account_by_id(account_id1).balance, 95);
|
||||
@ -539,6 +543,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
/// Include test programs in the builtin programs map
|
||||
#[must_use]
|
||||
pub fn with_test_programs(mut self) -> Self {
|
||||
self.insert_program(Program::nonce_changer_program());
|
||||
self.insert_program(Program::extra_output_program());
|
||||
@ -555,6 +560,7 @@ pub mod tests {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_non_default_accounts_but_default_program_owners(mut self) -> Self {
|
||||
let account_with_default_values_except_balance = Account {
|
||||
balance: 100,
|
||||
@ -583,6 +589,7 @@ pub mod tests {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_account_owned_by_burner_program(mut self) -> Self {
|
||||
let account = Account {
|
||||
program_owner: Program::burner().id(),
|
||||
@ -593,6 +600,7 @@ pub mod tests {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self {
|
||||
let commitment = Commitment::new(&keys.npk(), account);
|
||||
self.private_state.0.extend(&[commitment]);
|
||||
@ -916,7 +924,7 @@ pub mod tests {
|
||||
vec![sender, recipient],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![0, 2],
|
||||
vec![0xdeadbeef],
|
||||
vec![0xdead_beef],
|
||||
vec![(recipient_keys.npk(), shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
@ -1084,7 +1092,7 @@ pub mod tests {
|
||||
let sender_private_account = Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
nonce: 0xdead_beef,
|
||||
data: Data::default(),
|
||||
};
|
||||
let recipient_keys = test_private_account_keys_2();
|
||||
@ -1099,7 +1107,7 @@ pub mod tests {
|
||||
&sender_private_account,
|
||||
&recipient_keys,
|
||||
balance_to_move,
|
||||
[0xcafecafe, 0xfecafeca],
|
||||
[0xcafe_cafe, 0xfeca_feca],
|
||||
&state,
|
||||
);
|
||||
|
||||
@ -1107,7 +1115,7 @@ pub mod tests {
|
||||
&sender_keys.npk(),
|
||||
&Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
nonce: 0xcafecafe,
|
||||
nonce: 0xcafe_cafe,
|
||||
balance: sender_private_account.balance - balance_to_move,
|
||||
data: Data::default(),
|
||||
},
|
||||
@ -1121,7 +1129,7 @@ pub mod tests {
|
||||
&recipient_keys.npk(),
|
||||
&Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
nonce: 0xfecafeca,
|
||||
nonce: 0xfeca_feca,
|
||||
balance: balance_to_move,
|
||||
..Account::default()
|
||||
},
|
||||
@ -1150,7 +1158,7 @@ pub mod tests {
|
||||
let sender_private_account = Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
nonce: 0xdead_beef,
|
||||
data: Data::default(),
|
||||
};
|
||||
let recipient_keys = test_public_account_keys_1();
|
||||
@ -1174,7 +1182,7 @@ pub mod tests {
|
||||
&sender_private_account,
|
||||
&recipient_keys.account_id(),
|
||||
balance_to_move,
|
||||
0xcafecafe,
|
||||
0xcafe_cafe,
|
||||
&state,
|
||||
);
|
||||
|
||||
@ -1182,7 +1190,7 @@ pub mod tests {
|
||||
&sender_keys.npk(),
|
||||
&Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
nonce: 0xcafecafe,
|
||||
nonce: 0xcafe_cafe,
|
||||
balance: sender_private_account.balance - balance_to_move,
|
||||
data: Data::default(),
|
||||
},
|
||||
@ -1333,7 +1341,12 @@ pub mod tests {
|
||||
);
|
||||
|
||||
let large_data: Vec<u8> =
|
||||
vec![0; nssa_core::account::data::DATA_MAX_LENGTH.as_u64() as usize + 1];
|
||||
vec![
|
||||
0;
|
||||
usize::try_from(nssa_core::account::data::DATA_MAX_LENGTH.as_u64())
|
||||
.expect("DATA_MAX_LENGTH fits in usize")
|
||||
+ 1
|
||||
];
|
||||
|
||||
let result = execute_and_prove(
|
||||
vec![public_account],
|
||||
@ -1343,7 +1356,7 @@ pub mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
&program.to_owned().into(),
|
||||
&program.clone().into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::ProgramProveFailed(_))));
|
||||
@ -1531,7 +1544,7 @@ pub mod tests {
|
||||
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
|
||||
// Setting only one nonce for an execution with two private accounts.
|
||||
let private_account_nonces = [0xdeadbeef1];
|
||||
let private_account_nonces = [0xdead_beef1];
|
||||
let result = execute_and_prove(
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
@ -1580,7 +1593,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
private_account_keys.to_vec(),
|
||||
vec![sender_keys.nsk],
|
||||
vec![Some((0, vec![]))],
|
||||
@ -1613,7 +1626,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
@ -1655,7 +1668,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
@ -1713,7 +1726,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
private_account_keys.to_vec(),
|
||||
private_account_nsks.to_vec(),
|
||||
private_account_membership_proofs.to_vec(),
|
||||
@ -1751,7 +1764,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
@ -1799,7 +1812,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
@ -1846,7 +1859,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
@ -1882,7 +1895,7 @@ pub mod tests {
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
// Non default nonce
|
||||
nonce: 0xdeadbeef,
|
||||
nonce: 0xdead_beef,
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
@ -1893,7 +1906,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
@ -1938,7 +1951,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
@ -2006,7 +2019,7 @@ pub mod tests {
|
||||
|
||||
// Setting three new private account nonces for a circuit execution with only two private
|
||||
// accounts.
|
||||
let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3];
|
||||
let private_account_nonces = [0xdead_beef1, 0xdead_beef2, 0xdead_beef3];
|
||||
let result = execute_and_prove(
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
@ -2067,7 +2080,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
private_account_keys.to_vec(),
|
||||
vec![sender_keys.nsk],
|
||||
vec![Some((0, vec![]))],
|
||||
@ -2103,7 +2116,7 @@ pub mod tests {
|
||||
vec![private_account_1, private_account_2],
|
||||
Program::serialize_instruction(10u128).unwrap(),
|
||||
visibility_mask.to_vec(),
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
@ -2128,7 +2141,7 @@ pub mod tests {
|
||||
let sender_private_account = Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
nonce: 0xdead_beef,
|
||||
data: Data::default(),
|
||||
};
|
||||
let recipient_keys = test_private_account_keys_2();
|
||||
@ -2143,7 +2156,7 @@ pub mod tests {
|
||||
&sender_private_account,
|
||||
&recipient_keys,
|
||||
balance_to_move,
|
||||
[0xcafecafe, 0xfecafeca],
|
||||
[0xcafe_cafe, 0xfeca_feca],
|
||||
&state,
|
||||
);
|
||||
|
||||
@ -2154,7 +2167,7 @@ pub mod tests {
|
||||
let sender_private_account = Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100 - balance_to_move,
|
||||
nonce: 0xcafecafe,
|
||||
nonce: 0xcafe_cafe,
|
||||
data: Data::default(),
|
||||
};
|
||||
|
||||
@ -2199,7 +2212,7 @@ pub mod tests {
|
||||
vec![private_account_1.clone(), private_account_1],
|
||||
Program::serialize_instruction(100u128).unwrap(),
|
||||
visibility_mask.to_vec(),
|
||||
vec![0xdeadbeef1, 0xdeadbeef2],
|
||||
vec![0xdead_beef1, 0xdead_beef2],
|
||||
vec![
|
||||
(sender_keys.npk(), shared_secret),
|
||||
(sender_keys.npk(), shared_secret),
|
||||
@ -2308,7 +2321,8 @@ pub mod tests {
|
||||
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
|
||||
amount,
|
||||
Program::authenticated_transfer_program().id(),
|
||||
MAX_NUMBER_CHAINED_CALLS as u32 + 1,
|
||||
u32::try_from(MAX_NUMBER_CHAINED_CALLS).expect("MAX_NUMBER_CHAINED_CALLS fits in u32")
|
||||
+ 1,
|
||||
None,
|
||||
);
|
||||
|
||||
@ -3829,18 +3843,18 @@ pub mod tests {
|
||||
dependencies.insert(auth_transfers.id(), auth_transfers);
|
||||
let program_with_deps = ProgramWithDependencies::new(chain_caller, dependencies);
|
||||
|
||||
let from_new_nonce = 0xdeadbeef1;
|
||||
let to_new_nonce = 0xdeadbeef2;
|
||||
let from_new_nonce = 0xdead_beef1;
|
||||
let to_new_nonce = 0xdead_beef2;
|
||||
|
||||
let from_expected_post = Account {
|
||||
balance: initial_balance - number_of_calls as u128 * amount,
|
||||
balance: initial_balance - u128::from(number_of_calls) * amount,
|
||||
nonce: from_new_nonce,
|
||||
..from_account.account.clone()
|
||||
};
|
||||
let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post);
|
||||
|
||||
let to_expected_post = Account {
|
||||
balance: number_of_calls as u128 * amount,
|
||||
balance: u128::from(number_of_calls) * amount,
|
||||
nonce: to_new_nonce,
|
||||
..to_account.account.clone()
|
||||
};
|
||||
@ -3948,7 +3962,7 @@ pub mod tests {
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
// Submit a solution to the pinata program to claim the prize
|
||||
let solution: u128 = 989106;
|
||||
let solution: u128 = 989_106;
|
||||
let message = public_transaction::Message::try_new(
|
||||
pinata_token.id(),
|
||||
vec![
|
||||
@ -3994,7 +4008,7 @@ pub mod tests {
|
||||
|
||||
let result = state.transition_from_public_transaction(&tx);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)))
|
||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||
}
|
||||
|
||||
/// This test ensures that even if a malicious program tries to perform overflow of balances
|
||||
@ -4083,7 +4097,7 @@ pub mod tests {
|
||||
// Balance to initialize the account with (0 for a new account)
|
||||
let balance: u128 = 0;
|
||||
|
||||
let nonce = 0xdeadbeef1;
|
||||
let nonce = 0xdead_beef1;
|
||||
|
||||
// Execute and prove the circuit with the authorized account but no commitment proof
|
||||
let (output, proof) = execute_and_prove(
|
||||
@ -4136,7 +4150,7 @@ pub mod tests {
|
||||
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||
|
||||
let balance: u128 = 0;
|
||||
let nonce = 0xdeadbeef1;
|
||||
let nonce = 0xdead_beef1;
|
||||
|
||||
// Step 2: Execute claimer program to claim the account with authentication
|
||||
let (output, proof) = execute_and_prove(
|
||||
@ -4184,7 +4198,7 @@ pub mod tests {
|
||||
let esk2 = [4; 32];
|
||||
let shared_secret2 = SharedSecretKey::new(&esk2, &private_keys.vpk());
|
||||
|
||||
let nonce2 = 0xdeadbeef2;
|
||||
let nonce2 = 0xdead_beef2;
|
||||
|
||||
// Step 3: Try to execute noop program with authentication but without initialization
|
||||
let res = execute_and_prove(
|
||||
@ -4341,7 +4355,7 @@ pub mod tests {
|
||||
dependencies.insert(auth_transfers.id(), auth_transfers);
|
||||
let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies);
|
||||
|
||||
let recipient_new_nonce = 0xdeadbeef1;
|
||||
let recipient_new_nonce = 0xdead_beef1;
|
||||
|
||||
// Act - execute the malicious program - this should fail during proving
|
||||
let result = execute_and_prove(
|
||||
|
||||
@ -7,18 +7,17 @@ use nssa_core::{
|
||||
|
||||
/// Initializes a default account under the ownership of this program.
|
||||
fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState {
|
||||
let account_to_claim = AccountPostState::new_claimed(pre_state.account.clone());
|
||||
let account_to_claim = AccountPostState::new_claimed(pre_state.account);
|
||||
let is_authorized = pre_state.is_authorized;
|
||||
|
||||
// Continue only if the account to claim has default values
|
||||
if account_to_claim.account() != &Account::default() {
|
||||
panic!("Account must be uninitialized");
|
||||
}
|
||||
assert!(
|
||||
account_to_claim.account() == &Account::default(),
|
||||
"Account must be uninitialized"
|
||||
);
|
||||
|
||||
// Continue only if the owner authorized this operation
|
||||
if !is_authorized {
|
||||
panic!("Account must be authorized");
|
||||
}
|
||||
assert!(is_authorized, "Account must be authorized");
|
||||
|
||||
account_to_claim
|
||||
}
|
||||
@ -30,26 +29,25 @@ fn transfer(
|
||||
balance_to_move: u128,
|
||||
) -> Vec<AccountPostState> {
|
||||
// Continue only if the sender has authorized this operation
|
||||
if !sender.is_authorized {
|
||||
panic!("Sender must be authorized");
|
||||
}
|
||||
assert!(sender.is_authorized, "Sender must be authorized");
|
||||
|
||||
// Continue only if the sender has enough balance
|
||||
if sender.account.balance < balance_to_move {
|
||||
panic!("Sender has insufficient balance");
|
||||
}
|
||||
assert!(
|
||||
sender.account.balance >= balance_to_move,
|
||||
"Sender has insufficient balance"
|
||||
);
|
||||
|
||||
// Create accounts post states, with updated balances
|
||||
let sender_post = {
|
||||
// Modify sender's balance
|
||||
let mut sender_post_account = sender.account.clone();
|
||||
let mut sender_post_account = sender.account;
|
||||
sender_post_account.balance -= balance_to_move;
|
||||
AccountPostState::new(sender_post_account)
|
||||
};
|
||||
|
||||
let recipient_post = {
|
||||
// Modify recipient's balance
|
||||
let mut recipient_post_account = recipient.account.clone();
|
||||
let mut recipient_post_account = recipient.account;
|
||||
recipient_post_account.balance += balance_to_move;
|
||||
|
||||
// Claim recipient account if it has default program owner
|
||||
|
||||
@ -52,9 +52,8 @@ fn main() {
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pinata, winner] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
Err(_) => return,
|
||||
let Ok([pinata, winner]) = <[_; 2]>::try_from(pre_states) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let data = Challenge::new(&pinata.account.data);
|
||||
|
||||
@ -59,13 +59,15 @@ fn main() {
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [
|
||||
pinata_definition,
|
||||
pinata_token_holding,
|
||||
winner_token_holding,
|
||||
] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
Err(_) => return,
|
||||
let Ok(
|
||||
[
|
||||
pinata_definition,
|
||||
pinata_token_holding,
|
||||
winner_token_holding,
|
||||
],
|
||||
) = <[_; 3]>::try_from(pre_states)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let data = Challenge::new(&pinata_definition.account.data);
|
||||
|
||||
@ -113,7 +113,7 @@ impl ExecutionState {
|
||||
);
|
||||
execution_state.validate_and_sync_states(
|
||||
chained_call.program_id,
|
||||
authorized_pdas,
|
||||
&authorized_pdas,
|
||||
program_output.pre_states,
|
||||
program_output.post_states,
|
||||
);
|
||||
@ -153,7 +153,7 @@ impl ExecutionState {
|
||||
fn validate_and_sync_states(
|
||||
&mut self,
|
||||
program_id: ProgramId,
|
||||
authorized_pdas: HashSet<AccountId>,
|
||||
authorized_pdas: &HashSet<AccountId>,
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
post_states: Vec<AccountPostState>,
|
||||
) {
|
||||
@ -173,12 +173,12 @@ impl ExecutionState {
|
||||
.pre_states
|
||||
.iter()
|
||||
.find(|acc| acc.account_id == pre_account_id)
|
||||
.map(|acc| acc.is_authorized)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
.map_or_else(
|
||||
|| panic!(
|
||||
"Pre state must exist in execution state for account {pre_account_id:?}",
|
||||
)
|
||||
});
|
||||
),
|
||||
|acc| acc.is_authorized
|
||||
);
|
||||
|
||||
let is_authorized =
|
||||
previous_is_authorized || authorized_pdas.contains(&pre_account_id);
|
||||
@ -379,18 +379,8 @@ fn compute_nullifier_and_set_digest(
|
||||
npk: &NullifierPublicKey,
|
||||
nsk: &NullifierSecretKey,
|
||||
) -> (Nullifier, CommitmentSetDigest) {
|
||||
membership_proof_opt
|
||||
.as_ref()
|
||||
.map(|membership_proof| {
|
||||
// Compute commitment set digest associated with provided auth path
|
||||
let commitment_pre = Commitment::new(npk, pre_account);
|
||||
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
|
||||
|
||||
// Compute update nullifier
|
||||
let nullifier = Nullifier::for_account_update(&commitment_pre, nsk);
|
||||
(nullifier, set_digest)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
membership_proof_opt.as_ref().map_or_else(
|
||||
|| {
|
||||
assert_eq!(
|
||||
*pre_account,
|
||||
Account::default(),
|
||||
@ -400,5 +390,15 @@ fn compute_nullifier_and_set_digest(
|
||||
// Compute initialization nullifier
|
||||
let nullifier = Nullifier::for_account_initialization(npk);
|
||||
(nullifier, DUMMY_COMMITMENT_HASH)
|
||||
})
|
||||
},
|
||||
|membership_proof| {
|
||||
// Compute commitment set digest associated with provided auth path
|
||||
let commitment_pre = Commitment::new(npk, pre_account);
|
||||
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
|
||||
|
||||
// Compute update nullifier
|
||||
let nullifier = Nullifier::for_account_update(&commitment_pre, nsk);
|
||||
(nullifier, set_digest)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ pub struct PoolDefinition {
|
||||
pub fees: u128,
|
||||
/// A pool becomes inactive (active = false)
|
||||
/// once all of its liquidity has been removed (e.g., reserves are emptied and
|
||||
/// liquidity_pool_supply = 0)
|
||||
/// `liquidity_pool_supply` = 0)
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
@ -113,6 +113,7 @@ impl From<&PoolDefinition> for Data {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compute_pool_pda(
|
||||
amm_program_id: ProgramId,
|
||||
definition_token_a_id: AccountId,
|
||||
@ -124,6 +125,7 @@ pub fn compute_pool_pda(
|
||||
))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compute_pool_pda_seed(
|
||||
definition_token_a_id: AccountId,
|
||||
definition_token_b_id: AccountId,
|
||||
@ -151,6 +153,7 @@ pub fn compute_pool_pda_seed(
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compute_vault_pda(
|
||||
amm_program_id: ProgramId,
|
||||
pool_id: AccountId,
|
||||
@ -162,6 +165,7 @@ pub fn compute_vault_pda(
|
||||
))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId) -> PdaSeed {
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
|
||||
@ -177,10 +181,12 @@ pub fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId) -> AccountId {
|
||||
AccountId::from((&amm_program_id, &compute_liquidity_token_pda_seed(pool_id)))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed {
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ use nssa_core::{
|
||||
};
|
||||
|
||||
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
|
||||
#[must_use]
|
||||
pub fn add_liquidity(
|
||||
pool: AccountWithMetadata,
|
||||
vault_a: AccountWithMetadata,
|
||||
@ -123,7 +124,7 @@ pub fn add_liquidity(
|
||||
);
|
||||
|
||||
// 5. Update pool account
|
||||
let mut pool_post = pool.account.clone();
|
||||
let mut pool_post = pool.account;
|
||||
let pool_post_definition = PoolDefinition {
|
||||
liquidity_pool_supply: pool_def_data.liquidity_pool_supply + delta_lp,
|
||||
reserve_a: pool_def_data.reserve_a + actual_amount_a,
|
||||
@ -166,12 +167,12 @@ pub fn add_liquidity(
|
||||
|
||||
let post_states = vec![
|
||||
AccountPostState::new(pool_post),
|
||||
AccountPostState::new(vault_a.account.clone()),
|
||||
AccountPostState::new(vault_b.account.clone()),
|
||||
AccountPostState::new(pool_definition_lp.account.clone()),
|
||||
AccountPostState::new(user_holding_a.account.clone()),
|
||||
AccountPostState::new(user_holding_b.account.clone()),
|
||||
AccountPostState::new(user_holding_lp.account.clone()),
|
||||
AccountPostState::new(vault_a.account),
|
||||
AccountPostState::new(vault_b.account),
|
||||
AccountPostState::new(pool_definition_lp.account),
|
||||
AccountPostState::new(user_holding_a.account),
|
||||
AccountPostState::new(user_holding_b.account),
|
||||
AccountPostState::new(user_holding_lp.account),
|
||||
];
|
||||
|
||||
(post_states, chained_calls)
|
||||
|
||||
@ -10,6 +10,7 @@ use nssa_core::{
|
||||
};
|
||||
|
||||
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
|
||||
#[must_use]
|
||||
pub fn new_definition(
|
||||
pool: AccountWithMetadata,
|
||||
vault_a: AccountWithMetadata,
|
||||
@ -79,8 +80,20 @@ pub fn new_definition(
|
||||
// LP Token minting calculation
|
||||
let initial_lp = (token_a_amount.get() * token_b_amount.get()).isqrt();
|
||||
|
||||
// Chain call for liquidity token (TokenLP definition -> User LP Holding)
|
||||
let instruction = if pool.account == Account::default() {
|
||||
token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: initial_lp,
|
||||
}
|
||||
} else {
|
||||
token_core::Instruction::Mint {
|
||||
amount_to_mint: initial_lp,
|
||||
}
|
||||
};
|
||||
|
||||
// Update pool account
|
||||
let mut pool_post = pool.account.clone();
|
||||
let mut pool_post = pool.account;
|
||||
let pool_post_definition = PoolDefinition {
|
||||
definition_token_a_id,
|
||||
definition_token_b_id,
|
||||
@ -95,11 +108,7 @@ pub fn new_definition(
|
||||
};
|
||||
|
||||
pool_post.data = Data::from(&pool_post_definition);
|
||||
let pool_post: AccountPostState = if pool.account == Account::default() {
|
||||
AccountPostState::new_claimed(pool_post.clone())
|
||||
} else {
|
||||
AccountPostState::new(pool_post.clone())
|
||||
};
|
||||
let pool_post = AccountPostState::new_claimed_if_default(pool_post);
|
||||
|
||||
let token_program_id = user_holding_a.account.program_owner;
|
||||
|
||||
@ -120,18 +129,6 @@ pub fn new_definition(
|
||||
},
|
||||
);
|
||||
|
||||
// Chain call for liquidity token (TokenLP definition -> User LP Holding)
|
||||
let instruction = if pool.account == Account::default() {
|
||||
token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: initial_lp,
|
||||
}
|
||||
} else {
|
||||
token_core::Instruction::Mint {
|
||||
amount_to_mint: initial_lp,
|
||||
}
|
||||
};
|
||||
|
||||
let mut pool_lp_auth = pool_definition_lp.clone();
|
||||
pool_lp_auth.is_authorized = true;
|
||||
|
||||
@ -145,13 +142,13 @@ pub fn new_definition(
|
||||
let chained_calls = vec![call_token_lp, call_token_b, call_token_a];
|
||||
|
||||
let post_states = vec![
|
||||
pool_post.clone(),
|
||||
AccountPostState::new(vault_a.account.clone()),
|
||||
AccountPostState::new(vault_b.account.clone()),
|
||||
AccountPostState::new(pool_definition_lp.account.clone()),
|
||||
AccountPostState::new(user_holding_a.account.clone()),
|
||||
AccountPostState::new(user_holding_b.account.clone()),
|
||||
AccountPostState::new(user_holding_lp.account.clone()),
|
||||
pool_post,
|
||||
AccountPostState::new(vault_a.account),
|
||||
AccountPostState::new(vault_b.account),
|
||||
AccountPostState::new(pool_definition_lp.account),
|
||||
AccountPostState::new(user_holding_a.account),
|
||||
AccountPostState::new(user_holding_b.account),
|
||||
AccountPostState::new(user_holding_lp.account),
|
||||
];
|
||||
|
||||
(post_states.clone(), chained_calls)
|
||||
|
||||
@ -7,6 +7,7 @@ use nssa_core::{
|
||||
};
|
||||
|
||||
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
|
||||
#[must_use]
|
||||
pub fn remove_liquidity(
|
||||
pool: AccountWithMetadata,
|
||||
vault_a: AccountWithMetadata,
|
||||
@ -101,7 +102,7 @@ pub fn remove_liquidity(
|
||||
let active: bool = pool_def_data.liquidity_pool_supply - delta_lp != 0;
|
||||
|
||||
// 5. Update pool account
|
||||
let mut pool_post = pool.account.clone();
|
||||
let mut pool_post = pool.account;
|
||||
let pool_post_definition = PoolDefinition {
|
||||
liquidity_pool_supply: pool_def_data.liquidity_pool_supply - delta_lp,
|
||||
reserve_a: pool_def_data.reserve_a - withdraw_amount_a,
|
||||
@ -153,13 +154,13 @@ pub fn remove_liquidity(
|
||||
let chained_calls = vec![call_token_lp, call_token_b, call_token_a];
|
||||
|
||||
let post_states = vec![
|
||||
AccountPostState::new(pool_post.clone()),
|
||||
AccountPostState::new(vault_a.account.clone()),
|
||||
AccountPostState::new(vault_b.account.clone()),
|
||||
AccountPostState::new(pool_definition_lp.account.clone()),
|
||||
AccountPostState::new(user_holding_a.account.clone()),
|
||||
AccountPostState::new(user_holding_b.account.clone()),
|
||||
AccountPostState::new(user_holding_lp.account.clone()),
|
||||
AccountPostState::new(pool_post),
|
||||
AccountPostState::new(vault_a.account),
|
||||
AccountPostState::new(vault_b.account),
|
||||
AccountPostState::new(pool_definition_lp.account),
|
||||
AccountPostState::new(user_holding_a.account),
|
||||
AccountPostState::new(user_holding_b.account),
|
||||
AccountPostState::new(user_holding_lp.account),
|
||||
];
|
||||
|
||||
(post_states, chained_calls)
|
||||
|
||||
@ -5,6 +5,7 @@ use nssa_core::{
|
||||
};
|
||||
|
||||
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
|
||||
#[must_use]
|
||||
pub fn swap(
|
||||
pool: AccountWithMetadata,
|
||||
vault_a: AccountWithMetadata,
|
||||
@ -95,7 +96,7 @@ pub fn swap(
|
||||
};
|
||||
|
||||
// Update pool account
|
||||
let mut pool_post = pool.account.clone();
|
||||
let mut pool_post = pool.account;
|
||||
let pool_post_definition = PoolDefinition {
|
||||
reserve_a: pool_def_data.reserve_a + deposit_a - withdraw_a,
|
||||
reserve_b: pool_def_data.reserve_b + deposit_b - withdraw_b,
|
||||
@ -105,11 +106,11 @@ pub fn swap(
|
||||
pool_post.data = Data::from(&pool_post_definition);
|
||||
|
||||
let post_states = vec![
|
||||
AccountPostState::new(pool_post.clone()),
|
||||
AccountPostState::new(vault_a.account.clone()),
|
||||
AccountPostState::new(vault_b.account.clone()),
|
||||
AccountPostState::new(user_holding_a.account.clone()),
|
||||
AccountPostState::new(user_holding_b.account.clone()),
|
||||
AccountPostState::new(pool_post),
|
||||
AccountPostState::new(vault_a.account),
|
||||
AccountPostState::new(vault_b.account),
|
||||
AccountPostState::new(user_holding_a.account),
|
||||
AccountPostState::new(user_holding_b.account),
|
||||
];
|
||||
|
||||
(post_states, chained_calls)
|
||||
@ -151,7 +152,7 @@ fn swap_logic(
|
||||
},
|
||||
));
|
||||
|
||||
let mut vault_withdraw = vault_withdraw.clone();
|
||||
let mut vault_withdraw = vault_withdraw;
|
||||
vault_withdraw.is_authorized = true;
|
||||
|
||||
let pda_seed = compute_vault_pda_seed(
|
||||
|
||||
@ -127,6 +127,7 @@ pub enum TokenHolding {
|
||||
}
|
||||
|
||||
impl TokenHolding {
|
||||
#[must_use]
|
||||
pub fn zeroized_clone_from(other: &Self) -> Self {
|
||||
match other {
|
||||
TokenHolding::Fungible { definition_id, .. } => TokenHolding::Fungible {
|
||||
@ -144,6 +145,7 @@ impl TokenHolding {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn zeroized_from_definition(
|
||||
definition_id: AccountId,
|
||||
definition: &TokenDefinition,
|
||||
@ -160,11 +162,12 @@ impl TokenHolding {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn definition_id(&self) -> AccountId {
|
||||
match self {
|
||||
TokenHolding::Fungible { definition_id, .. } => *definition_id,
|
||||
TokenHolding::NftMaster { definition_id, .. } => *definition_id,
|
||||
TokenHolding::NftPrintedCopy { definition_id, .. } => *definition_id,
|
||||
TokenHolding::Fungible { definition_id, .. }
|
||||
| TokenHolding::NftMaster { definition_id, .. }
|
||||
| TokenHolding::NftPrintedCopy { definition_id, .. } => *definition_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ use nssa_core::{
|
||||
};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
#[must_use]
|
||||
pub fn burn(
|
||||
definition_account: AccountWithMetadata,
|
||||
user_holding_account: AccountWithMetadata,
|
||||
|
||||
@ -4,6 +4,7 @@ use nssa_core::{
|
||||
};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
#[must_use]
|
||||
pub fn initialize_account(
|
||||
definition_account: AccountWithMetadata,
|
||||
account_to_initialize: AccountWithMetadata,
|
||||
|
||||
@ -4,6 +4,7 @@ use nssa_core::{
|
||||
};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
#[must_use]
|
||||
pub fn mint(
|
||||
definition_account: AccountWithMetadata,
|
||||
user_holding_account: AccountWithMetadata,
|
||||
|
||||
@ -6,6 +6,7 @@ use token_core::{
|
||||
NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding, TokenMetadata,
|
||||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn new_fungible_definition(
|
||||
definition_target_account: AccountWithMetadata,
|
||||
holding_target_account: AccountWithMetadata,
|
||||
@ -46,6 +47,7 @@ pub fn new_fungible_definition(
|
||||
]
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new_definition_with_metadata(
|
||||
definition_target_account: AccountWithMetadata,
|
||||
holding_target_account: AccountWithMetadata,
|
||||
@ -107,13 +109,13 @@ pub fn new_definition_with_metadata(
|
||||
primary_sale_date: 0u64, // TODO #261: future works to implement this
|
||||
};
|
||||
|
||||
let mut definition_target_account_post = definition_target_account.account.clone();
|
||||
let mut definition_target_account_post = definition_target_account.account;
|
||||
definition_target_account_post.data = Data::from(&token_definition);
|
||||
|
||||
let mut holding_target_account_post = holding_target_account.account.clone();
|
||||
let mut holding_target_account_post = holding_target_account.account;
|
||||
holding_target_account_post.data = Data::from(&token_holding);
|
||||
|
||||
let mut metadata_target_account_post = metadata_target_account.account.clone();
|
||||
let mut metadata_target_account_post = metadata_target_account.account;
|
||||
metadata_target_account_post.data = Data::from(&token_metadata);
|
||||
|
||||
vec![
|
||||
|
||||
@ -4,6 +4,7 @@ use nssa_core::{
|
||||
};
|
||||
use token_core::TokenHolding;
|
||||
|
||||
#[must_use]
|
||||
pub fn print_nft(
|
||||
master_account: AccountWithMetadata,
|
||||
printed_account: AccountWithMetadata,
|
||||
|
||||
@ -4,6 +4,7 @@ use nssa_core::{
|
||||
};
|
||||
use token_core::TokenHolding;
|
||||
|
||||
#[must_use]
|
||||
pub fn transfer(
|
||||
sender: AccountWithMetadata,
|
||||
recipient: AccountWithMetadata,
|
||||
@ -95,7 +96,7 @@ pub fn transfer(
|
||||
_ => {
|
||||
panic!("Mismatched token holding types for transfer");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mut sender_post = sender.account;
|
||||
sender_post.data = Data::from(&sender_holding);
|
||||
|
||||
@ -77,20 +77,20 @@ pub trait BlockSettlementClientTrait: Clone {
|
||||
/// A component that posts block data to logos blockchain
|
||||
#[derive(Clone)]
|
||||
pub struct BlockSettlementClient {
|
||||
bedrock_client: BedrockClient,
|
||||
bedrock_signing_key: Ed25519Key,
|
||||
bedrock_channel_id: ChannelId,
|
||||
client: BedrockClient,
|
||||
signing_key: Ed25519Key,
|
||||
channel_id: ChannelId,
|
||||
}
|
||||
|
||||
impl BlockSettlementClientTrait for BlockSettlementClient {
|
||||
fn new(config: &BedrockConfig, bedrock_signing_key: Ed25519Key) -> Result<Self> {
|
||||
let bedrock_client =
|
||||
fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result<Self> {
|
||||
let client =
|
||||
BedrockClient::new(config.backoff, config.node_url.clone(), config.auth.clone())
|
||||
.context("Failed to initialize bedrock client")?;
|
||||
Ok(Self {
|
||||
bedrock_client,
|
||||
bedrock_signing_key,
|
||||
bedrock_channel_id: config.channel_id,
|
||||
client,
|
||||
signing_key,
|
||||
channel_id: config.channel_id,
|
||||
})
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ impl BlockSettlementClientTrait for BlockSettlementClient {
|
||||
Some(Op::ChannelInscribe(inscribe)) => (inscribe.parent, inscribe.id()),
|
||||
_ => panic!("Expected ChannelInscribe op"),
|
||||
};
|
||||
self.bedrock_client
|
||||
self.client
|
||||
.post_transaction(tx)
|
||||
.await
|
||||
.context("Failed to post transaction to Bedrock")?;
|
||||
@ -110,11 +110,11 @@ impl BlockSettlementClientTrait for BlockSettlementClient {
|
||||
}
|
||||
|
||||
fn bedrock_channel_id(&self) -> ChannelId {
|
||||
self.bedrock_channel_id
|
||||
self.channel_id
|
||||
}
|
||||
|
||||
fn bedrock_signing_key(&self) -> &Ed25519Key {
|
||||
&self.bedrock_signing_key
|
||||
&self.signing_key
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user