mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-06-26 08:59:45 +00:00
Merge pull request #511 from logos-blockchain/arjentix/add-vault-wallet-commands
feat(wallet): add vault cli commands
This commit is contained in:
commit
756e304cef
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -9819,7 +9819,6 @@ dependencies = [
|
||||
"testcontainers",
|
||||
"tokio",
|
||||
"url",
|
||||
"vault_core",
|
||||
"wallet",
|
||||
]
|
||||
|
||||
@ -10904,6 +10903,7 @@ dependencies = [
|
||||
"token_core",
|
||||
"tokio",
|
||||
"url",
|
||||
"vault_core",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
||||
45
Justfile
45
Justfile
@ -8,7 +8,7 @@ METHODS_PATH := "program_methods"
|
||||
TEST_METHODS_PATH := "test_program_methods"
|
||||
ARTIFACTS := "artifacts"
|
||||
|
||||
# Build risc0 program artifacts
|
||||
# Build risc0 program artifacts.
|
||||
build-artifacts:
|
||||
@echo "🔨 Building artifacts"
|
||||
@for methods_path in {{METHODS_PATH}} {{TEST_METHODS_PATH}}; do \
|
||||
@ -18,7 +18,13 @@ build-artifacts:
|
||||
cp target/$methods_path/riscv32im-risc0-zkvm-elf/docker/*.bin {{ARTIFACTS}}/$methods_path; \
|
||||
done
|
||||
|
||||
# Run tests
|
||||
# Format codebase.
|
||||
fmt:
|
||||
@echo "🎨 Formatting codebase"
|
||||
cargo +nightly fmt
|
||||
taplo fmt
|
||||
|
||||
# Run tests.
|
||||
test:
|
||||
@echo "🧪 Running tests"
|
||||
RISC0_DEV_MODE=1 cargo nextest run --no-fail-fast
|
||||
@ -29,42 +35,59 @@ bench:
|
||||
cargo bench -p crypto_primitives_bench --bench primitives
|
||||
cargo bench -p cycle_bench --features ppe --bench verify
|
||||
|
||||
# Run Bedrock node in docker
|
||||
# Run Bedrock node in docker.
|
||||
[working-directory: 'bedrock']
|
||||
run-bedrock:
|
||||
@echo "⛓️ Running bedrock"
|
||||
docker compose up
|
||||
|
||||
# Run Sequencer
|
||||
# Run Sequencer. Run with RISC0_DEV_MODE=1 to disable proof verification for faster iteration.
|
||||
[working-directory: 'lez/sequencer/service']
|
||||
run-sequencer:
|
||||
run-sequencer standalone="":
|
||||
@echo "🧠 Running sequencer"
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p sequencer_service configs/debug/sequencer_config.json
|
||||
@if [ "{{standalone}}" = "standalone" ]; then \
|
||||
echo "🧪 Running in standalone mode"; \
|
||||
RUST_LOG=info cargo run --features standalone --release -p sequencer_service configs/debug/sequencer_config.json; \
|
||||
else \
|
||||
echo "🚀 Running in normal mode"; \
|
||||
RUST_LOG=info cargo run --release -p sequencer_service configs/debug/sequencer_config.json; \
|
||||
fi
|
||||
|
||||
# Run Indexer
|
||||
# Run Indexer. Run with RISC0_DEV_MODE=1 to disable proof verification for faster iteration.
|
||||
[working-directory: 'lez/indexer/service']
|
||||
run-indexer mock="":
|
||||
@echo "🔍 Running indexer"
|
||||
@if [ "{{mock}}" = "mock" ]; then \
|
||||
echo "🧪 Using mock data"; \
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release --features mock-responses -p indexer_service configs/indexer_config.json; \
|
||||
RUST_LOG=info cargo run --release --features mock-responses -p indexer_service configs/indexer_config.json; \
|
||||
else \
|
||||
echo "🚀 Using real data"; \
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p indexer_service configs/indexer_config.json; \
|
||||
RUST_LOG=info cargo run --release -p indexer_service configs/indexer_config.json; \
|
||||
fi
|
||||
|
||||
# Run Explorer
|
||||
# Run Explorer.
|
||||
[working-directory: 'lez/explorer_service']
|
||||
run-explorer:
|
||||
@echo "🌐 Running explorer"
|
||||
RUST_LOG=info cargo leptos serve
|
||||
|
||||
# Run Wallet
|
||||
# Run Wallet.
|
||||
[working-directory: 'lez/wallet']
|
||||
run-wallet +args:
|
||||
@echo "🔑 Running wallet"
|
||||
LEE_WALLET_HOME_DIR=$(pwd)/configs/debug cargo run --release -p wallet -- {{args}}
|
||||
|
||||
# Import test accounts supplied in sequencer configuration.
|
||||
wallet-import-test-accounts:
|
||||
@echo "⚙️ Initializing accounts"
|
||||
just run-wallet account import public --private-key 7f273098f25b71e6c005a9519f2678da8d1c7f01f6a27778e2d9948abdf901fb
|
||||
just run-wallet vault claim --account-id Public/CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r --amount 10000
|
||||
|
||||
just run-wallet account import public --private-key f434f8741720014586ae43356d2aec6257da086222f604ddb75d69733b86fc4c
|
||||
just run-wallet vault claim --account-id Public/2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2 --amount 20000
|
||||
|
||||
just run-wallet account list
|
||||
|
||||
# Clean runtime data
|
||||
clean:
|
||||
@echo "🧹 Cleaning run artifacts"
|
||||
|
||||
@ -46,7 +46,7 @@ _wallet() {
|
||||
cword=$COMP_CWORD
|
||||
}
|
||||
|
||||
local commands="auth-transfer chain-info account pinata token amm ata check-health config restore-keys deploy-program help"
|
||||
local commands="auth-transfer chain-info account pinata token amm ata vault check-health config restore-keys deploy-program help"
|
||||
|
||||
# Find the main command and subcommand by scanning words before the cursor.
|
||||
# Global options that take a value are skipped along with their argument.
|
||||
@ -535,6 +535,38 @@ _wallet() {
|
||||
esac
|
||||
;;
|
||||
|
||||
vault)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "transfer claim help" -- "$cur"))
|
||||
;;
|
||||
transfer)
|
||||
case "$prev" in
|
||||
--from | --to)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--from --to --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
claim)
|
||||
case "$prev" in
|
||||
--account-id)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--account-id --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
config)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
|
||||
@ -25,6 +25,7 @@ _wallet() {
|
||||
'token:Token program interaction subcommand'
|
||||
'amm:AMM program interaction subcommand'
|
||||
'ata:Associated Token Account program interaction subcommand'
|
||||
'vault:Vault program interaction subcommand'
|
||||
'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions'
|
||||
'config:Command to setup config, get and set config fields'
|
||||
'restore-keys:Restoring keys from given password at given depth'
|
||||
@ -56,6 +57,9 @@ _wallet() {
|
||||
ata)
|
||||
_wallet_ata
|
||||
;;
|
||||
vault)
|
||||
_wallet_vault
|
||||
;;
|
||||
config)
|
||||
_wallet_config
|
||||
;;
|
||||
@ -442,6 +446,41 @@ _wallet_ata() {
|
||||
esac
|
||||
}
|
||||
|
||||
# vault subcommand
|
||||
_wallet_vault() {
|
||||
local -a subcommands
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'transfer:Transfer native tokens from sender to recipient vault account'
|
||||
'claim:Claim native tokens from account vault account'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t subcommands 'vault subcommands' subcommands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
transfer)
|
||||
_arguments \
|
||||
'--from[Source account with privacy prefix or label]:from:_wallet_account_ids' \
|
||||
'--to[Recipient account with privacy prefix or label]:to:_wallet_account_ids' \
|
||||
'--amount[Amount of native tokens to transfer]:amount:'
|
||||
;;
|
||||
claim)
|
||||
_arguments \
|
||||
'--account-id[Account with privacy prefix or label; vault id is derived automatically]:account:_wallet_account_ids' \
|
||||
'--amount[Amount of native tokens to claim]:amount:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# config subcommand
|
||||
_wallet_config() {
|
||||
local -a subcommands
|
||||
@ -515,6 +554,7 @@ _wallet_help() {
|
||||
'token:Token program interaction subcommand'
|
||||
'amm:AMM program interaction subcommand'
|
||||
'ata:Associated Token Account program interaction subcommand'
|
||||
'vault:Vault program interaction subcommand'
|
||||
'check-health:Check the wallet can connect to the node'
|
||||
'config:Command to setup config, get and set config fields'
|
||||
'restore-keys:Restoring keys from given password at given depth'
|
||||
|
||||
208
integration_tests/tests/vault.rs
Normal file
208
integration_tests/tests/vault.rs
Normal file
@ -0,0 +1,208 @@
|
||||
#![expect(
|
||||
clippy::tests_outside_test_module,
|
||||
reason = "We don't care about these in tests"
|
||||
)]
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use integration_tests::{TestContext, private_mention, public_mention};
|
||||
use lee::program::Program;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tokio::test;
|
||||
use wallet::cli::{Command, SubcommandReturnValue, programs::vault::VaultSubcommand};
|
||||
|
||||
#[test]
|
||||
async fn public_transfer_and_public_claim() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
let amount: u128 = 100;
|
||||
let sender = ctx.existing_public_accounts()[0];
|
||||
let recipient = ctx.existing_public_accounts()[1];
|
||||
|
||||
let vault_program_id = Program::vault().id();
|
||||
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient);
|
||||
|
||||
let sender_balance_before = ctx.sequencer_client().get_account_balance(sender).await?;
|
||||
let recipient_balance_before = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(recipient)
|
||||
.await?;
|
||||
let recipient_vault_balance_before = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(recipient_vault_id)
|
||||
.await?;
|
||||
|
||||
let transfer_result = wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
Command::Vault(VaultSubcommand::Transfer {
|
||||
from: public_mention(sender),
|
||||
to: public_mention(recipient),
|
||||
amount,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
assert!(
|
||||
matches!(transfer_result, SubcommandReturnValue::Empty),
|
||||
"Expected Empty return value for public vault transfer"
|
||||
);
|
||||
|
||||
let sender_balance_after_transfer = ctx.sequencer_client().get_account_balance(sender).await?;
|
||||
let recipient_balance_after_transfer = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(recipient)
|
||||
.await?;
|
||||
let recipient_vault_balance_after_transfer = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(recipient_vault_id)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
sender_balance_after_transfer,
|
||||
sender_balance_before - amount
|
||||
);
|
||||
assert_eq!(recipient_balance_after_transfer, recipient_balance_before);
|
||||
assert_eq!(
|
||||
recipient_vault_balance_after_transfer,
|
||||
recipient_vault_balance_before + amount
|
||||
);
|
||||
|
||||
let claim_result = wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
Command::Vault(VaultSubcommand::Claim {
|
||||
account_id: public_mention(recipient),
|
||||
amount,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
assert!(
|
||||
matches!(claim_result, SubcommandReturnValue::Empty),
|
||||
"Expected Empty return value for public vault claim"
|
||||
);
|
||||
|
||||
let sender_balance_after_claim = ctx.sequencer_client().get_account_balance(sender).await?;
|
||||
let recipient_balance_after_claim = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(recipient)
|
||||
.await?;
|
||||
let recipient_vault_balance_after_claim = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(recipient_vault_id)
|
||||
.await?;
|
||||
|
||||
assert_eq!(sender_balance_after_claim, sender_balance_before - amount);
|
||||
assert_eq!(
|
||||
recipient_balance_after_claim,
|
||||
recipient_balance_before + amount
|
||||
);
|
||||
assert_eq!(
|
||||
recipient_vault_balance_after_claim,
|
||||
recipient_vault_balance_before
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn private_transfer_and_private_claim() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
let amount: u128 = 100;
|
||||
let sender = ctx.existing_private_accounts()[0];
|
||||
let owner = ctx.existing_private_accounts()[1];
|
||||
|
||||
let vault_program_id = Program::vault().id();
|
||||
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner);
|
||||
|
||||
let sender_balance_before = ctx
|
||||
.wallet()
|
||||
.get_account_private(sender)
|
||||
.context("Failed to load sender private account")?
|
||||
.balance;
|
||||
let owner_balance_before = ctx
|
||||
.wallet()
|
||||
.get_account_private(owner)
|
||||
.context("Failed to load owner private account")?
|
||||
.balance;
|
||||
let owner_vault_balance_before = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(owner_vault_id)
|
||||
.await?;
|
||||
|
||||
let transfer_result = wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
Command::Vault(VaultSubcommand::Transfer {
|
||||
from: private_mention(sender),
|
||||
to: private_mention(owner),
|
||||
amount,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
assert!(
|
||||
matches!(
|
||||
transfer_result,
|
||||
SubcommandReturnValue::PrivacyPreservingTransfer { .. }
|
||||
),
|
||||
"Expected PrivacyPreservingTransfer return value for private vault transfer"
|
||||
);
|
||||
|
||||
let sender_balance_after_transfer = ctx
|
||||
.wallet()
|
||||
.get_account_private(sender)
|
||||
.context("Failed to load sender private account after transfer")?
|
||||
.balance;
|
||||
let owner_balance_after_transfer = ctx
|
||||
.wallet()
|
||||
.get_account_private(owner)
|
||||
.context("Failed to load owner private account after transfer")?
|
||||
.balance;
|
||||
let owner_vault_balance_after_transfer = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(owner_vault_id)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
sender_balance_after_transfer,
|
||||
sender_balance_before - amount
|
||||
);
|
||||
assert_eq!(owner_balance_after_transfer, owner_balance_before);
|
||||
assert_eq!(
|
||||
owner_vault_balance_after_transfer,
|
||||
owner_vault_balance_before + amount
|
||||
);
|
||||
|
||||
let claim_result = wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
Command::Vault(VaultSubcommand::Claim {
|
||||
account_id: private_mention(owner),
|
||||
amount,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
assert!(
|
||||
matches!(
|
||||
claim_result,
|
||||
SubcommandReturnValue::PrivacyPreservingTransfer { .. }
|
||||
),
|
||||
"Expected PrivacyPreservingTransfer return value for private vault claim"
|
||||
);
|
||||
|
||||
let sender_balance_after_claim = ctx
|
||||
.wallet()
|
||||
.get_account_private(sender)
|
||||
.context("Failed to load sender private account after claim")?
|
||||
.balance;
|
||||
let owner_balance_after_claim = ctx
|
||||
.wallet()
|
||||
.get_account_private(owner)
|
||||
.context("Failed to load owner private account after claim")?
|
||||
.balance;
|
||||
let owner_vault_balance_after_claim = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(owner_vault_id)
|
||||
.await?;
|
||||
|
||||
assert_eq!(sender_balance_after_claim, sender_balance_before - amount);
|
||||
assert_eq!(owner_balance_after_claim, owner_balance_before + amount);
|
||||
assert_eq!(owner_vault_balance_after_claim, owner_vault_balance_before);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -18,6 +18,7 @@ token_core.workspace = true
|
||||
amm_core.workspace = true
|
||||
testnet_initial_state.workspace = true
|
||||
ata_core.workspace = true
|
||||
vault_core.workspace = true
|
||||
bip39.workspace = true
|
||||
pyo3.workspace = true
|
||||
rpassword = "7"
|
||||
|
||||
@ -22,7 +22,7 @@ use crate::{
|
||||
programs::{
|
||||
amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand,
|
||||
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
||||
token::TokenProgramAgnosticSubcommand,
|
||||
token::TokenProgramAgnosticSubcommand, vault::VaultSubcommand,
|
||||
},
|
||||
},
|
||||
storage::Storage,
|
||||
@ -65,6 +65,9 @@ pub enum Command {
|
||||
/// Associated Token Account program interaction subcommand.
|
||||
#[command(subcommand)]
|
||||
Ata(AtaSubcommand),
|
||||
/// Vault program interaction subcommand.
|
||||
#[command(subcommand)]
|
||||
Vault(VaultSubcommand),
|
||||
/// Group key management (create, invite, join, derive keys).
|
||||
#[command(subcommand)]
|
||||
Group(GroupSubcommand),
|
||||
@ -254,6 +257,7 @@ pub async fn execute_subcommand(
|
||||
Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?,
|
||||
Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?,
|
||||
Command::Ata(ata_subcommand) => ata_subcommand.handle_subcommand(wallet_core).await?,
|
||||
Command::Vault(vault_subcommand) => vault_subcommand.handle_subcommand(wallet_core).await?,
|
||||
Command::Group(group_subcommand) => group_subcommand.handle_subcommand(wallet_core).await?,
|
||||
Command::Keycard(keycard_subcommand) => {
|
||||
keycard_subcommand.handle_subcommand(wallet_core).await?
|
||||
|
||||
@ -3,3 +3,4 @@ pub mod ata;
|
||||
pub mod native_token_transfer;
|
||||
pub mod pinata;
|
||||
pub mod token;
|
||||
pub mod vault;
|
||||
|
||||
140
lez/wallet/src/cli/programs/vault.rs
Normal file
140
lez/wallet/src/cli/programs/vault.rs
Normal file
@ -0,0 +1,140 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use common::transaction::LeeTransaction;
|
||||
use lee::AccountId;
|
||||
|
||||
use crate::{
|
||||
AccDecodeData::Decode,
|
||||
WalletCore,
|
||||
account::AccountIdWithPrivacy,
|
||||
cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand},
|
||||
program_facades::vault::Vault,
|
||||
};
|
||||
|
||||
/// Represents generic CLI subcommand for a wallet working with vault program.
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum VaultSubcommand {
|
||||
/// Transfer native tokens from sender to recipient's vault account.
|
||||
Transfer {
|
||||
/// Either 32 byte base58 account id string with privacy prefix or a label.
|
||||
#[arg(long)]
|
||||
from: CliAccountMention,
|
||||
/// Either 32 byte base58 account id string with privacy prefix or a label.
|
||||
#[arg(long)]
|
||||
to: CliAccountMention,
|
||||
/// Amount of native tokens to transfer.
|
||||
#[arg(long)]
|
||||
amount: u128,
|
||||
},
|
||||
/// Claim native tokens from account's vault account.
|
||||
Claim {
|
||||
/// Either 32 byte base58 account id string with privacy prefix or a label.
|
||||
///
|
||||
/// The owner's vault account id is computed from this account id.
|
||||
#[arg(long)]
|
||||
account_id: CliAccountMention,
|
||||
/// Amount of native tokens to claim.
|
||||
#[arg(long)]
|
||||
amount: u128,
|
||||
},
|
||||
}
|
||||
|
||||
impl WalletSubcommand for VaultSubcommand {
|
||||
async fn handle_subcommand(
|
||||
self,
|
||||
wallet_core: &mut WalletCore,
|
||||
) -> Result<SubcommandReturnValue> {
|
||||
match self {
|
||||
Self::Transfer { from, to, amount } => {
|
||||
let from = from.resolve(wallet_core.storage())?;
|
||||
let recipient = to.resolve(wallet_core.storage())?;
|
||||
let recipient_id = account_id_without_privacy(recipient);
|
||||
|
||||
match from {
|
||||
AccountIdWithPrivacy::Public(sender_id) => {
|
||||
let tx_hash = Vault(wallet_core)
|
||||
.send_transfer(sender_id, recipient_id, amount)
|
||||
.await?;
|
||||
|
||||
println!("Transaction hash is {tx_hash}");
|
||||
|
||||
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||
|
||||
println!("Transaction data is {transfer_tx:?}");
|
||||
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
AccountIdWithPrivacy::Private(sender_id) => {
|
||||
let (tx_hash, secret_sender) = Vault(wallet_core)
|
||||
.send_transfer_private_sender(sender_id, recipient_id, amount)
|
||||
.await?;
|
||||
|
||||
println!("Transaction hash is {tx_hash}");
|
||||
|
||||
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||
|
||||
println!("Transaction data is {transfer_tx:?}");
|
||||
|
||||
if let LeeTransaction::PrivacyPreserving(tx) = transfer_tx {
|
||||
wallet_core.decode_insert_privacy_preserving_transaction_results(
|
||||
&tx,
|
||||
&[Decode(secret_sender, sender_id)],
|
||||
)?;
|
||||
}
|
||||
|
||||
wallet_core.store_persistent_data()?;
|
||||
|
||||
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Claim { account_id, amount } => {
|
||||
let account_id = account_id.resolve(wallet_core.storage())?;
|
||||
|
||||
match account_id {
|
||||
AccountIdWithPrivacy::Public(owner_id) => {
|
||||
let tx_hash = Vault(wallet_core).send_claim(owner_id, amount).await?;
|
||||
|
||||
println!("Transaction hash is {tx_hash}");
|
||||
|
||||
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||
|
||||
println!("Transaction data is {transfer_tx:?}");
|
||||
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
AccountIdWithPrivacy::Private(owner_id) => {
|
||||
let (tx_hash, secret_owner) = Vault(wallet_core)
|
||||
.send_claim_private_owner(owner_id, amount)
|
||||
.await?;
|
||||
|
||||
println!("Transaction hash is {tx_hash}");
|
||||
|
||||
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||
|
||||
println!("Transaction data is {transfer_tx:?}");
|
||||
|
||||
if let LeeTransaction::PrivacyPreserving(tx) = transfer_tx {
|
||||
wallet_core.decode_insert_privacy_preserving_transaction_results(
|
||||
&tx,
|
||||
&[Decode(secret_owner, owner_id)],
|
||||
)?;
|
||||
}
|
||||
|
||||
wallet_core.store_persistent_data()?;
|
||||
|
||||
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn account_id_without_privacy(account_id: AccountIdWithPrivacy) -> AccountId {
|
||||
match account_id {
|
||||
AccountIdWithPrivacy::Public(account_id) | AccountIdWithPrivacy::Private(account_id) => {
|
||||
account_id
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,3 +6,4 @@ pub mod ata;
|
||||
pub mod native_token_transfer;
|
||||
pub mod pinata;
|
||||
pub mod token;
|
||||
pub mod vault;
|
||||
|
||||
139
lez/wallet/src/program_facades/vault.rs
Normal file
139
lez/wallet/src/program_facades/vault.rs
Normal file
@ -0,0 +1,139 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use common::HashType;
|
||||
use lee::{
|
||||
AccountId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
|
||||
};
|
||||
use lee_core::SharedSecretKey;
|
||||
|
||||
use crate::{AccountIdentity, ExecutionFailureKind, WalletCore};
|
||||
|
||||
pub struct Vault<'wallet>(pub &'wallet WalletCore);
|
||||
|
||||
impl Vault<'_> {
|
||||
pub async fn send_transfer(
|
||||
&self,
|
||||
sender_id: AccountId,
|
||||
recipient_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<HashType, ExecutionFailureKind> {
|
||||
let program = Program::vault();
|
||||
let vault_program_id = program.id();
|
||||
let recipient_vault_id =
|
||||
vault_core::compute_vault_account_id(vault_program_id, recipient_id);
|
||||
|
||||
let instruction = vault_core::Instruction::Transfer {
|
||||
recipient_id,
|
||||
amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
self.0
|
||||
.send_pub_tx(
|
||||
vec![
|
||||
AccountIdentity::Public(sender_id),
|
||||
AccountIdentity::PublicNoSign(recipient_vault_id),
|
||||
],
|
||||
instruction_data,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_transfer_private_sender(
|
||||
&self,
|
||||
sender_id: AccountId,
|
||||
recipient_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
||||
let vault_program_id = Program::vault().id();
|
||||
let recipient_vault_id =
|
||||
vault_core::compute_vault_account_id(vault_program_id, recipient_id);
|
||||
let instruction = vault_core::Instruction::Transfer {
|
||||
recipient_id,
|
||||
amount,
|
||||
};
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
self.0
|
||||
.resolve_private_account(sender_id)
|
||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
|
||||
AccountIdentity::Public(recipient_vault_id),
|
||||
],
|
||||
instruction_data,
|
||||
&vault_with_auth_dependency(),
|
||||
)
|
||||
.await
|
||||
.map(|(hash, mut secrets)| {
|
||||
let secret = secrets.pop().expect("expected sender's secret");
|
||||
(hash, secret)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_claim(
|
||||
&self,
|
||||
owner_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<HashType, ExecutionFailureKind> {
|
||||
let program = Program::vault();
|
||||
let vault_program_id = program.id();
|
||||
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
|
||||
|
||||
let instruction = vault_core::Instruction::Claim { amount };
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
self.0
|
||||
.send_pub_tx(
|
||||
vec![
|
||||
AccountIdentity::Public(owner_id),
|
||||
AccountIdentity::PublicNoSign(owner_vault_id),
|
||||
],
|
||||
instruction_data,
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_claim_private_owner(
|
||||
&self,
|
||||
owner_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
||||
let vault_program_id = Program::vault().id();
|
||||
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
|
||||
|
||||
let instruction = vault_core::Instruction::Claim { amount };
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
self.0
|
||||
.resolve_private_account(owner_id)
|
||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
|
||||
AccountIdentity::Public(owner_vault_id),
|
||||
],
|
||||
instruction_data,
|
||||
&vault_with_auth_dependency(),
|
||||
)
|
||||
.await
|
||||
.map(|(hash, mut secrets)| {
|
||||
let secret = secrets.pop().expect("expected owner's secret");
|
||||
(hash, secret)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn vault_with_auth_dependency() -> ProgramWithDependencies {
|
||||
let auth_transfer = Program::authenticated_transfer_program();
|
||||
let mut deps = HashMap::new();
|
||||
deps.insert(auth_transfer.id(), auth_transfer);
|
||||
ProgramWithDependencies::new(Program::vault(), deps)
|
||||
}
|
||||
@ -17,7 +17,6 @@ lee_core = { workspace = true, features = ["host"] }
|
||||
sequencer_core = { workspace = true, features = ["default", "testnet"] }
|
||||
sequencer_service.workspace = true
|
||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||
vault_core.workspace = true
|
||||
wallet.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
|
||||
@ -353,7 +353,7 @@ impl TestContextBuilder {
|
||||
)
|
||||
.context("Failed to setup wallet")?;
|
||||
|
||||
setup_public_accounts_with_initial_supply(&wallet, &initial_public_accounts)
|
||||
setup_public_accounts_with_initial_supply(&mut wallet, &initial_public_accounts)
|
||||
.await
|
||||
.context("Failed to initialize public accounts in wallet")?;
|
||||
|
||||
|
||||
@ -1,19 +1,22 @@
|
||||
use std::{collections::HashMap, net::SocketAddr, path::PathBuf};
|
||||
use std::{net::SocketAddr, path::PathBuf};
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use common::transaction::LeeTransaction;
|
||||
use indexer_service::IndexerHandle;
|
||||
use lee::{AccountId, PrivateKey, PublicKey, PublicTransaction, program::Program};
|
||||
use lee::{AccountId, PrivateKey, PublicKey};
|
||||
use log::{debug, warn};
|
||||
use sequencer_service::{GenesisAction, SequencerHandle};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tempfile::TempDir;
|
||||
use testcontainers::compose::DockerCompose;
|
||||
use wallet::{AccDecodeData::Decode, AccountIdentity, WalletCore, config::WalletConfigOverrides};
|
||||
use wallet::{
|
||||
WalletCore,
|
||||
cli::{Command, SubcommandReturnValue, programs::vault::VaultSubcommand},
|
||||
config::WalletConfigOverrides,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
BEDROCK_SERVICE_PORT, BEDROCK_SERVICE_WITH_OPEN_PORT,
|
||||
config::{self, InitialPrivateAccountForWallet},
|
||||
private_mention, public_mention,
|
||||
};
|
||||
|
||||
pub async fn setup_bedrock_node() -> Result<(DockerCompose, SocketAddr)> {
|
||||
@ -185,7 +188,7 @@ pub fn setup_wallet(
|
||||
}
|
||||
|
||||
pub async fn setup_public_accounts_with_initial_supply(
|
||||
wallet: &WalletCore,
|
||||
wallet: &mut WalletCore,
|
||||
initial_public_accounts: &[(PrivateKey, u128)],
|
||||
) -> Result<()> {
|
||||
for (private_key, amount) in initial_public_accounts {
|
||||
@ -219,45 +222,23 @@ pub async fn setup_private_accounts_with_initial_supply(
|
||||
}
|
||||
|
||||
async fn claim_funds_from_vault(
|
||||
wallet: &WalletCore,
|
||||
wallet: &mut WalletCore,
|
||||
owner_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<()> {
|
||||
let vault_program_id = Program::vault().id();
|
||||
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
|
||||
|
||||
let nonces = wallet
|
||||
.get_accounts_nonces(vec![owner_id])
|
||||
.await
|
||||
.context("Failed to fetch owner nonce")?;
|
||||
|
||||
let signing_key = wallet
|
||||
.storage()
|
||||
.key_chain()
|
||||
.pub_account_signing_key(owner_id)
|
||||
.with_context(|| format!("Missing signing key for public account {owner_id}"))?;
|
||||
|
||||
let message = lee::public_transaction::Message::try_new(
|
||||
vault_program_id,
|
||||
vec![owner_id, owner_vault_id],
|
||||
nonces,
|
||||
vault_core::Instruction::Claim { amount },
|
||||
let result = wallet::cli::execute_subcommand(
|
||||
wallet,
|
||||
Command::Vault(VaultSubcommand::Claim {
|
||||
account_id: public_mention(owner_id),
|
||||
amount,
|
||||
}),
|
||||
)
|
||||
.context("Failed to build vault claim message")?;
|
||||
.await
|
||||
.context("Failed to execute public vault claim command")?;
|
||||
|
||||
let witness_set = lee::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
let tx_hash = wallet
|
||||
.sequencer_client
|
||||
.send_transaction(LeeTransaction::Public(tx))
|
||||
.await
|
||||
.context("Failed to submit vault claim transaction")?;
|
||||
|
||||
wallet
|
||||
.poll_native_token_transfer(tx_hash)
|
||||
.await
|
||||
.context("Failed to confirm vault claim transaction")?;
|
||||
let SubcommandReturnValue::Empty = result else {
|
||||
bail!("Expected Empty return value for public vault claim");
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -271,55 +252,19 @@ async fn claim_funds_from_vault_to_private(
|
||||
bail!("Missing private account in wallet key chain for account {owner_id}");
|
||||
};
|
||||
|
||||
let vault_program = Program::vault();
|
||||
let vault_program_id = vault_program.id();
|
||||
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
|
||||
let result = wallet::cli::execute_subcommand(
|
||||
wallet,
|
||||
Command::Vault(VaultSubcommand::Claim {
|
||||
account_id: private_mention(owner_id),
|
||||
amount,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.context("Failed to execute private vault claim command")?;
|
||||
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(vault_core::Instruction::Claim { amount })
|
||||
.context("Failed to serialize vault private claim instruction")?;
|
||||
|
||||
let program_with_dependencies =
|
||||
lee::privacy_preserving_transaction::circuit::ProgramWithDependencies::new(
|
||||
vault_program,
|
||||
HashMap::from([(
|
||||
Program::authenticated_transfer_program().id(),
|
||||
Program::authenticated_transfer_program(),
|
||||
)]),
|
||||
);
|
||||
|
||||
let (tx_hash, mut secrets) = wallet
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
AccountIdentity::PrivateOwned(owner_id),
|
||||
AccountIdentity::Public(owner_vault_id),
|
||||
],
|
||||
instruction_data,
|
||||
&program_with_dependencies,
|
||||
)
|
||||
.await
|
||||
.context("Failed to submit private vault claim transaction")?;
|
||||
|
||||
let secret = secrets
|
||||
.pop()
|
||||
.context("Expected one private output secret for vault claim")?;
|
||||
|
||||
let transfer_tx = wallet
|
||||
.poll_native_token_transfer(tx_hash)
|
||||
.await
|
||||
.context("Failed to confirm private vault claim transaction")?;
|
||||
|
||||
let LeeTransaction::PrivacyPreserving(tx) = transfer_tx else {
|
||||
bail!("Expected privacy preserving transaction result for private vault claim");
|
||||
let SubcommandReturnValue::PrivacyPreservingTransfer { .. } = result else {
|
||||
bail!("Expected PrivacyPreservingTransfer return value for private vault claim");
|
||||
};
|
||||
|
||||
wallet
|
||||
.decode_insert_privacy_preserving_transaction_results(&tx, &[Decode(secret, owner_id)])
|
||||
.context("Failed to decode private vault claim transaction")?;
|
||||
|
||||
wallet
|
||||
.store_persistent_data()
|
||||
.context("Failed to store wallet data after private vault claim")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user