mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-02 13:23:10 +00:00
Merge branch 'main' into Pravdyvyi/wallet-redund-inputs-functions-refactor
This commit is contained in:
commit
d0931c83db
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -2657,6 +2657,8 @@ name = "nssa"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"borsh",
|
||||
"bytemuck",
|
||||
"env_logger",
|
||||
"hex",
|
||||
"hex-literal 1.1.0",
|
||||
"log",
|
||||
@ -4756,6 +4758,7 @@ dependencies = [
|
||||
"nssa_core",
|
||||
"paste",
|
||||
"rand 0.8.5",
|
||||
"risc0-zkvm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
||||
111
README.md
111
README.md
@ -190,6 +190,7 @@ Commands:
|
||||
account Account view and sync subcommand
|
||||
pinata Pinata program interaction subcommand
|
||||
token Token program interaction subcommand
|
||||
amm AMM program interaction subcommand
|
||||
check-health Check the wallet can connect to the node and builtin local programs match the remote versions
|
||||
```
|
||||
|
||||
@ -604,13 +605,13 @@ wallet account new public
|
||||
Generated new account with account_id Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6
|
||||
```
|
||||
|
||||
Let's send 10 B tokens to this new account. We'll debit this from the supply account used in the creation of the token.
|
||||
Let's send 1000 B tokens to this new account. We'll debit this from the supply account used in the creation of the token.
|
||||
|
||||
```bash
|
||||
wallet token send \
|
||||
--from Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF \
|
||||
--to Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
--amount 10
|
||||
--amount 1000
|
||||
```
|
||||
|
||||
Let's inspect the public account:
|
||||
@ -620,7 +621,7 @@ wallet account get --account-id Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6
|
||||
|
||||
# Output:
|
||||
Holding account owned by token program
|
||||
{"account_type":"Token holding","definition_id":"GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii","balance":10}
|
||||
{"account_type":"Token holding","definition_id":"GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii","balance":1000}
|
||||
```
|
||||
|
||||
### Chain information
|
||||
@ -644,3 +645,107 @@ Last block id is 65537
|
||||
```
|
||||
|
||||
|
||||
### Automated Market Maker (AMM)
|
||||
|
||||
NSSA includes an AMM program that manages liquidity pools and enables swaps between custom tokens. To test this functionality, we first need to create a liquidity pool.
|
||||
|
||||
#### Creating a liquidity pool for a token pair
|
||||
|
||||
We start by creating a new pool for the tokens previously created. In return for providing liquidity, we will receive liquidity provider (LP) tokens, which represent our share of the pool and are required to withdraw liquidity later.
|
||||
|
||||
>[!NOTE]
|
||||
> The AMM program does not currently charge swap fees or distribute rewards to liquidity providers. LP tokens therefore only represent a proportional share of the pool reserves and do not provide additional value from swap activity. Fee support for liquidity providers will be added in future versions of the AMM program.
|
||||
|
||||
To hold these LP tokens, we first create a new account:
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf
|
||||
```
|
||||
|
||||
Next, we initialize the liquidity pool by depositing tokens A and B and specifying the account that will receive the LP tokens:
|
||||
|
||||
```bash
|
||||
wallet amm new \
|
||||
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
|
||||
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
--user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \
|
||||
--balance-a 100 \
|
||||
--balance-b 200
|
||||
```
|
||||
|
||||
The newly created account is owned by the token program, meaning that LP tokens are managed by the same token infrastructure as regular tokens.
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf
|
||||
|
||||
# Output:
|
||||
Holding account owned by token program
|
||||
{"account_type":"Token holding","definition_id":"7BeDS3e28MA5Err7gBswmR1fUKdHXqmUpTefNPu3pJ9i","balance":100}
|
||||
```
|
||||
|
||||
If you inspect the `user-holding-a` and `user-holding-b` accounts passed to the `wallet amm new` command, you will see that 100 and 200 tokens were deducted, respectively. These tokens now reside in the liquidity pool and are available for swaps by any user.
|
||||
|
||||
|
||||
#### Swaping
|
||||
|
||||
Token swaps can be performed using the wallet amm swap command:
|
||||
|
||||
```bash
|
||||
wallet amm swap \
|
||||
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
|
||||
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
# The amount of tokens to swap
|
||||
--amount-in 5 \
|
||||
# The minimum number of tokens expected in return
|
||||
--min-amount-out 8 \
|
||||
# The definition ID of the token being provided to the swap
|
||||
# In this case, we are swapping from TOKENA to TOKENB, and so this is the definition ID of TOKENA
|
||||
--token-definition 4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7
|
||||
```
|
||||
|
||||
Once executed, 5 tokens are deducted from the Token A holding account and the corresponding amount (determined by the pool’s pricing function) is credited to the Token B holding account.
|
||||
|
||||
|
||||
#### Withdrawing liquidity from the pool
|
||||
|
||||
Liquidity providers can withdraw assets from the pool by redeeming (burning) LP tokens. The amount of tokens received is proportional to the share of LP tokens being redeemed relative to the total LP supply.
|
||||
|
||||
This operation is performed using the `wallet amm remove-liquidity` command:
|
||||
|
||||
```bash
|
||||
wallet amm remove-liquidity \
|
||||
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
|
||||
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
--user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \
|
||||
--balance-lp 20 \
|
||||
--min-amount-a 1 \
|
||||
--min-amount-b 1
|
||||
```
|
||||
|
||||
This instruction burns `balance-lp` LP tokens from the user’s LP holding account. In exchange, the AMM transfers tokens A and B from the pool’s vault accounts to the user’s holding accounts, according to the current pool reserves.
|
||||
|
||||
The `min-amount-a` and `min-amount-b` parameters specify the minimum acceptable amounts of tokens A and B to be received. If the computed outputs fall below either threshold, the instruction fails, protecting the user against unfavorable pool state changes.
|
||||
|
||||
#### Adding liquidity to the pool
|
||||
|
||||
Additional liquidity can be added to an existing pool by depositing tokens A and B in the ratio implied by the current pool reserves. In return, new LP tokens are minted to represent the user’s proportional share of the pool.
|
||||
|
||||
This is done using the `wallet amm add-liquidity` command:
|
||||
|
||||
```bash
|
||||
wallet amm add-liquidity \
|
||||
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
|
||||
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
--user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \
|
||||
--min-amount-lp 1 \
|
||||
--max-amount-a 10 \
|
||||
--max-amount-b 10
|
||||
```
|
||||
|
||||
In this instruction, `max-amount-a` and `max-amount-b` define upper bounds on the number of tokens A and B that may be withdrawn from the user’s accounts. The AMM computes the actual required amounts based on the pool’s reserve ratio.
|
||||
|
||||
The `min-amount-lp` parameter specifies the minimum number of LP tokens that must be minted for the transaction to succeed. If the resulting LP token amount is below this threshold, the instruction fails.
|
||||
|
||||
|
||||
BIN
artifacts/program_methods/amm.bin
Normal file
BIN
artifacts/program_methods/amm.bin
Normal file
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.
@ -1,3 +1,4 @@
|
||||
use nssa::AccountId;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::rpc_primitives::errors::RpcError;
|
||||
@ -49,4 +50,6 @@ pub enum ExecutionFailureKind {
|
||||
SequencerClientError(#[from] SequencerClientError),
|
||||
#[error("Can not pay for operation")]
|
||||
InsufficientFundsError,
|
||||
#[error("Account {0} data is invalid")]
|
||||
AccountDataError(AccountId),
|
||||
}
|
||||
|
||||
@ -24,8 +24,9 @@ use wallet::{
|
||||
config::ConfigSubcommand,
|
||||
programs::{
|
||||
ArgsDefinitionOwned, ArgsHolderMaybeUnowned, ArgsHolderOwned, ArgsReceiverMaybeUnowned,
|
||||
ArgsSenderOwned, ArgsSupplyOwned, native_token_transfer::AuthTransferSubcommand,
|
||||
pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand,
|
||||
ArgsSenderOwned, ArgsSupplyOwned, amm::AmmProgramAgnosticSubcommand,
|
||||
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
||||
token::TokenProgramAgnosticSubcommand,
|
||||
},
|
||||
},
|
||||
config::PersistentStorage,
|
||||
@ -374,11 +375,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
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) ]
|
||||
// [ 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, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -497,7 +501,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
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, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -547,7 +553,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
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, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -641,11 +649,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
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) ]
|
||||
// [ 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, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -765,7 +776,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
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, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -823,7 +836,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
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, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -911,7 +926,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
0, 65, 32, 78, 65, 77, 69, 53, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -1014,8 +1031,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
.account;
|
||||
|
||||
assert_eq!(supply_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) ]
|
||||
// The data of a token holding account has the following layout:
|
||||
// [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ]
|
||||
assert_eq!(
|
||||
supply_acc.data.as_ref(),
|
||||
&[
|
||||
@ -1085,7 +1102,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
0, 65, 32, 78, 65, 77, 69, 47, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -1149,7 +1168,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
0, 65, 32, 78, 65, 77, 69, 52, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -1205,7 +1226,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
0, 65, 32, 78, 65, 77, 69, 57, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -1259,7 +1282,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
0, 65, 32, 78, 65, 77, 69, 52, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -1310,7 +1335,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
assert_eq!(
|
||||
definition_acc.data.as_ref(),
|
||||
&[
|
||||
0, 65, 32, 78, 65, 77, 69, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
0, 65, 32, 78, 65, 77, 69, 47, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -1416,17 +1443,20 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
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) ]
|
||||
// [ 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, 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
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(supply_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) ]
|
||||
// The data of a token holding account has the following layout:
|
||||
// [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ]
|
||||
assert_eq!(
|
||||
supply_acc.data.as_ref(),
|
||||
&[
|
||||
@ -1512,11 +1542,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
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) ]
|
||||
// [ 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, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -1658,11 +1691,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
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) ]
|
||||
// [ 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, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -1808,11 +1844,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
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) ]
|
||||
// [ 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, 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
|
||||
]
|
||||
);
|
||||
|
||||
@ -2829,6 +2868,438 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
info!("Success!");
|
||||
}
|
||||
|
||||
#[nssa_integration_test]
|
||||
pub async fn test_amm_public() {
|
||||
info!("########## test_amm_public ##########");
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
|
||||
// Create new account for the token definition
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: definition_account_id_1,
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public { cci: None },
|
||||
)))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
// Create new account for the token supply holder
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: supply_account_id_1,
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public { cci: None },
|
||||
)))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
// Create new account for receiving a token transaction
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: recipient_account_id_1,
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public { cci: None },
|
||||
)))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
// Create new account for the token definition
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: definition_account_id_2,
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public { cci: None },
|
||||
)))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
// Create new account for the token supply holder
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: supply_account_id_2,
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public { cci: None },
|
||||
)))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
// Create new account for receiving a token transaction
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: recipient_account_id_2,
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public { cci: None },
|
||||
)))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
|
||||
// Create new token
|
||||
let subcommand = TokenProgramAgnosticSubcommand::New {
|
||||
definition: ArgsDefinitionOwned {
|
||||
definition_account_id: make_public_account_input_from_str(
|
||||
&definition_account_id_1.to_string(),
|
||||
),
|
||||
},
|
||||
supply: ArgsSupplyOwned {
|
||||
supply_account_id: make_public_account_input_from_str(
|
||||
&supply_account_id_1.to_string(),
|
||||
),
|
||||
},
|
||||
name: "A NAM1".to_string(),
|
||||
total_supply: 37,
|
||||
};
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1`
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Send {
|
||||
from: ArgsSenderOwned {
|
||||
from: make_public_account_input_from_str(&supply_account_id_1.to_string()),
|
||||
},
|
||||
to: ArgsReceiverMaybeUnowned {
|
||||
to: Some(make_public_account_input_from_str(
|
||||
&recipient_account_id_1.to_string(),
|
||||
)),
|
||||
to_npk: None,
|
||||
to_ipk: None,
|
||||
},
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
// Create new token
|
||||
let subcommand = TokenProgramAgnosticSubcommand::New {
|
||||
definition: ArgsDefinitionOwned {
|
||||
definition_account_id: make_public_account_input_from_str(
|
||||
&definition_account_id_2.to_string(),
|
||||
),
|
||||
},
|
||||
supply: ArgsSupplyOwned {
|
||||
supply_account_id: make_public_account_input_from_str(
|
||||
&supply_account_id_2.to_string(),
|
||||
),
|
||||
},
|
||||
name: "A NAM2".to_string(),
|
||||
total_supply: 37,
|
||||
};
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1`
|
||||
let subcommand = TokenProgramAgnosticSubcommand::Send {
|
||||
from: ArgsSenderOwned {
|
||||
from: make_public_account_input_from_str(&supply_account_id_2.to_string()),
|
||||
},
|
||||
to: ArgsReceiverMaybeUnowned {
|
||||
to: Some(make_public_account_input_from_str(
|
||||
&recipient_account_id_2.to_string(),
|
||||
)),
|
||||
to_npk: None,
|
||||
to_ipk: None,
|
||||
},
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
info!("=================== SETUP FINISHED ===============");
|
||||
|
||||
// Create new AMM
|
||||
|
||||
// Setup accounts
|
||||
// Create new account for the user holding lp
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: user_holding_lp,
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public { cci: None },
|
||||
)))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
|
||||
// Send creation tx
|
||||
let subcommand = AmmProgramAgnosticSubcommand::New {
|
||||
user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()),
|
||||
user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()),
|
||||
user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()),
|
||||
balance_a: 3,
|
||||
balance_b: 3,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(Command::AMM(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let user_holding_a_acc = seq_client
|
||||
.get_account(recipient_account_id_1.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_b_acc = seq_client
|
||||
.get_account(recipient_account_id_2.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_lp_acc = seq_client
|
||||
.get_account(user_holding_lp.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
4
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
|
||||
4
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
|
||||
3
|
||||
);
|
||||
|
||||
info!("=================== AMM DEFINITION FINISHED ===============");
|
||||
|
||||
// Make swap
|
||||
|
||||
let subcommand = AmmProgramAgnosticSubcommand::Swap {
|
||||
user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()),
|
||||
user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()),
|
||||
amount_in: 2,
|
||||
min_amount_out: 1,
|
||||
token_definition: definition_account_id_1.to_string(),
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(Command::AMM(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let user_holding_a_acc = seq_client
|
||||
.get_account(recipient_account_id_1.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_b_acc = seq_client
|
||||
.get_account(recipient_account_id_2.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_lp_acc = seq_client
|
||||
.get_account(user_holding_lp.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
2
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
|
||||
5
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
|
||||
3
|
||||
);
|
||||
|
||||
info!("=================== FIRST SWAP FINISHED ===============");
|
||||
|
||||
// Make swap
|
||||
|
||||
let subcommand = AmmProgramAgnosticSubcommand::Swap {
|
||||
user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()),
|
||||
user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()),
|
||||
amount_in: 2,
|
||||
min_amount_out: 1,
|
||||
token_definition: definition_account_id_2.to_string(),
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(Command::AMM(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let user_holding_a_acc = seq_client
|
||||
.get_account(recipient_account_id_1.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_b_acc = seq_client
|
||||
.get_account(recipient_account_id_2.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_lp_acc = seq_client
|
||||
.get_account(user_holding_lp.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
4
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
|
||||
3
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
|
||||
3
|
||||
);
|
||||
|
||||
info!("=================== SECOND SWAP FINISHED ===============");
|
||||
|
||||
// Add liquidity
|
||||
|
||||
let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity {
|
||||
user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()),
|
||||
user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()),
|
||||
user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()),
|
||||
min_amount_lp: 1,
|
||||
max_amount_a: 2,
|
||||
max_amount_b: 2,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(Command::AMM(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let user_holding_a_acc = seq_client
|
||||
.get_account(recipient_account_id_1.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_b_acc = seq_client
|
||||
.get_account(recipient_account_id_2.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_lp_acc = seq_client
|
||||
.get_account(user_holding_lp.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
3
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
|
||||
1
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
|
||||
4
|
||||
);
|
||||
|
||||
info!("=================== ADD LIQ FINISHED ===============");
|
||||
|
||||
// Remove liquidity
|
||||
|
||||
let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity {
|
||||
user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()),
|
||||
user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()),
|
||||
user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()),
|
||||
balance_lp: 2,
|
||||
min_amount_a: 1,
|
||||
min_amount_b: 1,
|
||||
};
|
||||
|
||||
wallet::cli::execute_subcommand(Command::AMM(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let user_holding_a_acc = seq_client
|
||||
.get_account(recipient_account_id_1.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_b_acc = seq_client
|
||||
.get_account(recipient_account_id_2.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let user_holding_lp_acc = seq_client
|
||||
.get_account(user_holding_lp.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
5
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
|
||||
4
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
|
||||
2
|
||||
);
|
||||
|
||||
info!("Success!");
|
||||
}
|
||||
|
||||
println!("{function_map:#?}");
|
||||
|
||||
function_map
|
||||
|
||||
@ -15,6 +15,7 @@ borsh.workspace = true
|
||||
hex.workspace = true
|
||||
secp256k1 = "0.31.1"
|
||||
risc0-binfmt = "3.0.2"
|
||||
bytemuck = "1.24.0"
|
||||
log.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
@ -24,6 +25,7 @@ risc0-binfmt = "3.0.2"
|
||||
[dev-dependencies]
|
||||
test_program_methods.workspace = true
|
||||
hex-literal = "1.0.0"
|
||||
env_logger.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@ -6,17 +6,18 @@ edition = "2024"
|
||||
[dependencies]
|
||||
risc0-zkvm.workspace = true
|
||||
borsh.workspace = true
|
||||
serde = { workspace = true }
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
chacha20 = { version = "0.9", default-features = false }
|
||||
bytemuck = { workspace = true, optional = true }
|
||||
bytemuck.workspace = true
|
||||
k256 = { workspace = true, optional = true }
|
||||
base58 = { workspace = true, optional = true }
|
||||
anyhow = { workspace = true, optional = true }
|
||||
|
||||
chacha20 = { version = "0.9", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
host = ["dep:bytemuck", "dep:k256", "dep:base58", "dep:anyhow"]
|
||||
host = ["dep:k256", "dep:base58", "dep:anyhow"]
|
||||
|
||||
@ -15,7 +15,7 @@ pub type Nonce = u128;
|
||||
|
||||
/// Account to be used both in public and private contexts
|
||||
#[derive(
|
||||
Serialize, Deserialize, Clone, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize,
|
||||
Clone, Default, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
||||
)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug))]
|
||||
pub struct Account {
|
||||
@ -25,7 +25,7 @@ pub struct Account {
|
||||
pub nonce: Nonce,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug))]
|
||||
pub struct AccountWithMetadata {
|
||||
pub account: Account,
|
||||
@ -45,9 +45,18 @@ impl AccountWithMetadata {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize,
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
BorshSerialize,
|
||||
BorshDeserialize,
|
||||
)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialOrd, Ord, Default))]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialOrd, Ord))]
|
||||
pub struct AccountId {
|
||||
value: [u8; 32],
|
||||
}
|
||||
@ -180,4 +189,11 @@ mod tests {
|
||||
let result = base58_str.parse::<AccountId>().unwrap_err();
|
||||
assert!(matches!(result, AccountIdError::InvalidLength(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_account_id() {
|
||||
let default_account_id = AccountId::default();
|
||||
let expected_account_id = AccountId::new([0; 32]);
|
||||
assert!(default_account_id == expected_account_id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ use crate::{Commitment, account::Account};
|
||||
|
||||
pub type Scalar = [u8; 32];
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy)]
|
||||
pub struct SharedSecretKey(pub [u8; 32]);
|
||||
|
||||
pub struct EncryptionScheme;
|
||||
|
||||
@ -3,9 +3,7 @@ use std::collections::HashSet;
|
||||
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
use crate::account::AccountId;
|
||||
use crate::account::{Account, AccountWithMetadata};
|
||||
use crate::account::{Account, AccountId, AccountWithMetadata};
|
||||
|
||||
pub type ProgramId = [u32; 8];
|
||||
pub type InstructionData = Vec<u32>;
|
||||
@ -22,8 +20,8 @@ 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)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug))]
|
||||
pub struct PdaSeed([u8; 32]);
|
||||
|
||||
impl PdaSeed {
|
||||
@ -32,7 +30,6 @@ impl PdaSeed {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
impl From<(&ProgramId, &PdaSeed)> for AccountId {
|
||||
fn from(value: (&ProgramId, &PdaSeed)) -> Self {
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
@ -54,8 +51,8 @@ impl From<(&ProgramId, &PdaSeed)> for AccountId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug,))]
|
||||
pub struct ChainedCall {
|
||||
/// The program ID of the program to execute
|
||||
pub program_id: ProgramId,
|
||||
|
||||
@ -219,7 +219,7 @@ mod tests {
|
||||
&Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
&[0, 2],
|
||||
&[0xdeadbeef],
|
||||
&[(recipient_keys.npk(), shared_secret.clone())],
|
||||
&[(recipient_keys.npk(), shared_secret)],
|
||||
&[],
|
||||
&[None],
|
||||
&Program::authenticated_transfer_program().into(),
|
||||
@ -316,8 +316,8 @@ mod tests {
|
||||
&[1, 2],
|
||||
&[0xdeadbeef1, 0xdeadbeef2],
|
||||
&[
|
||||
(sender_keys.npk(), shared_secret_1.clone()),
|
||||
(recipient_keys.npk(), shared_secret_2.clone()),
|
||||
(sender_keys.npk(), shared_secret_1),
|
||||
(recipient_keys.npk(), shared_secret_2),
|
||||
],
|
||||
&[sender_keys.nsk],
|
||||
&[commitment_set.get_proof_for(&commitment_sender), None],
|
||||
|
||||
@ -7,7 +7,7 @@ use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
error::NssaError,
|
||||
program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF},
|
||||
program_methods::{AMM_ELF, AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF},
|
||||
};
|
||||
|
||||
/// Maximum number of cycles for a public execution.
|
||||
@ -95,6 +95,10 @@ impl Program {
|
||||
// `program_methods`
|
||||
Self::new(TOKEN_ELF.to_vec()).unwrap()
|
||||
}
|
||||
|
||||
pub fn amm() -> Self {
|
||||
Self::new(AMM_ELF.to_vec()).expect("The AMM program must be a valid Risc0 program")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
||||
|
||||
@ -23,6 +23,7 @@ impl Message {
|
||||
instruction: T,
|
||||
) -> Result<Self, NssaError> {
|
||||
let instruction_data = Program::serialize_instruction(instruction)?;
|
||||
|
||||
Ok(Self {
|
||||
program_id,
|
||||
account_ids,
|
||||
@ -30,4 +31,18 @@ impl Message {
|
||||
instruction_data,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_preserialized(
|
||||
program_id: ProgramId,
|
||||
account_ids: Vec<AccountId>,
|
||||
nonces: Vec<Nonce>,
|
||||
instruction_data: InstructionData,
|
||||
) -> Self {
|
||||
Self {
|
||||
program_id,
|
||||
account_ids,
|
||||
nonces,
|
||||
instruction_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1573
nssa/src/state.rs
1573
nssa/src/state.rs
File diff suppressed because it is too large
Load Diff
3587
program_methods/guest/src/bin/amm.rs
Normal file
3587
program_methods/guest/src/bin/amm.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -82,7 +82,7 @@ fn main() {
|
||||
let winner_token_holding_post = winner_token_holding.account.clone();
|
||||
pinata_definition_post.data = data.next_data();
|
||||
|
||||
let mut instruction_data: [u8; 23] = [0; 23];
|
||||
let mut instruction_data = vec![0; 23];
|
||||
instruction_data[0] = 1;
|
||||
instruction_data[1..17].copy_from_slice(&PRIZE.to_le_bytes());
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -28,3 +28,4 @@ futures.workspace = true
|
||||
async-stream = "0.3.6"
|
||||
indicatif = { version = "0.18.3", features = ["improved_unicode"] }
|
||||
paste.workspace = true
|
||||
risc0-zkvm.workspace = true
|
||||
|
||||
@ -3,70 +3,15 @@ use base58::ToBase58;
|
||||
use clap::Subcommand;
|
||||
use itertools::Itertools as _;
|
||||
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
||||
use nssa::{Account, AccountId, program::Program};
|
||||
use nssa::{Account, program::Program};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
WalletCore,
|
||||
TokenDefinition, TokenHolding, WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
helperfunctions::{AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix},
|
||||
};
|
||||
|
||||
const TOKEN_DEFINITION_TYPE: u8 = 0;
|
||||
const TOKEN_DEFINITION_DATA_SIZE: usize = 23;
|
||||
|
||||
const TOKEN_HOLDING_TYPE: u8 = 1;
|
||||
const TOKEN_HOLDING_DATA_SIZE: usize = 49;
|
||||
|
||||
struct TokenDefinition {
|
||||
#[allow(unused)]
|
||||
account_type: u8,
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
}
|
||||
|
||||
struct TokenHolding {
|
||||
#[allow(unused)]
|
||||
account_type: u8,
|
||||
definition_id: AccountId,
|
||||
balance: u128,
|
||||
}
|
||||
|
||||
impl TokenDefinition {
|
||||
fn parse(data: &[u8]) -> Option<Self> {
|
||||
if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE {
|
||||
None
|
||||
} else {
|
||||
let account_type = data[0];
|
||||
let name = data[1..7].try_into().unwrap();
|
||||
let total_supply = u128::from_le_bytes(data[7..].try_into().unwrap());
|
||||
|
||||
Some(Self {
|
||||
account_type,
|
||||
name,
|
||||
total_supply,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents generic chain CLI subcommand
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum AccountSubcommand {
|
||||
@ -344,6 +289,8 @@ impl WalletSubcommand for AccountSubcommand {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nssa::AccountId;
|
||||
|
||||
use crate::cli::account::{TokedDefinitionAccountView, TokenDefinition};
|
||||
|
||||
#[test]
|
||||
@ -352,6 +299,7 @@ mod tests {
|
||||
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();
|
||||
@ -365,6 +313,7 @@ mod tests {
|
||||
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();
|
||||
@ -378,6 +327,7 @@ mod tests {
|
||||
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();
|
||||
|
||||
@ -11,8 +11,8 @@ use crate::{
|
||||
chain::ChainSubcommand,
|
||||
config::ConfigSubcommand,
|
||||
programs::{
|
||||
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
||||
token::TokenProgramAgnosticSubcommand,
|
||||
amm::AmmProgramAgnosticSubcommand, native_token_transfer::AuthTransferSubcommand,
|
||||
pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand,
|
||||
},
|
||||
},
|
||||
helperfunctions::{fetch_config, fetch_persistent_storage, merge_auth_config},
|
||||
@ -47,6 +47,9 @@ pub enum Command {
|
||||
/// Token program interaction subcommand
|
||||
#[command(subcommand)]
|
||||
Token(TokenProgramAgnosticSubcommand),
|
||||
/// AMM program interaction subcommand
|
||||
#[command(subcommand)]
|
||||
AMM(AmmProgramAgnosticSubcommand),
|
||||
/// Check the wallet can connect to the node and builtin local programs
|
||||
/// match the remote versions
|
||||
CheckHealth {},
|
||||
@ -165,6 +168,7 @@ pub async fn execute_subcommand_with_auth(
|
||||
Command::Token(token_subcommand) => {
|
||||
token_subcommand.handle_subcommand(&mut wallet_core).await?
|
||||
}
|
||||
Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(&mut wallet_core).await?,
|
||||
Command::Config(config_subcommand) => {
|
||||
config_subcommand
|
||||
.handle_subcommand(&mut wallet_core)
|
||||
|
||||
286
wallet/src/cli/programs/amm.rs
Normal file
286
wallet/src/cli/programs/amm.rs
Normal file
@ -0,0 +1,286 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use nssa::AccountId;
|
||||
|
||||
use crate::{
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix},
|
||||
program_facades::amm::Amm,
|
||||
};
|
||||
|
||||
/// Represents generic CLI subcommand for a wallet working with amm program
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum AmmProgramAgnosticSubcommand {
|
||||
/// Produce a new pool
|
||||
///
|
||||
/// user_holding_a and user_holding_b must be owned.
|
||||
///
|
||||
/// Only public execution allowed
|
||||
New {
|
||||
/// user_holding_a - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_a: String,
|
||||
/// user_holding_b - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_b: String,
|
||||
/// user_holding_lp - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_lp: String,
|
||||
#[arg(long)]
|
||||
balance_a: u128,
|
||||
#[arg(long)]
|
||||
balance_b: u128,
|
||||
},
|
||||
/// Swap
|
||||
///
|
||||
/// The account associated with swapping token must be owned
|
||||
///
|
||||
/// Only public execution allowed
|
||||
Swap {
|
||||
/// user_holding_a - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_a: String,
|
||||
/// user_holding_b - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_b: String,
|
||||
#[arg(long)]
|
||||
amount_in: u128,
|
||||
#[arg(long)]
|
||||
min_amount_out: u128,
|
||||
/// token_definition - valid 32 byte base58 string WITHOUT privacy prefix
|
||||
#[arg(long)]
|
||||
token_definition: String,
|
||||
},
|
||||
/// Add liquidity
|
||||
///
|
||||
/// user_holding_a and user_holding_b must be owned.
|
||||
///
|
||||
/// Only public execution allowed
|
||||
AddLiquidity {
|
||||
/// user_holding_a - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_a: String,
|
||||
/// user_holding_b - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_b: String,
|
||||
/// user_holding_lp - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_lp: String,
|
||||
#[arg(long)]
|
||||
min_amount_lp: u128,
|
||||
#[arg(long)]
|
||||
max_amount_a: u128,
|
||||
#[arg(long)]
|
||||
max_amount_b: u128,
|
||||
},
|
||||
/// Remove liquidity
|
||||
///
|
||||
/// user_holding_lp must be owned.
|
||||
///
|
||||
/// Only public execution allowed
|
||||
RemoveLiquidity {
|
||||
/// user_holding_a - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_a: String,
|
||||
/// user_holding_b - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_b: String,
|
||||
/// user_holding_lp - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
user_holding_lp: String,
|
||||
#[arg(long)]
|
||||
balance_lp: u128,
|
||||
#[arg(long)]
|
||||
min_amount_a: u128,
|
||||
#[arg(long)]
|
||||
min_amount_b: u128,
|
||||
},
|
||||
}
|
||||
|
||||
impl WalletSubcommand for AmmProgramAgnosticSubcommand {
|
||||
async fn handle_subcommand(
|
||||
self,
|
||||
wallet_core: &mut WalletCore,
|
||||
) -> Result<SubcommandReturnValue> {
|
||||
match self {
|
||||
AmmProgramAgnosticSubcommand::New {
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
user_holding_lp,
|
||||
balance_a,
|
||||
balance_b,
|
||||
} => {
|
||||
let (user_holding_a, user_holding_a_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_a)?;
|
||||
let (user_holding_b, user_holding_b_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_b)?;
|
||||
let (user_holding_lp, user_holding_lp_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_lp)?;
|
||||
|
||||
let user_holding_a: AccountId = user_holding_a.parse()?;
|
||||
let user_holding_b: AccountId = user_holding_b.parse()?;
|
||||
let user_holding_lp: AccountId = user_holding_lp.parse()?;
|
||||
|
||||
match (
|
||||
user_holding_a_privacy,
|
||||
user_holding_b_privacy,
|
||||
user_holding_lp_privacy,
|
||||
) {
|
||||
(
|
||||
AccountPrivacyKind::Public,
|
||||
AccountPrivacyKind::Public,
|
||||
AccountPrivacyKind::Public,
|
||||
) => {
|
||||
Amm(wallet_core)
|
||||
.send_new_definition(
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
user_holding_lp,
|
||||
balance_a,
|
||||
balance_b,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
_ => {
|
||||
// ToDo: Implement after private multi-chain calls is available
|
||||
anyhow::bail!("Only public execution allowed for Amm calls");
|
||||
}
|
||||
}
|
||||
}
|
||||
AmmProgramAgnosticSubcommand::Swap {
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
amount_in,
|
||||
min_amount_out,
|
||||
token_definition,
|
||||
} => {
|
||||
let (user_holding_a, user_holding_a_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_a)?;
|
||||
let (user_holding_b, user_holding_b_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_b)?;
|
||||
|
||||
let user_holding_a: AccountId = user_holding_a.parse()?;
|
||||
let user_holding_b: AccountId = user_holding_b.parse()?;
|
||||
|
||||
match (user_holding_a_privacy, user_holding_b_privacy) {
|
||||
(AccountPrivacyKind::Public, AccountPrivacyKind::Public) => {
|
||||
Amm(wallet_core)
|
||||
.send_swap(
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
amount_in,
|
||||
min_amount_out,
|
||||
token_definition.parse()?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
_ => {
|
||||
// ToDo: Implement after private multi-chain calls is available
|
||||
anyhow::bail!("Only public execution allowed for Amm calls");
|
||||
}
|
||||
}
|
||||
}
|
||||
AmmProgramAgnosticSubcommand::AddLiquidity {
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
user_holding_lp,
|
||||
min_amount_lp,
|
||||
max_amount_a,
|
||||
max_amount_b,
|
||||
} => {
|
||||
let (user_holding_a, user_holding_a_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_a)?;
|
||||
let (user_holding_b, user_holding_b_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_b)?;
|
||||
let (user_holding_lp, user_holding_lp_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_lp)?;
|
||||
|
||||
let user_holding_a: AccountId = user_holding_a.parse()?;
|
||||
let user_holding_b: AccountId = user_holding_b.parse()?;
|
||||
let user_holding_lp: AccountId = user_holding_lp.parse()?;
|
||||
|
||||
match (
|
||||
user_holding_a_privacy,
|
||||
user_holding_b_privacy,
|
||||
user_holding_lp_privacy,
|
||||
) {
|
||||
(
|
||||
AccountPrivacyKind::Public,
|
||||
AccountPrivacyKind::Public,
|
||||
AccountPrivacyKind::Public,
|
||||
) => {
|
||||
Amm(wallet_core)
|
||||
.send_add_liquidity(
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
user_holding_lp,
|
||||
min_amount_lp,
|
||||
max_amount_a,
|
||||
max_amount_b,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
_ => {
|
||||
// ToDo: Implement after private multi-chain calls is available
|
||||
anyhow::bail!("Only public execution allowed for Amm calls");
|
||||
}
|
||||
}
|
||||
}
|
||||
AmmProgramAgnosticSubcommand::RemoveLiquidity {
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
user_holding_lp,
|
||||
balance_lp,
|
||||
min_amount_a,
|
||||
min_amount_b,
|
||||
} => {
|
||||
let (user_holding_a, user_holding_a_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_a)?;
|
||||
let (user_holding_b, user_holding_b_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_b)?;
|
||||
let (user_holding_lp, user_holding_lp_privacy) =
|
||||
parse_addr_with_privacy_prefix(&user_holding_lp)?;
|
||||
|
||||
let user_holding_a: AccountId = user_holding_a.parse()?;
|
||||
let user_holding_b: AccountId = user_holding_b.parse()?;
|
||||
let user_holding_lp: AccountId = user_holding_lp.parse()?;
|
||||
|
||||
match (
|
||||
user_holding_a_privacy,
|
||||
user_holding_b_privacy,
|
||||
user_holding_lp_privacy,
|
||||
) {
|
||||
(
|
||||
AccountPrivacyKind::Public,
|
||||
AccountPrivacyKind::Public,
|
||||
AccountPrivacyKind::Public,
|
||||
) => {
|
||||
Amm(wallet_core)
|
||||
.send_remove_liquidity(
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
user_holding_lp,
|
||||
balance_lp,
|
||||
min_amount_a,
|
||||
min_amount_b,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
_ => {
|
||||
// ToDo: Implement after private multi-chain calls is available
|
||||
anyhow::bail!("Only public execution allowed for Amm calls");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod amm;
|
||||
pub mod native_token_transfer;
|
||||
pub mod pinata;
|
||||
pub mod token;
|
||||
|
||||
@ -18,7 +18,9 @@ use nssa::{
|
||||
circuit::ProgramWithDependencies, message::EncryptedAccountData,
|
||||
},
|
||||
};
|
||||
use nssa_core::{Commitment, MembershipProof, SharedSecretKey, program::InstructionData};
|
||||
use nssa_core::{
|
||||
Commitment, MembershipProof, SharedSecretKey, account::Data, program::InstructionData,
|
||||
};
|
||||
pub use privacy_preserving_tx::PrivacyPreservingAccount;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
@ -45,6 +47,82 @@ 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 {
|
||||
pub storage: WalletChainStore,
|
||||
pub poller: TxPoller,
|
||||
@ -251,7 +329,6 @@ impl WalletCore {
|
||||
}
|
||||
|
||||
println!("Transaction data is {:?}", tx.message);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -292,7 +369,7 @@ impl WalletCore {
|
||||
&produce_random_nonces(private_account_keys.len()),
|
||||
&private_account_keys
|
||||
.iter()
|
||||
.map(|keys| (keys.npk.clone(), keys.ssk.clone()))
|
||||
.map(|keys| (keys.npk.clone(), keys.ssk))
|
||||
.collect::<Vec<_>>(),
|
||||
&acc_manager.private_account_auth(),
|
||||
&acc_manager.private_account_membership_proofs(),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use common::error::ExecutionFailureKind;
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use nssa::{AccountId, PrivateKey};
|
||||
@ -27,6 +28,10 @@ impl PrivacyPreservingAccount {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_public(&self) -> bool {
|
||||
matches!(&self, Self::Public(_))
|
||||
}
|
||||
|
||||
pub fn is_private(&self) -> bool {
|
||||
matches!(
|
||||
&self,
|
||||
|
||||
539
wallet/src/program_facades/amm.rs
Normal file
539
wallet/src/program_facades/amm.rs
Normal file
@ -0,0 +1,539 @@
|
||||
use common::{error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse};
|
||||
use nssa::{AccountId, ProgramId, program::Program};
|
||||
use nssa_core::program::PdaSeed;
|
||||
|
||||
use crate::{TokenHolding, WalletCore};
|
||||
|
||||
fn compute_pool_pda(
|
||||
amm_program_id: ProgramId,
|
||||
definition_token_a_id: AccountId,
|
||||
definition_token_b_id: AccountId,
|
||||
) -> AccountId {
|
||||
AccountId::from((
|
||||
&amm_program_id,
|
||||
&compute_pool_pda_seed(definition_token_a_id, definition_token_b_id),
|
||||
))
|
||||
}
|
||||
|
||||
fn compute_pool_pda_seed(
|
||||
definition_token_a_id: AccountId,
|
||||
definition_token_b_id: AccountId,
|
||||
) -> PdaSeed {
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
|
||||
let mut i: usize = 0;
|
||||
let (token_1, token_2) = loop {
|
||||
if definition_token_a_id.value()[i] > definition_token_b_id.value()[i] {
|
||||
let token_1 = definition_token_a_id;
|
||||
let token_2 = definition_token_b_id;
|
||||
break (token_1, token_2);
|
||||
} else if definition_token_a_id.value()[i] < definition_token_b_id.value()[i] {
|
||||
let token_1 = definition_token_b_id;
|
||||
let token_2 = definition_token_a_id;
|
||||
break (token_1, token_2);
|
||||
}
|
||||
|
||||
if i == 32 {
|
||||
panic!("Definitions match");
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
};
|
||||
|
||||
let mut bytes = [0; 64];
|
||||
bytes[0..32].copy_from_slice(&token_1.to_bytes());
|
||||
bytes[32..].copy_from_slice(&token_2.to_bytes());
|
||||
|
||||
PdaSeed::new(
|
||||
Impl::hash_bytes(&bytes)
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.expect("Hash output must be exactly 32 bytes long"),
|
||||
)
|
||||
}
|
||||
|
||||
fn compute_vault_pda(
|
||||
amm_program_id: ProgramId,
|
||||
pool_id: AccountId,
|
||||
definition_token_id: AccountId,
|
||||
) -> AccountId {
|
||||
AccountId::from((
|
||||
&amm_program_id,
|
||||
&compute_vault_pda_seed(pool_id, definition_token_id),
|
||||
))
|
||||
}
|
||||
|
||||
fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId) -> PdaSeed {
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
|
||||
let mut bytes = [0; 64];
|
||||
bytes[0..32].copy_from_slice(&pool_id.to_bytes());
|
||||
bytes[32..].copy_from_slice(&definition_token_id.to_bytes());
|
||||
|
||||
PdaSeed::new(
|
||||
Impl::hash_bytes(&bytes)
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.expect("Hash output must be exactly 32 bytes long"),
|
||||
)
|
||||
}
|
||||
|
||||
fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId) -> AccountId {
|
||||
AccountId::from((&amm_program_id, &compute_liquidity_token_pda_seed(pool_id)))
|
||||
}
|
||||
|
||||
fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed {
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
|
||||
let mut bytes = [0; 64];
|
||||
bytes[0..32].copy_from_slice(&pool_id.to_bytes());
|
||||
bytes[32..].copy_from_slice(&[0; 32]);
|
||||
|
||||
PdaSeed::new(
|
||||
Impl::hash_bytes(&bytes)
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.expect("Hash output must be exactly 32 bytes long"),
|
||||
)
|
||||
}
|
||||
|
||||
pub struct Amm<'w>(pub &'w WalletCore);
|
||||
|
||||
impl Amm<'_> {
|
||||
pub async fn send_new_definition(
|
||||
&self,
|
||||
user_holding_a: AccountId,
|
||||
user_holding_b: AccountId,
|
||||
user_holding_lp: AccountId,
|
||||
balance_a: u128,
|
||||
balance_b: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let (instruction, program) = amm_program_preparation_definition(balance_a, balance_b);
|
||||
|
||||
let amm_program_id = Program::amm().id();
|
||||
|
||||
let user_a_acc = self
|
||||
.0
|
||||
.get_account_public(user_holding_a)
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
let user_b_acc = self
|
||||
.0
|
||||
.get_account_public(user_holding_b)
|
||||
.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 amm_pool =
|
||||
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id);
|
||||
let vault_holding_a = compute_vault_pda(amm_program_id, amm_pool, definition_token_a_id);
|
||||
let vault_holding_b = compute_vault_pda(amm_program_id, amm_pool, definition_token_b_id);
|
||||
let pool_lp = compute_liquidity_token_pda(amm_program_id, amm_pool);
|
||||
|
||||
let account_ids = vec![
|
||||
amm_pool,
|
||||
vault_holding_a,
|
||||
vault_holding_b,
|
||||
pool_lp,
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
user_holding_lp,
|
||||
];
|
||||
|
||||
let nonces = self
|
||||
.0
|
||||
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
|
||||
let signing_key_a = self
|
||||
.0
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&user_holding_a)
|
||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||
|
||||
let signing_key_b = self
|
||||
.0
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&user_holding_b)
|
||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program.id(),
|
||||
account_ids,
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[signing_key_a, signing_key_b],
|
||||
);
|
||||
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn send_swap(
|
||||
&self,
|
||||
user_holding_a: AccountId,
|
||||
user_holding_b: AccountId,
|
||||
amount_in: u128,
|
||||
min_amount_out: u128,
|
||||
token_definition_id: AccountId,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let (instruction, program) =
|
||||
amm_program_preparation_swap(amount_in, min_amount_out, token_definition_id);
|
||||
|
||||
let amm_program_id = Program::amm().id();
|
||||
|
||||
let user_a_acc = self
|
||||
.0
|
||||
.get_account_public(user_holding_a)
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
let user_b_acc = self
|
||||
.0
|
||||
.get_account_public(user_holding_b)
|
||||
.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 amm_pool =
|
||||
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id);
|
||||
let vault_holding_a = compute_vault_pda(amm_program_id, amm_pool, definition_token_a_id);
|
||||
let vault_holding_b = compute_vault_pda(amm_program_id, amm_pool, definition_token_b_id);
|
||||
|
||||
let account_ids = vec![
|
||||
amm_pool,
|
||||
vault_holding_a,
|
||||
vault_holding_b,
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
];
|
||||
|
||||
let account_id_auth;
|
||||
|
||||
// Checking, which account are associated with TokenDefinition
|
||||
let token_holder_acc_a = self
|
||||
.0
|
||||
.get_account_public(user_holding_a)
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
let token_holder_acc_b = self
|
||||
.0
|
||||
.get_account_public(user_holding_b)
|
||||
.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))?;
|
||||
|
||||
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 {
|
||||
account_id_auth = user_holding_b;
|
||||
} else {
|
||||
return Err(ExecutionFailureKind::AccountDataError(token_definition_id));
|
||||
}
|
||||
|
||||
let nonces = self
|
||||
.0
|
||||
.get_accounts_nonces(vec![account_id_auth])
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
|
||||
let signing_key = self
|
||||
.0
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&account_id_auth)
|
||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program.id(),
|
||||
account_ids,
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
||||
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn send_add_liquidity(
|
||||
&self,
|
||||
user_holding_a: AccountId,
|
||||
user_holding_b: AccountId,
|
||||
user_holding_lp: AccountId,
|
||||
min_amount_lp: u128,
|
||||
max_amount_a: u128,
|
||||
max_amount_b: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let (instruction, program) =
|
||||
amm_program_preparation_add_liq(min_amount_lp, max_amount_a, max_amount_b);
|
||||
|
||||
let amm_program_id = Program::amm().id();
|
||||
|
||||
let user_a_acc = self
|
||||
.0
|
||||
.get_account_public(user_holding_a)
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
let user_b_acc = self
|
||||
.0
|
||||
.get_account_public(user_holding_b)
|
||||
.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 amm_pool =
|
||||
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id);
|
||||
let vault_holding_a = compute_vault_pda(amm_program_id, amm_pool, definition_token_a_id);
|
||||
let vault_holding_b = compute_vault_pda(amm_program_id, amm_pool, definition_token_b_id);
|
||||
let pool_lp = compute_liquidity_token_pda(amm_program_id, amm_pool);
|
||||
|
||||
let account_ids = vec![
|
||||
amm_pool,
|
||||
vault_holding_a,
|
||||
vault_holding_b,
|
||||
pool_lp,
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
user_holding_lp,
|
||||
];
|
||||
|
||||
let nonces = self
|
||||
.0
|
||||
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
|
||||
let signing_key_a = self
|
||||
.0
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&user_holding_a)
|
||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||
|
||||
let signing_key_b = self
|
||||
.0
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&user_holding_b)
|
||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program.id(),
|
||||
account_ids,
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[signing_key_a, signing_key_b],
|
||||
);
|
||||
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn send_remove_liquidity(
|
||||
&self,
|
||||
user_holding_a: AccountId,
|
||||
user_holding_b: AccountId,
|
||||
user_holding_lp: AccountId,
|
||||
balance_lp: u128,
|
||||
min_amount_a: u128,
|
||||
min_amount_b: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let (instruction, program) =
|
||||
amm_program_preparation_remove_liq(balance_lp, min_amount_a, min_amount_b);
|
||||
|
||||
let amm_program_id = Program::amm().id();
|
||||
|
||||
let user_a_acc = self
|
||||
.0
|
||||
.get_account_public(user_holding_a)
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
let user_b_acc = self
|
||||
.0
|
||||
.get_account_public(user_holding_b)
|
||||
.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 amm_pool =
|
||||
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id);
|
||||
let vault_holding_a = compute_vault_pda(amm_program_id, amm_pool, definition_token_a_id);
|
||||
let vault_holding_b = compute_vault_pda(amm_program_id, amm_pool, definition_token_b_id);
|
||||
let pool_lp = compute_liquidity_token_pda(amm_program_id, amm_pool);
|
||||
|
||||
let account_ids = vec![
|
||||
amm_pool,
|
||||
vault_holding_a,
|
||||
vault_holding_b,
|
||||
pool_lp,
|
||||
user_holding_a,
|
||||
user_holding_b,
|
||||
user_holding_lp,
|
||||
];
|
||||
|
||||
let nonces = self
|
||||
.0
|
||||
.get_accounts_nonces(vec![user_holding_lp])
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::SequencerError)?;
|
||||
|
||||
let signing_key_lp = self
|
||||
.0
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&user_holding_lp)
|
||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program.id(),
|
||||
account_ids,
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key_lp]);
|
||||
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
}
|
||||
|
||||
fn amm_program_preparation_definition(balance_a: u128, balance_b: u128) -> (Vec<u8>, Program) {
|
||||
// An instruction data of 65-bytes, indicating the initial amm reserves' balances and
|
||||
// token_program_id with the following layout:
|
||||
// [0x00 || array of balances (little-endian 16 bytes) || AMM_PROGRAM_ID)]
|
||||
let amm_program_id = Program::amm().id();
|
||||
|
||||
let mut instruction = [0; 65];
|
||||
instruction[1..17].copy_from_slice(&balance_a.to_le_bytes());
|
||||
instruction[17..33].copy_from_slice(&balance_b.to_le_bytes());
|
||||
|
||||
// This can be done less verbose, but it is better to use same way, as in amm program
|
||||
instruction[33..37].copy_from_slice(&amm_program_id[0].to_le_bytes());
|
||||
instruction[37..41].copy_from_slice(&amm_program_id[1].to_le_bytes());
|
||||
instruction[41..45].copy_from_slice(&amm_program_id[2].to_le_bytes());
|
||||
instruction[45..49].copy_from_slice(&amm_program_id[3].to_le_bytes());
|
||||
instruction[49..53].copy_from_slice(&amm_program_id[4].to_le_bytes());
|
||||
instruction[53..57].copy_from_slice(&amm_program_id[5].to_le_bytes());
|
||||
instruction[57..61].copy_from_slice(&amm_program_id[6].to_le_bytes());
|
||||
instruction[61..].copy_from_slice(&amm_program_id[7].to_le_bytes());
|
||||
|
||||
let instruction_data = instruction.to_vec();
|
||||
let program = Program::amm();
|
||||
|
||||
(instruction_data, program)
|
||||
}
|
||||
|
||||
fn amm_program_preparation_swap(
|
||||
amount_in: u128,
|
||||
min_amount_out: u128,
|
||||
token_definition_id: AccountId,
|
||||
) -> (Vec<u8>, Program) {
|
||||
// An instruction data byte string of length 65, indicating which token type to swap, quantity
|
||||
// of tokens put into the swap (of type TOKEN_DEFINITION_ID) and min_amount_out.
|
||||
// [0x01 || amount (little-endian 16 bytes) || TOKEN_DEFINITION_ID].
|
||||
let mut instruction = [0; 65];
|
||||
instruction[0] = 0x01;
|
||||
instruction[1..17].copy_from_slice(&amount_in.to_le_bytes());
|
||||
instruction[17..33].copy_from_slice(&min_amount_out.to_le_bytes());
|
||||
|
||||
// This can be done less verbose, but it is better to use same way, as in amm program
|
||||
instruction[33..].copy_from_slice(&token_definition_id.to_bytes());
|
||||
|
||||
let instruction_data = instruction.to_vec();
|
||||
let program = Program::amm();
|
||||
|
||||
(instruction_data, program)
|
||||
}
|
||||
|
||||
fn amm_program_preparation_add_liq(
|
||||
min_amount_lp: u128,
|
||||
max_amount_a: u128,
|
||||
max_amount_b: u128,
|
||||
) -> (Vec<u8>, Program) {
|
||||
// An instruction data byte string of length 49, amounts for minimum amount of liquidity from
|
||||
// add (min_amount_lp), max amount added for each token (max_amount_a and max_amount_b);
|
||||
// indicate [0x02 || array of of balances (little-endian 16 bytes)].
|
||||
let mut instruction = [0; 49];
|
||||
instruction[0] = 0x02;
|
||||
|
||||
instruction[1..17].copy_from_slice(&min_amount_lp.to_le_bytes());
|
||||
instruction[17..33].copy_from_slice(&max_amount_a.to_le_bytes());
|
||||
instruction[33..49].copy_from_slice(&max_amount_b.to_le_bytes());
|
||||
|
||||
let instruction_data = instruction.to_vec();
|
||||
let program = Program::amm();
|
||||
|
||||
(instruction_data, program)
|
||||
}
|
||||
|
||||
fn amm_program_preparation_remove_liq(
|
||||
balance_lp: u128,
|
||||
min_amount_a: u128,
|
||||
min_amount_b: u128,
|
||||
) -> (Vec<u8>, Program) {
|
||||
// An instruction data byte string of length 49, amounts for minimum amount of liquidity to
|
||||
// redeem (balance_lp), minimum balance of each token to remove (min_amount_a and
|
||||
// min_amount_b); indicate [0x03 || array of balances (little-endian 16 bytes)].
|
||||
let mut instruction = [0; 49];
|
||||
instruction[0] = 0x03;
|
||||
|
||||
instruction[1..17].copy_from_slice(&balance_lp.to_le_bytes());
|
||||
instruction[17..33].copy_from_slice(&min_amount_a.to_le_bytes());
|
||||
instruction[33..49].copy_from_slice(&min_amount_b.to_le_bytes());
|
||||
|
||||
let instruction_data = instruction.to_vec();
|
||||
let program = Program::amm();
|
||||
|
||||
(instruction_data, program)
|
||||
}
|
||||
@ -7,6 +7,7 @@ use nssa_core::program::InstructionData;
|
||||
|
||||
use crate::{AccDecodeData, PrivacyPreservingAccount, WalletCore};
|
||||
|
||||
pub mod amm;
|
||||
pub mod native_token_transfer;
|
||||
pub mod pinata;
|
||||
pub mod token;
|
||||
|
||||
@ -17,7 +17,7 @@ impl Token<'_> {
|
||||
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 = [0; 23];
|
||||
let mut instruction = vec![0u8; 23];
|
||||
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
|
||||
instruction[17..].copy_from_slice(&name);
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
@ -45,7 +45,7 @@ impl Token<'_> {
|
||||
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 = [0; 23];
|
||||
let mut instruction = vec![0u8; 23];
|
||||
instruction[0] = 0x01;
|
||||
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
|
||||
let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user