fix(asb): use unlocked monero balance for quotes

This commit is contained in:
Byron Hambly 2022-11-07 09:52:34 +02:00
parent c8f3fcebe7
commit f224c49584
No known key found for this signature in database
GPG Key ID: DE8F6EA20A661697
11 changed files with 106 additions and 39 deletions

View File

@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- Changed ASB to quote on Monero unlocked balance instead of total balance
### Added
## [0.11.0] - 2022-08-11
### Changed
@ -320,7 +326,7 @@ It is possible to migrate critical data from the old db to the sqlite but there
- Fixed an issue where Alice would not verify if Bob's Bitcoin lock transaction is semantically correct, i.e. pays the agreed upon amount to an output owned by both of them.
Fixing this required a **breaking change** on the network layer and hence old versions are not compatible with this version.
[Unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.11.0...HEAD
[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.11.0...HEAD
[0.11.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.2...0.11.0
[0.10.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.1...0.10.2
[0.10.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.0...0.10.1

1
Cargo.lock generated
View File

@ -2278,6 +2278,7 @@ dependencies = [
"monero-epee-bin-serde",
"rand 0.7.3",
"reqwest",
"rust_decimal",
"serde",
"serde_json",
"tokio",

View File

@ -138,15 +138,30 @@ impl<'c> Monero {
let wallet = self.wallet(name)?;
let address = wallet.address().await?.address;
let mut expected_total = 0;
let mut expected_unlocked = 0;
let mut unlocked = 0;
for amount in amount_in_outputs {
if amount > 0 {
miner_wallet.transfer(&address, amount).await?;
expected_total += amount;
tracing::info!("Funded {} wallet with {}", wallet.name, amount);
// sanity checks for total/unlocked balance
let total = wallet.balance().await?;
assert_eq!(total, expected_total);
assert_eq!(unlocked, expected_unlocked);
monerod
.client()
.generateblocks(10, miner_address.clone())
.await?;
wallet.refresh().await?;
expected_unlocked += amount;
unlocked = wallet.unlocked_balance().await?;
assert_eq!(unlocked, expected_unlocked);
assert_eq!(total, expected_total);
}
}
@ -310,10 +325,18 @@ impl<'c> MoneroWalletRpc {
Ok(balance)
}
pub async fn unlocked_balance(&self) -> Result<u64> {
self.client().refresh().await?;
let balance = self.client().get_balance(0).await?.unlocked_balance;
Ok(balance)
}
pub async fn refresh(&self) -> Result<Refreshed> {
Ok(self.client().refresh().await?)
}
}
/// Mine a block ever BLOCK_TIME_SECS seconds.
async fn mine(monerod: monerod::Client, reward_address: String) -> Result<()> {
loop {

View File

@ -13,6 +13,7 @@ monero = "0.12"
monero-epee-bin-serde = "1"
rand = "0.7"
reqwest = { version = "0.11", default-features = false, features = [ "json" ] }
rust_decimal = { version = "1", features = [ "serde-float" ] }
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
tracing = "0.1"

View File

@ -1,4 +1,7 @@
use std::fmt;
use anyhow::{Context, Result};
use rust_decimal::Decimal;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
@ -86,10 +89,30 @@ pub struct GetAddress {
#[derive(Deserialize, Debug, Clone, Copy)]
pub struct GetBalance {
pub balance: u64,
pub blocks_to_unlock: u32,
pub multisig_import_needed: bool,
pub time_to_unlock: u32,
pub unlocked_balance: u64,
pub multisig_import_needed: bool,
pub blocks_to_unlock: u32,
pub time_to_unlock: u32,
}
impl fmt::Display for GetBalance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut total = Decimal::from(self.balance);
total
.set_scale(12)
.expect("12 is smaller than max precision of 28");
let mut unlocked = Decimal::from(self.unlocked_balance);
unlocked
.set_scale(12)
.expect("12 is smaller than max precision of 28");
write!(
f,
"total balance: {}, unlocked balance: {}",
total, unlocked
)
}
}
#[derive(Deserialize, Debug, Clone)]

View File

@ -1,4 +1,5 @@
use crate::asb::{Behaviour, OutEvent, Rate};
use crate::monero::Amount;
use crate::network::quote::BidQuote;
use crate::network::swap_setup::alice::WalletSnapshot;
use crate::network::transfer_proof;
@ -326,7 +327,10 @@ where
.ask()
.context("Failed to compute asking price")?;
let xmr = self.monero_wallet.get_balance().await?;
let balance = self.monero_wallet.get_balance().await?;
// use unlocked monero balance for quote
let xmr = Amount::from_piconero(balance.unlocked_balance);
let max_bitcoin_for_monero = xmr.max_bitcoin_for_price(ask_price).ok_or_else(|| {
anyhow::anyhow!("Bitcoin price ({}) x Monero ({}) overflow", ask_price, xmr)

View File

@ -31,7 +31,6 @@ use swap::asb::config::{
use swap::asb::{cancel, punish, redeem, refund, safely_abort, EventLoop, Finality, KrakenRate};
use swap::common::check_latest_version;
use swap::database::open_db;
use swap::monero::Amount;
use swap::network::rendezvous::XmrBtcNamespace;
use swap::network::swarm;
use swap::protocol::alice::{run, AliceState};
@ -103,24 +102,35 @@ async fn main() -> Result<()> {
match cmd {
Command::Start { resume_only } => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
let monero_wallet = init_monero_wallet(&config, env_config).await?;
let bitcoin_balance = bitcoin_wallet.balance().await?;
tracing::info!(%bitcoin_balance, "Initialized Bitcoin wallet");
let monero_balance = monero_wallet.get_balance().await?;
if monero_balance == Amount::ZERO {
let monero_address = monero_wallet.get_main_address();
tracing::warn!(
%monero_address,
"The Monero balance is 0, make sure to deposit funds at",
)
} else {
tracing::info!(%monero_balance, "Initialized Monero wallet");
let monero_address = monero_wallet.get_main_address();
tracing::info!(%monero_address, "Monero wallet address");
let monero = monero_wallet.get_balance().await?;
match (monero.balance, monero.unlocked_balance) {
(0, _) => {
tracing::warn!(
%monero_address,
"The Monero balance is 0, make sure to deposit funds at",
)
}
(total, 0) => {
let total = monero::Amount::from_piconero(total);
tracing::warn!(
%total,
"Unlocked Monero balance is 0, total balance is",
)
}
(total, unlocked) => {
let total = monero::Amount::from_piconero(total);
let unlocked = monero::Amount::from_piconero(unlocked);
tracing::info!(%total, %unlocked, "Monero wallet balance");
}
}
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
let bitcoin_balance = bitcoin_wallet.balance().await?;
tracing::info!(%bitcoin_balance, "Bitcoin wallet balance");
let kraken_price_updates = kraken::connect(config.maker.price_ticker_ws_url.clone())?;
// setup Tor hidden services
@ -236,16 +246,13 @@ async fn main() -> Result<()> {
bitcoin_wallet.broadcast(signed_tx, "withdraw").await?;
}
Command::Balance => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
let monero_wallet = init_monero_wallet(&config, env_config).await?;
let bitcoin_balance = bitcoin_wallet.balance().await?;
let monero_balance = monero_wallet.get_balance().await?;
tracing::info!(%monero_balance);
tracing::info!(
%bitcoin_balance,
%monero_balance,
"Current balance");
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
let bitcoin_balance = bitcoin_wallet.balance().await?;
tracing::info!(%bitcoin_balance);
}
Command::Cancel { swap_id } => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;

View File

@ -48,10 +48,10 @@ mod test {
#[tokio::test]
#[ignore = "For local testing, makes http requests to github."]
async fn it_compares_with_github() {
let result = check_latest_version("0.10.1").await.unwrap();
let result = check_latest_version("0.11.0").await.unwrap();
assert_eq!(result, Version::Available);
let result = check_latest_version("0.10.2").await.unwrap();
let result = check_latest_version("0.11.1").await.unwrap();
assert_eq!(result, Version::Current);
}
}

View File

@ -253,10 +253,8 @@ impl Wallet {
}
/// Get the balance of the primary account.
pub async fn get_balance(&self) -> Result<Amount> {
let amount = self.inner.lock().await.get_balance(0).await?.balance;
Ok(Amount::from_piconero(amount))
pub async fn get_balance(&self) -> Result<wallet::GetBalance> {
Ok(self.inner.lock().await.get_balance(0).await?)
}
pub async fn block_height(&self) -> Result<BlockHeight> {

View File

@ -1,4 +1,5 @@
use crate::asb::LatestRate;
use crate::monero::Amount;
use crate::network::swap_setup;
use crate::network::swap_setup::{
protocol, BlockchainNetwork, SpotPriceError, SpotPriceRequest, SpotPriceResponse,
@ -42,7 +43,7 @@ pub enum OutEvent {
#[derive(Debug)]
pub struct WalletSnapshot {
balance: monero::Amount,
balance: monero_rpc::wallet::GetBalance,
lock_fee: monero::Amount,
// TODO: Consider using the same address for punish and redeem (they are mutually exclusive, so
@ -323,7 +324,8 @@ where
.sell_quote(btc)
.map_err(Error::SellQuoteCalculationFailed)?;
if wallet_snapshot.balance < xmr + wallet_snapshot.lock_fee {
let unlocked = Amount::from_piconero(wallet_snapshot.balance.unlocked_balance);
if unlocked < xmr + wallet_snapshot.lock_fee {
return Err(Error::BalanceTooLow {
balance: wallet_snapshot.balance,
buy: btc,
@ -479,9 +481,9 @@ pub enum Error {
max: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Balance {balance} too low to fulfill swapping {buy}")]
#[error("Unlocked balance ({balance}) too low to fulfill swapping {buy}")]
BalanceTooLow {
balance: monero::Amount,
balance: monero_rpc::wallet::GetBalance,
buy: bitcoin::Amount,
},
#[error("Failed to fetch latest rate")]

View File

@ -867,7 +867,9 @@ impl Wallet for monero::Wallet {
}
async fn get_balance(&self) -> Result<Self::Amount> {
self.get_balance().await
let total = self.get_balance().await?;
let balance = Self::Amount::from_piconero(total.balance);
Ok(balance)
}
}