feat(token): verify definition ownership via self_program_id in initialize and mint

Pass `ctx.self_program_id` from `ProgramContext` into `initialize_account`
and `mint`, which now assert that the token definition account is owned by
the token program. This prevents callers from supplying a foreign-owned
account as the definition.

See https://github.com/logos-co/spel/issues/172
This commit is contained in:
Ricardo Guilherme Schmidt 2026-05-04 10:34:10 -03:00 committed by r4bbit
parent ceb8a4b597
commit 8005c74e26
25 changed files with 239 additions and 84 deletions

4
Cargo.lock generated
View File

@ -2970,7 +2970,7 @@ dependencies = [
[[package]]
name = "spel-framework-core"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"borsh",
"nssa_core",
@ -2985,7 +2985,7 @@ dependencies = [
[[package]]
name = "spel-framework-macros"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"proc-macro2",
"quote",

View File

@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] }
spel-framework-macros = { git = "https://github.com/logos-co/spel.git", rev = "ba6e87d086ed85c5ac095325d8a28f02e3d33ca2", package = "spel-framework-macros" }
spel-framework-macros = { git = "https://github.com/logos-co/spel.git", rev = "6473ab4c400bc59bac8db83a286faaeafa7d1999", package = "spel-framework-macros" }
token_core = { path = "../../token/core" }
borsh = { version = "1.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }

View File

@ -26,7 +26,7 @@ pub enum Instruction {
/// - Vault Holding Account for Token A
/// - Vault Holding Account for Token B
/// - Pool Liquidity Token Definition
/// - LP Lock Holding Account, derived as `compute_lp_lock_holding_pda(amm_program_id,
/// - LP Lock Holding Account, derived as `compute_lp_lock_holding_pda(self_program_id,
/// pool.account_id)`
/// - User Holding Account for Token A (authorized)
/// - User Holding Account for Token B (authorized)
@ -35,7 +35,6 @@ pub enum Instruction {
token_a_amount: u128,
token_b_amount: u128,
fees: u128,
amm_program_id: ProgramId,
/// Unix timestamp (milliseconds) after which this transaction is invalid.
deadline: u64,
},

View File

@ -2892,7 +2892,7 @@ dependencies = [
[[package]]
name = "spel-framework"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"borsh",
"nssa_core",
@ -2904,7 +2904,7 @@ dependencies = [
[[package]]
name = "spel-framework-core"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"borsh",
"nssa_core",
@ -2919,7 +2919,7 @@ dependencies = [
[[package]]
name = "spel-framework-macros"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"proc-macro2",
"quote",

View File

@ -10,7 +10,7 @@ name = "amm"
path = "src/bin/amm.rs"
[dependencies]
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "ba6e87d086ed85c5ac095325d8a28f02e3d33ca2", package = "spel-framework" }
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "6473ab4c400bc59bac8db83a286faaeafa7d1999", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3" }
risc0-zkvm = { version = "=3.0.5", default-features = false }
amm_core = { path = "../../core" }

View File

@ -3,9 +3,9 @@
use std::num::NonZeroU128;
use spel_framework::prelude::*;
use spel_framework::context::ProgramContext;
use nssa_core::{
account::{AccountId, AccountWithMetadata},
program::ProgramId,
};
risc0_zkvm::guest::entry!(main);
@ -19,6 +19,7 @@ mod amm {
/// A fresh user LP holding must be explicitly authorized by the caller.
#[instruction]
pub fn new_definition(
ctx: ProgramContext,
pool: AccountWithMetadata,
vault_a: AccountWithMetadata,
vault_b: AccountWithMetadata,
@ -30,7 +31,6 @@ mod amm {
token_a_amount: u128,
token_b_amount: u128,
fees: u128,
amm_program_id: ProgramId,
deadline: u64,
) -> SpelResult {
let (post_states, chained_calls) = amm_program::new_definition::new_definition(
@ -45,7 +45,7 @@ mod amm {
NonZeroU128::new(token_a_amount).expect("token_a_amount must be nonzero"),
NonZeroU128::new(token_b_amount).expect("token_b_amount must be nonzero"),
fees,
amm_program_id,
ctx.self_program_id,
);
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls)
.with_timestamp_validity_window(..deadline))

View File

@ -67,10 +67,6 @@
"name": "fees",
"type": "u128"
},
{
"name": "amm_program_id",
"type": "program_id"
},
{
"name": "deadline",
"type": "u64"

View File

@ -24,12 +24,7 @@
"init": false
}
],
"args": [
{
"name": "ata_program_id",
"type": "program_id"
}
]
"args": []
},
{
"name": "transfer",
@ -54,10 +49,6 @@
}
],
"args": [
{
"name": "ata_program_id",
"type": "program_id"
},
{
"name": "amount",
"type": "u128"
@ -87,10 +78,6 @@
}
],
"args": [
{
"name": "ata_program_id",
"type": "program_id"
},
{
"name": "amount",
"type": "u128"

View File

@ -16,7 +16,7 @@ pub enum Instruction {
/// - Associated token account (default/uninitialized, or already initialized)
///
/// `token_program_id` is derived from `token_definition.account.program_owner`.
Create { ata_program_id: ProgramId },
Create,
/// Transfer tokens FROM owner's ATA to a recipient token holding account.
/// Uses ATA PDA seeds to authorize the chained Token::Transfer call.
@ -27,10 +27,7 @@ pub enum Instruction {
/// - Recipient token holding (must be initialized)
///
/// `token_program_id` is derived from `sender_ata.account.program_owner`.
Transfer {
ata_program_id: ProgramId,
amount: u128,
},
Transfer { amount: u128 },
/// Burn tokens FROM owner's ATA.
/// Uses PDA seeds to authorize the ATA in the chained Token::Burn call.
@ -41,10 +38,7 @@ pub enum Instruction {
/// - Token definition account
///
/// `token_program_id` is derived from `holder_ata.account.program_owner`.
Burn {
ata_program_id: ProgramId,
amount: u128,
},
Burn { amount: u128 },
}
pub fn compute_ata_seed(owner_id: AccountId, definition_id: AccountId) -> PdaSeed {

View File

@ -2890,7 +2890,7 @@ dependencies = [
[[package]]
name = "spel-framework"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"borsh",
"nssa_core",
@ -2902,7 +2902,7 @@ dependencies = [
[[package]]
name = "spel-framework-core"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"borsh",
"nssa_core",
@ -2917,7 +2917,7 @@ dependencies = [
[[package]]
name = "spel-framework-macros"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"proc-macro2",
"quote",

View File

@ -10,7 +10,7 @@ name = "ata"
path = "src/bin/ata.rs"
[dependencies]
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "ba6e87d086ed85c5ac095325d8a28f02e3d33ca2", package = "spel-framework" }
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "6473ab4c400bc59bac8db83a286faaeafa7d1999", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3" }
risc0-zkvm = { version = "=3.0.5", default-features = false }
ata_core = { path = "../../core" }

View File

@ -1,7 +1,8 @@
#![no_main]
use spel_framework::prelude::*;
use nssa_core::{account::AccountWithMetadata, program::ProgramId};
use spel_framework::context::ProgramContext;
use nssa_core::account::AccountWithMetadata;
risc0_zkvm::guest::entry!(main);
@ -14,16 +15,16 @@ mod ata {
/// Idempotent: no-op if the account already exists.
#[instruction]
pub fn create(
ctx: ProgramContext,
owner: AccountWithMetadata,
token_definition: AccountWithMetadata,
ata_account: AccountWithMetadata,
ata_program_id: ProgramId,
) -> SpelResult {
let (post_states, chained_calls) = ata_program::create::create_associated_token_account(
owner,
token_definition,
ata_account,
ata_program_id,
ctx.self_program_id,
);
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls))
}
@ -32,10 +33,10 @@ mod ata {
/// The recipient holding account must already be initialized.
#[instruction]
pub fn transfer(
ctx: ProgramContext,
owner: AccountWithMetadata,
sender_ata: AccountWithMetadata,
recipient: AccountWithMetadata,
ata_program_id: ProgramId,
amount: u128,
) -> SpelResult {
let (post_states, chained_calls) =
@ -43,7 +44,7 @@ mod ata {
owner,
sender_ata,
recipient,
ata_program_id,
ctx.self_program_id,
amount,
);
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls))
@ -52,10 +53,10 @@ mod ata {
/// Burn tokens FROM owner's ATA.
#[instruction]
pub fn burn(
ctx: ProgramContext,
owner: AccountWithMetadata,
holder_ata: AccountWithMetadata,
token_definition: AccountWithMetadata,
ata_program_id: ProgramId,
amount: u128,
) -> SpelResult {
let (post_states, chained_calls) =
@ -63,7 +64,7 @@ mod ata {
owner,
holder_ata,
token_definition,
ata_program_id,
ctx.self_program_id,
amount,
);
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls))

View File

@ -965,7 +965,6 @@ fn try_execute_new_definition(
token_a_amount: Balances::vault_a_init(),
token_b_amount: Balances::vault_b_init(),
fees,
amm_program_id: Ids::amm_program(),
deadline: u64::MAX,
};
@ -1844,7 +1843,6 @@ fn amm_new_definition_rejects_expired_deadline() {
token_a_amount: Balances::vault_a_init(),
token_b_amount: Balances::vault_b_init(),
fees: amm_core::FEE_TIER_BPS_30,
amm_program_id: Ids::amm_program(),
deadline: deadline_ms,
};

View File

@ -144,9 +144,7 @@ fn ata_create() {
deploy_programs(&mut state);
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
let instruction = ata_core::Instruction::Create {
ata_program_id: Ids::ata_program(),
};
let instruction = ata_core::Instruction::Create;
let message = public_transaction::Message::try_new(
Ids::ata_program(),
@ -179,9 +177,7 @@ fn ata_create() {
fn ata_create_is_idempotent() {
let mut state = state_for_ata_tests();
let instruction = ata_core::Instruction::Create {
ata_program_id: Ids::ata_program(),
};
let instruction = ata_core::Instruction::Create;
let message = public_transaction::Message::try_new(
Ids::ata_program(),
@ -216,7 +212,6 @@ fn ata_transfer() {
let mut state = state_for_ata_tests_with_precreated_recipient_ata();
let instruction = ata_core::Instruction::Transfer {
ata_program_id: Ids::ata_program(),
amount: 400_000_u128,
};
@ -265,7 +260,6 @@ fn ata_burn() {
let mut state = state_for_ata_tests();
let instruction = ata_core::Instruction::Burn {
ata_program_id: Ids::ata_program(),
amount: 300_000_u128,
};
@ -337,9 +331,7 @@ fn ata_create_from_private_owner() {
);
let ata_pre = AccountWithMetadata::new(Account::default(), false, owner_ata_id);
let instruction = ata_core::Instruction::Create {
ata_program_id: Ids::ata_program(),
};
let instruction = ata_core::Instruction::Create;
let instruction_data = Program::serialize_instruction(instruction).unwrap();
// Ephemeral key for encrypting the private owner's post-state

View File

@ -36,6 +36,10 @@ impl Ids {
token_methods::TOKEN_ID
}
fn foreign_token_program() -> nssa_core::program::ProgramId {
[0xfeed_u32; 8]
}
fn token_definition() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&Keys::def_key()))
}
@ -63,6 +67,19 @@ impl Accounts {
}
}
fn token_definition_foreign_owner() -> Account {
Account {
program_owner: Ids::foreign_token_program(),
balance: 0_u128,
data: Data::from(&TokenDefinition::Fungible {
name: String::from("Gold"),
total_supply: 1_000_000_u128,
metadata_id: None,
}),
nonce: Nonce(0),
}
}
fn holder_init() -> Account {
Account {
program_owner: Ids::token_program(),
@ -167,6 +184,78 @@ fn token_new_fungible_definition() {
);
}
#[test]
fn token_initialize_account_succeeds_for_canonical_definition() {
let mut state = state_for_token_tests_without_recipient();
let instruction = token_core::Instruction::InitializeAccount;
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::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()),
Accounts::token_definition_init()
);
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: 0_u128,
}),
nonce: Nonce(1),
}
);
}
#[test]
fn token_initialize_account_rejects_foreign_owned_definition() {
let mut state = state_for_token_tests_without_recipient();
state.force_insert_account(
Ids::token_definition(),
Accounts::token_definition_foreign_owner(),
);
let instruction = token_core::Instruction::InitializeAccount;
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::recipient_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_foreign_owner()
);
assert_eq!(
state.get_account_by_id(Ids::recipient()),
Account::default()
);
}
#[test]
fn token_transfer() {
let mut state = state_for_token_tests();
@ -395,6 +484,44 @@ fn token_mint() {
);
}
#[test]
fn token_mint_rejects_foreign_owned_definition() {
let mut state = state_for_token_tests_without_recipient();
state.force_insert_account(
Ids::token_definition(),
Accounts::token_definition_foreign_owner(),
);
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);
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
assert_eq!(
state.get_account_by_id(Ids::token_definition()),
Accounts::token_definition_foreign_owner()
);
assert_eq!(
state.get_account_by_id(Ids::recipient()),
Account::default()
);
}
#[test]
fn token_mint_fresh_public_recipient_requires_authorization() {
let mut state = state_for_token_tests_without_recipient();

View File

@ -2857,7 +2857,7 @@ dependencies = [
[[package]]
name = "spel-framework"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"borsh",
"nssa_core",
@ -2869,7 +2869,7 @@ dependencies = [
[[package]]
name = "spel-framework-core"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"borsh",
"nssa_core",
@ -2884,7 +2884,7 @@ dependencies = [
[[package]]
name = "spel-framework-macros"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"proc-macro2",
"quote",

View File

@ -10,7 +10,7 @@ name = "stablecoin"
path = "src/bin/stablecoin.rs"
[dependencies]
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "ba6e87d086ed85c5ac095325d8a28f02e3d33ca2", package = "spel-framework" }
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "6473ab4c400bc59bac8db83a286faaeafa7d1999", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3" }
risc0-zkvm = { version = "=3.0.5", default-features = false }
stablecoin_core = { path = "../../core" }

View File

@ -5,6 +5,6 @@ edition = "2021"
[dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] }
spel-framework-macros = { git = "https://github.com/logos-co/spel.git", rev = "ba6e87d086ed85c5ac095325d8a28f02e3d33ca2", package = "spel-framework-macros" }
spel-framework-macros = { git = "https://github.com/logos-co/spel.git", rev = "6473ab4c400bc59bac8db83a286faaeafa7d1999", package = "spel-framework-macros" }
borsh = { version = "1.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }

View File

@ -2857,7 +2857,7 @@ dependencies = [
[[package]]
name = "spel-framework"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"borsh",
"nssa_core",
@ -2869,7 +2869,7 @@ dependencies = [
[[package]]
name = "spel-framework-core"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"borsh",
"nssa_core",
@ -2884,7 +2884,7 @@ dependencies = [
[[package]]
name = "spel-framework-macros"
version = "0.2.0"
source = "git+https://github.com/logos-co/spel.git?rev=ba6e87d086ed85c5ac095325d8a28f02e3d33ca2#ba6e87d086ed85c5ac095325d8a28f02e3d33ca2"
source = "git+https://github.com/logos-co/spel.git?rev=6473ab4c400bc59bac8db83a286faaeafa7d1999#6473ab4c400bc59bac8db83a286faaeafa7d1999"
dependencies = [
"proc-macro2",
"quote",

View File

@ -10,7 +10,7 @@ name = "token"
path = "src/bin/token.rs"
[dependencies]
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "ba6e87d086ed85c5ac095325d8a28f02e3d33ca2", package = "spel-framework" }
spel-framework = { git = "https://github.com/logos-co/spel.git", rev = "6473ab4c400bc59bac8db83a286faaeafa7d1999", package = "spel-framework" }
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3" }
risc0-zkvm = { version = "=3.0.5", default-features = false }
token_core = { path = "../../core" }

View File

@ -1,6 +1,7 @@
#![no_main]
use spel_framework::prelude::*;
use spel_framework::context::ProgramContext;
use nssa_core::account::AccountWithMetadata;
risc0_zkvm::guest::entry!(main);
@ -71,6 +72,7 @@ mod token {
/// The holding target must be uninitialized and authorized.
#[instruction]
pub fn initialize_account(
ctx: ProgramContext,
definition_account: AccountWithMetadata,
account_to_initialize: AccountWithMetadata,
) -> SpelResult {
@ -78,6 +80,7 @@ mod token {
token_program::initialize::initialize_account(
definition_account,
account_to_initialize,
ctx.self_program_id,
),
vec![],
))
@ -101,6 +104,7 @@ mod token {
/// Fresh public holders must be explicitly authorized in the same transaction.
#[instruction]
pub fn mint(
ctx: ProgramContext,
definition_account: AccountWithMetadata,
user_holding_account: AccountWithMetadata,
amount_to_mint: u128,
@ -109,6 +113,7 @@ mod token {
definition_account,
user_holding_account,
amount_to_mint,
ctx.self_program_id,
), vec![]))
}

View File

@ -1,12 +1,13 @@
use nssa_core::{
account::{Account, AccountWithMetadata, Data},
program::{AccountPostState, Claim},
program::{AccountPostState, Claim, ProgramId},
};
use token_core::{TokenDefinition, TokenHolding};
pub fn initialize_account(
definition_account: AccountWithMetadata,
account_to_initialize: AccountWithMetadata,
token_program_id: ProgramId,
) -> Vec<AccountPostState> {
assert_eq!(
account_to_initialize.account,
@ -17,11 +18,11 @@ pub fn initialize_account(
account_to_initialize.is_authorized,
"Account to initialize must be authorized"
);
assert_eq!(
definition_account.account.program_owner, token_program_id,
"Token definition must be owned by 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
let definition = TokenDefinition::try_from(&definition_account.account.data)
.expect("Definition account must be valid");
let holding =

View File

@ -1,6 +1,6 @@
use nssa_core::{
account::{Account, AccountWithMetadata, Data},
program::{AccountPostState, Claim},
program::{AccountPostState, Claim, ProgramId},
};
use token_core::{TokenDefinition, TokenHolding};
@ -8,11 +8,16 @@ pub fn mint(
definition_account: AccountWithMetadata,
user_holding_account: AccountWithMetadata,
amount_to_mint: u128,
token_program_id: ProgramId,
) -> Vec<AccountPostState> {
assert!(
definition_account.is_authorized,
"Definition authorization is missing"
);
assert_eq!(
definition_account.account.program_owner, token_program_id,
"Token definition must be owned by token program"
);
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
.expect("Token Definition account must be valid");

View File

@ -2,7 +2,7 @@
use nssa_core::{
account::{Account, AccountId, AccountWithMetadata, Data, Nonce},
program::Claim,
program::{Claim, ProgramId},
};
use token_core::{
MetadataStandard, NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding,
@ -25,11 +25,31 @@ struct IdForTests;
struct AccountForTests;
const TOKEN_PROGRAM_ID: ProgramId = [5u32; 8];
const FOREIGN_TOKEN_PROGRAM_ID: ProgramId = [6u32; 8];
impl AccountForTests {
fn definition_account_auth() -> AccountWithMetadata {
AccountWithMetadata {
account: Account {
program_owner: [5u32; 8],
program_owner: TOKEN_PROGRAM_ID,
balance: 0u128,
data: Data::from(&TokenDefinition::Fungible {
name: String::from("test"),
total_supply: BalanceForTests::init_supply(),
metadata_id: None,
}),
nonce: Nonce(0),
},
is_authorized: true,
account_id: IdForTests::pool_definition_id(),
}
}
fn definition_account_foreign_owner() -> AccountWithMetadata {
AccountWithMetadata {
account: Account {
program_owner: FOREIGN_TOKEN_PROGRAM_ID,
balance: 0u128,
data: Data::from(&TokenDefinition::Fungible {
name: String::from("test"),
@ -46,7 +66,7 @@ impl AccountForTests {
fn definition_account_without_auth() -> AccountWithMetadata {
AccountWithMetadata {
account: Account {
program_owner: [5u32; 8],
program_owner: TOKEN_PROGRAM_ID,
balance: 0u128,
data: Data::from(&TokenDefinition::Fungible {
name: String::from("test"),
@ -757,7 +777,7 @@ fn test_transfer_with_default_recipient_claims_recipient() {
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 post_states = initialize_account(definition_account, holding_account, TOKEN_PROGRAM_ID);
let [definition_post, holding_post] = post_states.try_into().unwrap();
assert_eq!(
@ -785,7 +805,15 @@ fn test_token_initialize_account_succeeds() {
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);
let _post_states = initialize_account(definition_account, holding_account, TOKEN_PROGRAM_ID);
}
#[test]
#[should_panic(expected = "Token definition must be owned by token program")]
fn test_token_initialize_account_rejects_foreign_owned_definition() {
let definition_account = AccountForTests::definition_account_foreign_owner();
let holding_account = AccountForTests::holding_account_uninit_auth();
let _post_states = initialize_account(definition_account, holding_account, TOKEN_PROGRAM_ID);
}
#[test]
@ -868,6 +896,7 @@ fn test_mint_not_valid_holding_account() {
definition_account,
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
}
@ -880,6 +909,7 @@ fn test_mint_not_valid_definition_account() {
definition_account,
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
}
@ -892,6 +922,20 @@ fn test_mint_missing_authorization() {
definition_account,
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
}
#[test]
#[should_panic(expected = "Token definition must be owned by token program")]
fn test_mint_rejects_foreign_owned_definition() {
let definition_account = AccountForTests::definition_account_foreign_owner();
let holding_account = AccountForTests::holding_account_uninit();
let _post_states = mint(
definition_account,
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
}
@ -904,6 +948,7 @@ fn test_mint_mismatched_token_definition() {
definition_account,
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
}
@ -915,6 +960,7 @@ fn test_mint_success() {
definition_account,
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
let [def_post, holding_post] = post_states.try_into().unwrap();
@ -939,6 +985,7 @@ fn test_mint_uninit_holding_success() {
definition_account,
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
let [def_post, holding_post] = post_states.try_into().unwrap();
@ -964,6 +1011,7 @@ fn test_mint_total_supply_overflow() {
definition_account,
holding_account,
BalanceForTests::mint_overflow(),
TOKEN_PROGRAM_ID,
);
}
@ -976,6 +1024,7 @@ fn test_mint_holding_account_overflow() {
definition_account,
holding_account,
BalanceForTests::mint_overflow(),
TOKEN_PROGRAM_ID,
);
}
@ -988,6 +1037,7 @@ fn test_mint_cannot_mint_unmintable_tokens() {
definition_account,
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
}

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 = "ba6e87d086ed85c5ac095325d8a28f02e3d33ca2", features = [
spel-framework-core = { git = "https://github.com/logos-co/spel.git", rev = "6473ab4c400bc59bac8db83a286faaeafa7d1999", features = [
"idl-gen",
] }
serde_json = "1.0"