mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-03 05:29:50 +00:00
refactor(authority): embed Authority type in TokenDefinition; fix AMM LP minting
Addresses @0x-r4bbit's review:
- lez-authority now provides an Authority(Option<[u8;32]>) newtype and an
Ownable trait (require_owner / transfer_ownership / renounce_ownership);
programs embed the authority slot in their account type instead of calling
a wrapper. Replaces the old AuthoritySlot.
- TokenDefinition::Fungible embeds authority: Authority; TokenDefinition
implements Ownable.
- Fold mint authority into NewFungibleDefinition { mint_authority: Option<AccountId> };
remove the separate NewFungibleDefinitionWithAuthority instruction.
- mint/set_authority authorize against the definition account itself (its id
must match the stored authority and be authorized in the tx), restoring the
2-account mint shape and supporting PDA authorities.
- Fix AMM: the pool-definition PDA is now the LP token's mint authority, so the
AMM mints LP at creation and on add-liquidity (was permanently revoked).
- Instruction params use AccountId; remove LP-0013-specific comments.
- Regenerate token/amm/ata/stablecoin IDLs.
Tests: lez-authority 8, token unit 56, token/amm/stablecoin/ata integration all
green under RISC0_DEV_MODE=1; fmt + clippy clean.
This commit is contained in:
parent
c2a7d753d7
commit
83df2037ef
@ -666,6 +666,12 @@
|
||||
"type": {
|
||||
"option": "account_id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "authority",
|
||||
"type": {
|
||||
"defined": "Authority"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -120,6 +120,12 @@
|
||||
"type": {
|
||||
"option": "account_id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "authority",
|
||||
"type": {
|
||||
"defined": "Authority"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -160,6 +160,12 @@
|
||||
"type": {
|
||||
"option": "account_id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "authority",
|
||||
"type": {
|
||||
"defined": "Authority"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -49,6 +49,12 @@
|
||||
{
|
||||
"name": "total_supply",
|
||||
"type": "u128"
|
||||
},
|
||||
{
|
||||
"name": "mint_authority",
|
||||
"type": {
|
||||
"option": "account_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -139,12 +145,6 @@
|
||||
"signer": true,
|
||||
"init": false
|
||||
},
|
||||
{
|
||||
"name": "authority_account",
|
||||
"writable": false,
|
||||
"signer": false,
|
||||
"init": false
|
||||
},
|
||||
{
|
||||
"name": "user_holding_account",
|
||||
"writable": true,
|
||||
@ -159,42 +159,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "new_fungible_definition_with_authority",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "definition_target_account",
|
||||
"writable": false,
|
||||
"signer": false,
|
||||
"init": false
|
||||
},
|
||||
{
|
||||
"name": "holding_target_account",
|
||||
"writable": false,
|
||||
"signer": false,
|
||||
"init": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "initial_supply",
|
||||
"type": "u128"
|
||||
},
|
||||
{
|
||||
"name": "mint_authority",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
32
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "set_authority",
|
||||
"accounts": [
|
||||
@ -203,24 +167,13 @@
|
||||
"writable": false,
|
||||
"signer": false,
|
||||
"init": false
|
||||
},
|
||||
{
|
||||
"name": "authority_account",
|
||||
"writable": false,
|
||||
"signer": false,
|
||||
"init": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "new_authority",
|
||||
"type": {
|
||||
"option": {
|
||||
"array": [
|
||||
"u8",
|
||||
32
|
||||
]
|
||||
}
|
||||
"option": "account_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -268,14 +221,9 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mint_authority",
|
||||
"name": "authority",
|
||||
"type": {
|
||||
"option": {
|
||||
"array": [
|
||||
"u8",
|
||||
32
|
||||
]
|
||||
}
|
||||
"defined": "Authority"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -8,4 +8,5 @@ license = "MIT OR Apache-2.0"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
borsh = { workspace = true, features = ["derive"] }
|
||||
borsh = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@ -1,40 +1,64 @@
|
||||
//! Agnostic mint authority library for LEZ programs.
|
||||
//! Agnostic admin/mint authority library for LEZ programs.
|
||||
//! Implements the approval model defined in RFP-001.
|
||||
//! No dependency on any specific program or nssa_core.
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AuthorityError {
|
||||
/// The authority slot is empty (renounced); the resource is permanently fixed.
|
||||
Revoked,
|
||||
/// The signer does not match the current authority.
|
||||
Unauthorized,
|
||||
/// Attempted to act on an already-renounced authority.
|
||||
AlreadyRevoked,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AuthorityError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Revoked => write!(f, "mint authority has been revoked; supply is fixed"),
|
||||
Self::Unauthorized => write!(f, "signer is not the current mint authority"),
|
||||
Self::Revoked => write!(f, "authority has been revoked; resource is fixed"),
|
||||
Self::Unauthorized => write!(f, "signer is not the current authority"),
|
||||
Self::AlreadyRevoked => write!(f, "authority already revoked; cannot set again"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mint authority slot. None = permanently fixed supply.
|
||||
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AuthoritySlot(pub Option<[u8; 32]>);
|
||||
/// An ownership/authority slot. `None` = permanently renounced (no further changes
|
||||
/// or privileged actions are possible).
|
||||
#[derive(
|
||||
BorshSerialize, BorshDeserialize, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq,
|
||||
)]
|
||||
pub struct Authority(Option<[u8; 32]>);
|
||||
|
||||
impl AuthoritySlot {
|
||||
pub fn new(authority: [u8; 32]) -> Self {
|
||||
Self(Some(authority))
|
||||
impl Authority {
|
||||
/// Create an authority owned by `owner`.
|
||||
#[must_use]
|
||||
pub fn new(owner: [u8; 32]) -> Self {
|
||||
Self(Some(owner))
|
||||
}
|
||||
|
||||
pub fn fixed() -> Self {
|
||||
/// Create a permanently renounced authority (fixed resource).
|
||||
#[must_use]
|
||||
pub fn renounced() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
|
||||
pub fn check(&self, signer: [u8; 32]) -> Result<(), AuthorityError> {
|
||||
/// The current authority key, or `None` if renounced.
|
||||
#[must_use]
|
||||
pub fn authority(&self) -> Option<[u8; 32]> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns `true` if the authority has been permanently renounced.
|
||||
#[must_use]
|
||||
pub fn is_renounced(&self) -> bool {
|
||||
self.0.is_none()
|
||||
}
|
||||
|
||||
/// Require that `signer` is the current authority.
|
||||
pub fn require(&self, signer: [u8; 32]) -> Result<(), AuthorityError> {
|
||||
match self.0 {
|
||||
None => Err(AuthorityError::Revoked),
|
||||
Some(auth) if auth != signer => Err(AuthorityError::Unauthorized),
|
||||
@ -42,24 +66,50 @@ impl AuthoritySlot {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotate or revoke. Only mutates AFTER all checks pass.
|
||||
pub fn set(
|
||||
/// Rotate to a new authority, or renounce with `None`.
|
||||
/// Only mutates AFTER all checks pass (atomic).
|
||||
pub fn rotate(
|
||||
&mut self,
|
||||
signer: [u8; 32],
|
||||
new_authority: Option<[u8; 32]>,
|
||||
new: Option<[u8; 32]>,
|
||||
) -> Result<(), AuthorityError> {
|
||||
match self.0 {
|
||||
None => Err(AuthorityError::AlreadyRevoked),
|
||||
Some(auth) if auth != signer => Err(AuthorityError::Unauthorized),
|
||||
Some(_) => {
|
||||
self.0 = new_authority;
|
||||
self.0 = new;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_revoked(&self) -> bool {
|
||||
self.0.is_none()
|
||||
/// A type that carries an [`Authority`] slot and can be guarded by it.
|
||||
///
|
||||
/// Programs "inherit the owner slot" by embedding an [`Authority`] field in their
|
||||
/// account type and implementing this trait; the default methods then provide the
|
||||
/// standard require / transfer / renounce semantics.
|
||||
pub trait Ownable {
|
||||
fn authority(&self) -> &Authority;
|
||||
fn authority_mut(&mut self) -> &mut Authority;
|
||||
|
||||
/// Require that `signer` is the current owner.
|
||||
fn require_owner(&self, signer: [u8; 32]) -> Result<(), AuthorityError> {
|
||||
self.authority().require(signer)
|
||||
}
|
||||
|
||||
/// Transfer ownership to `new`, authorized by the current owner `signer`.
|
||||
fn transfer_ownership(
|
||||
&mut self,
|
||||
signer: [u8; 32],
|
||||
new: [u8; 32],
|
||||
) -> Result<(), AuthorityError> {
|
||||
self.authority_mut().rotate(signer, Some(new))
|
||||
}
|
||||
|
||||
/// Permanently renounce ownership, authorized by the current owner `signer`.
|
||||
fn renounce_ownership(&mut self, signer: [u8; 32]) -> Result<(), AuthorityError> {
|
||||
self.authority_mut().rotate(signer, None)
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,55 +121,90 @@ mod tests {
|
||||
const BOB: [u8; 32] = [2u8; 32];
|
||||
|
||||
#[test]
|
||||
fn check_succeeds_for_correct_signer() {
|
||||
assert!(AuthoritySlot::new(ALICE).check(ALICE).is_ok());
|
||||
fn require_succeeds_for_correct_owner() {
|
||||
assert!(Authority::new(ALICE).require(ALICE).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_fails_unauthorized() {
|
||||
fn require_fails_unauthorized() {
|
||||
assert_eq!(
|
||||
AuthoritySlot::new(ALICE).check(BOB),
|
||||
Authority::new(ALICE).require(BOB),
|
||||
Err(AuthorityError::Unauthorized)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_fails_when_revoked() {
|
||||
fn require_fails_when_renounced() {
|
||||
assert_eq!(
|
||||
AuthoritySlot::fixed().check(ALICE),
|
||||
Authority::renounced().require(ALICE),
|
||||
Err(AuthorityError::Revoked)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_rotates_authority() {
|
||||
let mut slot = AuthoritySlot::new(ALICE);
|
||||
slot.set(ALICE, Some(BOB)).unwrap();
|
||||
assert_eq!(slot.0, Some(BOB));
|
||||
assert_eq!(slot.check(ALICE), Err(AuthorityError::Unauthorized));
|
||||
fn rotate_transfers_authority() {
|
||||
let mut auth = Authority::new(ALICE);
|
||||
auth.rotate(ALICE, Some(BOB)).unwrap();
|
||||
assert_eq!(auth.authority(), Some(BOB));
|
||||
assert_eq!(auth.require(ALICE), Err(AuthorityError::Unauthorized));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_revokes_permanently() {
|
||||
let mut slot = AuthoritySlot::new(ALICE);
|
||||
slot.set(ALICE, None).unwrap();
|
||||
assert!(slot.is_revoked());
|
||||
fn rotate_renounces_permanently() {
|
||||
let mut auth = Authority::new(ALICE);
|
||||
auth.rotate(ALICE, None).unwrap();
|
||||
assert!(auth.is_renounced());
|
||||
assert_eq!(
|
||||
slot.set(ALICE, Some(ALICE)),
|
||||
auth.rotate(ALICE, Some(ALICE)),
|
||||
Err(AuthorityError::AlreadyRevoked)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_authority_cannot_rotate_and_state_unchanged() {
|
||||
let mut slot = AuthoritySlot::new(ALICE);
|
||||
assert_eq!(slot.set(BOB, Some(BOB)), Err(AuthorityError::Unauthorized));
|
||||
assert_eq!(slot.0, Some(ALICE)); // state unchanged
|
||||
fn wrong_owner_cannot_rotate_and_state_unchanged() {
|
||||
let mut auth = Authority::new(ALICE);
|
||||
assert_eq!(
|
||||
auth.rotate(BOB, Some(BOB)),
|
||||
Err(AuthorityError::Unauthorized)
|
||||
);
|
||||
assert_eq!(auth.authority(), Some(ALICE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_none_on_already_fixed_fails() {
|
||||
let mut slot = AuthoritySlot::fixed();
|
||||
assert_eq!(slot.set(ALICE, None), Err(AuthorityError::AlreadyRevoked));
|
||||
fn renounce_on_already_renounced_fails() {
|
||||
let mut auth = Authority::renounced();
|
||||
assert_eq!(
|
||||
auth.rotate(ALICE, None),
|
||||
Err(AuthorityError::AlreadyRevoked)
|
||||
);
|
||||
}
|
||||
|
||||
// Ownable trait via a tiny embedding type.
|
||||
struct Resource {
|
||||
owner: Authority,
|
||||
}
|
||||
impl Ownable for Resource {
|
||||
fn authority(&self) -> &Authority {
|
||||
&self.owner
|
||||
}
|
||||
|
||||
fn authority_mut(&mut self) -> &mut Authority {
|
||||
&mut self.owner
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ownable_require_transfer_renounce() {
|
||||
let mut r = Resource {
|
||||
owner: Authority::new(ALICE),
|
||||
};
|
||||
assert!(r.require_owner(ALICE).is_ok());
|
||||
assert_eq!(r.require_owner(BOB), Err(AuthorityError::Unauthorized));
|
||||
|
||||
r.transfer_ownership(ALICE, BOB).unwrap();
|
||||
assert!(r.require_owner(BOB).is_ok());
|
||||
|
||||
r.renounce_ownership(BOB).unwrap();
|
||||
assert!(r.authority().is_renounced());
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,3 +12,4 @@ clock_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.g
|
||||
amm_core = { path = "core" }
|
||||
token_core = { path = "../token/core" }
|
||||
twap_oracle_core = { path = "../twap_oracle/core" }
|
||||
lez-authority = { path = "../../lez-authority" }
|
||||
|
||||
@ -7,7 +7,9 @@ use amm_core::{
|
||||
compute_vault_pda_seed, isqrt_product, spot_price_q64_64, AmmConfig, PoolDefinition,
|
||||
MINIMUM_LIQUIDITY,
|
||||
};
|
||||
|
||||
use clock_core::CLOCK_01_PROGRAM_ACCOUNT_ID;
|
||||
use lez_authority::Authority;
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
program::{AccountPostState, ChainedCall, Claim, ProgramId},
|
||||
@ -193,6 +195,7 @@ pub fn new_definition(
|
||||
&token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: MINIMUM_LIQUIDITY,
|
||||
mint_authority: Some(pool_definition_lp.account_id),
|
||||
},
|
||||
)
|
||||
.with_pda_seeds(vec![
|
||||
@ -206,9 +209,14 @@ pub fn new_definition(
|
||||
name: String::from("LP Token"),
|
||||
total_supply: MINIMUM_LIQUIDITY,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: Authority::new(
|
||||
pool_definition_lp
|
||||
.account_id
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes"),
|
||||
),
|
||||
});
|
||||
|
||||
let call_token_lp_user = ChainedCall::new(
|
||||
token_program_id,
|
||||
vec![pool_lp_after_lock, user_holding_lp.clone()],
|
||||
|
||||
@ -538,10 +538,11 @@ impl ChainedCallForTests {
|
||||
|
||||
ChainedCall::new(
|
||||
TOKEN_PROGRAM_ID,
|
||||
vec![pool_lp_auth, lp_lock_holding_auth],
|
||||
vec![pool_lp_auth.clone(), lp_lock_holding_auth],
|
||||
&token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: MINIMUM_LIQUIDITY,
|
||||
mint_authority: Some(pool_lp_auth.account_id),
|
||||
},
|
||||
)
|
||||
.with_pda_seeds(vec![
|
||||
@ -872,7 +873,7 @@ impl AccountWithMetadataForTests {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::lp_supply_init(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -898,7 +899,7 @@ impl AccountWithMetadataForTests {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: MINIMUM_LIQUIDITY,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -916,7 +917,7 @@ impl AccountWithMetadataForTests {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::lp_supply_init(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -3266,6 +3267,7 @@ fn test_new_definition_lp_symmetric_amounts() {
|
||||
&token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: MINIMUM_LIQUIDITY,
|
||||
mint_authority: Some(pool_lp_auth.account_id),
|
||||
},
|
||||
)
|
||||
.with_pda_seeds(vec![
|
||||
@ -3368,6 +3370,7 @@ fn test_minimum_liquidity_lock_and_remove_all_user_lp() {
|
||||
&token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: MINIMUM_LIQUIDITY,
|
||||
mint_authority: Some(pool_lp_auth.account_id),
|
||||
},
|
||||
)
|
||||
.with_pda_seeds(vec![
|
||||
|
||||
@ -41,7 +41,7 @@ fn definition_account() -> AccountWithMetadata {
|
||||
name: "TEST".to_string(),
|
||||
total_supply: 1000,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: nssa_core::account::Nonce(0),
|
||||
},
|
||||
|
||||
@ -401,7 +401,7 @@ impl Accounts {
|
||||
name: String::from("test"),
|
||||
total_supply: Balances::token_a_supply(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -415,7 +415,7 @@ impl Accounts {
|
||||
name: String::from("test"),
|
||||
total_supply: Balances::token_b_supply(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -429,7 +429,12 @@ impl Accounts {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: Balances::token_lp_supply(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_lp_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes"),
|
||||
),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -708,7 +713,12 @@ impl Accounts {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: Balances::token_lp_supply_add(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_lp_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes"),
|
||||
),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -801,7 +811,12 @@ impl Accounts {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: Balances::token_lp_supply_remove(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_lp_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes"),
|
||||
),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -815,7 +830,12 @@ impl Accounts {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: 0,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_lp_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes"),
|
||||
),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -908,7 +928,12 @@ impl Accounts {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: Balances::lp_supply_init(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_lp_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes"),
|
||||
),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -1397,7 +1422,7 @@ fn fungible_total_supply(account: &Account) -> u128 {
|
||||
name: _,
|
||||
total_supply,
|
||||
metadata_id: _,
|
||||
mint_authority: _,
|
||||
authority: _,
|
||||
} = definition
|
||||
else {
|
||||
panic!("expected fungible token definition")
|
||||
|
||||
@ -84,7 +84,7 @@ impl Accounts {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -122,7 +122,7 @@ impl Accounts {
|
||||
name: String::from("Foreign Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -497,7 +497,7 @@ fn ata_burn() {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 700_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ impl Accounts {
|
||||
name: String::from("Gold"),
|
||||
total_supply: Balances::user_holding_init(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
@ -134,7 +134,7 @@ impl Accounts {
|
||||
name: String::from("DAI"),
|
||||
total_supply: Balances::stablecoin_supply_init(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
|
||||
@ -69,8 +69,8 @@ impl Accounts {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: Some(
|
||||
Ids::authority()
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes"),
|
||||
@ -88,8 +88,8 @@ impl Accounts {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: Some(
|
||||
Ids::authority()
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes"),
|
||||
@ -168,6 +168,7 @@ fn token_new_fungible_definition() {
|
||||
let instruction = token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
mint_authority: None,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
@ -195,7 +196,7 @@ fn token_new_fungible_definition() {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
@ -447,8 +448,8 @@ fn token_burn() {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 800_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: Some(
|
||||
Ids::authority()
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes")
|
||||
@ -482,14 +483,13 @@ fn token_mint() {
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::authority(), Ids::holder()],
|
||||
vec![Ids::token_definition(), Ids::holder()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::authority_key()]);
|
||||
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, 0).unwrap();
|
||||
@ -503,14 +503,14 @@ fn token_mint() {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_500_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: Some(
|
||||
Ids::authority()
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes")
|
||||
),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
|
||||
@ -542,7 +542,7 @@ fn token_mint_rejects_foreign_owned_definition() {
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::authority(), Ids::recipient()],
|
||||
vec![Ids::token_definition(), Ids::recipient()],
|
||||
vec![Nonce(0), Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
@ -550,7 +550,7 @@ fn token_mint_rejects_foreign_owned_definition() {
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[&Keys::authority_key(), &Keys::recipient_key()],
|
||||
&[&Keys::def_key(), &Keys::recipient_key()],
|
||||
);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
@ -576,14 +576,13 @@ fn token_mint_fresh_public_recipient_requires_authorization() {
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::authority(), Ids::recipient()],
|
||||
vec![Ids::token_definition(), Ids::recipient()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::authority_key()]);
|
||||
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());
|
||||
@ -608,7 +607,7 @@ fn token_mint_fresh_authorized_public_recipient() {
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::authority(), Ids::recipient()],
|
||||
vec![Ids::token_definition(), Ids::recipient()],
|
||||
vec![Nonce(0), Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
@ -616,7 +615,7 @@ fn token_mint_fresh_authorized_public_recipient() {
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[&Keys::authority_key(), &Keys::recipient_key()],
|
||||
&[&Keys::def_key(), &Keys::recipient_key()],
|
||||
);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
@ -631,14 +630,14 @@ fn token_mint_fresh_authorized_public_recipient() {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_500_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: Some(
|
||||
Ids::authority()
|
||||
authority: token_core::Authority::new(
|
||||
Ids::token_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes")
|
||||
),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
|
||||
@ -976,10 +975,10 @@ fn token_new_fungible_definition_with_authority() {
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes");
|
||||
let instruction = token_core::Instruction::NewFungibleDefinitionWithAuthority {
|
||||
let instruction = token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("AuthCoin"),
|
||||
initial_supply: 1_000_000_u128,
|
||||
mint_authority: authority_key,
|
||||
total_supply: 1_000_000_u128,
|
||||
mint_authority: Some(AccountId::new(authority_key)),
|
||||
};
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
@ -1003,7 +1002,7 @@ fn token_new_fungible_definition_with_authority() {
|
||||
name: String::from("AuthCoin"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: Some(authority_key),
|
||||
authority: token_core::Authority::new(authority_key),
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
@ -1014,15 +1013,15 @@ fn token_new_fungible_definition_with_authority() {
|
||||
fn token_set_authority_revoke() {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_token(&mut state);
|
||||
let authority_key: [u8; 32] = Ids::authority()
|
||||
let authority_key: [u8; 32] = Ids::token_definition()
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes");
|
||||
// Create token with authority
|
||||
let instruction = token_core::Instruction::NewFungibleDefinitionWithAuthority {
|
||||
let instruction = token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("AuthCoin"),
|
||||
initial_supply: 1_000_000_u128,
|
||||
mint_authority: authority_key,
|
||||
total_supply: 1_000_000_u128,
|
||||
mint_authority: Some(AccountId::new(authority_key)),
|
||||
};
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
@ -1047,13 +1046,12 @@ fn token_set_authority_revoke() {
|
||||
};
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::authority()],
|
||||
vec![Nonce(0)],
|
||||
vec![Ids::token_definition()],
|
||||
vec![Nonce(1)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set =
|
||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::authority_key()]);
|
||||
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, 0).unwrap();
|
||||
assert_eq!(
|
||||
@ -1065,9 +1063,9 @@ fn token_set_authority_revoke() {
|
||||
name: String::from("AuthCoin"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
nonce: Nonce(2),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ fn collateral_definition_account() -> AccountWithMetadata {
|
||||
name: "SNT".to_owned(),
|
||||
total_supply: 1_000_000,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -157,7 +157,7 @@ fn stablecoin_definition_account() -> AccountWithMetadata {
|
||||
name: "DAI".to_owned(),
|
||||
total_supply: 1_000_000,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -391,7 +391,7 @@ fn open_position_rejects_mismatched_token_definition() {
|
||||
name: "OTHER".to_owned(),
|
||||
total_supply: 1,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: token_core::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
|
||||
@ -11,3 +11,4 @@ nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.gi
|
||||
spel-framework-macros = { git = "https://github.com/0x-r4bbit/spel.git", rev = "91023c9115bf88173b0d25d2e905f2a55ef0313b", package = "spel-framework-macros" }
|
||||
borsh = { version = "1.5", features = ["derive"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
lez-authority = { path = "../../../lez-authority" }
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
//! This crate contains core data structures and utilities for the Token Program.
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
pub use lez_authority::{Authority, Ownable};
|
||||
use nssa_core::account::{AccountId, Data};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use spel_framework_macros::account_type;
|
||||
@ -18,10 +19,18 @@ pub enum Instruction {
|
||||
|
||||
/// Create a new fungible token definition without metadata.
|
||||
///
|
||||
/// `mint_authority` decides the supply model:
|
||||
/// - `Some(id)` — `id` may mint additional supply and rotate/renounce the authority,
|
||||
/// - `None` — supply is permanently fixed at `total_supply`.
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Token Definition account (uninitialized, authorized),
|
||||
/// - Token Holding account (uninitialized, authorized).
|
||||
NewFungibleDefinition { name: String, total_supply: u128 },
|
||||
NewFungibleDefinition {
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
mint_authority: Option<AccountId>,
|
||||
},
|
||||
|
||||
/// Create a new fungible or non-fungible token definition with metadata.
|
||||
///
|
||||
@ -51,9 +60,13 @@ pub enum Instruction {
|
||||
|
||||
/// Mint new tokens to the holder's account.
|
||||
///
|
||||
/// Minting is gated on the definition's mint authority: the Token Definition
|
||||
/// account must be authorized in this transaction and its account id must match
|
||||
/// the stored authority. A definition with no authority has a fixed supply and
|
||||
/// rejects minting.
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Token Definition account (initialized).
|
||||
/// - Authority account: must sign and match the stored mint authority.
|
||||
/// - Token Definition account (initialized, authorized as the current mint authority),
|
||||
/// - Token Holding account (uninitialized or authorized and initialized).
|
||||
Mint { amount_to_mint: u128 },
|
||||
|
||||
@ -64,26 +77,12 @@ pub enum Instruction {
|
||||
/// - NFT Printed Copy Token Holding account (uninitialized, authorized).
|
||||
PrintNft,
|
||||
|
||||
/// Create a new fungible token definition with a mint authority.
|
||||
/// Unlike NewFungibleDefinition, this allows minting additional tokens later.
|
||||
/// Rotate or renounce the mint authority for a fungible token definition.
|
||||
/// Pass `new_authority: None` to permanently renounce minting (fixed supply).
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Token Definition account (uninitialized, authorized),
|
||||
/// - Token Holding account (uninitialized, authorized).
|
||||
NewFungibleDefinitionWithAuthority {
|
||||
name: String,
|
||||
initial_supply: u128,
|
||||
/// The initial mint authority. Can be rotated or revoked later via SetAuthority.
|
||||
mint_authority: [u8; 32],
|
||||
},
|
||||
|
||||
/// Set or rotate the mint authority for a fungible token definition.
|
||||
/// Pass `new_authority: None` to permanently revoke minting (fixed supply).
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Token Definition account (initialized).
|
||||
/// - Authority account: must sign and match the current mint authority.
|
||||
SetAuthority { new_authority: Option<[u8; 32]> },
|
||||
/// - Token Definition account (initialized, authorized as the current mint authority).
|
||||
SetAuthority { new_authority: Option<AccountId> },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -105,9 +104,9 @@ pub enum TokenDefinition {
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
metadata_id: Option<AccountId>,
|
||||
/// Mint authority. `None` = supply is permanently fixed (no further minting allowed).
|
||||
/// Added by LP-0013.
|
||||
mint_authority: Option<[u8; 32]>,
|
||||
/// Mint authority slot. `Some(id)` may mint and rotate/renounce;
|
||||
/// `None` means the supply is permanently fixed.
|
||||
authority: Authority,
|
||||
},
|
||||
NonFungible {
|
||||
name: String,
|
||||
@ -116,6 +115,26 @@ pub enum TokenDefinition {
|
||||
},
|
||||
}
|
||||
|
||||
impl Ownable for TokenDefinition {
|
||||
fn authority(&self) -> &Authority {
|
||||
match self {
|
||||
TokenDefinition::Fungible { authority, .. } => authority,
|
||||
TokenDefinition::NonFungible { .. } => {
|
||||
panic!("Authority is not supported for Non-Fungible Tokens")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn authority_mut(&mut self) -> &mut Authority {
|
||||
match self {
|
||||
TokenDefinition::Fungible { authority, .. } => authority,
|
||||
TokenDefinition::NonFungible { .. } => {
|
||||
panic!("Authority is not supported for Non-Fungible Tokens")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Data> for TokenDefinition {
|
||||
type Error = std::io::Error;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#![cfg_attr(not(test), no_main)]
|
||||
|
||||
use nssa_core::account::AccountWithMetadata;
|
||||
use nssa_core::account::{AccountId, AccountWithMetadata};
|
||||
use spel_framework::context::ProgramContext;
|
||||
use spel_framework::prelude::*;
|
||||
|
||||
@ -33,6 +33,7 @@ mod token {
|
||||
|
||||
/// Create a new fungible token definition without metadata.
|
||||
/// Definition and holding targets must be uninitialized and authorized.
|
||||
/// `mint_authority` is `Some(id)` for a mintable token or `None` for fixed supply.
|
||||
#[instruction]
|
||||
pub fn new_fungible_definition(
|
||||
#[account(init, signer)]
|
||||
@ -41,6 +42,7 @@ mod token {
|
||||
holding_target_account: AccountWithMetadata,
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
mint_authority: Option<AccountId>,
|
||||
) -> SpelResult {
|
||||
Ok(spel_framework::SpelOutput::execute(
|
||||
token_program::new_definition::new_fungible_definition(
|
||||
@ -48,6 +50,7 @@ mod token {
|
||||
holding_target_account,
|
||||
name,
|
||||
total_supply,
|
||||
mint_authority,
|
||||
),
|
||||
vec![],
|
||||
))
|
||||
@ -117,20 +120,19 @@ mod token {
|
||||
}
|
||||
|
||||
/// Mint new tokens to the holder's account.
|
||||
/// The definition account must be authorized as the current mint authority.
|
||||
/// Fresh public holders must be explicitly authorized in the same transaction.
|
||||
#[instruction]
|
||||
pub fn mint(
|
||||
ctx: ProgramContext,
|
||||
#[account(mut, signer)]
|
||||
definition_account: AccountWithMetadata,
|
||||
authority_account: AccountWithMetadata,
|
||||
user_holding_account: AccountWithMetadata,
|
||||
amount_to_mint: u128,
|
||||
) -> SpelResult {
|
||||
Ok(spel_framework::SpelOutput::execute(
|
||||
token_program::mint::mint(
|
||||
definition_account,
|
||||
authority_account,
|
||||
user_holding_account,
|
||||
amount_to_mint,
|
||||
ctx.self_program_id,
|
||||
@ -139,42 +141,16 @@ mod token {
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new fungible token definition with a mint authority.
|
||||
/// Unlike NewFungibleDefinition, this allows minting additional tokens later.
|
||||
#[instruction]
|
||||
pub fn new_fungible_definition_with_authority(
|
||||
definition_target_account: AccountWithMetadata,
|
||||
holding_target_account: AccountWithMetadata,
|
||||
name: String,
|
||||
initial_supply: u128,
|
||||
mint_authority: [u8; 32],
|
||||
) -> SpelResult {
|
||||
Ok(spel_framework::SpelOutput::execute(
|
||||
token_program::new_definition::new_fungible_definition_with_authority(
|
||||
definition_target_account,
|
||||
holding_target_account,
|
||||
name,
|
||||
initial_supply,
|
||||
mint_authority,
|
||||
),
|
||||
vec![],
|
||||
))
|
||||
}
|
||||
|
||||
/// Set or rotate the mint authority for a fungible token definition.
|
||||
/// Pass `new_authority: None` to permanently revoke minting (fixed supply).
|
||||
/// Rotate or renounce the mint authority for a fungible token definition.
|
||||
/// Pass `new_authority: None` to permanently renounce minting (fixed supply).
|
||||
/// The definition account must be authorized as the current mint authority.
|
||||
#[instruction]
|
||||
pub fn set_authority(
|
||||
definition_account: AccountWithMetadata,
|
||||
authority_account: AccountWithMetadata,
|
||||
new_authority: Option<[u8; 32]>,
|
||||
new_authority: Option<AccountId>,
|
||||
) -> SpelResult {
|
||||
Ok(spel_framework::SpelOutput::execute(
|
||||
token_program::set_authority::set_authority(
|
||||
definition_account,
|
||||
authority_account,
|
||||
new_authority,
|
||||
),
|
||||
token_program::set_authority::set_authority(definition_account, new_authority),
|
||||
vec![],
|
||||
))
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ pub fn burn(
|
||||
name: _,
|
||||
metadata_id: _,
|
||||
total_supply,
|
||||
mint_authority: _,
|
||||
authority: _,
|
||||
},
|
||||
TokenHolding::Fungible {
|
||||
definition_id: _,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use lez_authority::AuthoritySlot;
|
||||
use lez_authority::Ownable;
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
program::{AccountPostState, Claim, ProgramId},
|
||||
@ -7,7 +7,6 @@ use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
pub fn mint(
|
||||
definition_account: AccountWithMetadata,
|
||||
authority_account: AccountWithMetadata,
|
||||
user_holding_account: AccountWithMetadata,
|
||||
amount_to_mint: u128,
|
||||
token_program_id: ProgramId,
|
||||
@ -20,20 +19,24 @@ pub fn mint(
|
||||
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
|
||||
.expect("Token Definition account must be valid");
|
||||
|
||||
// LP-0013 / RFP-001: gate minting through lez-authority. The authority_account
|
||||
// is the signer and must match the stored mint authority.
|
||||
if let TokenDefinition::Fungible { mint_authority, .. } = &definition {
|
||||
// Minting is gated on the definition's mint authority: the definition account
|
||||
// must be authorized in this transaction and its id must match the stored
|
||||
// authority. This holds for an external owner that signs the definition key,
|
||||
// and for a program-controlled PDA authorized via its seeds (e.g. the AMM's
|
||||
// pool definition minting LP tokens).
|
||||
if let TokenDefinition::Fungible { .. } = &definition {
|
||||
assert!(
|
||||
authority_account.is_authorized,
|
||||
"Mint authority must sign the transaction"
|
||||
definition_account.is_authorized,
|
||||
"Mint authority must authorize the transaction"
|
||||
);
|
||||
let signer: [u8; 32] = authority_account
|
||||
let signer: [u8; 32] = definition_account
|
||||
.account_id
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes");
|
||||
let slot = AuthoritySlot(*mint_authority);
|
||||
slot.check(signer).expect("Mint authority check failed");
|
||||
definition
|
||||
.require_owner(signer)
|
||||
.expect("Mint authority check failed");
|
||||
}
|
||||
|
||||
let mut holding = if user_holding_account.account == Account::default() {
|
||||
@ -55,7 +58,7 @@ pub fn mint(
|
||||
name: _,
|
||||
metadata_id: _,
|
||||
total_supply,
|
||||
mint_authority: _,
|
||||
authority: _,
|
||||
},
|
||||
TokenHolding::Fungible {
|
||||
definition_id: _,
|
||||
@ -87,7 +90,6 @@ pub fn mint(
|
||||
|
||||
vec![
|
||||
AccountPostState::new(definition_post),
|
||||
AccountPostState::new(authority_account.account),
|
||||
AccountPostState::new_claimed_if_default(holding_post, Claim::Authorized),
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,16 +1,39 @@
|
||||
use lez_authority::Authority;
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
account::{Account, AccountId, AccountWithMetadata, Data},
|
||||
program::{AccountPostState, Claim},
|
||||
};
|
||||
use token_core::{
|
||||
NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding, TokenMetadata,
|
||||
};
|
||||
|
||||
/// Build the embedded [`Authority`] for a freshly created fungible definition.
|
||||
///
|
||||
/// `Some(id)` makes the token mintable by `id`; `None` fixes the supply.
|
||||
/// An all-zero authority id is rejected as it cannot be a real signer.
|
||||
fn authority_from(mint_authority: Option<AccountId>) -> Authority {
|
||||
match mint_authority {
|
||||
Some(id) => {
|
||||
let key: [u8; 32] = id
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes");
|
||||
assert!(
|
||||
key != [0u8; 32],
|
||||
"Mint authority must be a valid non-zero account ID"
|
||||
);
|
||||
Authority::new(key)
|
||||
}
|
||||
None => Authority::renounced(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_fungible_definition(
|
||||
definition_target_account: AccountWithMetadata,
|
||||
holding_target_account: AccountWithMetadata,
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
mint_authority: Option<AccountId>,
|
||||
) -> Vec<AccountPostState> {
|
||||
assert_eq!(
|
||||
definition_target_account.account,
|
||||
@ -36,7 +59,7 @@ pub fn new_fungible_definition(
|
||||
name,
|
||||
total_supply,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: authority_from(mint_authority),
|
||||
};
|
||||
let token_holding = TokenHolding::Fungible {
|
||||
definition_id: definition_target_account.account_id,
|
||||
@ -98,7 +121,7 @@ pub fn new_definition_with_metadata(
|
||||
name,
|
||||
total_supply,
|
||||
metadata_id: Some(metadata_target_account.account_id),
|
||||
mint_authority: None,
|
||||
authority: Authority::renounced(),
|
||||
},
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_target_account.account_id,
|
||||
@ -126,7 +149,7 @@ pub fn new_definition_with_metadata(
|
||||
standard: metadata.standard,
|
||||
uri: metadata.uri,
|
||||
creators: metadata.creators,
|
||||
primary_sale_date: 0u64, // TODO #261: future works to implement this
|
||||
primary_sale_date: 0u64,
|
||||
};
|
||||
|
||||
let mut definition_target_account_post = definition_target_account.account.clone();
|
||||
@ -144,56 +167,3 @@ pub fn new_definition_with_metadata(
|
||||
AccountPostState::new_claimed(metadata_target_account_post, Claim::Authorized),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn new_fungible_definition_with_authority(
|
||||
definition_target_account: AccountWithMetadata,
|
||||
holding_target_account: AccountWithMetadata,
|
||||
name: String,
|
||||
initial_supply: u128,
|
||||
mint_authority: [u8; 32],
|
||||
) -> Vec<AccountPostState> {
|
||||
assert_eq!(
|
||||
definition_target_account.account,
|
||||
Account::default(),
|
||||
"Definition target account must have default values"
|
||||
);
|
||||
assert_eq!(
|
||||
holding_target_account.account,
|
||||
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"
|
||||
);
|
||||
assert!(
|
||||
mint_authority != [0u8; 32],
|
||||
"Mint authority must be a valid non-zero account ID"
|
||||
);
|
||||
|
||||
let token_definition = TokenDefinition::Fungible {
|
||||
name,
|
||||
total_supply: initial_supply,
|
||||
metadata_id: None,
|
||||
mint_authority: Some(mint_authority),
|
||||
};
|
||||
let token_holding = TokenHolding::Fungible {
|
||||
definition_id: definition_target_account.account_id,
|
||||
balance: initial_supply,
|
||||
};
|
||||
|
||||
let mut definition_target_account_post = definition_target_account.account;
|
||||
definition_target_account_post.data = Data::from(&token_definition);
|
||||
|
||||
let mut holding_target_account_post = holding_target_account.account;
|
||||
holding_target_account_post.data = Data::from(&token_holding);
|
||||
|
||||
vec![
|
||||
AccountPostState::new_claimed(definition_target_account_post, Claim::Authorized),
|
||||
AccountPostState::new_claimed(holding_target_account_post, Claim::Authorized),
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,40 +1,52 @@
|
||||
use lez_authority::AuthoritySlot;
|
||||
use lez_authority::Ownable;
|
||||
use nssa_core::{
|
||||
account::{AccountWithMetadata, Data},
|
||||
account::{AccountId, AccountWithMetadata, Data},
|
||||
program::AccountPostState,
|
||||
};
|
||||
use token_core::TokenDefinition;
|
||||
|
||||
pub fn set_authority(
|
||||
definition_account: AccountWithMetadata,
|
||||
authority_account: AccountWithMetadata,
|
||||
new_authority: Option<[u8; 32]>,
|
||||
new_authority: Option<AccountId>,
|
||||
) -> Vec<AccountPostState> {
|
||||
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
|
||||
.expect("Token Definition account must be valid");
|
||||
|
||||
match &mut definition {
|
||||
TokenDefinition::Fungible { mint_authority, .. } => {
|
||||
TokenDefinition::Fungible { .. } => {
|
||||
// The current mint authority must authorize this transaction: the
|
||||
// definition account must be authorized and its id must match the
|
||||
// stored authority.
|
||||
assert!(
|
||||
authority_account.is_authorized,
|
||||
"Mint authority must sign the transaction"
|
||||
definition_account.is_authorized,
|
||||
"Mint authority must authorize the transaction"
|
||||
);
|
||||
|
||||
if let Some(new_key) = new_authority {
|
||||
assert!(
|
||||
new_key != [0u8; 32],
|
||||
"New mint authority must be a valid non-zero account ID"
|
||||
);
|
||||
}
|
||||
let signer: [u8; 32] = authority_account
|
||||
let signer: [u8; 32] = definition_account
|
||||
.account_id
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes");
|
||||
let mut slot = AuthoritySlot(*mint_authority);
|
||||
slot.set(signer, new_authority)
|
||||
.expect("SetAuthority failed");
|
||||
*mint_authority = slot.0;
|
||||
|
||||
match new_authority {
|
||||
Some(new) => {
|
||||
let new_key: [u8; 32] = new
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("AccountId is always 32 bytes");
|
||||
assert!(
|
||||
new_key != [0u8; 32],
|
||||
"New mint authority must be a valid non-zero account ID"
|
||||
);
|
||||
definition
|
||||
.transfer_ownership(signer, new_key)
|
||||
.expect("SetAuthority failed");
|
||||
}
|
||||
None => {
|
||||
definition
|
||||
.renounce_ownership(signer)
|
||||
.expect("SetAuthority failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenDefinition::NonFungible { .. } => {
|
||||
panic!("SetAuthority is not supported for Non-Fungible Tokens");
|
||||
@ -44,8 +56,5 @@ pub fn set_authority(
|
||||
let mut definition_post = definition_account.account;
|
||||
definition_post.data = Data::from(&definition);
|
||||
|
||||
vec![
|
||||
AccountPostState::new(definition_post),
|
||||
AccountPostState::new(authority_account.account),
|
||||
]
|
||||
vec![AccountPostState::new(definition_post)]
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ impl AccountForTests {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::init_supply(),
|
||||
metadata_id: None,
|
||||
mint_authority: Some([15_u8; 32]),
|
||||
authority: lez_authority::Authority::new([15_u8; 32]),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -51,16 +51,6 @@ impl AccountForTests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A signed authority account whose ID matches the [15; 32] mint authority
|
||||
/// used by definition_account_auth() / definition_account_mint().
|
||||
fn authority_account_auth() -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account::default(),
|
||||
is_authorized: true,
|
||||
account_id: IdForTests::pool_definition_id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn definition_account_foreign_owner() -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
@ -70,7 +60,7 @@ impl AccountForTests {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::init_supply(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: lez_authority::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -88,7 +78,7 @@ impl AccountForTests {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::init_supply(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: lez_authority::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -170,7 +160,7 @@ impl AccountForTests {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::init_supply_burned(),
|
||||
metadata_id: None,
|
||||
mint_authority: Some([15_u8; 32]),
|
||||
authority: lez_authority::Authority::new([15_u8; 32]),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -252,7 +242,7 @@ impl AccountForTests {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::init_supply_mint(),
|
||||
metadata_id: None,
|
||||
mint_authority: Some([15_u8; 32]),
|
||||
authority: lez_authority::Authority::new([15_u8; 32]),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -343,7 +333,7 @@ impl AccountForTests {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::init_supply(),
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: lez_authority::Authority::renounced(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
},
|
||||
@ -610,6 +600,7 @@ fn test_new_definition_non_default_first_account_should_fail() {
|
||||
holding_account,
|
||||
String::from("test"),
|
||||
10,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@ -634,6 +625,7 @@ fn test_new_definition_non_default_second_account_should_fail() {
|
||||
holding_account,
|
||||
String::from("test"),
|
||||
10,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@ -647,6 +639,7 @@ fn test_new_definition_requires_authorized_definition_target() {
|
||||
holding_account,
|
||||
String::from("test"),
|
||||
10,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@ -660,6 +653,7 @@ fn test_new_definition_requires_authorized_holding_target() {
|
||||
holding_account,
|
||||
String::from("test"),
|
||||
10,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@ -673,6 +667,7 @@ fn test_new_definition_with_valid_inputs_succeeds() {
|
||||
holding_account,
|
||||
String::from("test"),
|
||||
BalanceForTests::init_supply(),
|
||||
None,
|
||||
);
|
||||
|
||||
let [definition_account, holding_account] = post_states.try_into().unwrap();
|
||||
@ -914,7 +909,6 @@ fn test_mint_not_valid_holding_account() {
|
||||
let holding_account = AccountForTests::definition_account_without_auth();
|
||||
let _post_states = mint(
|
||||
definition_account,
|
||||
AccountForTests::authority_account_auth(),
|
||||
holding_account,
|
||||
BalanceForTests::mint_success(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -928,7 +922,6 @@ fn test_mint_not_valid_definition_account() {
|
||||
let holding_account = AccountForTests::holding_same_definition_without_authorization();
|
||||
let _post_states = mint(
|
||||
definition_account,
|
||||
AccountForTests::authority_account_auth(),
|
||||
holding_account,
|
||||
BalanceForTests::mint_success(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -936,19 +929,14 @@ fn test_mint_not_valid_definition_account() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Mint authority must sign the transaction")]
|
||||
#[should_panic(expected = "Mint authority must authorize the transaction")]
|
||||
fn test_mint_missing_authorization() {
|
||||
let definition_account = AccountForTests::definition_account_auth();
|
||||
// The definition account itself is the authority; mark it unauthorized.
|
||||
let mut definition_account = AccountForTests::definition_account_auth();
|
||||
definition_account.is_authorized = false;
|
||||
let holding_account = AccountForTests::holding_same_definition_without_authorization();
|
||||
// authority account that is NOT signed
|
||||
let unsigned_authority = AccountWithMetadata {
|
||||
account: Account::default(),
|
||||
is_authorized: false,
|
||||
account_id: IdForTests::pool_definition_id(),
|
||||
};
|
||||
let _post_states = mint(
|
||||
definition_account,
|
||||
unsigned_authority,
|
||||
holding_account,
|
||||
BalanceForTests::mint_success(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -962,7 +950,6 @@ fn test_mint_rejects_foreign_owned_definition() {
|
||||
let holding_account = AccountForTests::holding_account_uninit();
|
||||
let _post_states = mint(
|
||||
definition_account,
|
||||
AccountForTests::authority_account_auth(),
|
||||
holding_account,
|
||||
BalanceForTests::mint_success(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -977,7 +964,6 @@ fn test_mint_mismatched_token_definition() {
|
||||
let holding_account = AccountForTests::holding_different_definition();
|
||||
let _post_states = mint(
|
||||
definition_account,
|
||||
AccountForTests::authority_account_auth(),
|
||||
holding_account,
|
||||
BalanceForTests::mint_success(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -990,13 +976,12 @@ fn test_mint_success() {
|
||||
let holding_account = AccountForTests::holding_same_definition_without_authorization();
|
||||
let post_states = mint(
|
||||
definition_account,
|
||||
AccountForTests::authority_account_auth(),
|
||||
holding_account,
|
||||
BalanceForTests::mint_success(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
);
|
||||
|
||||
let [def_post, _authority_post, holding_post] = post_states.try_into().unwrap();
|
||||
let [def_post, holding_post] = post_states.try_into().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
*def_post.account(),
|
||||
@ -1016,13 +1001,12 @@ fn test_mint_uninit_holding_success() {
|
||||
let holding_account = AccountForTests::holding_account_uninit();
|
||||
let post_states = mint(
|
||||
definition_account,
|
||||
AccountForTests::authority_account_auth(),
|
||||
holding_account,
|
||||
BalanceForTests::mint_success(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
);
|
||||
|
||||
let [def_post, _authority_post, holding_post] = post_states.try_into().unwrap();
|
||||
let [def_post, holding_post] = post_states.try_into().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
*def_post.account(),
|
||||
@ -1043,7 +1027,6 @@ fn test_mint_total_supply_overflow() {
|
||||
let holding_account = AccountForTests::holding_same_definition_without_authorization();
|
||||
let _post_states = mint(
|
||||
definition_account,
|
||||
AccountForTests::authority_account_auth(),
|
||||
holding_account,
|
||||
BalanceForTests::mint_overflow(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -1057,7 +1040,6 @@ fn test_mint_holding_account_overflow() {
|
||||
let holding_account = AccountForTests::holding_same_definition_without_authorization_overflow();
|
||||
let _post_states = mint(
|
||||
definition_account,
|
||||
AccountForTests::authority_account_auth(),
|
||||
holding_account,
|
||||
BalanceForTests::mint_overflow(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -1071,7 +1053,6 @@ fn test_mint_cannot_mint_unmintable_tokens() {
|
||||
let holding_account = AccountForTests::holding_account_master_nft();
|
||||
let _post_states = mint(
|
||||
definition_account,
|
||||
AccountForTests::authority_account_auth(),
|
||||
holding_account,
|
||||
BalanceForTests::mint_success(),
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -1355,6 +1336,9 @@ mod authority_tests {
|
||||
const AUTHORITY: [u8; 32] = [15_u8; 32];
|
||||
const TOKEN_PROGRAM_ID: [u32; 8] = [5_u32; 8];
|
||||
|
||||
/// A fungible definition whose own account id ([15;32]) equals its stored
|
||||
/// mint authority, authorized in the transaction. This models both an external
|
||||
/// owner signing the definition key and a PDA authorized via its seeds.
|
||||
fn def_with_authority() -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
@ -1364,7 +1348,7 @@ mod authority_tests {
|
||||
name: String::from("test"),
|
||||
total_supply: 100_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: Some(AUTHORITY),
|
||||
authority: lez_authority::Authority::new(AUTHORITY),
|
||||
}),
|
||||
nonce: 0_u128.into(),
|
||||
},
|
||||
@ -1373,6 +1357,7 @@ mod authority_tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A definition whose authority has been renounced (fixed supply).
|
||||
fn def_with_authority_revoked() -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
@ -1382,7 +1367,7 @@ mod authority_tests {
|
||||
name: String::from("test"),
|
||||
total_supply: 100_000_u128,
|
||||
metadata_id: None,
|
||||
mint_authority: None,
|
||||
authority: lez_authority::Authority::renounced(),
|
||||
}),
|
||||
nonce: 0_u128.into(),
|
||||
},
|
||||
@ -1391,6 +1376,26 @@ mod authority_tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A definition whose account id ([99;32]) does NOT match its stored
|
||||
/// authority ([15;32]) — models a caller that isn't the current authority.
|
||||
fn def_wrong_authority() -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
program_owner: [5_u32; 8],
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("test"),
|
||||
total_supply: 100_000_u128,
|
||||
metadata_id: None,
|
||||
authority: lez_authority::Authority::new(AUTHORITY),
|
||||
}),
|
||||
nonce: 0_u128.into(),
|
||||
},
|
||||
is_authorized: true,
|
||||
account_id: AccountId::new([99; 32]),
|
||||
}
|
||||
}
|
||||
|
||||
fn holding_account() -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
@ -1407,34 +1412,15 @@ mod authority_tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Signed authority matching the [15; 32] stored mint authority.
|
||||
fn authority_signer() -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account::default(),
|
||||
is_authorized: true,
|
||||
account_id: AccountId::new([15; 32]),
|
||||
}
|
||||
}
|
||||
|
||||
/// A different signer (Bob) — NOT the current authority.
|
||||
fn wrong_authority_signer() -> AccountWithMetadata {
|
||||
AccountWithMetadata {
|
||||
account: Account::default(),
|
||||
is_authorized: true,
|
||||
account_id: AccountId::new([99; 32]),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mint_with_authority_succeeds() {
|
||||
let post_states = mint(
|
||||
def_with_authority(),
|
||||
authority_signer(),
|
||||
holding_account(),
|
||||
50_000,
|
||||
TOKEN_PROGRAM_ID,
|
||||
);
|
||||
let [def_post, _authority_post, holding_post] = post_states.try_into().unwrap();
|
||||
let [def_post, holding_post] = post_states.try_into().unwrap();
|
||||
|
||||
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
||||
let holding = TokenHolding::try_from(&holding_post.account().data).unwrap();
|
||||
@ -1443,7 +1429,6 @@ mod authority_tests {
|
||||
def,
|
||||
TokenDefinition::Fungible {
|
||||
total_supply: 150_000,
|
||||
mint_authority: Some(_),
|
||||
..
|
||||
}
|
||||
));
|
||||
@ -1461,7 +1446,6 @@ mod authority_tests {
|
||||
fn mint_with_revoked_authority_fails() {
|
||||
let _ = mint(
|
||||
def_with_authority_revoked(),
|
||||
authority_signer(),
|
||||
holding_account(),
|
||||
50_000,
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -1469,16 +1453,18 @@ mod authority_tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Mint authority must sign the transaction")]
|
||||
#[should_panic(expected = "Mint authority must authorize the transaction")]
|
||||
fn mint_without_is_authorized_fails() {
|
||||
let unsigned_authority = AccountWithMetadata {
|
||||
account: Account::default(),
|
||||
is_authorized: false,
|
||||
account_id: AccountId::new([15; 32]),
|
||||
};
|
||||
let mut def = def_with_authority();
|
||||
def.is_authorized = false;
|
||||
let _ = mint(def, holding_account(), 50_000, TOKEN_PROGRAM_ID);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Mint authority check failed")]
|
||||
fn mint_with_wrong_signer_fails() {
|
||||
let _ = mint(
|
||||
def_with_authority(),
|
||||
unsigned_authority,
|
||||
def_wrong_authority(),
|
||||
holding_account(),
|
||||
50_000,
|
||||
TOKEN_PROGRAM_ID,
|
||||
@ -1488,47 +1474,34 @@ mod authority_tests {
|
||||
#[test]
|
||||
#[should_panic(expected = "New mint authority must be a valid non-zero account ID")]
|
||||
fn set_authority_rejects_zero_new_authority() {
|
||||
let _ = set_authority(def_with_authority(), authority_signer(), Some([0u8; 32]));
|
||||
let _ = set_authority(def_with_authority(), Some(AccountId::new([0u8; 32])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_authority_rotates_to_new_key() {
|
||||
let new_key = [7_u8; 32];
|
||||
let post_states = set_authority(def_with_authority(), authority_signer(), Some(new_key));
|
||||
let [def_post, _authority_post] = post_states.try_into().unwrap();
|
||||
let new_key = AccountId::new([7_u8; 32]);
|
||||
let post_states = set_authority(def_with_authority(), Some(new_key));
|
||||
let [def_post] = post_states.try_into().unwrap();
|
||||
|
||||
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
||||
assert!(matches!(
|
||||
def,
|
||||
TokenDefinition::Fungible { mint_authority: Some(k), .. } if k == new_key
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Mint authority check failed")]
|
||||
fn mint_with_wrong_signer_fails() {
|
||||
let _ = mint(
|
||||
def_with_authority(),
|
||||
wrong_authority_signer(),
|
||||
holding_account(),
|
||||
50_000,
|
||||
TOKEN_PROGRAM_ID,
|
||||
);
|
||||
let auth = match def {
|
||||
TokenDefinition::Fungible { authority, .. } => authority.authority(),
|
||||
_ => None,
|
||||
};
|
||||
assert_eq!(auth, Some([7_u8; 32]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_authority_revokes_permanently() {
|
||||
let post_states = set_authority(def_with_authority(), authority_signer(), None);
|
||||
let [def_post, _authority_post] = post_states.try_into().unwrap();
|
||||
let post_states = set_authority(def_with_authority(), None);
|
||||
let [def_post] = post_states.try_into().unwrap();
|
||||
|
||||
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
||||
assert!(matches!(
|
||||
def,
|
||||
TokenDefinition::Fungible {
|
||||
mint_authority: None,
|
||||
..
|
||||
}
|
||||
));
|
||||
let renounced = match def {
|
||||
TokenDefinition::Fungible { authority, .. } => authority.is_renounced(),
|
||||
_ => false,
|
||||
};
|
||||
assert!(renounced);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1536,60 +1509,37 @@ mod authority_tests {
|
||||
fn set_authority_on_revoked_fails() {
|
||||
let _ = set_authority(
|
||||
def_with_authority_revoked(),
|
||||
authority_signer(),
|
||||
Some([7_u8; 32]),
|
||||
Some(AccountId::new([7_u8; 32])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Mint authority must sign the transaction")]
|
||||
#[should_panic(expected = "Mint authority must authorize the transaction")]
|
||||
fn set_authority_without_is_authorized_fails() {
|
||||
let unsigned_authority = AccountWithMetadata {
|
||||
account: Account::default(),
|
||||
is_authorized: false,
|
||||
account_id: AccountId::new([15; 32]),
|
||||
};
|
||||
let _ = set_authority(def_with_authority(), unsigned_authority, Some([7_u8; 32]));
|
||||
let mut def = def_with_authority();
|
||||
def.is_authorized = false;
|
||||
let _ = set_authority(def, Some(AccountId::new([7_u8; 32])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "SetAuthority failed")]
|
||||
fn set_authority_wrong_signer_fails() {
|
||||
let _ = set_authority(
|
||||
def_with_authority(),
|
||||
wrong_authority_signer(),
|
||||
Some([7_u8; 32]),
|
||||
);
|
||||
}
|
||||
|
||||
#[should_panic(expected = "Mint authority must be a valid non-zero account ID")]
|
||||
#[test]
|
||||
fn test_new_fungible_definition_with_authority_rejects_zero_authority() {
|
||||
let definition_account = AccountForTests::definition_account_uninit_auth();
|
||||
let holding_account = AccountForTests::holding_account_uninit_auth();
|
||||
let _post_states = crate::new_definition::new_fungible_definition_with_authority(
|
||||
definition_account,
|
||||
holding_account,
|
||||
String::from("test"),
|
||||
1000,
|
||||
[0u8; 32],
|
||||
);
|
||||
let _ = set_authority(def_wrong_authority(), Some(AccountId::new([7_u8; 32])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_authority_rotate_then_old_cannot_mint() {
|
||||
let new_key = [7_u8; 32];
|
||||
let post_states = set_authority(def_with_authority(), authority_signer(), Some(new_key));
|
||||
let [def_post, _authority_post] = post_states.try_into().unwrap();
|
||||
let new_key = AccountId::new([7_u8; 32]);
|
||||
let post_states = set_authority(def_with_authority(), Some(new_key));
|
||||
let [def_post] = post_states.try_into().unwrap();
|
||||
|
||||
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
||||
assert!(matches!(
|
||||
def,
|
||||
TokenDefinition::Fungible { mint_authority: Some(k), .. } if k == new_key
|
||||
));
|
||||
assert!(!matches!(
|
||||
def,
|
||||
TokenDefinition::Fungible { mint_authority: Some(k), .. } if k == AUTHORITY
|
||||
));
|
||||
let auth = match def {
|
||||
TokenDefinition::Fungible { authority, .. } => authority.authority(),
|
||||
_ => None,
|
||||
};
|
||||
// Rotated to the new key; the old authority no longer controls it.
|
||||
assert_eq!(auth, Some([7_u8; 32]));
|
||||
assert_ne!(auth, Some(AUTHORITY));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user