refactor!: Update dependencies and implement new required features across multiple modules

- Updated `nssa_core` and `spel-framework` dependencies to their respective release candidates in `Cargo.toml` and `Cargo.lock` files for `amm`, `ata`, and `token` modules.
- Enhanced the `new_definition` function in `amm/src/new_definition.rs` to include new claim logic and updated PDA seed calculations.
- Modified tests in `integration_tests/tests/amm.rs`, `integration_tests/tests/ata.rs`, and `integration_tests/tests/token.rs` to accommodate changes in transaction handling and account initialization.
- Refactored account initialization logic in `ata/src/create.rs` and `token/src/initialize.rs` to include authorization claims.
- Updated various functions in `token/src/mint.rs`, `token/src/new_definition.rs`, and `token/src/transfer.rs` to utilize the new claim system for account states.
- Adjusted the IDL generation tool to use the latest version of `spel-framework-core`.
This commit is contained in:
Ricardo Guilherme Schmidt 2026-04-15 14:55:04 -03:00 committed by r4bbit
parent 6c86b5b9ce
commit 471abef719
35 changed files with 829 additions and 175 deletions

18
Cargo.lock generated
View File

@ -598,6 +598,15 @@ dependencies = [
"inout",
]
[[package]]
name = "clock_core"
version = "0.1.0"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0-rc1#35d8df0d031315219f94d1546ceb862b0e5b208f"
dependencies = [
"borsh",
"nssa_core",
]
[[package]]
name = "cobs"
version = "0.3.0"
@ -1821,10 +1830,11 @@ checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e"
[[package]]
name = "nssa"
version = "0.1.0"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?rev=ffcbc15972adbf557939bf3e2852af276422631b#ffcbc15972adbf557939bf3e2852af276422631b"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0-rc1#35d8df0d031315219f94d1546ceb862b0e5b208f"
dependencies = [
"anyhow",
"borsh",
"clock_core",
"hex",
"k256",
"log",
@ -1842,7 +1852,7 @@ dependencies = [
[[package]]
name = "nssa_core"
version = "0.1.0"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?rev=ffcbc15972adbf557939bf3e2852af276422631b#ffcbc15972adbf557939bf3e2852af276422631b"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0-rc1#35d8df0d031315219f94d1546ceb862b0e5b208f"
dependencies = [
"base58",
"borsh",
@ -2962,8 +2972,8 @@ dependencies = [
[[package]]
name = "spel-framework-core"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"borsh",
"nssa_core",

View File

@ -20,8 +20,8 @@ exclude = [
resolver = "2"
[workspace.dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b", features = ["host"] }
nssa = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b", features = ["test-utils"] }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1", features = ["host"] }
nssa = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1", features = ["test-utils"] }
token_core = { path = "token/core" }
token_program = { path = "token" }
amm_core = { path = "amm/core" }
@ -33,4 +33,3 @@ borsh = { version = "1.0", features = ["derive"] }
risc0-zkvm = { version = "=3.0.5" }
serde_json = "1.0"
tokio = { version = "1.28.2", features = ["net", "rt-multi-thread", "sync", "macros"] }

View File

@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021"
[dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b", features = ["host"] }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1", features = ["host"] }
amm_core = { path = "core" }
token_core = { path = "../token/core" }

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b", features = ["host"] }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1", features = ["host"] }
token_core = { path = "../../token/core" }
borsh = { version = "1.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }

View File

@ -29,7 +29,7 @@ pub enum Instruction {
/// pool.account_id)`
/// - User Holding Account for Token A (authorized)
/// - User Holding Account for Token B (authorized)
/// - User Holding Account for Pool Liquidity
/// - User Holding Account for Pool Liquidity (authorized when uninitialized)
NewDefinition {
token_a_amount: u128,
token_b_amount: u128,

View File

@ -1778,7 +1778,7 @@ checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e"
[[package]]
name = "nssa_core"
version = "0.1.0"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?rev=ffcbc15972adbf557939bf3e2852af276422631b#ffcbc15972adbf557939bf3e2852af276422631b"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0-rc1#35d8df0d031315219f94d1546ceb862b0e5b208f"
dependencies = [
"base58",
"borsh",
@ -2897,8 +2897,8 @@ dependencies = [
[[package]]
name = "spel-framework"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"borsh",
"nssa_core",
@ -2908,8 +2908,8 @@ dependencies = [
[[package]]
name = "spel-framework-core"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"borsh",
"nssa_core",
@ -2921,8 +2921,8 @@ dependencies = [
[[package]]
name = "spel-framework-macros"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"proc-macro2",
"quote",

View File

@ -10,8 +10,8 @@ name = "amm"
path = "src/bin/amm.rs"
[dependencies]
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "57201f64b4542bb3f592bc9a0aa654c47aa908a6", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b" }
spel-framework = { git = "https://github.com/logos-co/spel.git", tag = "v0.2.0-rc.2", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1" }
risc0-zkvm = { version = "=3.0.5", default-features = false }
amm_core = { path = "../../core" }
amm_program = { path = "../..", package = "amm_program" }

View File

@ -16,6 +16,7 @@ mod amm {
use super::*;
/// Initializes a new Pool (or re-initializes an existing zero-supply Pool).
/// A fresh user LP holding must be explicitly authorized by the caller.
#[instruction]
pub fn new_definition(
pool: AccountWithMetadata,

View File

@ -2,12 +2,13 @@ use std::num::NonZeroU128;
use amm_core::{
assert_supported_fee_tier, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
compute_lp_lock_holding_pda, compute_pool_pda, compute_vault_pda, PoolDefinition,
compute_lp_lock_holding_pda, compute_lp_lock_holding_pda_seed, compute_pool_pda,
compute_pool_pda_seed, compute_vault_pda, compute_vault_pda_seed, PoolDefinition,
MINIMUM_LIQUIDITY,
};
use nssa_core::{
account::{Account, AccountWithMetadata, Data},
program::{AccountPostState, ChainedCall, ProgramId},
program::{AccountPostState, ChainedCall, Claim, ProgramId},
};
use token_core::TokenDefinition;
@ -78,6 +79,10 @@ pub fn new_definition(
Account::default(),
"Pool account must be uninitialized"
);
assert!(
user_holding_lp.account != Account::default() || user_holding_lp.is_authorized,
"Fresh user LP holding requires user authorization"
);
// LP Token minting calculation
let initial_lp = token_a_amount
@ -106,40 +111,63 @@ pub fn new_definition(
};
pool_post.data = Data::from(&pool_post_definition);
let pool_post: AccountPostState = AccountPostState::new_claimed(pool_post.clone());
let pool_post: AccountPostState = AccountPostState::new_claimed(
pool_post.clone(),
Claim::Pda(compute_pool_pda_seed(
definition_token_a_id,
definition_token_b_id,
)),
);
let token_program_id = user_holding_a.account.program_owner;
// Chain call for Token A (user_holding_a -> Vault_A)
let mut vault_a_authorized = vault_a.clone();
vault_a_authorized.is_authorized = true;
let call_token_a = ChainedCall::new(
token_program_id,
vec![user_holding_a.clone(), vault_a.clone()],
vec![user_holding_a.clone(), vault_a_authorized],
&token_core::Instruction::Transfer {
amount_to_transfer: token_a_amount.into(),
},
);
)
.with_pda_seeds(vec![compute_vault_pda_seed(
pool.account_id,
definition_token_a_id,
)]);
// Chain call for Token B (user_holding_b -> Vault_B)
let mut vault_b_authorized = vault_b.clone();
vault_b_authorized.is_authorized = true;
let call_token_b = ChainedCall::new(
token_program_id,
vec![user_holding_b.clone(), vault_b.clone()],
vec![user_holding_b.clone(), vault_b_authorized],
&token_core::Instruction::Transfer {
amount_to_transfer: token_b_amount.into(),
},
);
)
.with_pda_seeds(vec![compute_vault_pda_seed(
pool.account_id,
definition_token_b_id,
)]);
// Chain call for liquidity token lock holding
let mut pool_lp_auth = pool_definition_lp.clone();
pool_lp_auth.is_authorized = true;
let mut lp_lock_holding_auth = lp_lock_holding.clone();
lp_lock_holding_auth.is_authorized = true;
let call_token_lp_lock = ChainedCall::new(
token_program_id,
vec![pool_lp_auth.clone(), lp_lock_holding.clone()],
vec![pool_lp_auth.clone(), lp_lock_holding_auth],
&token_core::Instruction::NewFungibleDefinition {
name: String::from("LP Token"),
total_supply: MINIMUM_LIQUIDITY,
},
)
.with_pda_seeds(vec![compute_liquidity_token_pda_seed(pool.account_id)]);
.with_pda_seeds(vec![
compute_liquidity_token_pda_seed(pool.account_id),
compute_lp_lock_holding_pda_seed(pool.account_id),
]);
let mut pool_lp_after_lock = pool_lp_auth.clone();
pool_lp_after_lock.account.program_owner = token_program_id;

View File

@ -4,13 +4,13 @@ use std::num::NonZero;
use amm_core::{
compute_liquidity_token_pda, compute_liquidity_token_pda_seed, compute_lp_lock_holding_pda,
compute_pool_pda, compute_vault_pda, compute_vault_pda_seed, PoolDefinition,
FEE_BPS_DENOMINATOR, FEE_TIER_BPS_1, FEE_TIER_BPS_100, FEE_TIER_BPS_30, FEE_TIER_BPS_5,
MINIMUM_LIQUIDITY,
compute_lp_lock_holding_pda_seed, compute_pool_pda, compute_pool_pda_seed, compute_vault_pda,
compute_vault_pda_seed, PoolDefinition, FEE_BPS_DENOMINATOR, FEE_TIER_BPS_1, FEE_TIER_BPS_100,
FEE_TIER_BPS_30, FEE_TIER_BPS_5, MINIMUM_LIQUIDITY,
};
use nssa_core::{
account::{Account, AccountId, AccountWithMetadata, Data, Nonce},
program::{ChainedCall, ProgramId},
program::{ChainedCall, Claim, ProgramId},
};
use token_core::{TokenDefinition, TokenHolding};
@ -489,46 +489,57 @@ impl ChainedCallForTests {
}
fn cc_new_definition_token_a() -> ChainedCall {
let mut vault_a_auth = AccountWithMetadataForTests::vault_a_init();
vault_a_auth.is_authorized = true;
ChainedCall::new(
TOKEN_PROGRAM_ID,
vec![
AccountWithMetadataForTests::user_holding_a(),
AccountWithMetadataForTests::vault_a_init(),
],
vec![AccountWithMetadataForTests::user_holding_a(), vault_a_auth],
&token_core::Instruction::Transfer {
amount_to_transfer: BalanceForTests::vault_a_reserve_init(),
},
)
.with_pda_seeds(vec![compute_vault_pda_seed(
IdForTests::pool_definition_id(),
IdForTests::token_a_definition_id(),
)])
}
fn cc_new_definition_token_b() -> ChainedCall {
let mut vault_b_auth = AccountWithMetadataForTests::vault_b_init();
vault_b_auth.is_authorized = true;
ChainedCall::new(
TOKEN_PROGRAM_ID,
vec![
AccountWithMetadataForTests::user_holding_b(),
AccountWithMetadataForTests::vault_b_init(),
],
vec![AccountWithMetadataForTests::user_holding_b(), vault_b_auth],
&token_core::Instruction::Transfer {
amount_to_transfer: BalanceForTests::vault_b_reserve_init(),
},
)
.with_pda_seeds(vec![compute_vault_pda_seed(
IdForTests::pool_definition_id(),
IdForTests::token_b_definition_id(),
)])
}
fn cc_new_definition_token_lp_lock() -> ChainedCall {
let mut pool_lp_auth = AccountForTests::pool_lp_uninit();
pool_lp_auth.is_authorized = true;
let mut lp_lock_holding_auth = AccountForTests::lp_lock_holding_uninit();
lp_lock_holding_auth.is_authorized = true;
ChainedCall::new(
TOKEN_PROGRAM_ID,
vec![pool_lp_auth, AccountForTests::lp_lock_holding_uninit()],
vec![pool_lp_auth, lp_lock_holding_auth],
&token_core::Instruction::NewFungibleDefinition {
name: String::from("LP Token"),
total_supply: MINIMUM_LIQUIDITY,
},
)
.with_pda_seeds(vec![compute_liquidity_token_pda_seed(
IdForTests::pool_definition_id(),
)])
.with_pda_seeds(vec![
compute_liquidity_token_pda_seed(IdForTests::pool_definition_id()),
compute_lp_lock_holding_pda_seed(IdForTests::pool_definition_id()),
])
}
fn cc_new_definition_token_lp_user() -> ChainedCall {
@ -2068,6 +2079,13 @@ fn test_call_new_definition_chained_call_successful() {
let pool_post = post_states[0].clone();
assert!(AccountWithMetadataForTests::pool_definition_init().account == *pool_post.account());
assert_eq!(
pool_post.required_claim(),
Some(Claim::Pda(compute_pool_pda_seed(
IdForTests::token_a_definition_id(),
IdForTests::token_b_definition_id(),
)))
);
let chained_call_lp_lock = chained_calls[0].clone();
let chained_call_lp_user = chained_calls[1].clone();
@ -2752,20 +2770,20 @@ fn test_new_definition_lp_symmetric_amounts() {
let mut pool_lp_auth = AccountForTests::pool_lp_uninit();
pool_lp_auth.is_authorized = true;
let mut lp_lock_holding_auth = AccountForTests::lp_lock_holding_uninit();
lp_lock_holding_auth.is_authorized = true;
let expected_lp_lock_call = ChainedCall::new(
TOKEN_PROGRAM_ID,
vec![
pool_lp_auth.clone(),
AccountForTests::lp_lock_holding_uninit(),
],
vec![pool_lp_auth.clone(), lp_lock_holding_auth],
&token_core::Instruction::NewFungibleDefinition {
name: String::from("LP Token"),
total_supply: MINIMUM_LIQUIDITY,
},
)
.with_pda_seeds(vec![compute_liquidity_token_pda_seed(
IdForTests::pool_definition_id(),
)]);
.with_pda_seeds(vec![
compute_liquidity_token_pda_seed(IdForTests::pool_definition_id()),
compute_lp_lock_holding_pda_seed(IdForTests::pool_definition_id()),
]);
let expected_lp_user_call = ChainedCall::new(
TOKEN_PROGRAM_ID,
@ -2814,21 +2832,21 @@ fn test_minimum_liquidity_lock_and_remove_all_user_lp() {
let mut pool_lp_auth = AccountForTests::pool_lp_uninit();
pool_lp_auth.is_authorized = true;
let mut lp_lock_holding_auth = AccountForTests::lp_lock_holding_uninit();
lp_lock_holding_auth.is_authorized = true;
let expected_lock_call = ChainedCall::new(
TOKEN_PROGRAM_ID,
vec![
pool_lp_auth.clone(),
AccountForTests::lp_lock_holding_uninit(),
],
vec![pool_lp_auth.clone(), lp_lock_holding_auth],
&token_core::Instruction::NewFungibleDefinition {
name: String::from("LP Token"),
total_supply: MINIMUM_LIQUIDITY,
},
)
.with_pda_seeds(vec![compute_liquidity_token_pda_seed(
IdForTests::pool_definition_id(),
)]);
.with_pda_seeds(vec![
compute_liquidity_token_pda_seed(IdForTests::pool_definition_id()),
compute_lp_lock_holding_pda_seed(IdForTests::pool_definition_id()),
]);
let expected_user_call = ChainedCall::new(
TOKEN_PROGRAM_ID,
vec![

View File

@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021"
[dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b", features = ["host"] }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1", features = ["host"] }
ata_core = { path = "core" }
token_core = { path = "../token/core" }

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b", features = ["host"] }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1", features = ["host"] }
borsh = { version = "1.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
risc0-zkvm = { version = "=3.0.5", default-features = false }

View File

@ -18,13 +18,13 @@ pub enum Instruction {
/// `token_program_id` is derived from `token_definition.account.program_owner`.
Create { ata_program_id: ProgramId },
/// Transfer tokens FROM owner's ATA to a recipient holding account.
/// Uses PDA seeds to authorize the ATA in the chained Token::Transfer call.
/// Transfer tokens FROM owner's ATA to a recipient token holding account.
/// Uses ATA PDA seeds to authorize the chained Token::Transfer call.
///
/// Required accounts (3):
/// - Owner account (authorized)
/// - Sender ATA (owner's token holding)
/// - Recipient token holding (any account; auto-created if default)
/// - Recipient token holding (must be initialized)
///
/// `token_program_id` is derived from `sender_ata.account.program_owner`.
Transfer {

View File

@ -1777,7 +1777,7 @@ checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e"
[[package]]
name = "nssa_core"
version = "0.1.0"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?rev=ffcbc15972adbf557939bf3e2852af276422631b#ffcbc15972adbf557939bf3e2852af276422631b"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0-rc1#35d8df0d031315219f94d1546ceb862b0e5b208f"
dependencies = [
"base58",
"borsh",
@ -2896,8 +2896,8 @@ dependencies = [
[[package]]
name = "spel-framework"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"borsh",
"nssa_core",
@ -2907,8 +2907,8 @@ dependencies = [
[[package]]
name = "spel-framework-core"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"borsh",
"nssa_core",
@ -2920,8 +2920,8 @@ dependencies = [
[[package]]
name = "spel-framework-macros"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"proc-macro2",
"quote",

View File

@ -10,8 +10,8 @@ name = "ata"
path = "src/bin/ata.rs"
[dependencies]
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "57201f64b4542bb3f592bc9a0aa654c47aa908a6", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b" }
spel-framework = { git = "https://github.com/logos-co/spel.git", tag = "v0.2.0-rc.2", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1" }
risc0-zkvm = { version = "=3.0.5", default-features = false }
ata_core = { path = "../../core" }
ata_program = { path = "../..", package = "ata_program" }

View File

@ -28,7 +28,8 @@ mod ata {
Ok(SpelOutput::with_chained_calls(post_states, chained_calls))
}
/// Transfer tokens FROM owner's ATA to a recipient holding account.
/// Transfer tokens FROM owner's ATA to a recipient token holding account.
/// The recipient holding account must already be initialized.
#[instruction]
pub fn transfer(
owner: AccountWithMetadata,

View File

@ -1,6 +1,6 @@
use nssa_core::{
account::{Account, AccountWithMetadata},
program::{AccountPostState, ChainedCall, ProgramId},
program::{AccountPostState, ChainedCall, Claim, ProgramId},
};
pub fn create_associated_token_account(
@ -9,9 +9,12 @@ pub fn create_associated_token_account(
ata_account: AccountWithMetadata,
ata_program_id: ProgramId,
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
// No authorization check needed: create is idempotent, so anyone can call it safely.
// No explicit owner authorization check is needed here: ATA creation is idempotent, so the
// call itself may proceed without `owner.is_authorized`. If the owner account is still
// default, the returned post-state will still carry `Claim::Authorized` so the runtime can
// claim that owner account when needed.
let token_program_id = token_definition.account.program_owner;
ata_core::verify_ata_and_get_seed(
let seed = ata_core::verify_ata_and_get_seed(
&ata_account,
&owner,
token_definition.account_id,
@ -22,7 +25,7 @@ pub fn create_associated_token_account(
if ata_account.account != Account::default() {
return (
vec![
AccountPostState::new_claimed_if_default(owner.account.clone()),
AccountPostState::new_claimed_if_default(owner.account.clone(), Claim::Authorized),
AccountPostState::new(token_definition.account.clone()),
AccountPostState::new(ata_account.account.clone()),
],
@ -31,14 +34,17 @@ pub fn create_associated_token_account(
}
let post_states = vec![
AccountPostState::new_claimed_if_default(owner.account.clone()),
AccountPostState::new_claimed_if_default(owner.account.clone(), Claim::Authorized),
AccountPostState::new(token_definition.account.clone()),
AccountPostState::new(ata_account.account.clone()),
];
let mut ata_account_auth = ata_account.clone();
ata_account_auth.is_authorized = true;
let chained_call = ChainedCall::new(
token_program_id,
vec![token_definition.clone(), ata_account.clone()],
vec![token_definition.clone(), ata_account_auth],
&token_core::Instruction::InitializeAccount,
);
)
.with_pda_seeds(vec![seed]);
(post_states, vec![chained_call])
}

View File

@ -1,5 +1,8 @@
use ata_core::{compute_ata_seed, get_associated_token_account_id};
use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data};
use nssa_core::{
account::{Account, AccountId, AccountWithMetadata, Data},
program::{ChainedCall, Claim},
};
use token_core::{TokenDefinition, TokenHolding};
const ATA_PROGRAM_ID: nssa_core::program::ProgramId = [1u32; 8];
@ -79,8 +82,18 @@ fn create_emits_chained_call_for_uninitialized_ata() {
);
assert_eq!(post_states.len(), 3);
assert_eq!(chained_calls.len(), 1);
assert_eq!(chained_calls[0].program_id, TOKEN_PROGRAM_ID);
assert_eq!(post_states[0].required_claim(), Some(Claim::Authorized));
let mut authorized_ata = uninitialized_ata_account();
authorized_ata.is_authorized = true;
let expected_call = ChainedCall::new(
TOKEN_PROGRAM_ID,
vec![definition_account(), authorized_ata],
&token_core::Instruction::InitializeAccount,
)
.with_pda_seeds(vec![compute_ata_seed(owner_id(), definition_id())]);
assert_eq!(chained_calls, vec![expected_call]);
}
#[test]

View File

@ -16,7 +16,7 @@ pub fn transfer_from_associated_token_account(
let definition_id = TokenHolding::try_from(&sender_ata.account.data)
.expect("Sender ATA must hold a valid token")
.definition_id();
let seed =
let sender_seed =
ata_core::verify_ata_and_get_seed(&sender_ata, &owner, definition_id, ata_program_id);
let post_states = vec![
@ -29,11 +29,11 @@ pub fn transfer_from_associated_token_account(
let chained_call = ChainedCall::new(
token_program_id,
vec![sender_ata_auth, recipient.clone()],
vec![sender_ata_auth, recipient],
&token_core::Instruction::Transfer {
amount_to_transfer: amount,
},
)
.with_pda_seeds(vec![seed]);
.with_pda_seeds(vec![sender_seed]);
(post_states, vec![chained_call])
}

View File

@ -809,6 +809,18 @@ impl Accounts {
}
fn user_lp_holding_new_init() -> Account {
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenHolding::Fungible {
definition_id: Ids::token_lp_definition(),
balance: Balances::lp_user_init(),
}),
nonce: Nonce(1),
}
}
fn user_lp_holding_new_init_precreated() -> Account {
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
@ -895,7 +907,7 @@ fn deploy_programs(state: &mut V03State) {
}
fn state_for_amm_tests() -> V03State {
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
deploy_programs(&mut state);
state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_init());
state.force_insert_account(
@ -919,7 +931,7 @@ fn state_for_amm_tests() -> V03State {
}
fn state_for_amm_tests_with_new_def() -> V03State {
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
deploy_programs(&mut state);
state.force_insert_account(
Ids::token_a_definition(),
@ -938,7 +950,17 @@ fn current_nonce(state: &V03State, account_id: AccountId) -> Nonce {
state.get_account_by_id(account_id).nonce
}
fn try_execute_new_definition(state: &mut V03State, fees: u128) -> Result<(), NssaError> {
fn state_for_amm_tests_with_precreated_user_lp_for_new_def() -> V03State {
let mut state = state_for_amm_tests_with_new_def();
state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_init_zero());
state
}
fn try_execute_new_definition(
state: &mut V03State,
fees: u128,
authorize_user_lp: bool,
) -> Result<(), NssaError> {
let instruction = amm_core::Instruction::NewDefinition {
token_a_amount: Balances::vault_a_init(),
token_b_amount: Balances::vault_b_init(),
@ -958,23 +980,37 @@ fn try_execute_new_definition(state: &mut V03State, fees: u128) -> Result<(), Ns
Ids::user_b(),
Ids::user_lp(),
],
vec![
current_nonce(state, Ids::user_a()),
current_nonce(state, Ids::user_b()),
],
if authorize_user_lp {
vec![
current_nonce(state, Ids::user_a()),
current_nonce(state, Ids::user_b()),
current_nonce(state, Ids::user_lp()),
]
} else {
vec![
current_nonce(state, Ids::user_a()),
current_nonce(state, Ids::user_b()),
]
},
instruction,
)
.unwrap();
let witness_set =
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
let witness_set = if authorize_user_lp {
public_transaction::WitnessSet::for_message(
&message,
&[&Keys::user_a(), &Keys::user_b(), &Keys::user_lp()],
)
} else {
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()])
};
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0)
state.transition_from_public_transaction(&tx, 0, 0)
}
fn execute_new_definition(state: &mut V03State, fees: u128) {
try_execute_new_definition(state, fees).unwrap();
try_execute_new_definition(state, fees, true).unwrap();
}
fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_out: u128) {
@ -1001,7 +1037,7 @@ fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_ou
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
}
fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_out: u128) {
@ -1028,7 +1064,7 @@ fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_ou
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
}
fn execute_add_liquidity(
@ -1066,7 +1102,7 @@ fn execute_add_liquidity(
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
}
fn execute_remove_liquidity(
@ -1100,7 +1136,7 @@ fn execute_remove_liquidity(
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_lp()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
}
fn fungible_balance(account: &Account) -> u128 {
@ -1163,7 +1199,7 @@ fn amm_remove_liquidity() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_lp()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::pool_definition()),
@ -1225,7 +1261,7 @@ fn amm_remove_liquidity_insufficient_user_lp_fails() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_lp()]);
let tx = PublicTransaction::new(message, witness_set);
assert!(state.transition_from_public_transaction(&tx, 0).is_err());
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
}
#[test]
@ -1270,6 +1306,88 @@ fn amm_new_definition_uninitialized_pool() {
);
}
#[test]
fn amm_new_definition_without_user_lp_authorization_fails() {
let mut state = state_for_amm_tests_with_new_def();
state.force_insert_account(Ids::vault_a(), Accounts::vault_a_reinitializable());
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable());
let result = try_execute_new_definition(&mut state, Balances::fee_tier(), false);
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
assert_eq!(
state.get_account_by_id(Ids::pool_definition()),
Account::default()
);
assert_eq!(
state.get_account_by_id(Ids::vault_a()),
Accounts::vault_a_reinitializable()
);
assert_eq!(
state.get_account_by_id(Ids::vault_b()),
Accounts::vault_b_reinitializable()
);
assert_eq!(
state.get_account_by_id(Ids::token_lp_definition()),
Account::default()
);
assert_eq!(
state.get_account_by_id(Ids::lp_lock_holding()),
Account::default()
);
assert_eq!(
state.get_account_by_id(Ids::user_a()),
Accounts::user_a_holding()
);
assert_eq!(
state.get_account_by_id(Ids::user_b()),
Accounts::user_b_holding()
);
assert_eq!(state.get_account_by_id(Ids::user_lp()), Account::default());
}
#[test]
fn amm_new_definition_precreated_zero_balance_user_lp() {
let mut state = state_for_amm_tests_with_precreated_user_lp_for_new_def();
state.force_insert_account(Ids::vault_a(), Accounts::vault_a_reinitializable());
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable());
try_execute_new_definition(&mut state, Balances::fee_tier(), false).unwrap();
assert_eq!(
state.get_account_by_id(Ids::pool_definition()),
Accounts::pool_definition_new_init()
);
assert_eq!(
state.get_account_by_id(Ids::vault_a()),
Accounts::vault_a_init()
);
assert_eq!(
state.get_account_by_id(Ids::vault_b()),
Accounts::vault_b_init()
);
assert_eq!(
state.get_account_by_id(Ids::token_lp_definition()),
Accounts::token_lp_definition_new_init()
);
assert_eq!(
state.get_account_by_id(Ids::lp_lock_holding()),
Accounts::lp_lock_holding_new_init()
);
assert_eq!(
state.get_account_by_id(Ids::user_a()),
Accounts::user_a_holding_new_init()
);
assert_eq!(
state.get_account_by_id(Ids::user_b()),
Accounts::user_b_holding_new_init()
);
assert_eq!(
state.get_account_by_id(Ids::user_lp()),
Accounts::user_lp_holding_new_init_precreated()
);
}
#[test]
fn amm_new_definition_supports_all_fee_tiers() {
for fees in [
@ -1293,7 +1411,7 @@ fn amm_new_definition_supports_all_fee_tiers() {
#[test]
fn amm_new_definition_rejects_unsupported_fee_tier_transaction() {
let mut state = state_for_amm_tests_with_new_def();
let mut state = state_for_amm_tests_with_precreated_user_lp_for_new_def();
state.force_insert_account(Ids::vault_a(), Accounts::vault_a_reinitializable());
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable());
state.force_insert_account(
@ -1304,9 +1422,8 @@ fn amm_new_definition_rejects_unsupported_fee_tier_transaction() {
Ids::token_lp_definition(),
Accounts::token_lp_definition_reinitializable(),
);
state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_init_zero());
let result = try_execute_new_definition(&mut state, 2);
let result = try_execute_new_definition(&mut state, 2, false);
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
assert_eq!(
@ -1369,7 +1486,7 @@ fn amm_add_liquidity() {
public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::pool_definition()),
@ -1428,7 +1545,7 @@ fn amm_swap_b_to_a() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::pool_definition()),
@ -1479,7 +1596,7 @@ fn amm_swap_a_to_b() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::pool_definition()),

View File

@ -93,6 +93,18 @@ impl Accounts {
nonce: Nonce(0),
}
}
fn recipient_ata_init() -> Account {
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenHolding::Fungible {
definition_id: Ids::token_definition(),
balance: 0_u128,
}),
nonce: Nonce(0),
}
}
}
fn deploy_programs(state: &mut V03State) {
@ -113,16 +125,22 @@ fn deploy_programs(state: &mut V03State) {
}
fn state_for_ata_tests() -> V03State {
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
deploy_programs(&mut state);
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
state.force_insert_account(Ids::owner_ata(), Accounts::owner_ata_init());
state
}
fn state_for_ata_tests_with_precreated_recipient_ata() -> V03State {
let mut state = state_for_ata_tests();
state.force_insert_account(Ids::recipient_ata(), Accounts::recipient_ata_init());
state
}
#[test]
fn ata_create() {
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
deploy_programs(&mut state);
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
@ -141,7 +159,7 @@ fn ata_create() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::owner_ata()),
@ -176,7 +194,7 @@ fn ata_create_is_idempotent() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
// Already initialized — should remain unchanged
assert_eq!(
@ -195,7 +213,7 @@ fn ata_create_is_idempotent() {
#[test]
fn ata_transfer() {
let mut state = state_for_ata_tests();
let mut state = state_for_ata_tests_with_precreated_recipient_ata();
let instruction = ata_core::Instruction::Transfer {
ata_program_id: Ids::ata_program(),
@ -213,7 +231,7 @@ fn ata_transfer() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::owner_ata()),
@ -262,7 +280,7 @@ fn ata_burn() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::owner_ata()),
@ -294,7 +312,7 @@ fn ata_burn() {
#[test]
fn ata_create_from_private_owner() {
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
deploy_programs(&mut state);
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
@ -359,7 +377,7 @@ fn ata_create_from_private_owner() {
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
state
.transition_from_privacy_preserving_transaction(&tx, 0)
.transition_from_privacy_preserving_transaction(&tx, 0, 0)
.unwrap();
assert_eq!(

View File

@ -74,6 +74,18 @@ impl Accounts {
nonce: Nonce(0),
}
}
fn recipient_init() -> Account {
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenHolding::Fungible {
definition_id: Ids::token_definition(),
balance: 0_u128,
}),
nonce: Nonce(0),
}
}
}
fn deploy_token(state: &mut V03State) {
@ -85,7 +97,16 @@ fn deploy_token(state: &mut V03State) {
}
fn state_for_token_tests() -> V03State {
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
deploy_token(&mut state);
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
state.force_insert_account(Ids::holder(), Accounts::holder_init());
state.force_insert_account(Ids::recipient(), Accounts::recipient_init());
state
}
fn state_for_token_tests_without_recipient() -> V03State {
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
deploy_token(&mut state);
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
state.force_insert_account(Ids::holder(), Accounts::holder_init());
@ -94,7 +115,7 @@ fn state_for_token_tests() -> V03State {
#[test]
fn token_new_fungible_definition() {
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
let mut state = V03State::new_with_genesis_accounts(&[], &[], 0);
deploy_token(&mut state);
let instruction = token_core::Instruction::NewFungibleDefinition {
@ -116,7 +137,7 @@ fn token_new_fungible_definition() {
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::token_definition()),
@ -165,7 +186,7 @@ fn token_transfer() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::holder_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::holder()),
@ -194,6 +215,88 @@ fn token_transfer() {
);
}
#[test]
fn token_transfer_fresh_public_recipient_requires_authorization() {
let mut state = state_for_token_tests_without_recipient();
let instruction = token_core::Instruction::Transfer {
amount_to_transfer: 500_000_u128,
};
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::holder(), Ids::recipient()],
vec![Nonce(0)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::holder_key()]);
let tx = PublicTransaction::new(message, witness_set);
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
assert_eq!(
state.get_account_by_id(Ids::holder()),
Accounts::holder_init()
);
assert_eq!(
state.get_account_by_id(Ids::recipient()),
Account::default()
);
}
#[test]
fn token_transfer_fresh_authorized_public_recipient() {
let mut state = state_for_token_tests_without_recipient();
let instruction = token_core::Instruction::Transfer {
amount_to_transfer: 500_000_u128,
};
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::holder(), Ids::recipient()],
vec![Nonce(0), Nonce(0)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(
&message,
&[&Keys::holder_key(), &Keys::recipient_key()],
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::holder()),
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenHolding::Fungible {
definition_id: Ids::token_definition(),
balance: 500_000_u128,
}),
nonce: Nonce(1),
}
);
assert_eq!(
state.get_account_by_id(Ids::recipient()),
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenHolding::Fungible {
definition_id: Ids::token_definition(),
balance: 500_000_u128,
}),
nonce: Nonce(1),
}
);
}
#[test]
fn token_burn() {
let mut state = state_for_token_tests();
@ -213,7 +316,7 @@ fn token_burn() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::holder_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::token_definition()),
@ -262,7 +365,7 @@ fn token_mint() {
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0).unwrap();
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::token_definition()),
@ -292,6 +395,89 @@ fn token_mint() {
);
}
#[test]
fn token_mint_fresh_public_recipient_requires_authorization() {
let mut state = state_for_token_tests_without_recipient();
let instruction = token_core::Instruction::Mint {
amount_to_mint: 500_000_u128,
};
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition(), Ids::recipient()],
vec![Nonce(0)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]);
let tx = PublicTransaction::new(message, witness_set);
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
assert_eq!(
state.get_account_by_id(Ids::token_definition()),
Accounts::token_definition_init()
);
assert_eq!(
state.get_account_by_id(Ids::recipient()),
Account::default()
);
}
#[test]
fn token_mint_fresh_authorized_public_recipient() {
let mut state = state_for_token_tests_without_recipient();
let instruction = token_core::Instruction::Mint {
amount_to_mint: 500_000_u128,
};
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition(), Ids::recipient()],
vec![Nonce(0), Nonce(0)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(
&message,
&[&Keys::def_key(), &Keys::recipient_key()],
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
state.get_account_by_id(Ids::token_definition()),
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenDefinition::Fungible {
name: String::from("Gold"),
total_supply: 1_500_000_u128,
metadata_id: None,
}),
nonce: Nonce(1),
}
);
assert_eq!(
state.get_account_by_id(Ids::recipient()),
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenHolding::Fungible {
definition_id: Ids::token_definition(),
balance: 500_000_u128,
}),
nonce: Nonce(1),
}
);
}
struct PrivateKeys;
impl PrivateKeys {
@ -377,7 +563,7 @@ fn shielded_token_transfer(amount: u128, state: &mut V03State) -> Account {
let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::holder_key()]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
state
.transition_from_privacy_preserving_transaction(&tx, 0)
.transition_from_privacy_preserving_transaction(&tx, 0, 0)
.unwrap();
Account {
@ -476,7 +662,7 @@ fn token_private_transfer() {
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
state
.transition_from_privacy_preserving_transaction(&tx, 0)
.transition_from_privacy_preserving_transaction(&tx, 0, 0)
.unwrap();
let sender_nonce_after =
@ -559,7 +745,7 @@ fn token_deshielded_transfer() {
let witness_set = WitnessSet::for_message(&message, proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
state
.transition_from_privacy_preserving_transaction(&tx, 0)
.transition_from_privacy_preserving_transaction(&tx, 0, 0)
.unwrap();
assert_eq!(

View File

@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2021"
[dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b", features = ["host"] }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1", features = ["host"] }
token_core = { path = "core" }

View File

@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021"
[dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b", features = ["host"] }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1", features = ["host"] }
borsh = { version = "1.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }

View File

@ -10,23 +10,24 @@ pub enum Instruction {
/// Transfer tokens from sender to recipient.
///
/// Required accounts:
/// - Sender's Token Holding account (authorized),
/// - Recipient's Token Holding account.
/// - Sender's Token Holding account (initialized, authorized),
/// - Recipient's Token Holding account (initialized, or uninitialized with recipient
/// authorization in the same transaction).
Transfer { amount_to_transfer: u128 },
/// Create a new fungible token definition without metadata.
///
/// Required accounts:
/// - Token Definition account (uninitialized),
/// - Token Holding account (uninitialized).
/// - Token Definition account (uninitialized, authorized),
/// - Token Holding account (uninitialized, authorized).
NewFungibleDefinition { name: String, total_supply: u128 },
/// Create a new fungible or non-fungible token definition with metadata.
///
/// Required accounts:
/// - Token Definition account (uninitialized),
/// - Token Holding account (uninitialized),
/// - Token Metadata account (uninitialized).
/// - Token Definition account (uninitialized, authorized),
/// - Token Holding account (uninitialized, authorized),
/// - Token Metadata account (uninitialized, authorized).
NewDefinitionWithMetadata {
new_definition: NewTokenDefinition,
/// Boxed to avoid large enum variant size
@ -37,7 +38,7 @@ pub enum Instruction {
///
/// Required accounts:
/// - Token Definition account (initialized),
/// - Token Holding account (uninitialized),
/// - Token Holding account (uninitialized, authorized),
InitializeAccount,
/// Burn tokens from the holder's account.
@ -50,15 +51,16 @@ pub enum Instruction {
/// Mint new tokens to the holder's account.
///
/// Required accounts:
/// - Token Definition account (authorized),
/// - Token Holding account (uninitialized or initialized).
/// - Token Definition account (initialized, authorized),
/// - Token Holding account (initialized, or uninitialized with holder authorization in the
/// same transaction).
Mint { amount_to_mint: u128 },
/// Print a new NFT from the master copy.
///
/// Required accounts:
/// - NFT Master Token Holding account (authorized),
/// - NFT Printed Copy Token Holding account (uninitialized).
/// - NFT Printed Copy Token Holding account (uninitialized, authorized).
PrintNft,
}

View File

@ -1744,7 +1744,7 @@ checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e"
[[package]]
name = "nssa_core"
version = "0.1.0"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?rev=ffcbc15972adbf557939bf3e2852af276422631b#ffcbc15972adbf557939bf3e2852af276422631b"
source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0-rc1#35d8df0d031315219f94d1546ceb862b0e5b208f"
dependencies = [
"base58",
"borsh",
@ -2863,8 +2863,8 @@ dependencies = [
[[package]]
name = "spel-framework"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"borsh",
"nssa_core",
@ -2874,8 +2874,8 @@ dependencies = [
[[package]]
name = "spel-framework-core"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"borsh",
"nssa_core",
@ -2887,8 +2887,8 @@ dependencies = [
[[package]]
name = "spel-framework-macros"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?tag=v0.2.0-rc.2#9005e9fbbd78b0530412f9987273f753ed32eb2d"
dependencies = [
"proc-macro2",
"quote",

View File

@ -10,8 +10,8 @@ name = "token"
path = "src/bin/token.rs"
[dependencies]
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "57201f64b4542bb3f592bc9a0aa654c47aa908a6", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "ffcbc15972adbf557939bf3e2852af276422631b" }
spel-framework = { git = "https://github.com/logos-co/spel.git", tag = "v0.2.0-rc.2", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc1" }
risc0-zkvm = { version = "=3.0.5", default-features = false }
token_core = { path = "../../core" }
token_program = { path = "../..", package = "token_program" }

View File

@ -11,6 +11,7 @@ mod token {
use super::*;
/// Transfer tokens from sender to recipient.
/// Fresh public recipients must be explicitly authorized in the same transaction.
#[instruction]
pub fn transfer(
sender: AccountWithMetadata,
@ -25,6 +26,7 @@ mod token {
}
/// Create a new fungible token definition without metadata.
/// Definition and holding targets must be uninitialized and authorized.
#[instruction]
pub fn new_fungible_definition(
definition_target_account: AccountWithMetadata,
@ -43,6 +45,7 @@ mod token {
}
/// Create a new fungible or non-fungible token definition with metadata.
/// Definition, holding, and metadata targets must be uninitialized and authorized.
#[instruction]
pub fn new_definition_with_metadata(
definition_target_account: AccountWithMetadata,
@ -63,6 +66,7 @@ mod token {
}
/// Initialize a token holding account for a given token definition.
/// The holding target must be uninitialized and authorized.
#[instruction]
pub fn initialize_account(
definition_account: AccountWithMetadata,
@ -91,6 +95,7 @@ mod token {
}
/// Mint new tokens to the holder's account.
/// Fresh public holders must be explicitly authorized in the same transaction.
#[instruction]
pub fn mint(
definition_account: AccountWithMetadata,
@ -105,6 +110,7 @@ mod token {
}
/// Print a new NFT from the master copy.
/// The printed copy target must be uninitialized and authorized.
#[instruction]
pub fn print_nft(
master_account: AccountWithMetadata,

View File

@ -1,6 +1,6 @@
use nssa_core::{
account::{Account, AccountWithMetadata, Data},
program::AccountPostState,
program::{AccountPostState, Claim},
};
use token_core::{TokenDefinition, TokenHolding};
@ -13,6 +13,10 @@ pub fn initialize_account(
Account::default(),
"Only Uninitialized accounts can be initialized"
);
assert!(
account_to_initialize.is_authorized,
"Account to initialize must be authorized"
);
// 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
@ -29,6 +33,6 @@ pub fn initialize_account(
vec![
AccountPostState::new(definition_post),
AccountPostState::new_claimed(account_to_initialize),
AccountPostState::new_claimed(account_to_initialize, Claim::Authorized),
]
}

View File

@ -1,6 +1,6 @@
use nssa_core::{
account::{Account, AccountWithMetadata, Data},
program::AccountPostState,
program::{AccountPostState, Claim},
};
use token_core::{TokenDefinition, TokenHolding};
@ -66,6 +66,6 @@ pub fn mint(
vec![
AccountPostState::new(definition_post),
AccountPostState::new_claimed_if_default(holding_post),
AccountPostState::new_claimed_if_default(holding_post, Claim::Authorized),
]
}

View File

@ -1,6 +1,6 @@
use nssa_core::{
account::{Account, AccountWithMetadata, Data},
program::AccountPostState,
program::{AccountPostState, Claim},
};
use token_core::{
NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding, TokenMetadata,
@ -23,6 +23,14 @@ pub fn new_fungible_definition(
Account::default(),
"Holding target account must have default values"
);
assert!(
definition_target_account.is_authorized,
"Definition target account must be authorized"
);
assert!(
holding_target_account.is_authorized,
"Holding target account must be authorized"
);
let token_definition = TokenDefinition::Fungible {
name,
@ -41,8 +49,8 @@ pub fn new_fungible_definition(
holding_target_account_post.data = Data::from(&token_holding);
vec![
AccountPostState::new_claimed(definition_target_account_post),
AccountPostState::new_claimed(holding_target_account_post),
AccountPostState::new_claimed(definition_target_account_post, Claim::Authorized),
AccountPostState::new_claimed(holding_target_account_post, Claim::Authorized),
]
}
@ -70,6 +78,18 @@ pub fn new_definition_with_metadata(
Account::default(),
"Metadata target account must have default values"
);
assert!(
definition_target_account.is_authorized,
"Definition target account must be authorized"
);
assert!(
holding_target_account.is_authorized,
"Holding target account must be authorized"
);
assert!(
metadata_target_account.is_authorized,
"Metadata target account must be authorized"
);
let (token_definition, token_holding) = match new_definition {
NewTokenDefinition::Fungible { name, total_supply } => (
@ -117,8 +137,8 @@ pub fn new_definition_with_metadata(
metadata_target_account_post.data = Data::from(&token_metadata);
vec![
AccountPostState::new_claimed(definition_target_account_post),
AccountPostState::new_claimed(holding_target_account_post),
AccountPostState::new_claimed(metadata_target_account_post),
AccountPostState::new_claimed(definition_target_account_post, Claim::Authorized),
AccountPostState::new_claimed(holding_target_account_post, Claim::Authorized),
AccountPostState::new_claimed(metadata_target_account_post, Claim::Authorized),
]
}

View File

@ -1,6 +1,6 @@
use nssa_core::{
account::{Account, AccountWithMetadata, Data},
program::AccountPostState,
program::{AccountPostState, Claim},
};
use token_core::TokenHolding;
@ -18,6 +18,10 @@ pub fn print_nft(
Account::default(),
"Printed Account must be uninitialized"
);
assert!(
printed_account.is_authorized,
"Printed Account must be authorized"
);
let mut master_account_data =
TokenHolding::try_from(&master_account.account.data).expect("Invalid Token Holding data");
@ -49,6 +53,6 @@ pub fn print_nft(
vec![
AccountPostState::new(master_account_post),
AccountPostState::new_claimed(printed_account_post),
AccountPostState::new_claimed(printed_account_post, Claim::Authorized),
]
}

View File

@ -1,12 +1,16 @@
#![cfg(test)]
use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data, Nonce};
use nssa_core::{
account::{Account, AccountId, AccountWithMetadata, Data, Nonce},
program::Claim,
};
use token_core::{
MetadataStandard, NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding,
};
use crate::{
burn::burn,
initialize::initialize_account,
mint::mint,
new_definition::{new_definition_with_metadata, new_fungible_definition},
print_nft::print_nft,
@ -161,6 +165,14 @@ impl AccountForTests {
}
}
fn holding_account_uninit_auth() -> AccountWithMetadata {
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: IdForTests::holding_id_2(),
}
}
fn init_mint() -> AccountWithMetadata {
AccountWithMetadata {
account: Account {
@ -251,6 +263,22 @@ impl AccountForTests {
}
}
fn definition_account_uninit_auth() -> AccountWithMetadata {
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: IdForTests::pool_definition_id(),
}
}
fn metadata_account_uninit_auth() -> AccountWithMetadata {
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([19; 32]),
}
}
fn holding_account_init() -> AccountWithMetadata {
AccountWithMetadata {
account: Account {
@ -569,10 +597,36 @@ fn test_new_definition_non_default_second_account_should_fail() {
);
}
#[should_panic(expected = "Definition target account must be authorized")]
#[test]
fn test_new_definition_requires_authorized_definition_target() {
let definition_account = AccountForTests::definition_account_uninit();
let holding_account = AccountForTests::holding_account_uninit_auth();
let _post_states = new_fungible_definition(
definition_account,
holding_account,
String::from("test"),
10,
);
}
#[should_panic(expected = "Holding target account must be authorized")]
#[test]
fn test_new_definition_requires_authorized_holding_target() {
let definition_account = AccountForTests::definition_account_uninit_auth();
let holding_account = AccountForTests::holding_account_uninit();
let _post_states = new_fungible_definition(
definition_account,
holding_account,
String::from("test"),
10,
);
}
#[test]
fn test_new_definition_with_valid_inputs_succeeds() {
let definition_account = AccountForTests::definition_account_uninit();
let holding_account = AccountForTests::holding_account_uninit();
let definition_account = AccountForTests::definition_account_uninit_auth();
let holding_account = AccountForTests::holding_account_uninit_auth();
let post_states = new_fungible_definition(
definition_account,
@ -586,11 +640,13 @@ fn test_new_definition_with_valid_inputs_succeeds() {
*definition_account.account(),
AccountForTests::definition_account_unclaimed().account
);
assert_eq!(definition_account.required_claim(), Some(Claim::Authorized));
assert_eq!(
*holding_account.account(),
AccountForTests::holding_account_unclaimed().account
);
assert_eq!(holding_account.required_claim(), Some(Claim::Authorized));
}
#[should_panic(expected = "Sender and recipient definition id mismatch")]
@ -633,6 +689,8 @@ fn test_transfer_with_valid_inputs_succeeds() {
*recipient_post.account(),
AccountForTests::holding_account2_init_post_transfer().account
);
assert_eq!(sender_post.required_claim(), None);
assert_eq!(recipient_post.required_claim(), None);
}
#[should_panic(expected = "Invalid balance for NFT Master transfer")]
@ -669,9 +727,9 @@ fn test_transfer_with_master_nft_success() {
}
#[test]
fn test_token_initialize_account_succeeds() {
fn test_transfer_with_default_recipient_claims_recipient() {
let sender = AccountForTests::holding_account_init();
let recipient = AccountForTests::holding_account2_init();
let recipient = AccountForTests::holding_account_uninit();
let post_states = transfer(sender, recipient, BalanceForTests::transfer_amount());
let [sender_post, recipient_post] = post_states.try_into().unwrap();
@ -681,8 +739,53 @@ fn test_token_initialize_account_succeeds() {
);
assert_eq!(
*recipient_post.account(),
AccountForTests::holding_account2_init_post_transfer().account
Account {
program_owner: [0u32; 8],
balance: 0u128,
data: Data::from(&TokenHolding::Fungible {
definition_id: IdForTests::pool_definition_id(),
balance: BalanceForTests::transfer_amount(),
}),
nonce: Nonce(0),
}
);
assert_eq!(sender_post.required_claim(), None);
assert_eq!(recipient_post.required_claim(), Some(Claim::Authorized));
}
#[test]
fn test_token_initialize_account_succeeds() {
let definition_account = AccountForTests::definition_account_auth();
let holding_account = AccountForTests::holding_account_uninit_auth();
let post_states = initialize_account(definition_account, holding_account);
let [definition_post, holding_post] = post_states.try_into().unwrap();
assert_eq!(
*definition_post.account(),
AccountForTests::definition_account_auth().account
);
assert_eq!(
*holding_post.account(),
Account {
program_owner: [0u32; 8],
balance: 0u128,
data: Data::from(&TokenHolding::Fungible {
definition_id: IdForTests::pool_definition_id(),
balance: 0,
}),
nonce: Nonce(0),
}
);
assert_eq!(definition_post.required_claim(), None);
assert_eq!(holding_post.required_claim(), Some(Claim::Authorized));
}
#[test]
#[should_panic(expected = "Account to initialize must be authorized")]
fn test_token_initialize_account_requires_authorization() {
let definition_account = AccountForTests::definition_account_auth();
let holding_account = AccountForTests::holding_account_uninit();
let _post_states = initialize_account(definition_account, holding_account);
}
#[test]
@ -824,6 +927,8 @@ fn test_mint_success() {
*holding_post.account(),
AccountForTests::holding_account_same_definition_mint().account
);
assert_eq!(def_post.required_claim(), None);
assert_eq!(holding_post.required_claim(), None);
}
#[test]
@ -846,7 +951,8 @@ fn test_mint_uninit_holding_success() {
*holding_post.account(),
AccountForTests::init_mint().account
);
assert!(holding_post.requires_claim());
assert_eq!(def_post.required_claim(), None);
assert_eq!(holding_post.required_claim(), Some(Claim::Authorized));
}
#[test]
@ -885,6 +991,111 @@ fn test_mint_cannot_mint_unmintable_tokens() {
);
}
#[test]
fn test_new_definition_with_metadata_success() {
let definition_account = AccountForTests::definition_account_uninit_auth();
let holding_account = AccountForTests::holding_account_uninit_auth();
let metadata_account = AccountForTests::metadata_account_uninit_auth();
let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"),
total_supply: 15u128,
};
let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple,
uri: "test_uri".to_string(),
creators: "test_creators".to_string(),
};
let post_states = new_definition_with_metadata(
definition_account,
holding_account,
metadata_account,
new_definition,
metadata,
);
let [definition_post, holding_post, metadata_post] = post_states.try_into().unwrap();
assert_eq!(definition_post.required_claim(), Some(Claim::Authorized));
assert_eq!(holding_post.required_claim(), Some(Claim::Authorized));
assert_eq!(metadata_post.required_claim(), Some(Claim::Authorized));
}
#[should_panic(expected = "Definition target account must be authorized")]
#[test]
fn test_call_new_definition_metadata_requires_authorized_definition() {
let definition_account = AccountForTests::definition_account_uninit();
let holding_account = AccountForTests::holding_account_uninit_auth();
let metadata_account = AccountForTests::metadata_account_uninit_auth();
let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"),
total_supply: 15u128,
};
let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple,
uri: "test_uri".to_string(),
creators: "test_creators".to_string(),
};
let _post_states = new_definition_with_metadata(
definition_account,
holding_account,
metadata_account,
new_definition,
metadata,
);
}
#[should_panic(expected = "Holding target account must be authorized")]
#[test]
fn test_call_new_definition_metadata_requires_authorized_holding() {
let definition_account = AccountForTests::definition_account_uninit_auth();
let holding_account = AccountForTests::holding_account_uninit();
let metadata_account = AccountForTests::metadata_account_uninit_auth();
let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"),
total_supply: 15u128,
};
let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple,
uri: "test_uri".to_string(),
creators: "test_creators".to_string(),
};
let _post_states = new_definition_with_metadata(
definition_account,
holding_account,
metadata_account,
new_definition,
metadata,
);
}
#[should_panic(expected = "Metadata target account must be authorized")]
#[test]
fn test_call_new_definition_metadata_requires_authorized_metadata() {
let definition_account = AccountForTests::definition_account_uninit_auth();
let holding_account = AccountForTests::holding_account_uninit_auth();
let metadata_account = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
account_id: AccountId::new([20; 32]),
};
let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"),
total_supply: 15u128,
};
let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple,
uri: "test_uri".to_string(),
creators: "test_creators".to_string(),
};
let _post_states = new_definition_with_metadata(
definition_account,
holding_account,
metadata_account,
new_definition,
metadata,
);
}
#[should_panic(expected = "Definition target account must have default values")]
#[test]
fn test_call_new_definition_metadata_with_init_definition() {
@ -997,11 +1208,19 @@ fn test_print_nft_print_account_initialized() {
let _post_states = print_nft(master_account, printed_account);
}
#[should_panic(expected = "Printed Account must be authorized")]
#[test]
fn test_print_nft_print_account_must_be_authorized() {
let master_account = AccountForTests::holding_account_master_nft();
let printed_account = AccountForTests::holding_account_uninit();
let _post_states = print_nft(master_account, printed_account);
}
#[should_panic(expected = "Invalid Token Holding data")]
#[test]
fn test_print_nft_master_nft_invalid_token_holding() {
let master_account = AccountForTests::definition_account_auth();
let printed_account = AccountForTests::holding_account_uninit();
let printed_account = AccountForTests::holding_account_uninit_auth();
let _post_states = print_nft(master_account, printed_account);
}
@ -1009,7 +1228,7 @@ fn test_print_nft_master_nft_invalid_token_holding() {
#[test]
fn test_print_nft_master_nft_not_nft_master_account() {
let master_account = AccountForTests::holding_account_init();
let printed_account = AccountForTests::holding_account_uninit();
let printed_account = AccountForTests::holding_account_uninit_auth();
let _post_states = print_nft(master_account, printed_account);
}
@ -1017,14 +1236,14 @@ fn test_print_nft_master_nft_not_nft_master_account() {
#[test]
fn test_print_nft_master_nft_insufficient_balance() {
let master_account = AccountForTests::holding_account_master_nft_insufficient_balance();
let printed_account = AccountForTests::holding_account_uninit();
let printed_account = AccountForTests::holding_account_uninit_auth();
let _post_states = print_nft(master_account, printed_account);
}
#[test]
fn test_print_nft_success() {
let master_account = AccountForTests::holding_account_master_nft();
let printed_account = AccountForTests::holding_account_uninit();
let printed_account = AccountForTests::holding_account_uninit_auth();
let post_states = print_nft(master_account, printed_account);
let [post_master_nft, post_printed] = post_states.try_into().unwrap();
@ -1037,4 +1256,6 @@ fn test_print_nft_success() {
*post_printed.account(),
AccountForTests::holding_account_printed_nft().account
);
assert_eq!(post_master_nft.required_claim(), None);
assert_eq!(post_printed.required_claim(), Some(Claim::Authorized));
}

View File

@ -1,6 +1,6 @@
use nssa_core::{
account::{Account, AccountWithMetadata, Data},
program::AccountPostState,
program::{AccountPostState, Claim},
};
use token_core::TokenHolding;
@ -105,6 +105,6 @@ pub fn transfer(
vec![
AccountPostState::new(sender_post),
AccountPostState::new_claimed_if_default(recipient_post),
AccountPostState::new_claimed_if_default(recipient_post, Claim::Authorized),
]
}

View File

@ -8,7 +8,7 @@ name = "idl-gen"
path = "src/main.rs"
[dependencies]
spel-framework-core = { git = "https://github.com/logos-co/spel.git", rev = "57201f64b4542bb3f592bc9a0aa654c47aa908a6", features = [
spel-framework-core = { git = "https://github.com/logos-co/spel.git", tag = "v0.2.0-rc.2", features = [
"idl-gen",
] }
serde_json = "1.0"