mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-08 08:13:08 +00:00
Merge branch 'main' into Pravdyvy/keys-restoration-from-mnemonic
This commit is contained in:
commit
1309185ecc
@ -30,16 +30,25 @@ use crate::{
|
||||
pub struct SequencerClient {
|
||||
pub client: reqwest::Client,
|
||||
pub sequencer_addr: String,
|
||||
pub basic_auth: Option<(String, Option<String>)>,
|
||||
}
|
||||
|
||||
impl SequencerClient {
|
||||
pub fn new(sequencer_addr: String) -> Result<Self> {
|
||||
Self::new_with_auth(sequencer_addr, None)
|
||||
}
|
||||
|
||||
pub fn new_with_auth(
|
||||
sequencer_addr: String,
|
||||
basic_auth: Option<(String, Option<String>)>,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
client: Client::builder()
|
||||
//Add more fiedls if needed
|
||||
.timeout(std::time::Duration::from_secs(60))
|
||||
.build()?,
|
||||
sequencer_addr,
|
||||
basic_auth,
|
||||
})
|
||||
}
|
||||
|
||||
@ -51,13 +60,16 @@ impl SequencerClient {
|
||||
let request =
|
||||
rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload);
|
||||
|
||||
let call_builder = self.client.post(&self.sequencer_addr);
|
||||
let mut call_builder = self.client.post(&self.sequencer_addr);
|
||||
|
||||
if let Some((username, password)) = &self.basic_auth {
|
||||
call_builder = call_builder.basic_auth(username, password.as_deref());
|
||||
}
|
||||
|
||||
let call_res = call_builder.json(&request).send().await?;
|
||||
|
||||
let response_vall = call_res.json::<Value>().await?;
|
||||
|
||||
// TODO: Actually why we need separation of `result` and `error` in rpc response?
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct SequencerRpcResponse {
|
||||
|
||||
Binary file not shown.
@ -357,8 +357,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// The data of a token definition account has the following layout:
|
||||
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
|
||||
assert_eq!(
|
||||
definition_acc.data,
|
||||
vec![
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
);
|
||||
@ -376,11 +376,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// The data of a token definition account has the following layout:
|
||||
// [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16
|
||||
// bytes) ] First byte of the data equal to 1 means it's a token holding account
|
||||
assert_eq!(supply_acc.data[0], 1);
|
||||
assert_eq!(supply_acc.data.as_ref()[0], 1);
|
||||
// Bytes from 1 to 33 represent the id of the token this account is associated with.
|
||||
// In this example, this is a token account of the newly created token, so it is expected
|
||||
// to be equal to the account_id of the token definition account.
|
||||
assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes());
|
||||
assert_eq!(
|
||||
&supply_acc.data.as_ref()[1..33],
|
||||
definition_account_id.to_bytes()
|
||||
);
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()),
|
||||
37
|
||||
@ -519,8 +522,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// The data of a token definition account has the following layout:
|
||||
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
|
||||
assert_eq!(
|
||||
definition_acc.data,
|
||||
vec![
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
);
|
||||
@ -680,8 +683,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// The data of a token definition account has the following layout:
|
||||
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
|
||||
assert_eq!(
|
||||
definition_acc.data,
|
||||
vec![
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
);
|
||||
@ -822,8 +825,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// The data of a token definition account has the following layout:
|
||||
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
|
||||
assert_eq!(
|
||||
definition_acc.data,
|
||||
vec![
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
);
|
||||
@ -964,8 +967,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// The data of a token definition account has the following layout:
|
||||
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
|
||||
assert_eq!(
|
||||
definition_acc.data,
|
||||
vec![
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
);
|
||||
@ -1464,7 +1467,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
data_changer.id(),
|
||||
vec![account_id],
|
||||
vec![],
|
||||
(),
|
||||
vec![0],
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
@ -1481,7 +1484,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
.account;
|
||||
assert_eq!(post_state_account.program_owner, data_changer.id());
|
||||
assert_eq!(post_state_account.balance, 0);
|
||||
assert_eq!(post_state_account.data, vec![0]);
|
||||
assert_eq!(post_state_account.data.as_ref(), &[0]);
|
||||
assert_eq!(post_state_account.nonce, 0);
|
||||
|
||||
info!("Success!");
|
||||
|
||||
@ -8,7 +8,8 @@ use nssa::{
|
||||
public_transaction as putx,
|
||||
};
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, account::AccountWithMetadata,
|
||||
MembershipProof, NullifierPublicKey,
|
||||
account::{AccountWithMetadata, data::Data},
|
||||
encryption::IncomingViewingPublicKey,
|
||||
};
|
||||
use sequencer_core::config::{AccountInitialData, CommitmentsInitialData, SequencerConfig};
|
||||
@ -90,7 +91,7 @@ impl TpsTestManager {
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
};
|
||||
let initial_commitment = CommitmentsInitialData {
|
||||
npk: sender_npk,
|
||||
@ -129,7 +130,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
program_owner: program.id(),
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
},
|
||||
true,
|
||||
AccountId::from(&sender_npk),
|
||||
|
||||
@ -6,14 +6,17 @@ edition = "2024"
|
||||
[dependencies]
|
||||
risc0-zkvm = { version = "3.0.3", features = ['std'] }
|
||||
serde = { version = "1.0", default-features = false }
|
||||
thiserror = { version = "2.0.12", optional = true }
|
||||
thiserror = { version = "2.0.12" }
|
||||
bytemuck = { version = "1.13", optional = true }
|
||||
chacha20 = { version = "0.9", default-features = false }
|
||||
k256 = { version = "0.13.3", optional = true }
|
||||
base58 = { version = "0.2.0", optional = true }
|
||||
anyhow = { version = "1.0.98", optional = true }
|
||||
borsh.workspace = true
|
||||
borsh = "1.5.7"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.81"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
host = ["thiserror", "bytemuck", "k256", "base58", "anyhow"]
|
||||
host = ["dep:bytemuck", "dep:k256", "dep:base58", "dep:anyhow"]
|
||||
|
||||
@ -4,12 +4,14 @@ use std::{fmt::Display, str::FromStr};
|
||||
#[cfg(feature = "host")]
|
||||
use base58::{FromBase58, ToBase58};
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
pub use data::Data;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::program::ProgramId;
|
||||
|
||||
pub mod data;
|
||||
|
||||
pub type Nonce = u128;
|
||||
pub type Data = Vec<u8>;
|
||||
|
||||
/// Account to be used both in public and private contexts
|
||||
#[derive(
|
||||
@ -139,7 +141,10 @@ mod tests {
|
||||
let account = Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 1337,
|
||||
data: b"testing_account_with_metadata_constructor".to_vec(),
|
||||
data: b"testing_account_with_metadata_constructor"
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
nonce: 0xdeadbeef,
|
||||
};
|
||||
let fingerprint = AccountId::new([8; 32]);
|
||||
|
||||
174
nssa/core/src/account/data.rs
Normal file
174
nssa/core/src/account/data.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const DATA_MAX_LENGTH_IN_BYTES: usize = 100 * 1024; // 100 KiB
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug))]
|
||||
pub struct Data(Vec<u8>);
|
||||
|
||||
impl Data {
|
||||
pub fn into_inner(self) -> Vec<u8> {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
pub fn from_cursor(
|
||||
cursor: &mut std::io::Cursor<&[u8]>,
|
||||
) -> Result<Self, crate::error::NssaCoreError> {
|
||||
use std::io::Read as _;
|
||||
|
||||
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_IN_BYTES {
|
||||
return Err(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidData, DataTooBigError).into(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut data = vec![0; data_length as usize];
|
||||
cursor.read_exact(&mut data)?;
|
||||
Ok(Self(data))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
|
||||
#[error("data length exceeds maximum allowed length of {DATA_MAX_LENGTH_IN_BYTES} bytes")]
|
||||
pub struct DataTooBigError;
|
||||
|
||||
impl From<Data> for Vec<u8> {
|
||||
fn from(data: Data) -> Self {
|
||||
data.0
|
||||
}
|
||||
}
|
||||
|
||||
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_IN_BYTES {
|
||||
Err(DataTooBigError)
|
||||
} else {
|
||||
Ok(Self(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Data {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Data {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Data {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
/// Data deserialization visitor.
|
||||
///
|
||||
/// Compared to a simple deserialization into a `Vec<u8>`, this visitor enforces
|
||||
/// early length check defined by [`DATA_MAX_LENGTH_IN_BYTES`].
|
||||
struct DataVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for DataVisitor {
|
||||
type Value = Data;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
formatter,
|
||||
"a byte array with length not exceeding {} bytes",
|
||||
DATA_MAX_LENGTH_IN_BYTES
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut vec =
|
||||
Vec::with_capacity(seq.size_hint().unwrap_or(0).min(DATA_MAX_LENGTH_IN_BYTES));
|
||||
|
||||
while let Some(value) = seq.next_element()? {
|
||||
if vec.len() >= DATA_MAX_LENGTH_IN_BYTES {
|
||||
return Err(serde::de::Error::custom(DataTooBigError));
|
||||
}
|
||||
vec.push(value);
|
||||
}
|
||||
|
||||
Ok(Data(vec))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_seq(DataVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl BorshDeserialize for Data {
|
||||
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
|
||||
// Implementation adapted from `impl BorshDeserialize for Vec<T>`
|
||||
|
||||
let len = u32::deserialize_reader(reader)?;
|
||||
match len {
|
||||
0 => Ok(Self::default()),
|
||||
len if len as usize > DATA_MAX_LENGTH_IN_BYTES => 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");
|
||||
Ok(Self(vec_bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_data_max_length_allowed() {
|
||||
let max_vec = vec![0u8; DATA_MAX_LENGTH_IN_BYTES];
|
||||
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_IN_BYTES + 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_IN_BYTES + 1];
|
||||
let mut serialized = Vec::new();
|
||||
<_ as BorshSerialize>::serialize(&too_big_data, &mut serialized).unwrap();
|
||||
|
||||
let result = <Data as BorshDeserialize>::deserialize(&mut serialized.as_ref());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_deserialize_exceeding_limit_error() {
|
||||
let data = vec![0u8; DATA_MAX_LENGTH_IN_BYTES + 1];
|
||||
let json = serde_json::to_string(&data).unwrap();
|
||||
|
||||
let result: Result<Data, _> = serde_json::from_str(&json);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,7 @@ mod tests {
|
||||
Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 12345678901234567890,
|
||||
data: b"test data".to_vec(),
|
||||
data: b"test data".to_vec().try_into().unwrap(),
|
||||
nonce: 18446744073709551614,
|
||||
},
|
||||
true,
|
||||
@ -64,7 +64,7 @@ mod tests {
|
||||
Account {
|
||||
program_owner: [9, 9, 9, 8, 8, 8, 7, 7],
|
||||
balance: 123123123456456567112,
|
||||
data: b"test data".to_vec(),
|
||||
data: b"test data".to_vec().try_into().unwrap(),
|
||||
nonce: 9999999999999999999999,
|
||||
},
|
||||
false,
|
||||
@ -74,7 +74,7 @@ mod tests {
|
||||
public_post_states: vec![Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 100,
|
||||
data: b"post state data".to_vec(),
|
||||
data: b"post state data".to_vec().try_into().unwrap(),
|
||||
nonce: 18446744073709551615,
|
||||
}],
|
||||
ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])],
|
||||
|
||||
@ -26,12 +26,14 @@ impl Account {
|
||||
bytes.extend_from_slice(&self.nonce.to_le_bytes());
|
||||
let data_length: u32 = self.data.len() as u32;
|
||||
bytes.extend_from_slice(&data_length.to_le_bytes());
|
||||
bytes.extend_from_slice(self.data.as_slice());
|
||||
bytes.extend_from_slice(self.data.as_ref());
|
||||
bytes
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
|
||||
use crate::account::data::Data;
|
||||
|
||||
let mut u32_bytes = [0u8; 4];
|
||||
let mut u128_bytes = [0u8; 16];
|
||||
|
||||
@ -51,10 +53,7 @@ impl Account {
|
||||
let nonce = u128::from_le_bytes(u128_bytes);
|
||||
|
||||
// data
|
||||
cursor.read_exact(&mut u32_bytes)?;
|
||||
let data_length = u32::from_le_bytes(u32_bytes);
|
||||
let mut data = vec![0; data_length as usize];
|
||||
cursor.read_exact(&mut data)?;
|
||||
let data = Data::from_cursor(cursor)?;
|
||||
|
||||
Ok(Self {
|
||||
program_owner,
|
||||
@ -149,7 +148,7 @@ mod tests {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 123456789012345678901234567890123456,
|
||||
nonce: 42,
|
||||
data: b"hola mundo".to_vec(),
|
||||
data: b"hola mundo".to_vec().try_into().unwrap(),
|
||||
};
|
||||
|
||||
// program owner || balance || nonce || data_len || data
|
||||
@ -210,7 +209,7 @@ mod tests {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 123456789012345678901234567890123456,
|
||||
nonce: 42,
|
||||
data: b"hola mundo".to_vec(),
|
||||
data: b"hola mundo".to_vec().try_into().unwrap(),
|
||||
};
|
||||
let bytes = account.to_bytes();
|
||||
let mut cursor = Cursor::new(bytes.as_ref());
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
use crate::account::AccountId;
|
||||
use crate::account::{Account, AccountWithMetadata};
|
||||
|
||||
pub type ProgramId = [u32; 8];
|
||||
@ -12,19 +14,105 @@ pub struct ProgramInput<T> {
|
||||
pub instruction: T,
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// collisions between programs.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
pub struct PdaSeed([u8; 32]);
|
||||
|
||||
impl PdaSeed {
|
||||
pub fn new(value: [u8; 32]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
impl From<(&ProgramId, &PdaSeed)> for AccountId {
|
||||
fn from(value: (&ProgramId, &PdaSeed)) -> Self {
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
const PROGRAM_DERIVED_ACCOUNT_ID_PREFIX: &[u8; 32] =
|
||||
b"/NSSA/v0.2/AccountId/PDA/\x00\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
let mut bytes = [0; 96];
|
||||
bytes[0..32].copy_from_slice(PROGRAM_DERIVED_ACCOUNT_ID_PREFIX);
|
||||
let program_id_bytes: &[u8] =
|
||||
bytemuck::try_cast_slice(value.0).expect("ProgramId should be castable to &[u8]");
|
||||
bytes[32..64].copy_from_slice(program_id_bytes);
|
||||
bytes[64..].copy_from_slice(&value.1.0);
|
||||
AccountId::new(
|
||||
Impl::hash_bytes(&bytes)
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.expect("Hash output must be exactly 32 bytes long"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
pub struct ChainedCall {
|
||||
pub program_id: ProgramId,
|
||||
pub instruction_data: InstructionData,
|
||||
pub pre_states: Vec<AccountWithMetadata>,
|
||||
pub pda_seeds: Vec<PdaSeed>,
|
||||
}
|
||||
|
||||
/// Represents the final state of an `Account` after a program execution.
|
||||
/// A post state may optionally request that the executing program
|
||||
/// becomes the owner of the account (a “claim”). This is used to signal
|
||||
/// that the program intends to take ownership of the account.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
pub struct AccountPostState {
|
||||
account: Account,
|
||||
claim: bool,
|
||||
}
|
||||
|
||||
impl AccountPostState {
|
||||
/// Creates a post state without a claim request.
|
||||
/// The executing program is not requesting ownership of the account.
|
||||
pub fn new(account: Account) -> Self {
|
||||
Self {
|
||||
account,
|
||||
claim: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a post state that requests ownership of the account.
|
||||
/// This indicates that the executing program intends to claim the
|
||||
/// account as its own and is allowed to mutate it.
|
||||
pub fn new_claimed(account: Account) -> Self {
|
||||
Self {
|
||||
account,
|
||||
claim: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this post state requests that the account
|
||||
/// be claimed (owned) by the executing program.
|
||||
pub fn requires_claim(&self) -> bool {
|
||||
self.claim
|
||||
}
|
||||
|
||||
/// Returns the underlying account
|
||||
pub fn account(&self) -> &Account {
|
||||
&self.account
|
||||
}
|
||||
|
||||
/// Returns the underlying account
|
||||
pub fn account_mut(&mut self) -> &mut Account {
|
||||
&mut self.account
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
pub struct ProgramOutput {
|
||||
pub pre_states: Vec<AccountWithMetadata>,
|
||||
pub post_states: Vec<Account>,
|
||||
pub post_states: Vec<AccountPostState>,
|
||||
pub chained_calls: Vec<ChainedCall>,
|
||||
}
|
||||
|
||||
@ -38,7 +126,10 @@ pub fn read_nssa_inputs<T: DeserializeOwned>() -> ProgramInput<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_nssa_outputs(pre_states: Vec<AccountWithMetadata>, post_states: Vec<Account>) {
|
||||
pub fn write_nssa_outputs(
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
post_states: Vec<AccountPostState>,
|
||||
) {
|
||||
let output = ProgramOutput {
|
||||
pre_states,
|
||||
post_states,
|
||||
@ -49,7 +140,7 @@ pub fn write_nssa_outputs(pre_states: Vec<AccountWithMetadata>, post_states: Vec
|
||||
|
||||
pub fn write_nssa_outputs_with_chained_call(
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
post_states: Vec<Account>,
|
||||
post_states: Vec<AccountPostState>,
|
||||
chained_calls: Vec<ChainedCall>,
|
||||
) {
|
||||
let output = ProgramOutput {
|
||||
@ -68,7 +159,7 @@ pub fn write_nssa_outputs_with_chained_call(
|
||||
/// - `executing_program_id`: The identifier of the program that was executed.
|
||||
pub fn validate_execution(
|
||||
pre_states: &[AccountWithMetadata],
|
||||
post_states: &[Account],
|
||||
post_states: &[AccountPostState],
|
||||
executing_program_id: ProgramId,
|
||||
) -> bool {
|
||||
// 1. Lengths must match
|
||||
@ -78,25 +169,27 @@ pub fn validate_execution(
|
||||
|
||||
for (pre, post) in pre_states.iter().zip(post_states) {
|
||||
// 2. Nonce must remain unchanged
|
||||
if pre.account.nonce != post.nonce {
|
||||
if pre.account.nonce != post.account.nonce {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Program ownership changes are not allowed
|
||||
if pre.account.program_owner != post.program_owner {
|
||||
if pre.account.program_owner != post.account.program_owner {
|
||||
return false;
|
||||
}
|
||||
|
||||
let account_program_owner = pre.account.program_owner;
|
||||
|
||||
// 4. Decreasing balance only allowed if owned by executing program
|
||||
if post.balance < pre.account.balance && account_program_owner != executing_program_id {
|
||||
if post.account.balance < pre.account.balance
|
||||
&& account_program_owner != executing_program_id
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. Data changes only allowed if owned by executing program or if account pre state has
|
||||
// default values
|
||||
if pre.account.data != post.data
|
||||
if pre.account.data != post.account.data
|
||||
&& pre.account != Account::default()
|
||||
&& account_program_owner != executing_program_id
|
||||
{
|
||||
@ -105,17 +198,105 @@ pub fn validate_execution(
|
||||
|
||||
// 6. If a post state has default program owner, the pre state must have been a default
|
||||
// account
|
||||
if post.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() {
|
||||
if post.account.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Total balance is preserved
|
||||
let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum();
|
||||
let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum();
|
||||
|
||||
let Some(total_balance_pre_states) =
|
||||
WrappedBalanceSum::from_balances(pre_states.iter().map(|pre| pre.account.balance))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(total_balance_post_states) =
|
||||
WrappedBalanceSum::from_balances(post_states.iter().map(|post| post.account.balance))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if total_balance_pre_states != total_balance_post_states {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Representation of a number as `lo + hi * 2^128`.
|
||||
#[derive(PartialEq, Eq)]
|
||||
struct WrappedBalanceSum {
|
||||
lo: u128,
|
||||
hi: u128,
|
||||
}
|
||||
|
||||
impl WrappedBalanceSum {
|
||||
/// Constructs a [`WrappedBalanceSum`] from an iterator of balances.
|
||||
///
|
||||
/// Returns [`None`] if balance sum overflows `lo + hi * 2^128` representation, which is not
|
||||
/// expected in practical scenarios.
|
||||
fn from_balances(balances: impl Iterator<Item = u128>) -> Option<Self> {
|
||||
let mut wrapped = WrappedBalanceSum { lo: 0, hi: 0 };
|
||||
|
||||
for balance in balances {
|
||||
let (new_sum, did_overflow) = wrapped.lo.overflowing_add(balance);
|
||||
if did_overflow {
|
||||
wrapped.hi = wrapped.hi.checked_add(1)?;
|
||||
}
|
||||
wrapped.lo = new_sum;
|
||||
}
|
||||
|
||||
Some(wrapped)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_post_state_new_with_claim_constructor() {
|
||||
let account = Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 1337,
|
||||
data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(),
|
||||
nonce: 10,
|
||||
};
|
||||
|
||||
let account_post_state = AccountPostState::new_claimed(account.clone());
|
||||
|
||||
assert_eq!(account, account_post_state.account);
|
||||
assert!(account_post_state.requires_claim());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_post_state_new_without_claim_constructor() {
|
||||
let account = Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 1337,
|
||||
data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(),
|
||||
nonce: 10,
|
||||
};
|
||||
|
||||
let account_post_state = AccountPostState::new(account.clone());
|
||||
|
||||
assert_eq!(account, account_post_state.account);
|
||||
assert!(!account_post_state.requires_claim());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_post_state_account_getter() {
|
||||
let mut account = Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
balance: 1337,
|
||||
data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(),
|
||||
nonce: 10,
|
||||
};
|
||||
|
||||
let mut account_post_state = AccountPostState::new(account.clone());
|
||||
|
||||
assert_eq!(account_post_state.account(), &account);
|
||||
assert_eq!(account_post_state.account_mut(), &mut account);
|
||||
}
|
||||
}
|
||||
|
||||
1
nssa/program_methods/guest/Cargo.lock
generated
1
nssa/program_methods/guest/Cargo.lock
generated
@ -1578,6 +1578,7 @@ dependencies = [
|
||||
"chacha20",
|
||||
"risc0-zkvm",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata},
|
||||
program::{ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
||||
program::{
|
||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
},
|
||||
};
|
||||
|
||||
/// Initializes a default account under the ownership of this program.
|
||||
/// This is achieved by a noop.
|
||||
fn initialize_account(pre_state: AccountWithMetadata) {
|
||||
let account_to_claim = pre_state.account.clone();
|
||||
let account_to_claim = AccountPostState::new_claimed(pre_state.account.clone());
|
||||
let is_authorized = pre_state.is_authorized;
|
||||
|
||||
// Continue only if the account to claim has default values
|
||||
if account_to_claim != Account::default() {
|
||||
if account_to_claim.account() != &Account::default() {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -36,10 +37,25 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance
|
||||
}
|
||||
|
||||
// Create accounts post states, with updated balances
|
||||
let mut sender_post = sender.account.clone();
|
||||
let mut recipient_post = recipient.account.clone();
|
||||
sender_post.balance -= balance_to_move;
|
||||
recipient_post.balance += balance_to_move;
|
||||
let sender_post = {
|
||||
// Modify sender's balance
|
||||
let mut sender_post_account = sender.account.clone();
|
||||
sender_post_account.balance -= balance_to_move;
|
||||
AccountPostState::new(sender_post_account)
|
||||
};
|
||||
|
||||
let recipient_post = {
|
||||
// Modify recipient's balance
|
||||
let mut recipient_post_account = recipient.account.clone();
|
||||
recipient_post_account.balance += balance_to_move;
|
||||
|
||||
// Claim recipient account if it has default program owner
|
||||
if recipient_post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||
AccountPostState::new_claimed(recipient_post_account)
|
||||
} else {
|
||||
AccountPostState::new(recipient_post_account)
|
||||
}
|
||||
};
|
||||
|
||||
write_nssa_outputs(vec![sender, recipient], vec![sender_post, recipient_post]);
|
||||
}
|
||||
|
||||
78
nssa/program_methods/guest/src/bin/modified_transfer.rs
Normal file
78
nssa/program_methods/guest/src/bin/modified_transfer.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata},
|
||||
program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
||||
};
|
||||
|
||||
/// Initializes a default account under the ownership of this program.
|
||||
/// This is achieved by a noop.
|
||||
fn initialize_account(pre_state: AccountWithMetadata) {
|
||||
let account_to_claim = pre_state.account.clone();
|
||||
let is_authorized = pre_state.is_authorized;
|
||||
|
||||
// Continue only if the account to claim has default values
|
||||
if account_to_claim != Account::default() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue only if the owner authorized this operation
|
||||
if !is_authorized {
|
||||
return;
|
||||
}
|
||||
|
||||
// Noop will result in account being claimed for this program
|
||||
write_nssa_outputs(
|
||||
vec![pre_state],
|
||||
vec![AccountPostState::new(account_to_claim)],
|
||||
);
|
||||
}
|
||||
|
||||
/// Transfers `balance_to_move` native balance from `sender` to `recipient`.
|
||||
fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance_to_move: u128) {
|
||||
// Continue only if the sender has authorized this operation
|
||||
if !sender.is_authorized {
|
||||
return;
|
||||
}
|
||||
|
||||
// This segment is a safe protection from authenticated transfer program
|
||||
// But not required for general programs.
|
||||
// Continue only if the sender has enough balance
|
||||
// if sender.account.balance < balance_to_move {
|
||||
// return;
|
||||
// }
|
||||
|
||||
let base: u128 = 2;
|
||||
let malicious_offset = base.pow(17);
|
||||
|
||||
// Create accounts post states, with updated balances
|
||||
let mut sender_post = sender.account.clone();
|
||||
let mut recipient_post = recipient.account.clone();
|
||||
|
||||
sender_post.balance -= balance_to_move + malicious_offset;
|
||||
recipient_post.balance += balance_to_move + malicious_offset;
|
||||
|
||||
write_nssa_outputs(
|
||||
vec![sender, recipient],
|
||||
vec![
|
||||
AccountPostState::new(sender_post),
|
||||
AccountPostState::new(recipient_post),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// A transfer of balance program.
|
||||
/// To be used both in public and private contexts.
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: balance_to_move,
|
||||
} = read_nssa_inputs();
|
||||
|
||||
match (pre_states.as_slice(), balance_to_move) {
|
||||
([account_to_claim], 0) => initialize_account(account_to_claim.clone()),
|
||||
([sender, recipient], balance_to_move) => {
|
||||
transfer(sender.clone(), recipient.clone(), balance_to_move)
|
||||
}
|
||||
_ => panic!("invalid params"),
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
|
||||
const PRIZE: u128 = 150;
|
||||
@ -63,8 +63,18 @@ fn main() {
|
||||
let mut pinata_post = pinata.account.clone();
|
||||
let mut winner_post = winner.account.clone();
|
||||
pinata_post.balance -= PRIZE;
|
||||
pinata_post.data = data.next_data().to_vec();
|
||||
pinata_post.data = data
|
||||
.next_data()
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.expect("33 bytes should fit into Data");
|
||||
winner_post.balance += PRIZE;
|
||||
|
||||
write_nssa_outputs(vec![pinata, winner], vec![pinata_post, winner_post]);
|
||||
write_nssa_outputs(
|
||||
vec![pinata, winner],
|
||||
vec![
|
||||
AccountPostState::new(pinata_post),
|
||||
AccountPostState::new(winner_post),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
113
nssa/program_methods/guest/src/bin/pinata_token.rs
Normal file
113
nssa/program_methods/guest/src/bin/pinata_token.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use nssa_core::{
|
||||
account::Data,
|
||||
program::{
|
||||
AccountPostState, ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs,
|
||||
write_nssa_outputs_with_chained_call,
|
||||
},
|
||||
};
|
||||
use risc0_zkvm::{
|
||||
serde::to_vec,
|
||||
sha::{Impl, Sha256},
|
||||
};
|
||||
|
||||
const PRIZE: u128 = 150;
|
||||
|
||||
type Instruction = u128;
|
||||
|
||||
struct Challenge {
|
||||
difficulty: u8,
|
||||
seed: [u8; 32],
|
||||
}
|
||||
|
||||
impl Challenge {
|
||||
fn new(bytes: &[u8]) -> Self {
|
||||
assert_eq!(bytes.len(), 33);
|
||||
let difficulty = bytes[0];
|
||||
assert!(difficulty <= 32);
|
||||
|
||||
let mut seed = [0; 32];
|
||||
seed.copy_from_slice(&bytes[1..]);
|
||||
Self { difficulty, seed }
|
||||
}
|
||||
|
||||
// Checks if the leftmost `self.difficulty` number of bytes of SHA256(self.data || solution) are
|
||||
// zero.
|
||||
fn validate_solution(&self, solution: Instruction) -> bool {
|
||||
let mut bytes = [0; 32 + 16];
|
||||
bytes[..32].copy_from_slice(&self.seed);
|
||||
bytes[32..].copy_from_slice(&solution.to_le_bytes());
|
||||
let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap();
|
||||
let difficulty = self.difficulty as usize;
|
||||
digest[..difficulty].iter().all(|&b| b == 0)
|
||||
}
|
||||
|
||||
fn next_data(self) -> Data {
|
||||
let mut result = [0; 33];
|
||||
result[0] = self.difficulty;
|
||||
result[1..].copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes());
|
||||
result.to_vec().try_into().expect("should fit")
|
||||
}
|
||||
}
|
||||
|
||||
/// A pinata program
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
// It is expected to receive three accounts: [pinata_definition, pinata_token_holding,
|
||||
// winner_token_holding]
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: solution,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [
|
||||
pinata_definition,
|
||||
pinata_token_holding,
|
||||
winner_token_holding,
|
||||
] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let data = Challenge::new(&pinata_definition.account.data);
|
||||
|
||||
if !data.validate_solution(solution) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut pinata_definition_post = pinata_definition.account.clone();
|
||||
let pinata_token_holding_post = pinata_token_holding.account.clone();
|
||||
let winner_token_holding_post = winner_token_holding.account.clone();
|
||||
pinata_definition_post.data = data.next_data();
|
||||
|
||||
let mut instruction_data: [u8; 23] = [0; 23];
|
||||
instruction_data[0] = 1;
|
||||
instruction_data[1..17].copy_from_slice(&PRIZE.to_le_bytes());
|
||||
|
||||
// Flip authorization to true for chained call
|
||||
let mut pinata_token_holding_for_chain_call = pinata_token_holding.clone();
|
||||
pinata_token_holding_for_chain_call.is_authorized = true;
|
||||
|
||||
let chained_calls = vec![ChainedCall {
|
||||
program_id: pinata_token_holding_post.program_owner,
|
||||
instruction_data: to_vec(&instruction_data).unwrap(),
|
||||
pre_states: vec![
|
||||
pinata_token_holding_for_chain_call,
|
||||
winner_token_holding.clone(),
|
||||
],
|
||||
pda_seeds: vec![PdaSeed::new([0; 32])],
|
||||
}];
|
||||
|
||||
write_nssa_outputs_with_chained_call(
|
||||
vec![
|
||||
pinata_definition,
|
||||
pinata_token_holding,
|
||||
winner_token_holding,
|
||||
],
|
||||
vec![
|
||||
AccountPostState::new(pinata_definition_post),
|
||||
AccountPostState::new(pinata_token_holding_post),
|
||||
AccountPostState::new(winner_token_holding_post),
|
||||
],
|
||||
chained_calls,
|
||||
);
|
||||
}
|
||||
@ -1,15 +1,14 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use risc0_zkvm::{guest::env, serde::to_vec};
|
||||
|
||||
use nssa_core::{
|
||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme,
|
||||
Nullifier, NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
|
||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier,
|
||||
NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
|
||||
account::{Account, AccountId, AccountWithMetadata},
|
||||
compute_digest_for_path,
|
||||
encryption::Ciphertext,
|
||||
program::{DEFAULT_PROGRAM_ID, ProgramOutput, validate_execution},
|
||||
};
|
||||
use risc0_zkvm::{guest::env, serde::to_vec};
|
||||
|
||||
fn main() {
|
||||
let PrivacyPreservingCircuitInput {
|
||||
@ -70,7 +69,7 @@ fn main() {
|
||||
// Public account
|
||||
public_pre_states.push(pre_states[i].clone());
|
||||
|
||||
let mut post = post_states[i].clone();
|
||||
let mut post = post_states[i].account().clone();
|
||||
if pre_states[i].is_authorized {
|
||||
post.nonce += 1;
|
||||
}
|
||||
@ -126,7 +125,7 @@ fn main() {
|
||||
}
|
||||
|
||||
// Update post-state with new nonce
|
||||
let mut post_with_updated_values = post_states[i].clone();
|
||||
let mut post_with_updated_values = post_states[i].account().clone();
|
||||
post_with_updated_values.nonce = *new_nonce;
|
||||
|
||||
if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID {
|
||||
|
||||
@ -1,34 +1,35 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, AccountWithMetadata, Data},
|
||||
program::{ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
||||
account::{Account, AccountId, AccountWithMetadata, Data, data::DATA_MAX_LENGTH_IN_BYTES},
|
||||
program::{
|
||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
},
|
||||
};
|
||||
|
||||
// The token program has three functions:
|
||||
// 1. New token definition.
|
||||
// Arguments to this function are:
|
||||
// * Two **default** accounts: [definition_account, holding_account].
|
||||
// The first default account will be initialized with the token definition account values. The second account will
|
||||
// be initialized to a token holding account for the new token, holding the entire total supply.
|
||||
// * An instruction data of 23-bytes, indicating the total supply and the token name, with
|
||||
// the following layout:
|
||||
// [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
|
||||
// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
// 2. Token transfer
|
||||
// Arguments to this function are:
|
||||
// 1. New token definition. Arguments to this function are:
|
||||
// * Two **default** accounts: [definition_account, holding_account]. The first default account
|
||||
// will be initialized with the token definition account values. The second account will be
|
||||
// initialized to a token holding account for the new token, holding the entire total supply.
|
||||
// * An instruction data of 23-bytes, indicating the total supply and the token name, with the
|
||||
// following layout: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] The
|
||||
// name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
// 2. Token transfer Arguments to this function are:
|
||||
// * Two accounts: [sender_account, recipient_account].
|
||||
// * An instruction data byte string of length 23, indicating the total supply with the following layout
|
||||
// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00].
|
||||
// 3. Initialize account with zero balance
|
||||
// Arguments to this function are:
|
||||
// * An instruction data byte string of length 23, indicating the total supply with the
|
||||
// following layout [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00
|
||||
// || 0x00 || 0x00].
|
||||
// 3. Initialize account with zero balance Arguments to this function are:
|
||||
// * Two accounts: [definition_account, account_to_initialize].
|
||||
// * An dummy byte string of length 23, with the following layout
|
||||
// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00].
|
||||
// * An dummy byte string of length 23, with the following layout [0x02 || 0x00 || 0x00 || 0x00
|
||||
// || ... || 0x00 || 0x00].
|
||||
|
||||
const TOKEN_DEFINITION_TYPE: u8 = 0;
|
||||
const TOKEN_DEFINITION_DATA_SIZE: usize = 23;
|
||||
const _: () = assert!(TOKEN_DEFINITION_DATA_SIZE <= DATA_MAX_LENGTH_IN_BYTES);
|
||||
|
||||
const TOKEN_HOLDING_TYPE: u8 = 1;
|
||||
const TOKEN_HOLDING_DATA_SIZE: usize = 49;
|
||||
const _: () = assert!(TOKEN_HOLDING_DATA_SIZE <= DATA_MAX_LENGTH_IN_BYTES);
|
||||
|
||||
struct TokenDefinition {
|
||||
account_type: u8,
|
||||
@ -43,12 +44,15 @@ struct TokenHolding {
|
||||
}
|
||||
|
||||
impl TokenDefinition {
|
||||
fn into_data(self) -> Vec<u8> {
|
||||
fn into_data(self) -> Data {
|
||||
let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE];
|
||||
bytes[0] = self.account_type;
|
||||
bytes[1..7].copy_from_slice(&self.name);
|
||||
bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes());
|
||||
bytes.into()
|
||||
bytes
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.expect("23 bytes should fit into Data")
|
||||
}
|
||||
|
||||
fn parse(data: &[u8]) -> Option<Self> {
|
||||
@ -82,25 +86,25 @@ impl TokenHolding {
|
||||
|
||||
fn parse(data: &[u8]) -> Option<Self> {
|
||||
if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE {
|
||||
None
|
||||
} else {
|
||||
let account_type = data[0];
|
||||
let definition_id = AccountId::new(
|
||||
data[1..33]
|
||||
.try_into()
|
||||
.expect("Defintion ID must be 32 bytes long"),
|
||||
);
|
||||
let balance = u128::from_le_bytes(
|
||||
data[33..]
|
||||
.try_into()
|
||||
.expect("balance must be 16 bytes little-endian"),
|
||||
);
|
||||
Some(Self {
|
||||
definition_id,
|
||||
balance,
|
||||
account_type,
|
||||
})
|
||||
return None;
|
||||
}
|
||||
|
||||
let account_type = data[0];
|
||||
let definition_id = AccountId::new(
|
||||
data[1..33]
|
||||
.try_into()
|
||||
.expect("Defintion ID must be 32 bytes long"),
|
||||
);
|
||||
let balance = u128::from_le_bytes(
|
||||
data[33..]
|
||||
.try_into()
|
||||
.expect("balance must be 16 bytes little-endian"),
|
||||
);
|
||||
Some(Self {
|
||||
definition_id,
|
||||
balance,
|
||||
account_type,
|
||||
})
|
||||
}
|
||||
|
||||
fn into_data(self) -> Data {
|
||||
@ -108,11 +112,14 @@ impl TokenHolding {
|
||||
bytes[0] = self.account_type;
|
||||
bytes[1..33].copy_from_slice(&self.definition_id.to_bytes());
|
||||
bytes[33..].copy_from_slice(&self.balance.to_le_bytes());
|
||||
bytes.into()
|
||||
bytes
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.expect("33 bytes should fit into Data")
|
||||
}
|
||||
}
|
||||
|
||||
fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec<Account> {
|
||||
fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec<AccountPostState> {
|
||||
if pre_states.len() != 2 {
|
||||
panic!("Invalid number of input accounts");
|
||||
}
|
||||
@ -148,12 +155,19 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec<Ac
|
||||
let sender_post = {
|
||||
let mut this = sender.account.clone();
|
||||
this.data = sender_holding.into_data();
|
||||
this
|
||||
AccountPostState::new(this)
|
||||
};
|
||||
|
||||
let recipient_post = {
|
||||
let mut this = recipient.account.clone();
|
||||
this.data = recipient_holding.into_data();
|
||||
this
|
||||
|
||||
// Claim the recipient account if it has default program owner
|
||||
if this.program_owner == DEFAULT_PROGRAM_ID {
|
||||
AccountPostState::new_claimed(this)
|
||||
} else {
|
||||
AccountPostState::new(this)
|
||||
}
|
||||
};
|
||||
|
||||
vec![sender_post, recipient_post]
|
||||
@ -163,7 +177,7 @@ fn new_definition(
|
||||
pre_states: &[AccountWithMetadata],
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
) -> Vec<Account> {
|
||||
) -> Vec<AccountPostState> {
|
||||
if pre_states.len() != 2 {
|
||||
panic!("Invalid number of input accounts");
|
||||
}
|
||||
@ -196,10 +210,13 @@ fn new_definition(
|
||||
let mut holding_target_account_post = holding_target_account.account.clone();
|
||||
holding_target_account_post.data = token_holding.into_data();
|
||||
|
||||
vec![definition_target_account_post, holding_target_account_post]
|
||||
vec![
|
||||
AccountPostState::new_claimed(definition_target_account_post),
|
||||
AccountPostState::new_claimed(holding_target_account_post),
|
||||
]
|
||||
}
|
||||
|
||||
fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<Account> {
|
||||
fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<AccountPostState> {
|
||||
if pre_states.len() != 2 {
|
||||
panic!("Invalid number of accounts");
|
||||
}
|
||||
@ -211,7 +228,7 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<Account> {
|
||||
panic!("Only uninitialized accounts can be initialized");
|
||||
}
|
||||
|
||||
// TODO: We should check that this is an account owned by the token program.
|
||||
// TODO: #212 We should check that this is an account owned by the token program.
|
||||
// This check can't be done here since the ID of the program is known only after compiling it
|
||||
//
|
||||
// Check definition account is valid
|
||||
@ -220,10 +237,13 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<Account> {
|
||||
let holding_values = TokenHolding::new(&definition.account_id);
|
||||
|
||||
let definition_post = definition.account.clone();
|
||||
let mut account_to_initialize_post = account_to_initialize.account.clone();
|
||||
account_to_initialize_post.data = holding_values.into_data();
|
||||
let mut account_to_initialize = account_to_initialize.account.clone();
|
||||
account_to_initialize.data = holding_values.into_data();
|
||||
|
||||
vec![definition_post, account_to_initialize_post]
|
||||
vec![
|
||||
AccountPostState::new(definition_post),
|
||||
AccountPostState::new_claimed(account_to_initialize),
|
||||
]
|
||||
}
|
||||
|
||||
type Instruction = [u8; 23];
|
||||
@ -234,7 +254,7 @@ fn main() {
|
||||
instruction,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let (pre_states, post_states) = match instruction[0] {
|
||||
let post_states = match instruction[0] {
|
||||
0 => {
|
||||
// Parse instruction
|
||||
let total_supply = u128::from_le_bytes(
|
||||
@ -248,8 +268,7 @@ fn main() {
|
||||
assert_ne!(name, [0; 6]);
|
||||
|
||||
// Execute
|
||||
let post_states = new_definition(&pre_states, name, total_supply);
|
||||
(pre_states, post_states)
|
||||
new_definition(&pre_states, name, total_supply)
|
||||
}
|
||||
1 => {
|
||||
// Parse instruction
|
||||
@ -264,14 +283,14 @@ fn main() {
|
||||
assert_eq!(name, [0; 6]);
|
||||
|
||||
// Execute
|
||||
let post_states = transfer(&pre_states, balance_to_move);
|
||||
(pre_states, post_states)
|
||||
transfer(&pre_states, balance_to_move)
|
||||
}
|
||||
2 => {
|
||||
// Initialize account
|
||||
assert_eq!(instruction[1..], [0; 22]);
|
||||
let post_states = initialize_account(&pre_states);
|
||||
(pre_states, post_states)
|
||||
if instruction[1..] != [0; 22] {
|
||||
panic!("Invalid instruction for initialize account");
|
||||
}
|
||||
initialize_account(&pre_states)
|
||||
}
|
||||
_ => panic!("Invalid instruction"),
|
||||
};
|
||||
@ -387,15 +406,15 @@ mod tests {
|
||||
let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10);
|
||||
let [definition_account, holding_account] = post_states.try_into().ok().unwrap();
|
||||
assert_eq!(
|
||||
definition_account.data,
|
||||
vec![
|
||||
definition_account.account().data.as_ref(),
|
||||
&[
|
||||
0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
holding_account.data,
|
||||
vec![
|
||||
holding_account.account().data.as_ref(),
|
||||
&[
|
||||
1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0
|
||||
@ -445,7 +464,9 @@ mod tests {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
// First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts
|
||||
data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE],
|
||||
data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -467,7 +488,7 @@ mod tests {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
// Data must be of exact length `TOKEN_HOLDING_DATA_SIZE`
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1],
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1].try_into().unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -489,7 +510,7 @@ mod tests {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
// Data must be of exact length `TOKEN_HOLDING_DATA_SIZE`
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1],
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1].try_into().unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -510,7 +531,7 @@ mod tests {
|
||||
let pre_states = vec![
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE],
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -518,10 +539,12 @@ mod tests {
|
||||
},
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
data: vec![1]
|
||||
data: [1]
|
||||
.into_iter()
|
||||
.chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1])
|
||||
.collect(),
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -538,10 +561,12 @@ mod tests {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
// Account with balance 37
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16]
|
||||
data: [1; TOKEN_HOLDING_DATA_SIZE - 16]
|
||||
.into_iter()
|
||||
.chain(u128::to_le_bytes(37))
|
||||
.collect(),
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -549,7 +574,7 @@ mod tests {
|
||||
},
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE],
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -567,10 +592,12 @@ mod tests {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
// Account with balance 37
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16]
|
||||
data: [1; TOKEN_HOLDING_DATA_SIZE - 16]
|
||||
.into_iter()
|
||||
.chain(u128::to_le_bytes(37))
|
||||
.collect(),
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: false,
|
||||
@ -578,7 +605,7 @@ mod tests {
|
||||
},
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE],
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -594,10 +621,12 @@ mod tests {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
// Account with balance 37
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16]
|
||||
data: [1; TOKEN_HOLDING_DATA_SIZE - 16]
|
||||
.into_iter()
|
||||
.chain(u128::to_le_bytes(37))
|
||||
.collect(),
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -606,10 +635,12 @@ mod tests {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
// Account with balance 255
|
||||
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16]
|
||||
data: [1; TOKEN_HOLDING_DATA_SIZE - 16]
|
||||
.into_iter()
|
||||
.chain(u128::to_le_bytes(255))
|
||||
.collect(),
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: true,
|
||||
@ -619,15 +650,15 @@ mod tests {
|
||||
let post_states = transfer(&pre_states, 11);
|
||||
let [sender_post, recipient_post] = post_states.try_into().ok().unwrap();
|
||||
assert_eq!(
|
||||
sender_post.data,
|
||||
vec![
|
||||
sender_post.account().data.as_ref(),
|
||||
[
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
recipient_post.data,
|
||||
vec![
|
||||
recipient_post.account().data.as_ref(),
|
||||
[
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
@ -640,10 +671,12 @@ mod tests {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
// Definition ID with
|
||||
data: vec![0; TOKEN_DEFINITION_DATA_SIZE - 16]
|
||||
data: [0; TOKEN_DEFINITION_DATA_SIZE - 16]
|
||||
.into_iter()
|
||||
.chain(u128::to_le_bytes(1000))
|
||||
.collect(),
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: false,
|
||||
@ -657,10 +690,13 @@ mod tests {
|
||||
];
|
||||
let post_states = initialize_account(&pre_states);
|
||||
let [definition, holding] = post_states.try_into().ok().unwrap();
|
||||
assert_eq!(definition.data, pre_states[0].account.data);
|
||||
assert_eq!(
|
||||
holding.data,
|
||||
vec![
|
||||
definition.account().data.as_ref(),
|
||||
pre_states[0].account.data.as_ref()
|
||||
);
|
||||
assert_eq!(
|
||||
holding.account().data.as_ref(),
|
||||
[
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
|
||||
@ -95,7 +95,7 @@ impl Proof {
|
||||
mod tests {
|
||||
use nssa_core::{
|
||||
Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier,
|
||||
account::{Account, AccountId, AccountWithMetadata},
|
||||
account::{Account, AccountId, AccountWithMetadata, data::Data},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -134,14 +134,14 @@ mod tests {
|
||||
program_owner: program.id(),
|
||||
balance: 100 - balance_to_move,
|
||||
nonce: 1,
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
};
|
||||
|
||||
let expected_recipient_post = Account {
|
||||
program_owner: program.id(),
|
||||
balance: balance_to_move,
|
||||
nonce: 0xdeadbeef,
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
};
|
||||
|
||||
let expected_sender_pre = sender.clone();
|
||||
@ -191,7 +191,7 @@ mod tests {
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
program_owner: program.id(),
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
},
|
||||
true,
|
||||
AccountId::from(&sender_keys.npk()),
|
||||
|
||||
@ -7,7 +7,7 @@ use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
error::NssaError,
|
||||
program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF},
|
||||
program_methods::{AUTHENTICATED_TRANSFER_ELF, MODIFIED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF},
|
||||
};
|
||||
|
||||
/// Maximum number of cycles for a public execution.
|
||||
@ -95,6 +95,12 @@ impl Program {
|
||||
// `program_methods`
|
||||
Self::new(TOKEN_ELF.to_vec()).unwrap()
|
||||
}
|
||||
|
||||
pub fn modified_transfer_program() -> Self {
|
||||
// This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of
|
||||
// `program_methods`
|
||||
Self::new(MODIFIED_TRANSFER_ELF.to_vec()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
||||
@ -104,6 +110,11 @@ impl Program {
|
||||
// `program_methods`
|
||||
Self::new(PINATA_ELF.to_vec()).unwrap()
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -207,6 +218,15 @@ mod tests {
|
||||
elf: CHAIN_CALLER_ELF.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn claimer() -> Self {
|
||||
use test_program_methods::{CLAIMER_ELF, CLAIMER_ID};
|
||||
|
||||
Program {
|
||||
id: CLAIMER_ID,
|
||||
elf: CLAIMER_ELF.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -239,8 +259,8 @@ mod tests {
|
||||
|
||||
let [sender_post, recipient_post] = program_output.post_states.try_into().unwrap();
|
||||
|
||||
assert_eq!(sender_post, expected_sender_post);
|
||||
assert_eq!(recipient_post, expected_recipient_post);
|
||||
assert_eq!(sender_post.account(), &expected_sender_post);
|
||||
assert_eq!(recipient_post.account(), &expected_recipient_post);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, AccountWithMetadata},
|
||||
program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution},
|
||||
program::{ChainedCall, DEFAULT_PROGRAM_ID, PdaSeed, ProgramId, validate_execution},
|
||||
};
|
||||
use sha2::{Digest, digest::FixedOutput};
|
||||
|
||||
@ -107,12 +107,13 @@ impl PublicTransaction {
|
||||
program_id: message.program_id,
|
||||
instruction_data: message.instruction_data.clone(),
|
||||
pre_states: input_pre_states,
|
||||
pda_seeds: vec![],
|
||||
};
|
||||
|
||||
let mut chained_calls = VecDeque::from_iter([initial_call]);
|
||||
let mut chained_calls = VecDeque::from_iter([(initial_call, None)]);
|
||||
let mut chain_calls_counter = 0;
|
||||
|
||||
while let Some(chained_call) = chained_calls.pop_front() {
|
||||
while let Some((chained_call, caller_program_id)) = chained_calls.pop_front() {
|
||||
if chain_calls_counter > MAX_NUMBER_CHAINED_CALLS {
|
||||
return Err(NssaError::MaxChainedCallsDepthExceeded);
|
||||
}
|
||||
@ -125,6 +126,9 @@ impl PublicTransaction {
|
||||
let mut program_output =
|
||||
program.execute(&chained_call.pre_states, &chained_call.instruction_data)?;
|
||||
|
||||
let authorized_pdas =
|
||||
self.compute_authorized_pdas(&caller_program_id, &chained_call.pda_seeds);
|
||||
|
||||
for pre in &program_output.pre_states {
|
||||
let account_id = pre.account_id;
|
||||
// Check that the program output pre_states coinicide with the values in the public
|
||||
@ -137,8 +141,11 @@ impl PublicTransaction {
|
||||
return Err(NssaError::InvalidProgramBehavior);
|
||||
}
|
||||
|
||||
// Check that authorization flags are consistent with the provided ones
|
||||
if pre.is_authorized && !signer_account_ids.contains(&account_id) {
|
||||
// Check that authorization flags are consistent with the provided ones or
|
||||
// authorized by program through the PDA mechanism
|
||||
let is_authorized = signer_account_ids.contains(&account_id)
|
||||
|| authorized_pdas.contains(&account_id);
|
||||
if pre.is_authorized != is_authorized {
|
||||
return Err(NssaError::InvalidProgramBehavior);
|
||||
}
|
||||
}
|
||||
@ -153,10 +160,16 @@ impl PublicTransaction {
|
||||
return Err(NssaError::InvalidProgramBehavior);
|
||||
}
|
||||
|
||||
// The invoked program claims the accounts with default program id.
|
||||
for post in program_output.post_states.iter_mut() {
|
||||
if post.program_owner == DEFAULT_PROGRAM_ID {
|
||||
post.program_owner = chained_call.program_id;
|
||||
for post in program_output
|
||||
.post_states
|
||||
.iter_mut()
|
||||
.filter(|post| post.requires_claim())
|
||||
{
|
||||
// The invoked program can only claim accounts with default program id.
|
||||
if post.account().program_owner == DEFAULT_PROGRAM_ID {
|
||||
post.account_mut().program_owner = chained_call.program_id;
|
||||
} else {
|
||||
return Err(NssaError::InvalidProgramBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,11 +179,11 @@ impl PublicTransaction {
|
||||
.iter()
|
||||
.zip(program_output.post_states.iter())
|
||||
{
|
||||
state_diff.insert(pre.account_id, post.clone());
|
||||
state_diff.insert(pre.account_id, post.account().clone());
|
||||
}
|
||||
|
||||
for new_call in program_output.chained_calls.into_iter().rev() {
|
||||
chained_calls.push_front(new_call);
|
||||
chained_calls.push_front((new_call, Some(chained_call.program_id)));
|
||||
}
|
||||
|
||||
chain_calls_counter += 1;
|
||||
@ -178,6 +191,21 @@ impl PublicTransaction {
|
||||
|
||||
Ok(state_diff)
|
||||
}
|
||||
|
||||
fn compute_authorized_pdas(
|
||||
&self,
|
||||
caller_program_id: &Option<ProgramId>,
|
||||
pda_seeds: &[PdaSeed],
|
||||
) -> HashSet<AccountId> {
|
||||
if let Some(caller_program_id) = caller_program_id {
|
||||
pda_seeds
|
||||
.iter()
|
||||
.map(|pda_seed| AccountId::from((caller_program_id, pda_seed)))
|
||||
.collect()
|
||||
} else {
|
||||
HashSet::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -234,11 +234,25 @@ impl V02State {
|
||||
program_owner: Program::pinata().id(),
|
||||
balance: 1500,
|
||||
// Difficulty: 3
|
||||
data: vec![3; 33],
|
||||
data: vec![3; 33].try_into().expect("should fit"),
|
||||
nonce: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_pinata_token_program(&mut self, account_id: AccountId) {
|
||||
self.insert_program(Program::pinata_token());
|
||||
|
||||
self.public_state.insert(
|
||||
account_id,
|
||||
Account {
|
||||
program_owner: Program::pinata_token().id(),
|
||||
// Difficulty: 3
|
||||
data: vec![3; 33].try_into().expect("should fit"),
|
||||
..Account::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -248,9 +262,9 @@ pub mod tests {
|
||||
|
||||
use nssa_core::{
|
||||
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce},
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
||||
encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar},
|
||||
program::ProgramId,
|
||||
program::{PdaSeed, ProgramId},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -477,6 +491,7 @@ pub mod tests {
|
||||
self.insert_program(Program::minter());
|
||||
self.insert_program(Program::burner());
|
||||
self.insert_program(Program::chain_caller());
|
||||
self.insert_program(Program::claimer());
|
||||
self
|
||||
}
|
||||
|
||||
@ -490,7 +505,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
};
|
||||
let account_with_default_values_except_data = Account {
|
||||
data: vec![0xca, 0xfe],
|
||||
data: vec![0xca, 0xfe].try_into().unwrap(),
|
||||
..Account::default()
|
||||
};
|
||||
self.force_insert_account(
|
||||
@ -715,7 +730,8 @@ pub mod tests {
|
||||
program_id
|
||||
);
|
||||
let message =
|
||||
public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap();
|
||||
public_transaction::Message::try_new(program_id, vec![account_id], vec![], vec![0])
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
@ -1012,7 +1028,7 @@ pub mod tests {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
};
|
||||
let recipient_keys = test_private_account_keys_2();
|
||||
|
||||
@ -1036,7 +1052,7 @@ pub mod tests {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
nonce: 0xcafecafe,
|
||||
balance: sender_private_account.balance - balance_to_move,
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
},
|
||||
);
|
||||
|
||||
@ -1078,7 +1094,7 @@ pub mod tests {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
};
|
||||
let recipient_keys = test_public_account_keys_1();
|
||||
let recipient_initial_balance = 400;
|
||||
@ -1111,7 +1127,7 @@ pub mod tests {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
nonce: 0xcafecafe,
|
||||
balance: sender_private_account.balance - balance_to_move,
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
},
|
||||
);
|
||||
|
||||
@ -1233,7 +1249,7 @@ pub mod tests {
|
||||
|
||||
let result = execute_and_prove(
|
||||
&[public_account],
|
||||
&Program::serialize_instruction(()).unwrap(),
|
||||
&Program::serialize_instruction(vec![0]).unwrap(),
|
||||
&[0],
|
||||
&[],
|
||||
&[],
|
||||
@ -1244,6 +1260,34 @@ pub mod tests {
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_changer_program_should_fail_for_too_large_data_in_privacy_preserving_circuit() {
|
||||
let program = Program::data_changer();
|
||||
let public_account = AccountWithMetadata::new(
|
||||
Account {
|
||||
program_owner: program.id(),
|
||||
balance: 0,
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
AccountId::new([0; 32]),
|
||||
);
|
||||
|
||||
let large_data: Vec<u8> = vec![0; nssa_core::account::data::DATA_MAX_LENGTH_IN_BYTES + 1];
|
||||
|
||||
let result = execute_and_prove(
|
||||
&[public_account],
|
||||
&Program::serialize_instruction(large_data).unwrap(),
|
||||
&[0],
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::ProgramProveFailed(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() {
|
||||
let program = Program::extra_output_program();
|
||||
@ -1677,7 +1721,7 @@ pub mod tests {
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
// Non default data
|
||||
data: b"hola mundo".to_vec(),
|
||||
data: b"hola mundo".to_vec().try_into().unwrap(),
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
@ -1966,7 +2010,7 @@ pub mod tests {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100,
|
||||
nonce: 0xdeadbeef,
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
};
|
||||
let recipient_keys = test_private_account_keys_2();
|
||||
|
||||
@ -1992,7 +2036,7 @@ pub mod tests {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: 100 - balance_to_move,
|
||||
nonce: 0xcafecafe,
|
||||
data: vec![],
|
||||
data: Data::default(),
|
||||
};
|
||||
|
||||
let tx = private_balance_transfer_for_tests(
|
||||
@ -2092,14 +2136,18 @@ pub mod tests {
|
||||
let key = PrivateKey::try_new([1; 32]).unwrap();
|
||||
let from = AccountId::from(&PublicKey::new_from_private_key(&key));
|
||||
let to = AccountId::new([2; 32]);
|
||||
let initial_balance = 100;
|
||||
let initial_balance = 1000;
|
||||
let initial_data = [(from, initial_balance), (to, 0)];
|
||||
let mut state =
|
||||
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
|
||||
let from_key = key;
|
||||
let amount: u128 = 0;
|
||||
let instruction: (u128, ProgramId, u32) =
|
||||
(amount, Program::authenticated_transfer_program().id(), 2);
|
||||
let amount: u128 = 37;
|
||||
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
|
||||
amount,
|
||||
Program::authenticated_transfer_program().id(),
|
||||
2,
|
||||
None,
|
||||
);
|
||||
|
||||
let expected_to_post = Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
@ -2139,10 +2187,11 @@ pub mod tests {
|
||||
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
|
||||
let from_key = key;
|
||||
let amount: u128 = 0;
|
||||
let instruction: (u128, ProgramId, u32) = (
|
||||
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
|
||||
amount,
|
||||
Program::authenticated_transfer_program().id(),
|
||||
MAX_NUMBER_CHAINED_CALLS as u32 + 1,
|
||||
None,
|
||||
);
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
@ -2162,4 +2211,274 @@ pub mod tests {
|
||||
Err(NssaError::MaxChainedCallsDepthExceeded)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execution_that_requires_authentication_of_a_program_derived_account_id_succeeds() {
|
||||
let chain_caller = Program::chain_caller();
|
||||
let pda_seed = PdaSeed::new([37; 32]);
|
||||
let from = AccountId::from((&chain_caller.id(), &pda_seed));
|
||||
let to = AccountId::new([2; 32]);
|
||||
let initial_balance = 1000;
|
||||
let initial_data = [(from, initial_balance), (to, 0)];
|
||||
let mut state =
|
||||
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
|
||||
let amount: u128 = 58;
|
||||
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
|
||||
amount,
|
||||
Program::authenticated_transfer_program().id(),
|
||||
1,
|
||||
Some(pda_seed),
|
||||
);
|
||||
|
||||
let expected_to_post = Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
balance: amount, // The `chain_caller` chains the program twice
|
||||
..Account::default()
|
||||
};
|
||||
let message = public_transaction::Message::try_new(
|
||||
chain_caller.id(),
|
||||
vec![to, from], // The chain_caller program permutes the account order in the chain
|
||||
// call
|
||||
vec![],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
let from_post = state.get_account_by_id(&from);
|
||||
let to_post = state.get_account_by_id(&to);
|
||||
assert_eq!(from_post.balance, initial_balance - amount);
|
||||
assert_eq!(to_post, expected_to_post);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_claiming_mechanism_within_chain_call() {
|
||||
// This test calls the authenticated transfer program through the chain_caller program.
|
||||
// The transfer is made from an initialized sender to an uninitialized recipient. And
|
||||
// it is expected that the recipient account is claimed by the authenticated transfer
|
||||
// program and not the chained_caller program.
|
||||
let chain_caller = Program::chain_caller();
|
||||
let auth_transfer = Program::authenticated_transfer_program();
|
||||
let key = PrivateKey::try_new([1; 32]).unwrap();
|
||||
let account_id = AccountId::from(&PublicKey::new_from_private_key(&key));
|
||||
let initial_balance = 100;
|
||||
let initial_data = [(account_id, initial_balance)];
|
||||
let mut state =
|
||||
V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
|
||||
let from = account_id;
|
||||
let from_key = key;
|
||||
let to = AccountId::new([2; 32]);
|
||||
let amount: u128 = 37;
|
||||
|
||||
// Check the recipient is an uninitialized account
|
||||
assert_eq!(state.get_account_by_id(&to), Account::default());
|
||||
|
||||
let expected_to_post = Account {
|
||||
// The expected program owner is the authenticated transfer program
|
||||
program_owner: auth_transfer.id(),
|
||||
balance: amount,
|
||||
..Account::default()
|
||||
};
|
||||
|
||||
// The transaction executes the chain_caller program, which internally calls the
|
||||
// authenticated_transfer program
|
||||
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
|
||||
amount,
|
||||
Program::authenticated_transfer_program().id(),
|
||||
1,
|
||||
None,
|
||||
);
|
||||
let message = public_transaction::Message::try_new(
|
||||
chain_caller.id(),
|
||||
vec![to, from], // The chain_caller program permutes the account order in the chain
|
||||
// call
|
||||
vec![0],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
let from_post = state.get_account_by_id(&from);
|
||||
let to_post = state.get_account_by_id(&to);
|
||||
assert_eq!(from_post.balance, initial_balance - amount);
|
||||
assert_eq!(to_post, expected_to_post);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pda_mechanism_with_pinata_token_program() {
|
||||
let pinata_token = Program::pinata_token();
|
||||
let token = Program::token();
|
||||
|
||||
let pinata_definition_id = AccountId::new([1; 32]);
|
||||
let pinata_token_definition_id = AccountId::new([2; 32]);
|
||||
// Total supply of pinata token will be in an account under a PDA.
|
||||
let pinata_token_holding_id = AccountId::from((&pinata_token.id(), &PdaSeed::new([0; 32])));
|
||||
let winner_token_holding_id = AccountId::new([3; 32]);
|
||||
|
||||
let mut expected_winner_account_data = [0; 49];
|
||||
expected_winner_account_data[0] = 1;
|
||||
expected_winner_account_data[1..33].copy_from_slice(pinata_token_definition_id.value());
|
||||
expected_winner_account_data[33..].copy_from_slice(&150u128.to_le_bytes());
|
||||
let expected_winner_token_holding_post = Account {
|
||||
program_owner: token.id(),
|
||||
data: expected_winner_account_data.to_vec().try_into().unwrap(),
|
||||
..Account::default()
|
||||
};
|
||||
|
||||
let mut state = V02State::new_with_genesis_accounts(&[], &[]);
|
||||
state.add_pinata_token_program(pinata_definition_id);
|
||||
|
||||
// Execution of the token program to create new token for the pinata token
|
||||
// definition and supply accounts
|
||||
let total_supply: u128 = 10_000_000;
|
||||
// instruction: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
|
||||
let mut instruction: [u8; 23] = [0; 23];
|
||||
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
|
||||
instruction[17..].copy_from_slice(b"PINATA");
|
||||
let message = public_transaction::Message::try_new(
|
||||
token.id(),
|
||||
vec![pinata_token_definition_id, pinata_token_holding_id],
|
||||
vec![],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
// Execution of the token program transfer just to initialize the winner token account
|
||||
let mut instruction: [u8; 23] = [0; 23];
|
||||
instruction[0] = 2;
|
||||
let message = public_transaction::Message::try_new(
|
||||
token.id(),
|
||||
vec![pinata_token_definition_id, winner_token_holding_id],
|
||||
vec![],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
// Submit a solution to the pinata program to claim the prize
|
||||
let solution: u128 = 989106;
|
||||
let message = public_transaction::Message::try_new(
|
||||
pinata_token.id(),
|
||||
vec![
|
||||
pinata_definition_id,
|
||||
pinata_token_holding_id,
|
||||
winner_token_holding_id,
|
||||
],
|
||||
vec![],
|
||||
solution,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
let winner_token_holding_post = state.get_account_by_id(&winner_token_holding_id);
|
||||
assert_eq!(
|
||||
winner_token_holding_post,
|
||||
expected_winner_token_holding_post
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_claiming_mechanism_cannot_claim_initialied_accounts() {
|
||||
let claimer = Program::claimer();
|
||||
let mut state = V02State::new_with_genesis_accounts(&[], &[]).with_test_programs();
|
||||
let account_id = AccountId::new([2; 32]);
|
||||
|
||||
// Insert an account with non-default program owner
|
||||
state.force_insert_account(
|
||||
account_id,
|
||||
Account {
|
||||
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
..Account::default()
|
||||
},
|
||||
);
|
||||
|
||||
let message =
|
||||
public_transaction::Message::try_new(claimer.id(), vec![account_id], vec![], ())
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
let result = state.transition_from_public_transaction(&tx);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)))
|
||||
}
|
||||
|
||||
/// This test ensures that even if a malicious program tries to perform overflow of balances
|
||||
/// it will not be able to break the balance validation.
|
||||
#[test]
|
||||
fn test_malicious_program_cannot_break_balance_validation() {
|
||||
let sender_key = PrivateKey::try_new([37; 32]).unwrap();
|
||||
let sender_id = AccountId::from(&PublicKey::new_from_private_key(&sender_key));
|
||||
let sender_init_balance: u128 = 10;
|
||||
|
||||
let recipient_key = PrivateKey::try_new([42; 32]).unwrap();
|
||||
let recipient_id = AccountId::from(&PublicKey::new_from_private_key(&recipient_key));
|
||||
let recipient_init_balance: u128 = 10;
|
||||
|
||||
let mut state = V02State::new_with_genesis_accounts(
|
||||
&[
|
||||
(sender_id, sender_init_balance),
|
||||
(recipient_id, recipient_init_balance),
|
||||
],
|
||||
&[],
|
||||
);
|
||||
|
||||
state.insert_program(Program::modified_transfer_program());
|
||||
|
||||
let balance_to_move: u128 = 4;
|
||||
|
||||
let sender =
|
||||
AccountWithMetadata::new(state.get_account_by_id(&sender_id.clone()), true, sender_id);
|
||||
|
||||
let sender_nonce = sender.account.nonce;
|
||||
|
||||
let _recipient =
|
||||
AccountWithMetadata::new(state.get_account_by_id(&recipient_id), false, sender_id);
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Program::modified_transfer_program().id(),
|
||||
vec![sender_id, recipient_id],
|
||||
vec![sender_nonce],
|
||||
balance_to_move,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
let res = state.transition_from_public_transaction(&tx);
|
||||
assert!(matches!(res, Err(NssaError::InvalidProgramBehavior)));
|
||||
|
||||
let sender_post = state.get_account_by_id(&sender_id);
|
||||
let recipient_post = state.get_account_by_id(&recipient_id);
|
||||
|
||||
let expected_sender_post = {
|
||||
let mut this = state.get_account_by_id(&sender_id);
|
||||
this.balance = sender_init_balance;
|
||||
this.nonce = 0;
|
||||
this
|
||||
};
|
||||
|
||||
let expected_recipient_post = {
|
||||
let mut this = state.get_account_by_id(&sender_id);
|
||||
this.balance = recipient_init_balance;
|
||||
this.nonce = 0;
|
||||
this
|
||||
};
|
||||
|
||||
assert!(expected_sender_post == sender_post);
|
||||
assert!(expected_recipient_post == recipient_post);
|
||||
}
|
||||
}
|
||||
|
||||
1
nssa/test_program_methods/guest/Cargo.lock
generated
1
nssa/test_program_methods/guest/Cargo.lock
generated
@ -1583,6 +1583,7 @@ dependencies = [
|
||||
"chacha20",
|
||||
"risc0-zkvm",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||
|
||||
type Instruction = u128;
|
||||
|
||||
@ -17,5 +17,5 @@ fn main() {
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.balance -= balance_to_burn;
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
}
|
||||
|
||||
@ -1,43 +1,54 @@
|
||||
use nssa_core::program::{
|
||||
ChainedCall, ProgramId, ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call,
|
||||
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs,
|
||||
write_nssa_outputs_with_chained_call,
|
||||
};
|
||||
use risc0_zkvm::serde::to_vec;
|
||||
|
||||
type Instruction = (u128, ProgramId, u32);
|
||||
type Instruction = (u128, ProgramId, u32, Option<PdaSeed>);
|
||||
|
||||
/// A program that calls another program `num_chain_calls` times.
|
||||
/// It permutes the order of the input accounts on the subsequent call
|
||||
/// The `ProgramId` in the instruction must be the program_id of the authenticated transfers program
|
||||
fn main() {
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: (balance, program_id, num_chain_calls),
|
||||
instruction: (balance, auth_transfer_id, num_chain_calls, pda_seed),
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [sender_pre, receiver_pre] = match pre_states.try_into() {
|
||||
let [recipient_pre, sender_pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let instruction_data = to_vec(&balance).unwrap();
|
||||
|
||||
let mut chained_call = vec![
|
||||
ChainedCall {
|
||||
program_id,
|
||||
instruction_data: instruction_data.clone(),
|
||||
pre_states: vec![receiver_pre.clone(), sender_pre.clone()], // <- Account order permutation here
|
||||
};
|
||||
num_chain_calls as usize - 1
|
||||
];
|
||||
let mut running_recipient_pre = recipient_pre.clone();
|
||||
let mut running_sender_pre = sender_pre.clone();
|
||||
|
||||
chained_call.push(ChainedCall {
|
||||
program_id,
|
||||
instruction_data,
|
||||
pre_states: vec![receiver_pre.clone(), sender_pre.clone()], // <- Account order permutation here
|
||||
});
|
||||
if pda_seed.is_some() {
|
||||
running_sender_pre.is_authorized = true;
|
||||
}
|
||||
|
||||
let mut chained_calls = Vec::new();
|
||||
for _i in 0..num_chain_calls {
|
||||
let new_chained_call = ChainedCall {
|
||||
program_id: auth_transfer_id,
|
||||
instruction_data: instruction_data.clone(),
|
||||
pre_states: vec![running_sender_pre.clone(), running_recipient_pre.clone()], // <- Account order permutation here
|
||||
pda_seeds: pda_seed.iter().cloned().collect(),
|
||||
};
|
||||
chained_calls.push(new_chained_call);
|
||||
|
||||
running_sender_pre.account.balance -= balance;
|
||||
running_recipient_pre.account.balance += balance;
|
||||
}
|
||||
|
||||
write_nssa_outputs_with_chained_call(
|
||||
vec![sender_pre.clone(), receiver_pre.clone()],
|
||||
vec![sender_pre.account, receiver_pre.account],
|
||||
chained_call,
|
||||
vec![sender_pre.clone(), recipient_pre.clone()],
|
||||
vec![
|
||||
AccountPostState::new(sender_pre.account),
|
||||
AccountPostState::new(recipient_pre.account),
|
||||
],
|
||||
chained_calls,
|
||||
);
|
||||
}
|
||||
|
||||
19
nssa/test_program_methods/guest/src/bin/claimer.rs
Normal file
19
nssa/test_program_methods/guest/src/bin/claimer.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||
|
||||
type Instruction = ();
|
||||
|
||||
fn main() {
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: _,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let account_post = AccountPostState::new_claimed(pre.account.clone());
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||
|
||||
type Instruction = ();
|
||||
type Instruction = Vec<u8>;
|
||||
|
||||
/// A program that modifies the account data by setting bytes sent in instruction.
|
||||
fn main() {
|
||||
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
|
||||
let ProgramInput { pre_states, instruction: data } = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -12,7 +13,7 @@ fn main() {
|
||||
|
||||
let account_pre = &pre.account;
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.data.push(0);
|
||||
account_post.data = data.try_into().expect("provided data should fit into data limit");
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use nssa_core::{
|
||||
account::Account,
|
||||
program::{read_nssa_inputs, write_nssa_outputs, ProgramInput},
|
||||
program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
||||
};
|
||||
|
||||
type Instruction = ();
|
||||
@ -15,5 +15,11 @@ fn main() {
|
||||
|
||||
let account_pre = pre.account.clone();
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![account_pre, Account::default()]);
|
||||
write_nssa_outputs(
|
||||
vec![pre],
|
||||
vec![
|
||||
AccountPostState::new(account_pre),
|
||||
AccountPostState::new(Account::default()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput};
|
||||
|
||||
type Instruction = ();
|
||||
|
||||
@ -14,5 +14,5 @@ fn main() {
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.balance += 1;
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||
|
||||
type Instruction = ();
|
||||
|
||||
@ -12,5 +12,5 @@ fn main() {
|
||||
|
||||
let account_pre1 = pre1.account.clone();
|
||||
|
||||
write_nssa_outputs(vec![pre1, pre2], vec![account_pre1]);
|
||||
write_nssa_outputs(vec![pre1, pre2], vec![AccountPostState::new(account_pre1)]);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput};
|
||||
|
||||
type Instruction = ();
|
||||
|
||||
@ -14,5 +14,5 @@ fn main() {
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.nonce += 1;
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput};
|
||||
|
||||
type Instruction = ();
|
||||
|
||||
@ -14,5 +14,5 @@ fn main() {
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7];
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput};
|
||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||
|
||||
type Instruction = u128;
|
||||
|
||||
@ -20,6 +20,9 @@ fn main() {
|
||||
|
||||
write_nssa_outputs(
|
||||
vec![sender_pre, receiver_pre],
|
||||
vec![sender_post, receiver_post],
|
||||
vec![
|
||||
AccountPostState::new(sender_post),
|
||||
AccountPostState::new(receiver_post),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -263,6 +263,7 @@ mod tests {
|
||||
seq_poll_max_retries: 10,
|
||||
seq_block_poll_max_amount: 100,
|
||||
initial_accounts: create_initial_accounts(),
|
||||
basic_auth: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -176,7 +176,12 @@ impl From<TokenDefinition> for TokedDefinitionAccountView {
|
||||
fn from(value: TokenDefinition) -> Self {
|
||||
Self {
|
||||
account_type: "Token definition".to_string(),
|
||||
name: hex::encode(value.name),
|
||||
name: {
|
||||
// Assuming, that name does not have UTF-8 NULL and all zeroes are padding.
|
||||
let name_trimmed: Vec<_> =
|
||||
value.name.into_iter().take_while(|ch| *ch != 0).collect();
|
||||
String::from_utf8(name_trimmed).unwrap_or(hex::encode(value.name))
|
||||
},
|
||||
total_supply: value.total_supply,
|
||||
}
|
||||
}
|
||||
@ -334,3 +339,47 @@ impl WalletSubcommand for AccountSubcommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::account::{TokedDefinitionAccountView, TokenDefinition};
|
||||
|
||||
#[test]
|
||||
fn test_invalid_utf_8_name_of_token() {
|
||||
let token_def = TokenDefinition {
|
||||
account_type: 1,
|
||||
name: [137, 12, 14, 3, 5, 4],
|
||||
total_supply: 100,
|
||||
};
|
||||
|
||||
let token_def_view: TokedDefinitionAccountView = token_def.into();
|
||||
|
||||
assert_eq!(token_def_view.name, "890c0e030504");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_utf_8_name_of_token_all_bytes() {
|
||||
let token_def = TokenDefinition {
|
||||
account_type: 1,
|
||||
name: [240, 159, 146, 150, 66, 66],
|
||||
total_supply: 100,
|
||||
};
|
||||
|
||||
let token_def_view: TokedDefinitionAccountView = token_def.into();
|
||||
|
||||
assert_eq!(token_def_view.name, "💖BB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_utf_8_name_of_token_less_bytes() {
|
||||
let token_def = TokenDefinition {
|
||||
account_type: 1,
|
||||
name: [78, 65, 77, 69, 0, 0],
|
||||
total_supply: 100,
|
||||
};
|
||||
|
||||
let token_def_view: TokedDefinitionAccountView = token_def.into();
|
||||
|
||||
assert_eq!(token_def_view.name, "NAME");
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,6 +73,13 @@ impl WalletSubcommand for ConfigSubcommand {
|
||||
"initial_accounts" => {
|
||||
println!("{:#?}", wallet_core.storage.wallet_config.initial_accounts);
|
||||
}
|
||||
"basic_auth" => {
|
||||
if let Some(basic_auth) = &wallet_core.storage.wallet_config.basic_auth {
|
||||
println!("{basic_auth}");
|
||||
} else {
|
||||
println!("Not set");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("Unknown field");
|
||||
}
|
||||
@ -99,6 +106,9 @@ impl WalletSubcommand for ConfigSubcommand {
|
||||
wallet_core.storage.wallet_config.seq_block_poll_max_amount =
|
||||
value.parse()?;
|
||||
}
|
||||
"basic_auth" => {
|
||||
wallet_core.storage.wallet_config.basic_auth = Some(value.parse()?);
|
||||
}
|
||||
"initial_accounts" => {
|
||||
anyhow::bail!("Setting this field from wallet is not supported");
|
||||
}
|
||||
@ -141,6 +151,9 @@ impl WalletSubcommand for ConfigSubcommand {
|
||||
"initial_accounts" => {
|
||||
println!("List of initial accounts' keys(both public and private)");
|
||||
}
|
||||
"basic_auth" => {
|
||||
println!("Basic authentication credentials for sequencer HTTP requests");
|
||||
}
|
||||
_ => {
|
||||
println!("Unknown field");
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ use crate::{
|
||||
token::TokenProgramAgnosticSubcommand,
|
||||
},
|
||||
},
|
||||
helperfunctions::fetch_config,
|
||||
helperfunctions::{fetch_config, merge_auth_config},
|
||||
};
|
||||
|
||||
pub mod account;
|
||||
@ -76,7 +76,7 @@ pub enum OverCommand {
|
||||
|
||||
/// To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
|
||||
///
|
||||
/// All account adresses must be valid 32 byte base58 strings.
|
||||
/// All account addresses must be valid 32 byte base58 strings.
|
||||
///
|
||||
/// All account account_ids must be provided as {privacy_prefix}/{account_id},
|
||||
/// where valid options for `privacy_prefix` is `Public` and `Private`
|
||||
@ -86,6 +86,9 @@ pub struct Args {
|
||||
/// Continious run flag
|
||||
#[arg(short, long)]
|
||||
pub continuous_run: bool,
|
||||
/// Basic authentication in the format `user` or `user:password`
|
||||
#[arg(long)]
|
||||
pub auth: Option<String>,
|
||||
/// Wallet command
|
||||
#[command(subcommand)]
|
||||
pub command: Option<OverCommand>,
|
||||
@ -101,7 +104,15 @@ pub enum SubcommandReturnValue {
|
||||
}
|
||||
|
||||
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
|
||||
execute_subcommand_with_auth(command, None).await
|
||||
}
|
||||
|
||||
pub async fn execute_subcommand_with_auth(
|
||||
command: Command,
|
||||
auth: Option<String>,
|
||||
) -> Result<SubcommandReturnValue> {
|
||||
let wallet_config = fetch_config().await?;
|
||||
let wallet_config = merge_auth_config(wallet_config, auth)?;
|
||||
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?;
|
||||
|
||||
let subcommand_ret = match command {
|
||||
@ -167,7 +178,11 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
|
||||
}
|
||||
|
||||
pub async fn execute_continuous_run() -> Result<()> {
|
||||
execute_continuous_run_with_auth(None).await
|
||||
}
|
||||
pub async fn execute_continuous_run_with_auth(auth: Option<String>) -> Result<()> {
|
||||
let config = fetch_config().await?;
|
||||
let config = merge_auth_config(config, auth)?;
|
||||
let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?;
|
||||
|
||||
loop {
|
||||
@ -186,7 +201,12 @@ pub async fn execute_continuous_run() -> Result<()> {
|
||||
}
|
||||
|
||||
pub async fn execute_setup(password: String) -> Result<()> {
|
||||
execute_setup_with_auth(password, None).await
|
||||
}
|
||||
|
||||
pub async fn execute_setup_with_auth(password: String, auth: Option<String>) -> Result<()> {
|
||||
let config = fetch_config().await?;
|
||||
let config = merge_auth_config(config, auth)?;
|
||||
let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?;
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
@ -195,7 +215,16 @@ pub async fn execute_setup(password: String) -> Result<()> {
|
||||
}
|
||||
|
||||
pub async fn execute_keys_restoration(password: String, depth: u32) -> Result<()> {
|
||||
execute_keys_restoration_with_auth(password, depth, None).await
|
||||
}
|
||||
|
||||
pub async fn execute_keys_restoration_with_auth(
|
||||
password: String,
|
||||
depth: u32,
|
||||
auth: Option<String>,
|
||||
) -> Result<()> {
|
||||
let config = fetch_config().await?;
|
||||
let config = merge_auth_config(config, auth)?;
|
||||
let mut wallet_core =
|
||||
WalletCore::start_from_config_new_storage(config.clone(), password.clone()).await?;
|
||||
|
||||
|
||||
@ -197,6 +197,7 @@ async fn find_solution(wallet: &WalletCore, pinata_account_id: nssa::AccountId)
|
||||
let account = wallet.get_account_public(pinata_account_id).await?;
|
||||
let data: [u8; 33] = account
|
||||
.data
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::Error::msg("invalid pinata account data"))?;
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use key_protocol::key_management::{
|
||||
KeyChain,
|
||||
key_tree::{
|
||||
@ -6,6 +8,49 @@ use key_protocol::key_management::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BasicAuth {
|
||||
pub username: String,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BasicAuth {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.username)?;
|
||||
if let Some(password) = &self.password {
|
||||
write!(f, ":{password}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BasicAuth {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parse = || {
|
||||
let mut parts = s.splitn(2, ':');
|
||||
let username = parts.next()?;
|
||||
let password = parts.next().filter(|p| !p.is_empty());
|
||||
if parts.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((username, password))
|
||||
};
|
||||
|
||||
let (username, password) = parse().ok_or_else(|| {
|
||||
anyhow::anyhow!("Invalid auth format. Expected 'user' or 'user:password'")
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
username: username.to_string(),
|
||||
password: password.map(|p| p.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InitialAccountDataPublic {
|
||||
pub account_id: String,
|
||||
@ -143,6 +188,8 @@ pub struct WalletConfig {
|
||||
pub seq_block_poll_max_amount: u64,
|
||||
/// Initial accounts for wallet
|
||||
pub initial_accounts: Vec<InitialAccountData>,
|
||||
/// Basic authentication credentials
|
||||
pub basic_auth: Option<BasicAuth>,
|
||||
}
|
||||
|
||||
impl Default for WalletConfig {
|
||||
@ -154,6 +201,7 @@ impl Default for WalletConfig {
|
||||
seq_tx_poll_max_blocks: 5,
|
||||
seq_poll_max_retries: 5,
|
||||
seq_block_poll_max_amount: 100,
|
||||
basic_auth: None,
|
||||
initial_accounts: {
|
||||
let init_acc_json = r#"
|
||||
[
|
||||
|
||||
@ -12,7 +12,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use crate::{
|
||||
HOME_DIR_ENV_VAR,
|
||||
config::{
|
||||
InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic,
|
||||
BasicAuth, InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic,
|
||||
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig,
|
||||
},
|
||||
};
|
||||
@ -89,6 +89,23 @@ pub async fn fetch_config() -> Result<WalletConfig> {
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Parse CLI auth string and merge with config auth, prioritizing CLI
|
||||
pub fn merge_auth_config(
|
||||
mut config: WalletConfig,
|
||||
cli_auth: Option<String>,
|
||||
) -> Result<WalletConfig> {
|
||||
if let Some(auth_str) = cli_auth {
|
||||
let cli_auth_config: BasicAuth = auth_str.parse()?;
|
||||
|
||||
if config.basic_auth.is_some() {
|
||||
println!("Warning: CLI auth argument takes precedence over config basic-auth");
|
||||
}
|
||||
|
||||
config.basic_auth = Some(cli_auth_config);
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Fetch data stored at home
|
||||
///
|
||||
/// File must be created through setup beforehand.
|
||||
|
||||
@ -47,7 +47,14 @@ pub struct WalletCore {
|
||||
|
||||
impl WalletCore {
|
||||
pub async fn start_from_config_update_chain(config: WalletConfig) -> Result<Self> {
|
||||
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
|
||||
let basic_auth = config
|
||||
.basic_auth
|
||||
.as_ref()
|
||||
.map(|auth| (auth.username.clone(), auth.password.clone()));
|
||||
let client = Arc::new(SequencerClient::new_with_auth(
|
||||
config.sequencer_addr.clone(),
|
||||
basic_auth,
|
||||
)?);
|
||||
let tx_poller = TxPoller::new(config.clone(), client.clone());
|
||||
|
||||
let PersistentStorage {
|
||||
@ -69,7 +76,14 @@ impl WalletCore {
|
||||
config: WalletConfig,
|
||||
password: String,
|
||||
) -> Result<Self> {
|
||||
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
|
||||
let basic_auth = config
|
||||
.basic_auth
|
||||
.as_ref()
|
||||
.map(|auth| (auth.username.clone(), auth.password.clone()));
|
||||
let client = Arc::new(SequencerClient::new_with_auth(
|
||||
config.sequencer_addr.clone(),
|
||||
basic_auth,
|
||||
)?);
|
||||
let tx_poller = TxPoller::new(config.clone(), client.clone());
|
||||
|
||||
let storage = WalletChainStore::new_storage(config, password)?;
|
||||
|
||||
@ -2,8 +2,8 @@ use anyhow::Result;
|
||||
use clap::{CommandFactory as _, Parser as _};
|
||||
use tokio::runtime::Builder;
|
||||
use wallet::cli::{
|
||||
Args, OverCommand, execute_continuous_run, execute_keys_restoration, execute_setup,
|
||||
execute_subcommand,
|
||||
Args, OverCommand, execute_continuous_run_with_auth, execute_keys_restoration_with_auth,
|
||||
execute_setup_with_auth, execute_subcommand_with_auth,
|
||||
};
|
||||
|
||||
pub const NUM_THREADS: usize = 2;
|
||||
@ -13,7 +13,6 @@ pub const NUM_THREADS: usize = 2;
|
||||
// file path?
|
||||
// TODO #172: Why it requires config as env var while sequencer_runner accepts as
|
||||
// argument?
|
||||
// TODO #171: Running pinata doesn't give output about transaction hash and etc.
|
||||
fn main() -> Result<()> {
|
||||
let runtime = Builder::new_multi_thread()
|
||||
.worker_threads(NUM_THREADS)
|
||||
@ -29,16 +28,18 @@ fn main() -> Result<()> {
|
||||
if let Some(over_command) = args.command {
|
||||
match over_command {
|
||||
OverCommand::Command(command) => {
|
||||
let _output = execute_subcommand(command).await?;
|
||||
let _output = execute_subcommand_with_auth(command, args.auth).await?;
|
||||
Ok(())
|
||||
}
|
||||
OverCommand::RestoreKeys { password, depth } => {
|
||||
execute_keys_restoration(password, depth).await
|
||||
execute_keys_restoration_with_auth(password, depth, args.auth).await
|
||||
}
|
||||
OverCommand::Setup { password } => {
|
||||
execute_setup_with_auth(password, args.auth).await
|
||||
}
|
||||
OverCommand::Setup { password } => execute_setup(password).await,
|
||||
}
|
||||
} else if args.continuous_run {
|
||||
execute_continuous_run().await
|
||||
execute_continuous_run_with_auth(args.auth).await
|
||||
} else {
|
||||
let help = Args::command().render_long_help();
|
||||
println!("{help}");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user