mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-02-17 11:53:14 +00:00
refactor: split token program into crates
This commit is contained in:
parent
fb62143398
commit
652be426ae
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -2263,6 +2263,7 @@ dependencies = [
|
||||
"sequencer_core",
|
||||
"sequencer_runner",
|
||||
"tempfile",
|
||||
"token_core",
|
||||
"tokio",
|
||||
"wallet",
|
||||
]
|
||||
@ -2671,6 +2672,7 @@ dependencies = [
|
||||
"test-case",
|
||||
"test_program_methods",
|
||||
"thiserror",
|
||||
"token_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3052,6 +3054,8 @@ dependencies = [
|
||||
"nssa_core",
|
||||
"risc0-zkvm",
|
||||
"serde",
|
||||
"token_core",
|
||||
"token_program",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4440,6 +4444,23 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "token_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"borsh",
|
||||
"nssa_core",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "token_program"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nssa_core",
|
||||
"token_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.48.0"
|
||||
@ -4801,6 +4822,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"token_core",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
||||
@ -12,6 +12,8 @@ members = [
|
||||
"common",
|
||||
"nssa",
|
||||
"nssa/core",
|
||||
"programs/token/core",
|
||||
"programs/token",
|
||||
"program_methods",
|
||||
"program_methods/guest",
|
||||
"test_program_methods",
|
||||
@ -32,6 +34,8 @@ sequencer_core = { path = "sequencer_core" }
|
||||
sequencer_rpc = { path = "sequencer_rpc" }
|
||||
sequencer_runner = { path = "sequencer_runner" }
|
||||
wallet = { path = "wallet" }
|
||||
token_core = { path = "programs/token/core" }
|
||||
token_program = { path = "programs/token" }
|
||||
test_program_methods = { path = "test_program_methods" }
|
||||
|
||||
tokio = { version = "1.28.2", features = [
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -11,6 +11,7 @@ sequencer_runner.workspace = true
|
||||
wallet.workspace = true
|
||||
common.workspace = true
|
||||
key_protocol.workspace = true
|
||||
token_core.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
||||
@ -8,6 +8,7 @@ use integration_tests::{
|
||||
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
||||
use log::info;
|
||||
use nssa::program::Program;
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
use tokio::test;
|
||||
use wallet::cli::{
|
||||
Command, SubcommandReturnValue,
|
||||
@ -59,11 +60,13 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
};
|
||||
|
||||
// Create new token
|
||||
let name = "A NAME".to_string();
|
||||
let total_supply = 37;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::New {
|
||||
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
|
||||
supply_account_id: format_public_account_id(&supply_account_id.to_string()),
|
||||
name: "A NAME".to_string(),
|
||||
total_supply: 37,
|
||||
name: name.clone(),
|
||||
total_supply,
|
||||
};
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
|
||||
@ -76,16 +79,16 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
.get_account(definition_account_id.to_string())
|
||||
.await?
|
||||
.account;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(definition_acc.program_owner, Program::token().id());
|
||||
// The data of a token definition account has the following layout:
|
||||
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 bytes)]
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
token_definition,
|
||||
TokenDefinition::Fungible {
|
||||
name: name.clone(),
|
||||
total_supply,
|
||||
metadata_id: None
|
||||
}
|
||||
);
|
||||
|
||||
// Check the status of the token holding account with the total supply
|
||||
@ -97,24 +100,23 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
|
||||
// The account must be owned by the token program
|
||||
assert_eq!(supply_acc.program_owner, Program::token().id());
|
||||
// The data of a token holding account has the following layout:
|
||||
// [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 bytes) ]
|
||||
// First byte of the data equal to 1 means it's a token holding account
|
||||
assert_eq!(supply_acc.data.as_ref()[0], 1);
|
||||
// Bytes from 1 to 33 represent the id of the token this account is associated with
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
assert_eq!(
|
||||
&supply_acc.data.as_ref()[1..33],
|
||||
definition_account_id.to_bytes()
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: total_supply
|
||||
}
|
||||
);
|
||||
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37);
|
||||
|
||||
// Transfer 7 tokens from supply_acc to recipient_account_id
|
||||
let transfer_amount = 7;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Send {
|
||||
from: format_public_account_id(&supply_account_id.to_string()),
|
||||
to: Some(format_public_account_id(&recipient_account_id.to_string())),
|
||||
to_npk: None,
|
||||
to_ipk: None,
|
||||
amount: 7,
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -129,9 +131,14 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
.await?
|
||||
.account;
|
||||
assert_eq!(supply_acc.program_owner, Program::token().id());
|
||||
assert_eq!(supply_acc.data[0], 1);
|
||||
assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes());
|
||||
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: total_supply - transfer_amount
|
||||
}
|
||||
);
|
||||
|
||||
// Check the status of the recipient account after transfer
|
||||
let recipient_acc = ctx
|
||||
@ -140,15 +147,21 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
.await?
|
||||
.account;
|
||||
assert_eq!(recipient_acc.program_owner, Program::token().id());
|
||||
assert_eq!(recipient_acc.data[0], 1);
|
||||
assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes());
|
||||
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: transfer_amount
|
||||
}
|
||||
);
|
||||
|
||||
// Burn 3 tokens from recipient_acc
|
||||
let burn_amount = 3;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Burn {
|
||||
definition: format_public_account_id(&definition_account_id.to_string()),
|
||||
holder: format_public_account_id(&recipient_account_id.to_string()),
|
||||
amount: 3,
|
||||
amount: burn_amount,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -162,13 +175,15 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
.get_account(definition_account_id.to_string())
|
||||
.await?
|
||||
.account;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
token_definition,
|
||||
TokenDefinition::Fungible {
|
||||
name: name.clone(),
|
||||
total_supply: total_supply - burn_amount,
|
||||
metadata_id: None
|
||||
}
|
||||
);
|
||||
|
||||
// Check the status of the recipient account after burn
|
||||
@ -177,16 +192,24 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
.get_account(recipient_account_id.to_string())
|
||||
.await?
|
||||
.account;
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
|
||||
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 4);
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: transfer_amount - burn_amount
|
||||
}
|
||||
);
|
||||
|
||||
// Mint 10 tokens at recipient_acc
|
||||
let mint_amount = 10;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Mint {
|
||||
definition: format_public_account_id(&definition_account_id.to_string()),
|
||||
holder: Some(format_public_account_id(&recipient_account_id.to_string())),
|
||||
holder_npk: None,
|
||||
holder_ipk: None,
|
||||
amount: 10,
|
||||
amount: mint_amount,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -200,13 +223,15 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
.get_account(definition_account_id.to_string())
|
||||
.await?
|
||||
.account;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
token_definition,
|
||||
TokenDefinition::Fungible {
|
||||
name,
|
||||
total_supply: total_supply - burn_amount + mint_amount,
|
||||
metadata_id: None
|
||||
}
|
||||
);
|
||||
|
||||
// Check the status of the recipient account after mint
|
||||
@ -215,10 +240,14 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
.get_account(recipient_account_id.to_string())
|
||||
.await?
|
||||
.account;
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(recipient_acc.data[33..].try_into()?),
|
||||
14
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: transfer_amount - burn_amount + mint_amount
|
||||
}
|
||||
);
|
||||
|
||||
info!("Successfully created and transferred public token");
|
||||
@ -270,11 +299,13 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
|
||||
};
|
||||
|
||||
// Create new token
|
||||
let name = "A NAME".to_string();
|
||||
let total_supply = 37;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::New {
|
||||
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
|
||||
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
|
||||
name: "A NAME".to_string(),
|
||||
total_supply: 37,
|
||||
name: name.clone(),
|
||||
total_supply,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -288,14 +319,16 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
|
||||
.get_account(definition_account_id.to_string())
|
||||
.await?
|
||||
.account;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(definition_acc.program_owner, Program::token().id());
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
token_definition,
|
||||
TokenDefinition::Fungible {
|
||||
name: name.clone(),
|
||||
total_supply,
|
||||
metadata_id: None
|
||||
}
|
||||
);
|
||||
|
||||
let new_commitment1 = ctx
|
||||
@ -305,12 +338,13 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
|
||||
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
|
||||
|
||||
// Transfer 7 tokens from supply_acc to recipient_account_id
|
||||
let transfer_amount = 7;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Send {
|
||||
from: format_private_account_id(&supply_account_id.to_string()),
|
||||
to: Some(format_private_account_id(&recipient_account_id.to_string())),
|
||||
to_npk: None,
|
||||
to_ipk: None,
|
||||
amount: 7,
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -331,10 +365,11 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
|
||||
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
|
||||
|
||||
// Burn 3 tokens from recipient_acc
|
||||
let burn_amount = 3;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Burn {
|
||||
definition: format_public_account_id(&definition_account_id.to_string()),
|
||||
holder: format_private_account_id(&recipient_account_id.to_string()),
|
||||
amount: 3,
|
||||
amount: burn_amount,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -348,13 +383,15 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
|
||||
.get_account(definition_account_id.to_string())
|
||||
.await?
|
||||
.account;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
token_definition,
|
||||
TokenDefinition::Fungible {
|
||||
name,
|
||||
total_supply: total_supply - burn_amount,
|
||||
metadata_id: None
|
||||
}
|
||||
);
|
||||
|
||||
let new_commitment2 = ctx
|
||||
@ -368,10 +405,14 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
|
||||
.wallet()
|
||||
.get_account_private(&recipient_account_id)
|
||||
.context("Failed to get recipient account")?;
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(recipient_acc.data[33..].try_into()?),
|
||||
4 // 7 - 3
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: transfer_amount - burn_amount
|
||||
}
|
||||
);
|
||||
|
||||
info!("Successfully created and transferred token with private supply");
|
||||
@ -414,11 +455,13 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
};
|
||||
|
||||
// Create token with private definition
|
||||
let name = "A NAME".to_string();
|
||||
let total_supply = 37;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::New {
|
||||
definition_account_id: format_private_account_id(&definition_account_id.to_string()),
|
||||
supply_account_id: format_public_account_id(&supply_account_id.to_string()),
|
||||
name: "A NAME".to_string(),
|
||||
total_supply: 37,
|
||||
name: name.clone(),
|
||||
total_supply,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -441,8 +484,14 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
.account;
|
||||
|
||||
assert_eq!(supply_acc.program_owner, Program::token().id());
|
||||
assert_eq!(supply_acc.data.as_ref()[0], 1);
|
||||
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37);
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: total_supply
|
||||
}
|
||||
);
|
||||
|
||||
// Create private recipient account
|
||||
let result = wallet::cli::execute_subcommand(
|
||||
@ -471,6 +520,7 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
};
|
||||
|
||||
// Mint to public account
|
||||
let mint_amount_public = 10;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Mint {
|
||||
definition: format_private_account_id(&definition_account_id.to_string()),
|
||||
holder: Some(format_public_account_id(
|
||||
@ -478,7 +528,7 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
)),
|
||||
holder_npk: None,
|
||||
holder_ipk: None,
|
||||
amount: 10,
|
||||
amount: mint_amount_public,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -491,10 +541,15 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
.wallet()
|
||||
.get_account_private(&definition_account_id)
|
||||
.context("Failed to get definition account")?;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(definition_acc.data[7..23].try_into()?),
|
||||
47 // 37 + 10
|
||||
token_definition,
|
||||
TokenDefinition::Fungible {
|
||||
name: name.clone(),
|
||||
total_supply: total_supply + mint_amount_public,
|
||||
metadata_id: None
|
||||
}
|
||||
);
|
||||
|
||||
// Verify public recipient received tokens
|
||||
@ -503,13 +558,18 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
.get_account(recipient_account_id_public.to_string())
|
||||
.await?
|
||||
.account;
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(recipient_acc.data[33..].try_into()?),
|
||||
10
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: mint_amount_public
|
||||
}
|
||||
);
|
||||
|
||||
// Mint to private account
|
||||
let mint_amount_private = 5;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Mint {
|
||||
definition: format_private_account_id(&definition_account_id.to_string()),
|
||||
holder: Some(format_private_account_id(
|
||||
@ -517,7 +577,7 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
)),
|
||||
holder_npk: None,
|
||||
holder_ipk: None,
|
||||
amount: 5,
|
||||
amount: mint_amount_private,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -537,10 +597,14 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
.wallet()
|
||||
.get_account_private(&recipient_account_id_private)
|
||||
.context("Failed to get private recipient account")?;
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc_private.data)?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(recipient_acc_private.data[33..].try_into()?),
|
||||
5
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: mint_amount_private
|
||||
}
|
||||
);
|
||||
|
||||
info!("Successfully created token with private definition and minted to both account types");
|
||||
@ -579,11 +643,13 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
|
||||
};
|
||||
|
||||
// Create token with both private definition and supply
|
||||
let name = "A NAME".to_string();
|
||||
let total_supply = 37;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::New {
|
||||
definition_account_id: format_private_account_id(&definition_account_id.to_string()),
|
||||
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
|
||||
name: "A NAME".to_string(),
|
||||
total_supply: 37,
|
||||
name,
|
||||
total_supply,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -610,8 +676,15 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
|
||||
.wallet()
|
||||
.get_account_private(&supply_account_id)
|
||||
.context("Failed to get supply account")?;
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
|
||||
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37);
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: total_supply
|
||||
}
|
||||
);
|
||||
|
||||
// Create recipient account
|
||||
let result = wallet::cli::execute_subcommand(
|
||||
@ -627,12 +700,13 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
|
||||
};
|
||||
|
||||
// Transfer tokens
|
||||
let transfer_amount = 7;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Send {
|
||||
from: format_private_account_id(&supply_account_id.to_string()),
|
||||
to: Some(format_private_account_id(&recipient_account_id.to_string())),
|
||||
to_npk: None,
|
||||
to_ipk: None,
|
||||
amount: 7,
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -658,13 +732,27 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
|
||||
.wallet()
|
||||
.get_account_private(&supply_account_id)
|
||||
.context("Failed to get supply account")?;
|
||||
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: total_supply - transfer_amount
|
||||
}
|
||||
);
|
||||
|
||||
let recipient_acc = ctx
|
||||
.wallet()
|
||||
.get_account_private(&recipient_account_id)
|
||||
.context("Failed to get recipient account")?;
|
||||
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: transfer_amount
|
||||
}
|
||||
);
|
||||
|
||||
info!("Successfully created and transferred token with both private definition and supply");
|
||||
|
||||
@ -715,11 +803,13 @@ async fn shielded_token_transfer() -> Result<()> {
|
||||
};
|
||||
|
||||
// Create token
|
||||
let name = "A NAME".to_string();
|
||||
let total_supply = 37;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::New {
|
||||
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
|
||||
supply_account_id: format_public_account_id(&supply_account_id.to_string()),
|
||||
name: "A NAME".to_string(),
|
||||
total_supply: 37,
|
||||
name,
|
||||
total_supply,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -728,12 +818,13 @@ async fn shielded_token_transfer() -> Result<()> {
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
// Perform shielded transfer: public supply -> private recipient
|
||||
let transfer_amount = 7;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Send {
|
||||
from: format_public_account_id(&supply_account_id.to_string()),
|
||||
to: Some(format_private_account_id(&recipient_account_id.to_string())),
|
||||
to_npk: None,
|
||||
to_ipk: None,
|
||||
amount: 7,
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -747,7 +838,14 @@ async fn shielded_token_transfer() -> Result<()> {
|
||||
.get_account(supply_account_id.to_string())
|
||||
.await?
|
||||
.account;
|
||||
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: total_supply - transfer_amount
|
||||
}
|
||||
);
|
||||
|
||||
// Verify recipient commitment exists
|
||||
let new_commitment = ctx
|
||||
@ -761,7 +859,14 @@ async fn shielded_token_transfer() -> Result<()> {
|
||||
.wallet()
|
||||
.get_account_private(&recipient_account_id)
|
||||
.context("Failed to get recipient account")?;
|
||||
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: transfer_amount
|
||||
}
|
||||
);
|
||||
|
||||
info!("Successfully performed shielded token transfer");
|
||||
|
||||
@ -812,11 +917,13 @@ async fn deshielded_token_transfer() -> Result<()> {
|
||||
};
|
||||
|
||||
// Create token with private supply
|
||||
let name = "A NAME".to_string();
|
||||
let total_supply = 37;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::New {
|
||||
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
|
||||
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
|
||||
name: "A NAME".to_string(),
|
||||
total_supply: 37,
|
||||
name,
|
||||
total_supply,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -825,12 +932,13 @@ async fn deshielded_token_transfer() -> Result<()> {
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
// Perform deshielded transfer: private supply -> public recipient
|
||||
let transfer_amount = 7;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Send {
|
||||
from: format_private_account_id(&supply_account_id.to_string()),
|
||||
to: Some(format_public_account_id(&recipient_account_id.to_string())),
|
||||
to_npk: None,
|
||||
to_ipk: None,
|
||||
amount: 7,
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -850,7 +958,14 @@ async fn deshielded_token_transfer() -> Result<()> {
|
||||
.wallet()
|
||||
.get_account_private(&supply_account_id)
|
||||
.context("Failed to get supply account")?;
|
||||
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: total_supply - transfer_amount
|
||||
}
|
||||
);
|
||||
|
||||
// Verify recipient balance
|
||||
let recipient_acc = ctx
|
||||
@ -858,7 +973,14 @@ async fn deshielded_token_transfer() -> Result<()> {
|
||||
.get_account(recipient_account_id.to_string())
|
||||
.await?
|
||||
.account;
|
||||
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: transfer_amount
|
||||
}
|
||||
);
|
||||
|
||||
info!("Successfully performed deshielded token transfer");
|
||||
|
||||
@ -896,11 +1018,13 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
|
||||
};
|
||||
|
||||
// Create token
|
||||
let name = "A NAME".to_string();
|
||||
let total_supply = 37;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::New {
|
||||
definition_account_id: format_private_account_id(&definition_account_id.to_string()),
|
||||
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
|
||||
name: "A NAME".to_string(),
|
||||
total_supply: 37,
|
||||
name,
|
||||
total_supply,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -931,12 +1055,13 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
|
||||
.context("Failed to get private account keys")?;
|
||||
|
||||
// Mint using claiming path (foreign account)
|
||||
let mint_amount = 9;
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Mint {
|
||||
definition: format_private_account_id(&definition_account_id.to_string()),
|
||||
holder: None,
|
||||
holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)),
|
||||
holder_ipk: Some(hex::encode(holder_keys.incoming_viewing_public_key.0)),
|
||||
amount: 9,
|
||||
amount: mint_amount,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -960,7 +1085,14 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
|
||||
.wallet()
|
||||
.get_account_private(&recipient_account_id)
|
||||
.context("Failed to get recipient account")?;
|
||||
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 9);
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_account_id,
|
||||
balance: mint_amount
|
||||
}
|
||||
);
|
||||
|
||||
info!("Successfully minted tokens using claiming path");
|
||||
|
||||
|
||||
@ -23,7 +23,9 @@ risc0-build = "3.0.3"
|
||||
risc0-binfmt = "3.0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
token_core.workspace = true
|
||||
test_program_methods.workspace = true
|
||||
|
||||
env_logger.workspace = true
|
||||
hex-literal = "1.0.0"
|
||||
test-case = "3.3.1"
|
||||
|
||||
@ -20,8 +20,7 @@ pub struct ProgramInput<T> {
|
||||
/// Each program can derive up to `2^256` unique account IDs by choosing different
|
||||
/// seeds. PDAs allow programs to control namespaced account identifiers without
|
||||
/// collisions between programs.
|
||||
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug))]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||
pub struct PdaSeed([u8; 32]);
|
||||
|
||||
impl PdaSeed {
|
||||
@ -65,23 +64,44 @@ impl From<(&ProgramId, &PdaSeed)> for AccountId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug,))]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct ChainedCall {
|
||||
/// The program ID of the program to execute
|
||||
pub program_id: ProgramId,
|
||||
pub pre_states: Vec<AccountWithMetadata>,
|
||||
/// The instruction data to pass
|
||||
pub instruction_data: InstructionData,
|
||||
pub pre_states: Vec<AccountWithMetadata>,
|
||||
pub pda_seeds: Vec<PdaSeed>,
|
||||
}
|
||||
|
||||
impl ChainedCall {
|
||||
/// Creates a new chained call serializing the given instruction.
|
||||
pub fn new<I: Serialize>(
|
||||
program_id: ProgramId,
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
instruction: &I,
|
||||
) -> Self {
|
||||
Self {
|
||||
program_id,
|
||||
pre_states,
|
||||
instruction_data: risc0_zkvm::serde::to_vec(instruction)
|
||||
.expect("Serialization to Vec<u32> should not fail"),
|
||||
pda_seeds: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_pda_seeds(mut self, pda_seeds: Vec<PdaSeed>) -> Self {
|
||||
self.pda_seeds = pda_seeds;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the final state of an `Account` after a program execution.
|
||||
/// A post state may optionally request that the executing program
|
||||
/// becomes the owner of the account (a “claim”). This is used to signal
|
||||
/// that the program intends to take ownership of the account.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(PartialEq, Eq))]
|
||||
pub struct AccountPostState {
|
||||
account: Account,
|
||||
claim: bool,
|
||||
|
||||
@ -273,6 +273,7 @@ pub mod tests {
|
||||
encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar},
|
||||
program::{PdaSeed, ProgramId},
|
||||
};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
use crate::{
|
||||
PublicKey, PublicTransaction, V02State,
|
||||
@ -2284,53 +2285,6 @@ pub mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: repeated code needs to be cleaned up
|
||||
// from token.rs (also repeated in amm.rs)
|
||||
const TOKEN_DEFINITION_DATA_SIZE: usize = 55;
|
||||
|
||||
const TOKEN_HOLDING_DATA_SIZE: usize = 49;
|
||||
|
||||
struct TokenDefinition {
|
||||
account_type: u8,
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
metadata_id: AccountId,
|
||||
}
|
||||
|
||||
struct TokenHolding {
|
||||
account_type: u8,
|
||||
definition_id: AccountId,
|
||||
balance: u128,
|
||||
}
|
||||
impl TokenDefinition {
|
||||
fn into_data(self) -> Data {
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
bytes.extend_from_slice(&[self.account_type]);
|
||||
bytes.extend_from_slice(&self.name);
|
||||
bytes.extend_from_slice(&self.total_supply.to_le_bytes());
|
||||
bytes.extend_from_slice(&self.metadata_id.to_bytes());
|
||||
|
||||
if bytes.len() != TOKEN_DEFINITION_DATA_SIZE {
|
||||
panic!("Invalid Token Definition data");
|
||||
}
|
||||
|
||||
Data::try_from(bytes).expect("Token definition data size must fit into data")
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenHolding {
|
||||
fn into_data(self) -> Data {
|
||||
let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE];
|
||||
bytes[0] = self.account_type;
|
||||
bytes[1..33].copy_from_slice(&self.definition_id.to_bytes());
|
||||
bytes[33..].copy_from_slice(&self.balance.to_le_bytes());
|
||||
bytes
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.expect("33 bytes should fit into Data")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO repeated code should ultimately be removed;
|
||||
fn compute_pool_pda(
|
||||
amm_program_id: ProgramId,
|
||||
@ -2703,8 +2657,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::user_token_a_holding_init(),
|
||||
}),
|
||||
@ -2716,8 +2669,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::user_token_b_holding_init(),
|
||||
}),
|
||||
@ -2749,11 +2701,10 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenDefinition::into_data(TokenDefinition {
|
||||
account_type: 0u8,
|
||||
name: [1u8; 6],
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::token_a_supply(),
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: 0,
|
||||
}
|
||||
@ -2763,11 +2714,10 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenDefinition::into_data(TokenDefinition {
|
||||
account_type: 0u8,
|
||||
name: [1u8; 6],
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("test"),
|
||||
total_supply: BalanceForTests::token_b_supply(),
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: 0,
|
||||
}
|
||||
@ -2777,11 +2727,10 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenDefinition::into_data(TokenDefinition {
|
||||
account_type: 0u8,
|
||||
name: [1u8; 6],
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: BalanceForTests::token_lp_supply(),
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: 0,
|
||||
}
|
||||
@ -2791,8 +2740,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::vault_a_balance_init(),
|
||||
}),
|
||||
@ -2804,8 +2752,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::vault_b_balance_init(),
|
||||
}),
|
||||
@ -2817,8 +2764,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_lp_definition_id(),
|
||||
balance: BalanceForTests::user_token_lp_holding_init(),
|
||||
}),
|
||||
@ -2830,8 +2776,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::vault_a_balance_swap_1(),
|
||||
}),
|
||||
@ -2843,8 +2788,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::vault_b_balance_swap_1(),
|
||||
}),
|
||||
@ -2876,8 +2820,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::user_token_a_holding_swap_1(),
|
||||
}),
|
||||
@ -2889,8 +2832,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::user_token_b_holding_swap_1(),
|
||||
}),
|
||||
@ -2902,8 +2844,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::vault_a_balance_swap_2(),
|
||||
}),
|
||||
@ -2915,8 +2856,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::vault_b_balance_swap_2(),
|
||||
}),
|
||||
@ -2948,8 +2888,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::user_token_a_holding_swap_2(),
|
||||
}),
|
||||
@ -2961,8 +2900,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::user_token_b_holding_swap_2(),
|
||||
}),
|
||||
@ -2974,8 +2912,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::vault_a_balance_add(),
|
||||
}),
|
||||
@ -2987,8 +2924,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::vault_b_balance_add(),
|
||||
}),
|
||||
@ -3020,8 +2956,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::user_token_a_holding_add(),
|
||||
}),
|
||||
@ -3033,8 +2968,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::user_token_b_holding_add(),
|
||||
}),
|
||||
@ -3046,8 +2980,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_lp_definition_id(),
|
||||
balance: BalanceForTests::user_token_lp_holding_add(),
|
||||
}),
|
||||
@ -3059,11 +2992,10 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenDefinition::into_data(TokenDefinition {
|
||||
account_type: 0u8,
|
||||
name: [1u8; 6],
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: BalanceForTests::token_lp_supply_add(),
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: 0,
|
||||
}
|
||||
@ -3073,8 +3005,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::vault_a_balance_remove(),
|
||||
}),
|
||||
@ -3086,8 +3017,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::vault_b_balance_remove(),
|
||||
}),
|
||||
@ -3119,8 +3049,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::user_token_a_holding_remove(),
|
||||
}),
|
||||
@ -3132,8 +3061,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::user_token_b_holding_remove(),
|
||||
}),
|
||||
@ -3145,8 +3073,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_lp_definition_id(),
|
||||
balance: BalanceForTests::user_token_lp_holding_remove(),
|
||||
}),
|
||||
@ -3158,11 +3085,10 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenDefinition::into_data(TokenDefinition {
|
||||
account_type: 0u8,
|
||||
name: [1u8; 6],
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: BalanceForTests::token_lp_supply_remove(),
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: 0,
|
||||
}
|
||||
@ -3172,11 +3098,10 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenDefinition::into_data(TokenDefinition {
|
||||
account_type: 0u8,
|
||||
name: [1u8; 6],
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: 0,
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: 0,
|
||||
}
|
||||
@ -3186,8 +3111,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: 0,
|
||||
}),
|
||||
@ -3199,8 +3123,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: 0,
|
||||
}),
|
||||
@ -3232,8 +3155,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_a_definition_id(),
|
||||
balance: BalanceForTests::user_token_a_holding_new_definition(),
|
||||
}),
|
||||
@ -3245,8 +3167,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_b_definition_id(),
|
||||
balance: BalanceForTests::user_token_b_holding_new_definition(),
|
||||
}),
|
||||
@ -3258,8 +3179,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_lp_definition_id(),
|
||||
balance: BalanceForTests::user_token_a_holding_new_definition(),
|
||||
}),
|
||||
@ -3271,11 +3191,10 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenDefinition::into_data(TokenDefinition {
|
||||
account_type: 0u8,
|
||||
name: [1u8; 6],
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("LP Token"),
|
||||
total_supply: BalanceForTests::vault_a_balance_init(),
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: 0,
|
||||
}
|
||||
@ -3305,8 +3224,7 @@ pub mod tests {
|
||||
Account {
|
||||
program_owner: Program::token().id(),
|
||||
balance: 0u128,
|
||||
data: TokenHolding::into_data(TokenHolding {
|
||||
account_type: 1u8,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: IdForTests::token_lp_definition_id(),
|
||||
balance: 0,
|
||||
}),
|
||||
@ -4071,13 +3989,13 @@ pub mod tests {
|
||||
let pinata_token_holding_id = AccountId::from((&pinata_token.id(), &PdaSeed::new([0; 32])));
|
||||
let winner_token_holding_id = AccountId::new([3; 32]);
|
||||
|
||||
let mut expected_winner_account_data = [0; 49];
|
||||
expected_winner_account_data[0] = 1;
|
||||
expected_winner_account_data[1..33].copy_from_slice(pinata_token_definition_id.value());
|
||||
expected_winner_account_data[33..].copy_from_slice(&150u128.to_le_bytes());
|
||||
let expected_winner_account_holding = token_core::TokenHolding::Fungible {
|
||||
definition_id: pinata_token_definition_id,
|
||||
balance: 150,
|
||||
};
|
||||
let expected_winner_token_holding_post = Account {
|
||||
program_owner: token.id(),
|
||||
data: expected_winner_account_data.to_vec().try_into().unwrap(),
|
||||
data: Data::from(&expected_winner_account_holding),
|
||||
..Account::default()
|
||||
};
|
||||
|
||||
@ -4087,10 +4005,10 @@ pub mod tests {
|
||||
// Execution of the token program to create new token for the pinata token
|
||||
// definition and supply accounts
|
||||
let total_supply: u128 = 10_000_000;
|
||||
// instruction: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
|
||||
let mut instruction = vec![0; 23];
|
||||
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
|
||||
instruction[17..].copy_from_slice(b"PINATA");
|
||||
let instruction = token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("PINATA"),
|
||||
total_supply,
|
||||
};
|
||||
let message = public_transaction::Message::try_new(
|
||||
token.id(),
|
||||
vec![pinata_token_definition_id, pinata_token_holding_id],
|
||||
@ -4102,9 +4020,8 @@ pub mod tests {
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx).unwrap();
|
||||
|
||||
// Execution of the token program transfer just to initialize the winner token account
|
||||
let mut instruction = vec![0; 23];
|
||||
instruction[0] = 2;
|
||||
// Execution of winner's token holding account initialization
|
||||
let instruction = token_core::Instruction::InitializeAccount;
|
||||
let message = public_transaction::Message::try_new(
|
||||
token.id(),
|
||||
vec![pinata_token_definition_id, winner_token_holding_id],
|
||||
|
||||
@ -5,6 +5,7 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
nssa_core.workspace = true
|
||||
|
||||
token_core.workspace = true
|
||||
token_program.workspace = true
|
||||
risc0-zkvm.workspace = true
|
||||
serde = { workspace = true, default-features = false }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -5,10 +5,7 @@ use nssa_core::{
|
||||
write_nssa_outputs_with_chained_call,
|
||||
},
|
||||
};
|
||||
use risc0_zkvm::{
|
||||
serde::to_vec,
|
||||
sha::{Impl, Sha256},
|
||||
};
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
|
||||
const PRIZE: u128 = 150;
|
||||
|
||||
@ -82,23 +79,21 @@ fn main() {
|
||||
let winner_token_holding_post = winner_token_holding.account.clone();
|
||||
pinata_definition_post.data = data.next_data();
|
||||
|
||||
let mut instruction_data = vec![0; 23];
|
||||
instruction_data[0] = 1;
|
||||
instruction_data[1..17].copy_from_slice(&PRIZE.to_le_bytes());
|
||||
|
||||
// Flip authorization to true for chained call
|
||||
let mut pinata_token_holding_for_chain_call = pinata_token_holding.clone();
|
||||
pinata_token_holding_for_chain_call.is_authorized = true;
|
||||
|
||||
let chained_calls = vec![ChainedCall {
|
||||
program_id: pinata_token_holding_post.program_owner,
|
||||
instruction_data: to_vec(&instruction_data).unwrap(),
|
||||
pre_states: vec![
|
||||
let chained_call = ChainedCall::new(
|
||||
pinata_token_holding_post.program_owner,
|
||||
vec![
|
||||
pinata_token_holding_for_chain_call,
|
||||
winner_token_holding.clone(),
|
||||
],
|
||||
pda_seeds: vec![PdaSeed::new([0; 32])],
|
||||
}];
|
||||
&token_core::Instruction::Transfer {
|
||||
amount_to_transfer: PRIZE,
|
||||
},
|
||||
)
|
||||
.with_pda_seeds(vec![PdaSeed::new([0; 32])]);
|
||||
|
||||
write_nssa_outputs_with_chained_call(
|
||||
instruction_words,
|
||||
@ -112,6 +107,6 @@ fn main() {
|
||||
AccountPostState::new(pinata_token_holding_post),
|
||||
AccountPostState::new(winner_token_holding_post),
|
||||
],
|
||||
chained_calls,
|
||||
vec![chained_call],
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
8
programs/token/Cargo.toml
Normal file
8
programs/token/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "token_program"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
nssa_core.workspace = true
|
||||
token_core.workspace = true
|
||||
9
programs/token/core/Cargo.toml
Normal file
9
programs/token/core/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "token_core"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
nssa_core.workspace = true
|
||||
serde.workspace = true
|
||||
borsh.workspace = true
|
||||
241
programs/token/core/src/lib.rs
Normal file
241
programs/token/core/src/lib.rs
Normal file
@ -0,0 +1,241 @@
|
||||
//! This crate contains core data structures and utilities for the Token Program.
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use nssa_core::account::{AccountId, Data};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Token Program Instruction.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Instruction {
|
||||
/// Transfer tokens from sender to recipient.
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Sender's Token Holding account (authorized),
|
||||
/// - Recipient's Token Holding account.
|
||||
Transfer { amount_to_transfer: u128 },
|
||||
|
||||
/// Create a new fungible token definition without metadata.
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Token Definition account (uninitialized),
|
||||
/// - Token Holding account (uninitialized).
|
||||
NewFungibleDefinition { name: String, total_supply: u128 },
|
||||
|
||||
/// Create a new fungible or non-fungible token definition with metadata.
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Token Definition account (uninitialized),
|
||||
/// - Token Holding account (uninitialized),
|
||||
/// - Token Metadata account (uninitialized).
|
||||
NewDefinitionWithMetadata {
|
||||
new_definition: NewTokenDefinition,
|
||||
/// Boxed to avoid large enum variant size
|
||||
metadata: Box<NewTokenMetadata>,
|
||||
},
|
||||
|
||||
/// Initialize a token holding account for a given token definition.
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Token Definition account (initialized),
|
||||
/// - Token Holding account (uninitialized),
|
||||
InitializeAccount,
|
||||
|
||||
/// Burn tokens from the holder's account.
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Token Definition account (initialized),
|
||||
/// - Token Holding account (authorized).
|
||||
Burn { amount_to_burn: u128 },
|
||||
|
||||
/// Mint new tokens to the holder's account.
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - Token Definition account (authorized),
|
||||
/// - Token Holding account (uninitialized or initialized).
|
||||
Mint { amount_to_mint: u128 },
|
||||
|
||||
/// Print a new NFT from the master copy.
|
||||
///
|
||||
/// Required accounts:
|
||||
/// - NFT Master Token Holding account (authorized),
|
||||
/// - NFT Printed Copy Token Holding account (uninitialized).
|
||||
PrintNft,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum NewTokenDefinition {
|
||||
Fungible {
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
},
|
||||
NonFungible {
|
||||
name: String,
|
||||
printable_supply: u128,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
pub enum TokenDefinition {
|
||||
Fungible {
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
metadata_id: Option<AccountId>,
|
||||
},
|
||||
NonFungible {
|
||||
name: String,
|
||||
printable_supply: u128,
|
||||
metadata_id: AccountId,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<&Data> for TokenDefinition {
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn try_from(data: &Data) -> Result<Self, Self::Error> {
|
||||
TokenDefinition::try_from_slice(data.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TokenDefinition> for Data {
|
||||
fn from(definition: &TokenDefinition) -> Self {
|
||||
// Using size_of_val as size hint for Vec allocation
|
||||
let mut data = Vec::with_capacity(std::mem::size_of_val(definition));
|
||||
|
||||
BorshSerialize::serialize(definition, &mut data)
|
||||
.expect("Serialization to Vec should not fail");
|
||||
|
||||
Data::try_from(data).expect("Token definition encoded data should fit into Data")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
pub enum TokenHolding {
|
||||
Fungible {
|
||||
definition_id: AccountId,
|
||||
balance: u128,
|
||||
},
|
||||
NftMaster {
|
||||
definition_id: AccountId,
|
||||
/// The amount of printed copies left - 1 (1 reserved for master copy itself).
|
||||
print_balance: u128,
|
||||
},
|
||||
NftPrintedCopy {
|
||||
definition_id: AccountId,
|
||||
/// Whether nft is owned by the holder.
|
||||
owned: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl TokenHolding {
|
||||
pub fn zeroized_clone_from(other: &Self) -> Self {
|
||||
match other {
|
||||
TokenHolding::Fungible { definition_id, .. } => TokenHolding::Fungible {
|
||||
definition_id: *definition_id,
|
||||
balance: 0,
|
||||
},
|
||||
TokenHolding::NftMaster { definition_id, .. } => TokenHolding::NftMaster {
|
||||
definition_id: *definition_id,
|
||||
print_balance: 0,
|
||||
},
|
||||
TokenHolding::NftPrintedCopy { definition_id, .. } => TokenHolding::NftPrintedCopy {
|
||||
definition_id: *definition_id,
|
||||
owned: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zeroized_from_definition(
|
||||
definition_id: AccountId,
|
||||
definition: &TokenDefinition,
|
||||
) -> Self {
|
||||
match definition {
|
||||
TokenDefinition::Fungible { .. } => TokenHolding::Fungible {
|
||||
definition_id,
|
||||
balance: 0,
|
||||
},
|
||||
TokenDefinition::NonFungible { .. } => TokenHolding::NftPrintedCopy {
|
||||
definition_id,
|
||||
owned: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn definition_id(&self) -> AccountId {
|
||||
match self {
|
||||
TokenHolding::Fungible { definition_id, .. } => *definition_id,
|
||||
TokenHolding::NftMaster { definition_id, .. } => *definition_id,
|
||||
TokenHolding::NftPrintedCopy { definition_id, .. } => *definition_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Data> for TokenHolding {
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn try_from(data: &Data) -> Result<Self, Self::Error> {
|
||||
TokenHolding::try_from_slice(data.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TokenHolding> for Data {
|
||||
fn from(holding: &TokenHolding) -> Self {
|
||||
// Using size_of_val as size hint for Vec allocation
|
||||
let mut data = Vec::with_capacity(std::mem::size_of_val(holding));
|
||||
|
||||
BorshSerialize::serialize(holding, &mut data)
|
||||
.expect("Serialization to Vec should not fail");
|
||||
|
||||
Data::try_from(data).expect("Token holding encoded data should fit into Data")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct NewTokenMetadata {
|
||||
/// Metadata standard.
|
||||
pub standard: MetadataStandard,
|
||||
/// Pointer to off-chain metadata
|
||||
pub uri: String,
|
||||
/// Creators of the token.
|
||||
pub creators: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
pub struct TokenMetadata {
|
||||
/// Token Definition account id.
|
||||
pub definition_id: AccountId,
|
||||
/// Metadata standard .
|
||||
pub standard: MetadataStandard,
|
||||
/// Pointer to off-chain metadata.
|
||||
pub uri: String,
|
||||
/// Creators of the token.
|
||||
pub creators: String,
|
||||
/// Block id of primary sale.
|
||||
pub primary_sale_date: u64,
|
||||
}
|
||||
|
||||
/// Metadata standard defining the expected format of JSON located off-chain.
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
pub enum MetadataStandard {
|
||||
Simple,
|
||||
Expanded,
|
||||
}
|
||||
|
||||
impl TryFrom<&Data> for TokenMetadata {
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn try_from(data: &Data) -> Result<Self, Self::Error> {
|
||||
TokenMetadata::try_from_slice(data.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TokenMetadata> for Data {
|
||||
fn from(metadata: &TokenMetadata) -> Self {
|
||||
// Using size_of_val as size hint for Vec allocation
|
||||
let mut data = Vec::with_capacity(std::mem::size_of_val(metadata));
|
||||
|
||||
BorshSerialize::serialize(metadata, &mut data)
|
||||
.expect("Serialization to Vec should not fail");
|
||||
|
||||
Data::try_from(data).expect("Token metadata encoded data should fit into Data")
|
||||
}
|
||||
}
|
||||
104
programs/token/src/burn.rs
Normal file
104
programs/token/src/burn.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use nssa_core::{
|
||||
account::{AccountWithMetadata, Data},
|
||||
program::AccountPostState,
|
||||
};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
pub fn burn(
|
||||
definition_account: AccountWithMetadata,
|
||||
user_holding_account: AccountWithMetadata,
|
||||
amount_to_burn: u128,
|
||||
) -> Vec<AccountPostState> {
|
||||
assert!(
|
||||
user_holding_account.is_authorized,
|
||||
"Authorization is missing"
|
||||
);
|
||||
|
||||
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
|
||||
.expect("Token Definition account must be valid");
|
||||
let mut holding = TokenHolding::try_from(&user_holding_account.account.data)
|
||||
.expect("Token Holding account must be valid");
|
||||
|
||||
assert_eq!(
|
||||
definition_account.account_id,
|
||||
holding.definition_id(),
|
||||
"Mismatch Token Definition and Token Holding"
|
||||
);
|
||||
|
||||
match (&mut definition, &mut holding) {
|
||||
(
|
||||
TokenDefinition::Fungible {
|
||||
name: _,
|
||||
metadata_id: _,
|
||||
total_supply,
|
||||
},
|
||||
TokenHolding::Fungible {
|
||||
definition_id: _,
|
||||
balance,
|
||||
},
|
||||
) => {
|
||||
*balance = balance
|
||||
.checked_sub(amount_to_burn)
|
||||
.expect("Insufficient balance to burn");
|
||||
|
||||
*total_supply = total_supply
|
||||
.checked_sub(amount_to_burn)
|
||||
.expect("Total supply underflow");
|
||||
}
|
||||
(
|
||||
TokenDefinition::NonFungible {
|
||||
name: _,
|
||||
printable_supply,
|
||||
metadata_id: _,
|
||||
},
|
||||
TokenHolding::NftMaster {
|
||||
definition_id: _,
|
||||
print_balance,
|
||||
},
|
||||
) => {
|
||||
*printable_supply = printable_supply
|
||||
.checked_sub(amount_to_burn)
|
||||
.expect("Printable supply underflow");
|
||||
|
||||
*print_balance = print_balance
|
||||
.checked_sub(amount_to_burn)
|
||||
.expect("Insufficient balance to burn");
|
||||
}
|
||||
(
|
||||
TokenDefinition::NonFungible {
|
||||
name: _,
|
||||
printable_supply,
|
||||
metadata_id: _,
|
||||
},
|
||||
TokenHolding::NftPrintedCopy {
|
||||
definition_id: _,
|
||||
owned,
|
||||
},
|
||||
) => {
|
||||
assert_eq!(
|
||||
amount_to_burn, 1,
|
||||
"Invalid balance to burn for NFT Printed Copy"
|
||||
);
|
||||
|
||||
assert!(*owned, "Cannot burn unowned NFT Printed Copy");
|
||||
|
||||
*printable_supply = printable_supply
|
||||
.checked_sub(1)
|
||||
.expect("Printable supply underflow");
|
||||
|
||||
*owned = false;
|
||||
}
|
||||
_ => panic!("Mismatched Token Definition and Token Holding types"),
|
||||
}
|
||||
|
||||
let mut definition_post = definition_account.account;
|
||||
definition_post.data = Data::from(&definition);
|
||||
|
||||
let mut holding_post = user_holding_account.account;
|
||||
holding_post.data = Data::from(&holding);
|
||||
|
||||
vec![
|
||||
AccountPostState::new(definition_post),
|
||||
AccountPostState::new(holding_post),
|
||||
]
|
||||
}
|
||||
34
programs/token/src/initialize.rs
Normal file
34
programs/token/src/initialize.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
program::AccountPostState,
|
||||
};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
pub fn initialize_account(
|
||||
definition_account: AccountWithMetadata,
|
||||
account_to_initialize: AccountWithMetadata,
|
||||
) -> Vec<AccountPostState> {
|
||||
assert_eq!(
|
||||
account_to_initialize.account,
|
||||
Account::default(),
|
||||
"Only Uninitialized accounts can be initialized"
|
||||
);
|
||||
|
||||
// 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 =
|
||||
TokenHolding::zeroized_from_definition(definition_account.account_id, &definition);
|
||||
|
||||
let definition_post = definition_account.account;
|
||||
let mut account_to_initialize = account_to_initialize.account;
|
||||
account_to_initialize.data = Data::from(&holding);
|
||||
|
||||
vec![
|
||||
AccountPostState::new(definition_post),
|
||||
AccountPostState::new_claimed(account_to_initialize),
|
||||
]
|
||||
}
|
||||
12
programs/token/src/lib.rs
Normal file
12
programs/token/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
//! The Token Program implementation.
|
||||
|
||||
pub use token_core as core;
|
||||
|
||||
pub mod burn;
|
||||
pub mod initialize;
|
||||
pub mod mint;
|
||||
pub mod new_definition;
|
||||
pub mod print_nft;
|
||||
pub mod transfer;
|
||||
|
||||
mod tests;
|
||||
71
programs/token/src/mint.rs
Normal file
71
programs/token/src/mint.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
program::AccountPostState,
|
||||
};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
pub fn mint(
|
||||
definition_account: AccountWithMetadata,
|
||||
user_holding_account: AccountWithMetadata,
|
||||
amount_to_mint: u128,
|
||||
) -> Vec<AccountPostState> {
|
||||
assert!(
|
||||
definition_account.is_authorized,
|
||||
"Definition authorization is missing"
|
||||
);
|
||||
|
||||
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
|
||||
.expect("Token Definition account must be valid");
|
||||
let mut holding = if user_holding_account.account == Account::default() {
|
||||
TokenHolding::zeroized_from_definition(definition_account.account_id, &definition)
|
||||
} else {
|
||||
TokenHolding::try_from(&user_holding_account.account.data)
|
||||
.expect("Token Holding account must be valid")
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
definition_account.account_id,
|
||||
holding.definition_id(),
|
||||
"Mismatch Token Definition and Token Holding"
|
||||
);
|
||||
|
||||
match (&mut definition, &mut holding) {
|
||||
(
|
||||
TokenDefinition::Fungible {
|
||||
name: _,
|
||||
metadata_id: _,
|
||||
total_supply,
|
||||
},
|
||||
TokenHolding::Fungible {
|
||||
definition_id: _,
|
||||
balance,
|
||||
},
|
||||
) => {
|
||||
*balance = balance
|
||||
.checked_add(amount_to_mint)
|
||||
.expect("Balance overflow on minting");
|
||||
|
||||
*total_supply = total_supply
|
||||
.checked_add(amount_to_mint)
|
||||
.expect("Total supply overflow");
|
||||
}
|
||||
(
|
||||
TokenDefinition::NonFungible { .. },
|
||||
TokenHolding::NftMaster { .. } | TokenHolding::NftPrintedCopy { .. },
|
||||
) => {
|
||||
panic!("Cannot mint additional supply for Non-Fungible Tokens");
|
||||
}
|
||||
_ => panic!("Mismatched Token Definition and Token Holding types"),
|
||||
}
|
||||
|
||||
let mut definition_post = definition_account.account;
|
||||
definition_post.data = Data::from(&definition);
|
||||
|
||||
let mut holding_post = user_holding_account.account;
|
||||
holding_post.data = Data::from(&holding);
|
||||
|
||||
vec![
|
||||
AccountPostState::new(definition_post),
|
||||
AccountPostState::new_claimed_if_default(holding_post),
|
||||
]
|
||||
}
|
||||
124
programs/token/src/new_definition.rs
Normal file
124
programs/token/src/new_definition.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
program::AccountPostState,
|
||||
};
|
||||
use token_core::{
|
||||
NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding, TokenMetadata,
|
||||
};
|
||||
|
||||
pub fn new_fungible_definition(
|
||||
definition_target_account: AccountWithMetadata,
|
||||
holding_target_account: AccountWithMetadata,
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
) -> 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"
|
||||
);
|
||||
|
||||
let token_definition = TokenDefinition::Fungible {
|
||||
name,
|
||||
total_supply,
|
||||
metadata_id: None,
|
||||
};
|
||||
let token_holding = TokenHolding::Fungible {
|
||||
definition_id: definition_target_account.account_id,
|
||||
balance: total_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),
|
||||
AccountPostState::new_claimed(holding_target_account_post),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn new_definition_with_metadata(
|
||||
definition_target_account: AccountWithMetadata,
|
||||
holding_target_account: AccountWithMetadata,
|
||||
metadata_target_account: AccountWithMetadata,
|
||||
new_definition: NewTokenDefinition,
|
||||
metadata: NewTokenMetadata,
|
||||
) -> 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_eq!(
|
||||
metadata_target_account.account,
|
||||
Account::default(),
|
||||
"Metadata target account must have default values"
|
||||
);
|
||||
|
||||
let (token_definition, token_holding) = match new_definition {
|
||||
NewTokenDefinition::Fungible { name, total_supply } => (
|
||||
TokenDefinition::Fungible {
|
||||
name,
|
||||
total_supply,
|
||||
metadata_id: Some(metadata_target_account.account_id),
|
||||
},
|
||||
TokenHolding::Fungible {
|
||||
definition_id: definition_target_account.account_id,
|
||||
balance: total_supply,
|
||||
},
|
||||
),
|
||||
NewTokenDefinition::NonFungible {
|
||||
name,
|
||||
printable_supply,
|
||||
} => (
|
||||
TokenDefinition::NonFungible {
|
||||
name,
|
||||
printable_supply,
|
||||
metadata_id: metadata_target_account.account_id,
|
||||
},
|
||||
TokenHolding::NftMaster {
|
||||
definition_id: definition_target_account.account_id,
|
||||
print_balance: printable_supply,
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
let token_metadata = TokenMetadata {
|
||||
definition_id: definition_target_account.account_id,
|
||||
standard: metadata.standard,
|
||||
uri: metadata.uri,
|
||||
creators: metadata.creators,
|
||||
primary_sale_date: 0u64, // TODO #261: future works to implement this
|
||||
};
|
||||
|
||||
let mut definition_target_account_post = definition_target_account.account.clone();
|
||||
definition_target_account_post.data = Data::from(&token_definition);
|
||||
|
||||
let mut holding_target_account_post = holding_target_account.account.clone();
|
||||
holding_target_account_post.data = Data::from(&token_holding);
|
||||
|
||||
let mut metadata_target_account_post = metadata_target_account.account.clone();
|
||||
metadata_target_account_post.data = Data::from(&token_metadata);
|
||||
|
||||
vec![
|
||||
AccountPostState::new_claimed(definition_target_account_post),
|
||||
AccountPostState::new_claimed(holding_target_account_post),
|
||||
AccountPostState::new_claimed(metadata_target_account_post),
|
||||
]
|
||||
}
|
||||
54
programs/token/src/print_nft.rs
Normal file
54
programs/token/src/print_nft.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
program::AccountPostState,
|
||||
};
|
||||
use token_core::TokenHolding;
|
||||
|
||||
pub fn print_nft(
|
||||
master_account: AccountWithMetadata,
|
||||
printed_account: AccountWithMetadata,
|
||||
) -> Vec<AccountPostState> {
|
||||
assert!(
|
||||
master_account.is_authorized,
|
||||
"Master NFT Account must be authorized"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
printed_account.account,
|
||||
Account::default(),
|
||||
"Printed Account must be uninitialized"
|
||||
);
|
||||
|
||||
let mut master_account_data =
|
||||
TokenHolding::try_from(&master_account.account.data).expect("Invalid Token Holding data");
|
||||
|
||||
let TokenHolding::NftMaster {
|
||||
definition_id,
|
||||
print_balance,
|
||||
} = &mut master_account_data
|
||||
else {
|
||||
panic!("Invalid Token Holding provided as NFT Master Account");
|
||||
};
|
||||
|
||||
let definition_id = *definition_id;
|
||||
|
||||
assert!(
|
||||
*print_balance > 1,
|
||||
"Insufficient balance to print another NFT copy"
|
||||
);
|
||||
*print_balance -= 1;
|
||||
|
||||
let mut master_account_post = master_account.account;
|
||||
master_account_post.data = Data::from(&master_account_data);
|
||||
|
||||
let mut printed_account_post = printed_account.account;
|
||||
printed_account_post.data = Data::from(&TokenHolding::NftPrintedCopy {
|
||||
definition_id,
|
||||
owned: true,
|
||||
});
|
||||
|
||||
vec![
|
||||
AccountPostState::new(master_account_post),
|
||||
AccountPostState::new_claimed(printed_account_post),
|
||||
]
|
||||
}
|
||||
1040
programs/token/src/tests.rs
Normal file
1040
programs/token/src/tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
110
programs/token/src/transfer.rs
Normal file
110
programs/token/src/transfer.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
program::AccountPostState,
|
||||
};
|
||||
use token_core::TokenHolding;
|
||||
|
||||
pub fn transfer(
|
||||
sender: AccountWithMetadata,
|
||||
recipient: AccountWithMetadata,
|
||||
balance_to_move: u128,
|
||||
) -> Vec<AccountPostState> {
|
||||
assert!(sender.is_authorized, "Sender authorization is missing");
|
||||
|
||||
let mut sender_holding =
|
||||
TokenHolding::try_from(&sender.account.data).expect("Invalid sender data");
|
||||
|
||||
let mut recipient_holding = if recipient.account == Account::default() {
|
||||
TokenHolding::zeroized_clone_from(&sender_holding)
|
||||
} else {
|
||||
TokenHolding::try_from(&recipient.account.data).expect("Invalid recipient data")
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
sender_holding.definition_id(),
|
||||
recipient_holding.definition_id(),
|
||||
"Sender and recipient definition id mismatch"
|
||||
);
|
||||
|
||||
match (&mut sender_holding, &mut recipient_holding) {
|
||||
(
|
||||
TokenHolding::Fungible {
|
||||
definition_id: _,
|
||||
balance: sender_balance,
|
||||
},
|
||||
TokenHolding::Fungible {
|
||||
definition_id: _,
|
||||
balance: recipient_balance,
|
||||
},
|
||||
) => {
|
||||
*sender_balance = sender_balance
|
||||
.checked_sub(balance_to_move)
|
||||
.expect("Insufficient balance");
|
||||
|
||||
*recipient_balance = recipient_balance
|
||||
.checked_add(balance_to_move)
|
||||
.expect("Recipient balance overflow");
|
||||
}
|
||||
(
|
||||
TokenHolding::NftMaster {
|
||||
definition_id: _,
|
||||
print_balance: sender_print_balance,
|
||||
},
|
||||
TokenHolding::NftMaster {
|
||||
definition_id: _,
|
||||
print_balance: recipient_print_balance,
|
||||
},
|
||||
) => {
|
||||
assert_eq!(
|
||||
*recipient_print_balance, 0,
|
||||
"Invalid balance in recipient account for NFT transfer"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
*sender_print_balance, balance_to_move,
|
||||
"Invalid balance for NFT Master transfer"
|
||||
);
|
||||
|
||||
std::mem::swap(sender_print_balance, recipient_print_balance);
|
||||
}
|
||||
(
|
||||
TokenHolding::NftPrintedCopy {
|
||||
definition_id: _,
|
||||
owned: sender_owned,
|
||||
},
|
||||
TokenHolding::NftPrintedCopy {
|
||||
definition_id: _,
|
||||
owned: recipient_owned,
|
||||
},
|
||||
) => {
|
||||
assert_eq!(
|
||||
balance_to_move, 1,
|
||||
"Invalid balance for NFT Printed Copy transfer"
|
||||
);
|
||||
|
||||
assert!(*sender_owned, "Sender does not own the NFT Printed Copy");
|
||||
|
||||
assert!(
|
||||
!*recipient_owned,
|
||||
"Recipient already owns the NFT Printed Copy"
|
||||
);
|
||||
|
||||
*sender_owned = false;
|
||||
*recipient_owned = true;
|
||||
}
|
||||
_ => {
|
||||
panic!("Mismatched token holding types for transfer");
|
||||
}
|
||||
};
|
||||
|
||||
let mut sender_post = sender.account;
|
||||
sender_post.data = Data::from(&sender_holding);
|
||||
|
||||
let mut recipient_post = recipient.account;
|
||||
recipient_post.data = Data::from(&recipient_holding);
|
||||
|
||||
vec![
|
||||
AccountPostState::new(sender_post),
|
||||
AccountPostState::new_claimed_if_default(recipient_post),
|
||||
]
|
||||
}
|
||||
@ -8,6 +8,7 @@ nssa_core.workspace = true
|
||||
nssa.workspace = true
|
||||
common.workspace = true
|
||||
key_protocol.workspace = true
|
||||
token_core.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@ -4,10 +4,10 @@ use clap::Subcommand;
|
||||
use itertools::Itertools as _;
|
||||
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
||||
use nssa::{Account, PublicKey, program::Program};
|
||||
use serde::Serialize;
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
use crate::{
|
||||
TokenDefinition, TokenHolding, WalletCore,
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
helperfunctions::{AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix},
|
||||
};
|
||||
@ -111,83 +111,26 @@ impl WalletSubcommand for NewSubcommand {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AuthenticatedTransferAccountView {
|
||||
pub balance: u128,
|
||||
}
|
||||
|
||||
impl From<nssa::Account> for AuthenticatedTransferAccountView {
|
||||
fn from(value: nssa::Account) -> Self {
|
||||
Self {
|
||||
balance: value.balance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TokedDefinitionAccountView {
|
||||
pub account_type: String,
|
||||
pub name: String,
|
||||
pub total_supply: u128,
|
||||
}
|
||||
|
||||
impl From<TokenDefinition> for TokedDefinitionAccountView {
|
||||
fn from(value: TokenDefinition) -> Self {
|
||||
Self {
|
||||
account_type: "Token definition".to_string(),
|
||||
name: {
|
||||
// Assuming, that name does not have UTF-8 NULL and all zeroes are padding.
|
||||
let name_trimmed: Vec<_> =
|
||||
value.name.into_iter().take_while(|ch| *ch != 0).collect();
|
||||
String::from_utf8(name_trimmed).unwrap_or(hex::encode(value.name))
|
||||
},
|
||||
total_supply: value.total_supply,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TokedHoldingAccountView {
|
||||
pub account_type: String,
|
||||
pub definition_id: String,
|
||||
pub balance: u128,
|
||||
}
|
||||
|
||||
impl From<TokenHolding> for TokedHoldingAccountView {
|
||||
fn from(value: TokenHolding) -> Self {
|
||||
Self {
|
||||
account_type: "Token holding".to_string(),
|
||||
definition_id: value.definition_id.to_string(),
|
||||
balance: value.balance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats account details for display, returning (description, json_view)
|
||||
fn format_account_details(account: &Account) -> (String, String) {
|
||||
let auth_tr_prog_id = Program::authenticated_transfer_program().id();
|
||||
let token_prog_id = Program::token().id();
|
||||
|
||||
match &account.program_owner {
|
||||
_ if account.program_owner == auth_tr_prog_id => {
|
||||
let acc_view: AuthenticatedTransferAccountView = account.clone().into();
|
||||
(
|
||||
"Account owned by authenticated transfer program".to_string(),
|
||||
serde_json::to_string(&acc_view).unwrap(),
|
||||
)
|
||||
}
|
||||
_ if account.program_owner == token_prog_id => {
|
||||
if let Some(token_def) = TokenDefinition::parse(&account.data) {
|
||||
let acc_view: TokedDefinitionAccountView = token_def.into();
|
||||
o if *o == auth_tr_prog_id => (
|
||||
"Account owned by authenticated transfer program".to_string(),
|
||||
serde_json::to_string(&account).unwrap(),
|
||||
),
|
||||
o if *o == token_prog_id => {
|
||||
if let Ok(token_def) = TokenDefinition::try_from(&account.data) {
|
||||
(
|
||||
"Definition account owned by token program".to_string(),
|
||||
serde_json::to_string(&acc_view).unwrap(),
|
||||
serde_json::to_string(&token_def).unwrap(),
|
||||
)
|
||||
} else if let Some(token_hold) = TokenHolding::parse(&account.data) {
|
||||
let acc_view: TokedHoldingAccountView = token_hold.into();
|
||||
} else if let Ok(token_hold) = TokenHolding::try_from(&account.data) {
|
||||
(
|
||||
"Holding account owned by token program".to_string(),
|
||||
serde_json::to_string(&acc_view).unwrap(),
|
||||
serde_json::to_string(&token_hold).unwrap(),
|
||||
)
|
||||
} else {
|
||||
let account_hr: HumanReadableAccount = account.clone().into();
|
||||
@ -410,52 +353,3 @@ impl WalletSubcommand for AccountSubcommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nssa::AccountId;
|
||||
|
||||
use crate::cli::account::{TokedDefinitionAccountView, TokenDefinition};
|
||||
|
||||
#[test]
|
||||
fn test_invalid_utf_8_name_of_token() {
|
||||
let token_def = TokenDefinition {
|
||||
account_type: 1,
|
||||
name: [137, 12, 14, 3, 5, 4],
|
||||
total_supply: 100,
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
};
|
||||
|
||||
let token_def_view: TokedDefinitionAccountView = token_def.into();
|
||||
|
||||
assert_eq!(token_def_view.name, "890c0e030504");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_utf_8_name_of_token_all_bytes() {
|
||||
let token_def = TokenDefinition {
|
||||
account_type: 1,
|
||||
name: [240, 159, 146, 150, 66, 66],
|
||||
total_supply: 100,
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
};
|
||||
|
||||
let token_def_view: TokedDefinitionAccountView = token_def.into();
|
||||
|
||||
assert_eq!(token_def_view.name, "💖BB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_utf_8_name_of_token_less_bytes() {
|
||||
let token_def = TokenDefinition {
|
||||
account_type: 1,
|
||||
name: [78, 65, 77, 69, 0, 0],
|
||||
total_supply: 100,
|
||||
metadata_id: AccountId::new([0; 32]),
|
||||
};
|
||||
|
||||
let token_def_view: TokedDefinitionAccountView = token_def.into();
|
||||
|
||||
assert_eq!(token_def_view.name, "NAME");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1258,14 +1258,6 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
|
||||
name,
|
||||
total_supply,
|
||||
} => {
|
||||
let name = name.as_bytes();
|
||||
if name.len() > 6 {
|
||||
// TODO: return error
|
||||
panic!("Name length mismatch");
|
||||
}
|
||||
let mut name_bytes = [0; 6];
|
||||
name_bytes[..name.len()].copy_from_slice(name);
|
||||
|
||||
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
|
||||
let supply_account_id: AccountId = supply_account_id.parse().unwrap();
|
||||
|
||||
@ -1273,7 +1265,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
|
||||
.send_new_definition_private_owned_definiton_and_supply(
|
||||
definition_account_id,
|
||||
supply_account_id,
|
||||
name_bytes,
|
||||
name,
|
||||
total_supply,
|
||||
)
|
||||
.await?;
|
||||
@ -1307,14 +1299,6 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
|
||||
name,
|
||||
total_supply,
|
||||
} => {
|
||||
let name = name.as_bytes();
|
||||
if name.len() > 6 {
|
||||
// TODO: return error
|
||||
panic!("Name length mismatch");
|
||||
}
|
||||
let mut name_bytes = [0; 6];
|
||||
name_bytes[..name.len()].copy_from_slice(name);
|
||||
|
||||
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
|
||||
let supply_account_id: AccountId = supply_account_id.parse().unwrap();
|
||||
|
||||
@ -1322,7 +1306,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
|
||||
.send_new_definition_private_owned_definiton(
|
||||
definition_account_id,
|
||||
supply_account_id,
|
||||
name_bytes,
|
||||
name,
|
||||
total_supply,
|
||||
)
|
||||
.await?;
|
||||
@ -1353,14 +1337,6 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
|
||||
name,
|
||||
total_supply,
|
||||
} => {
|
||||
let name = name.as_bytes();
|
||||
if name.len() > 6 {
|
||||
// TODO: return error
|
||||
panic!("Name length mismatch");
|
||||
}
|
||||
let mut name_bytes = [0; 6];
|
||||
name_bytes[..name.len()].copy_from_slice(name);
|
||||
|
||||
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
|
||||
let supply_account_id: AccountId = supply_account_id.parse().unwrap();
|
||||
|
||||
@ -1368,7 +1344,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
|
||||
.send_new_definition_private_owned_supply(
|
||||
definition_account_id,
|
||||
supply_account_id,
|
||||
name_bytes,
|
||||
name,
|
||||
total_supply,
|
||||
)
|
||||
.await?;
|
||||
@ -1399,18 +1375,11 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
|
||||
name,
|
||||
total_supply,
|
||||
} => {
|
||||
let name = name.as_bytes();
|
||||
if name.len() > 6 {
|
||||
// TODO: return error
|
||||
panic!();
|
||||
}
|
||||
let mut name_bytes = [0; 6];
|
||||
name_bytes[..name.len()].copy_from_slice(name);
|
||||
Token(wallet_core)
|
||||
.send_new_definition(
|
||||
definition_account_id.parse().unwrap(),
|
||||
supply_account_id.parse().unwrap(),
|
||||
name_bytes,
|
||||
name,
|
||||
total_supply,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -18,9 +18,7 @@ use nssa::{
|
||||
circuit::ProgramWithDependencies, message::EncryptedAccountData,
|
||||
},
|
||||
};
|
||||
use nssa_core::{
|
||||
Commitment, MembershipProof, SharedSecretKey, account::Data, program::InstructionData,
|
||||
};
|
||||
use nssa_core::{Commitment, MembershipProof, SharedSecretKey, program::InstructionData};
|
||||
pub use privacy_preserving_tx::PrivacyPreservingAccount;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
@ -45,82 +43,6 @@ pub enum AccDecodeData {
|
||||
Decode(nssa_core::SharedSecretKey, AccountId),
|
||||
}
|
||||
|
||||
const TOKEN_DEFINITION_DATA_SIZE: usize = 55;
|
||||
|
||||
const TOKEN_HOLDING_TYPE: u8 = 1;
|
||||
const TOKEN_HOLDING_DATA_SIZE: usize = 49;
|
||||
const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0;
|
||||
const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2;
|
||||
|
||||
struct TokenDefinition {
|
||||
#[allow(unused)]
|
||||
account_type: u8,
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
#[allow(unused)]
|
||||
metadata_id: AccountId,
|
||||
}
|
||||
|
||||
struct TokenHolding {
|
||||
#[allow(unused)]
|
||||
account_type: u8,
|
||||
definition_id: AccountId,
|
||||
balance: u128,
|
||||
}
|
||||
|
||||
impl TokenDefinition {
|
||||
fn parse(data: &Data) -> Option<Self> {
|
||||
let data = Vec::<u8>::from(data.clone());
|
||||
|
||||
if data.len() != TOKEN_DEFINITION_DATA_SIZE {
|
||||
None
|
||||
} else {
|
||||
let account_type = data[0];
|
||||
let name = data[1..7].try_into().expect("Name must be a 6 bytes");
|
||||
let total_supply = u128::from_le_bytes(
|
||||
data[7..23]
|
||||
.try_into()
|
||||
.expect("Total supply must be 16 bytes little-endian"),
|
||||
);
|
||||
let metadata_id = AccountId::new(
|
||||
data[23..TOKEN_DEFINITION_DATA_SIZE]
|
||||
.try_into()
|
||||
.expect("Token Program expects valid Account Id for Metadata"),
|
||||
);
|
||||
|
||||
let this = Some(Self {
|
||||
account_type,
|
||||
name,
|
||||
total_supply,
|
||||
metadata_id,
|
||||
});
|
||||
|
||||
match account_type {
|
||||
TOKEN_STANDARD_NONFUNGIBLE if total_supply != 1 => None,
|
||||
TOKEN_STANDARD_FUNGIBLE_TOKEN if metadata_id != AccountId::new([0; 32]) => None,
|
||||
_ => this,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenHolding {
|
||||
fn parse(data: &[u8]) -> Option<Self> {
|
||||
if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE {
|
||||
None
|
||||
} else {
|
||||
let account_type = data[0];
|
||||
let definition_id = AccountId::new(data[1..33].try_into().unwrap());
|
||||
let balance = u128::from_le_bytes(data[33..].try_into().unwrap());
|
||||
Some(Self {
|
||||
definition_id,
|
||||
balance,
|
||||
account_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WalletCore {
|
||||
config_path: PathBuf,
|
||||
storage: WalletChainStore,
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
use common::{error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse};
|
||||
use nssa::{AccountId, ProgramId, program::Program};
|
||||
use nssa_core::program::PdaSeed;
|
||||
use token_core::TokenHolding;
|
||||
|
||||
use crate::{TokenHolding, WalletCore};
|
||||
use crate::WalletCore;
|
||||
|
||||
fn compute_pool_pda(
|
||||
amm_program_id: ProgramId,
|
||||
@ -123,12 +124,12 @@ impl Amm<'_> {
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
|
||||
let definition_token_a_id = TokenHolding::parse(&user_a_acc.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id;
|
||||
let definition_token_b_id = TokenHolding::parse(&user_b_acc.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id;
|
||||
let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id();
|
||||
let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))?
|
||||
.definition_id();
|
||||
|
||||
let amm_pool =
|
||||
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id);
|
||||
@ -208,12 +209,12 @@ impl Amm<'_> {
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
|
||||
let definition_token_a_id = TokenHolding::parse(&user_a_acc.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id;
|
||||
let definition_token_b_id = TokenHolding::parse(&user_b_acc.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_b))?
|
||||
.definition_id;
|
||||
let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id();
|
||||
let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))?
|
||||
.definition_id();
|
||||
|
||||
let amm_pool =
|
||||
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id);
|
||||
@ -242,14 +243,14 @@ impl Amm<'_> {
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
|
||||
let token_holder_a = TokenHolding::parse(&token_holder_acc_a.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?;
|
||||
let token_holder_b = TokenHolding::parse(&token_holder_acc_b.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_b))?;
|
||||
let token_holder_a = TokenHolding::try_from(&token_holder_acc_a.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))?;
|
||||
let token_holder_b = TokenHolding::try_from(&token_holder_acc_b.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))?;
|
||||
|
||||
if token_holder_a.definition_id == token_definition_id {
|
||||
if token_holder_a.definition_id() == token_definition_id {
|
||||
account_id_auth = user_holding_a;
|
||||
} else if token_holder_b.definition_id == token_definition_id {
|
||||
} else if token_holder_b.definition_id() == token_definition_id {
|
||||
account_id_auth = user_holding_b;
|
||||
} else {
|
||||
return Err(ExecutionFailureKind::AccountDataError(token_definition_id));
|
||||
@ -309,12 +310,12 @@ impl Amm<'_> {
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
|
||||
let definition_token_a_id = TokenHolding::parse(&user_a_acc.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id;
|
||||
let definition_token_b_id = TokenHolding::parse(&user_b_acc.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id;
|
||||
let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id();
|
||||
let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))?
|
||||
.definition_id();
|
||||
|
||||
let amm_pool =
|
||||
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id);
|
||||
@ -395,12 +396,12 @@ impl Amm<'_> {
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
|
||||
let definition_token_a_id = TokenHolding::parse(&user_a_acc.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id;
|
||||
let definition_token_b_id = TokenHolding::parse(&user_b_acc.data)
|
||||
.ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id;
|
||||
let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))?
|
||||
.definition_id();
|
||||
let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data)
|
||||
.map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))?
|
||||
.definition_id();
|
||||
|
||||
let amm_pool =
|
||||
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use common::{error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse};
|
||||
use nssa::{AccountId, program::Program};
|
||||
use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey};
|
||||
use token_core::Instruction;
|
||||
|
||||
use crate::{PrivacyPreservingAccount, WalletCore};
|
||||
|
||||
@ -11,15 +12,12 @@ impl Token<'_> {
|
||||
&self,
|
||||
definition_account_id: AccountId,
|
||||
supply_account_id: AccountId,
|
||||
name: [u8; 6],
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![definition_account_id, supply_account_id];
|
||||
let program_id = nssa::program::Program::token().id();
|
||||
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
|
||||
let mut instruction = vec![0u8; 23];
|
||||
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
|
||||
instruction[17..].copy_from_slice(&name);
|
||||
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program_id,
|
||||
account_ids,
|
||||
@ -39,10 +37,10 @@ impl Token<'_> {
|
||||
&self,
|
||||
definition_account_id: AccountId,
|
||||
supply_account_id: AccountId,
|
||||
name: [u8; 6],
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_definition(name, total_supply);
|
||||
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -69,10 +67,10 @@ impl Token<'_> {
|
||||
&self,
|
||||
definition_account_id: AccountId,
|
||||
supply_account_id: AccountId,
|
||||
name: [u8; 6],
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_definition(name, total_supply);
|
||||
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -99,10 +97,10 @@ impl Token<'_> {
|
||||
&self,
|
||||
definition_account_id: AccountId,
|
||||
supply_account_id: AccountId,
|
||||
name: [u8; 6],
|
||||
name: String,
|
||||
total_supply: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_definition(name, total_supply);
|
||||
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -132,11 +130,9 @@ impl Token<'_> {
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![sender_account_id, recipient_account_id];
|
||||
let program_id = nssa::program::Program::token().id();
|
||||
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
|
||||
// 0x00 || 0x00 || 0x00].
|
||||
let mut instruction = vec![0u8; 23];
|
||||
instruction[0] = 0x01;
|
||||
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
|
||||
let instruction = Instruction::Transfer {
|
||||
amount_to_transfer: amount,
|
||||
};
|
||||
let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else {
|
||||
return Err(ExecutionFailureKind::SequencerError);
|
||||
};
|
||||
@ -170,7 +166,9 @@ impl Token<'_> {
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_transfer(amount);
|
||||
let instruction = Instruction::Transfer {
|
||||
amount_to_transfer: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -199,7 +197,9 @@ impl Token<'_> {
|
||||
recipient_ipk: IncomingViewingPublicKey,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_transfer(amount);
|
||||
let instruction = Instruction::Transfer {
|
||||
amount_to_transfer: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -230,7 +230,9 @@ impl Token<'_> {
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_transfer(amount);
|
||||
let instruction = Instruction::Transfer {
|
||||
amount_to_transfer: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -259,7 +261,9 @@ impl Token<'_> {
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_transfer(amount);
|
||||
let instruction = Instruction::Transfer {
|
||||
amount_to_transfer: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -289,7 +293,9 @@ impl Token<'_> {
|
||||
recipient_ipk: IncomingViewingPublicKey,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_transfer(amount);
|
||||
let instruction = Instruction::Transfer {
|
||||
amount_to_transfer: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -322,7 +328,9 @@ impl Token<'_> {
|
||||
amount: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![definition_account_id, holder_account_id];
|
||||
let instruction = token_program_preparation_burn(amount);
|
||||
let instruction = Instruction::Burn {
|
||||
amount_to_burn: amount,
|
||||
};
|
||||
|
||||
let Ok(nonces) = self.0.get_accounts_nonces(vec![holder_account_id]).await else {
|
||||
return Err(ExecutionFailureKind::SequencerError);
|
||||
@ -355,7 +363,9 @@ impl Token<'_> {
|
||||
holder_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_burn(amount);
|
||||
let instruction = Instruction::Burn {
|
||||
amount_to_burn: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -383,7 +393,9 @@ impl Token<'_> {
|
||||
holder_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_burn(amount);
|
||||
let instruction = Instruction::Burn {
|
||||
amount_to_burn: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -412,7 +424,9 @@ impl Token<'_> {
|
||||
holder_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_burn(amount);
|
||||
let instruction = Instruction::Burn {
|
||||
amount_to_burn: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -442,7 +456,9 @@ impl Token<'_> {
|
||||
amount: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![definition_account_id, holder_account_id];
|
||||
let instruction = token_program_preparation_mint(amount);
|
||||
let instruction = Instruction::Mint {
|
||||
amount_to_mint: amount,
|
||||
};
|
||||
|
||||
let Ok(nonces) = self
|
||||
.0
|
||||
@ -481,7 +497,9 @@ impl Token<'_> {
|
||||
holder_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_mint(amount);
|
||||
let instruction = Instruction::Mint {
|
||||
amount_to_mint: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -510,7 +528,9 @@ impl Token<'_> {
|
||||
holder_ipk: IncomingViewingPublicKey,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_mint(amount);
|
||||
let instruction = Instruction::Mint {
|
||||
amount_to_mint: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -541,7 +561,9 @@ impl Token<'_> {
|
||||
holder_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_mint(amount);
|
||||
let instruction = Instruction::Mint {
|
||||
amount_to_mint: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -570,7 +592,9 @@ impl Token<'_> {
|
||||
holder_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_mint(amount);
|
||||
let instruction = Instruction::Mint {
|
||||
amount_to_mint: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -600,7 +624,9 @@ impl Token<'_> {
|
||||
holder_ipk: IncomingViewingPublicKey,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = token_program_preparation_mint(amount);
|
||||
let instruction = Instruction::Mint {
|
||||
amount_to_mint: amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
@ -626,42 +652,3 @@ impl Token<'_> {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn token_program_preparation_transfer(amount: u128) -> Vec<u8> {
|
||||
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
|
||||
// 0x00 || 0x00 || 0x00].
|
||||
let mut instruction = vec![0u8; 23];
|
||||
instruction[0] = 0x01;
|
||||
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
fn token_program_preparation_definition(name: [u8; 6], total_supply: u128) -> Vec<u8> {
|
||||
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
|
||||
let mut instruction = vec![0u8; 23];
|
||||
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
|
||||
instruction[17..].copy_from_slice(&name);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
fn token_program_preparation_burn(amount: u128) -> Vec<u8> {
|
||||
// Instruction must be: [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
|
||||
// 0x00 || 0x00 || 0x00].
|
||||
let mut instruction = vec![0; 23];
|
||||
instruction[0] = 0x03;
|
||||
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
fn token_program_preparation_mint(amount: u128) -> Vec<u8> {
|
||||
// Instruction must be: [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
|
||||
// 0x00 || 0x00 || 0x00].
|
||||
let mut instruction = vec![0; 23];
|
||||
instruction[0] = 0x04;
|
||||
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user