Skip to content

Commit

Permalink
Encourage users to explicitly choose the timelock type
Browse files Browse the repository at this point in the history
After adding `proptest` as a dev-dependency to `baru/Cargo.toml`, we
could no longer build `baru` with `wasm32-unknown-unknown` as a
target:
https://github.com/comit-network/baru/runs/3318619052?check_suite_focus=true#step:6:15.

To fix it we had to follow the advice of the compile error and
explicitly enable the `js` feature of our transitive dependency
`getrandom`.

Unfortunately explicitly depending on `getrandom` created a cyclic
dependency explained here:
tkaitchuck/aHash#95. To solve this new
problem we have to depend on fix `indexmap`'s version to `1.6.2`.
  • Loading branch information
luckysori committed Aug 13, 2021
1 parent 6019072 commit c27ab0b
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
The provided wallet comes with a UTXO cache which this is updated using `Wallet::sync`.
This allows users of the library to optimise the number of requests to their backend.
Users can also sign said UTXOs by calling `Wallet::sign`.
- `Timelock` type to allow users of the library to explicitly choose the type of timelock they want to use when building the loan transaction.
For the time being, users can still pass in a `u32`, but they are encouraged not to.

### Changed

Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ conquer-once = "0.3"
elements = { version = "0.18", features = ["serde-feature"] }
elements-miniscript = { version = "0.1", features = ["use-serde"] }
futures = "0.3"
getrandom = { version = "0.2", features = ["js"] }
hex = "0.4"
hkdf = { version = "0.10", features = ["std"] }
indexmap = "=1.6.2"
itertools = "0.10"
log = "0.4"
rand = { version = "0.6", features = ["wasm-bindgen"] }
Expand All @@ -33,6 +35,7 @@ thiserror = "1"
[dev-dependencies]
elements-consensus = { git = "https://github.com/comit-network/rust-elements-consensus", rev = "ac88dbedcd019eef44f58499417dcdbeda994b0b" }
link-cplusplus = "1"
proptest = { version = "1", default-features = false, features = ["std"] }
rand_chacha = "0.1"
serde_json = "1"
tokio = { version = "1", default-features = false, features = ["macros", "rt"] }
88 changes: 83 additions & 5 deletions src/loan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,7 @@ impl Lender0 {
secp: &Secp256k1<C>,
coin_selector: CS,
loan_request: LoanRequest,
timelock: u32,
timelock: Timelock,
rate: u64,
) -> Result<Lender1>
where
Expand Down Expand Up @@ -971,13 +971,14 @@ impl Lender0 {
repayment_amount: Amount,
min_collateral_price: u64,
(borrower_pk, borrower_address): (PublicKey, Address),
timelock: u32,
timelock: impl Into<Timelock>,
) -> Result<Lender1>
where
R: RngCore + CryptoRng,
C: Verification + Signing,
{
let chain = Chain::new(&borrower_address, &self.address)?;
let timelock = Into::<Timelock>::into(timelock);

let collateral_inputs = collateral_inputs
.into_iter()
Expand Down Expand Up @@ -1138,7 +1139,7 @@ impl Lender0 {
let collateral_contract = CollateralContract::new(
borrower_pk,
lender_pk,
timelock,
timelock.into(),
(repayment_principal_output, self.address_blinder),
self.oracle_pk,
min_collateral_price,
Expand Down Expand Up @@ -1289,7 +1290,7 @@ struct LoanAmounts {
pub struct Lender1 {
keypair: (SecretKey, PublicKey),
address: Address,
timelock: u32,
timelock: Timelock,
loan_transaction: Transaction,
collateral_contract: CollateralContract,
collateral_amount: Amount,
Expand Down Expand Up @@ -1375,7 +1376,7 @@ impl Lender1 {

let mut liquidation_transaction = Transaction {
version: 2,
lock_time: self.timelock,
lock_time: self.timelock.into(),
input: tx_ins,
output: tx_outs,
};
Expand Down Expand Up @@ -1505,6 +1506,61 @@ pub mod transaction_as_string {
}
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Timelock {
Timestamp(u32),
BlockHeight(u32),
}

impl Timelock {
// https://github.com/bitcoin/bitcoin/blob/b620b2d58a55a88ad21da70cb2000863ef17b651/src/script/script.h#L37-L39
const LOCKTIME_THRESHOLD: u32 = 500000000;

pub fn new_timestamp(n: u32) -> Result<Self, NotATimestamp> {
if n < Self::LOCKTIME_THRESHOLD {
return Err(NotATimestamp(n));
}

Ok(Self::Timestamp(n))
}

pub fn new_block_height(n: u32) -> Result<Self, NotABlockHeight> {
if n >= Self::LOCKTIME_THRESHOLD {
return Err(NotABlockHeight(n));
}

Ok(Self::BlockHeight(n))
}
}

#[derive(thiserror::Error, Debug, PartialEq)]
#[error("Timelock based on timestamp must be over 500000000, got {0}")]
pub struct NotATimestamp(u32);

#[derive(thiserror::Error, Debug, PartialEq)]
#[error("Timelock based on block height must be under 500000000, got {0}")]
pub struct NotABlockHeight(u32);

impl From<Timelock> for u32 {
fn from(timelock: Timelock) -> Self {
match timelock {
Timelock::Timestamp(inner) | Timelock::BlockHeight(inner) => inner,
}
}
}

impl From<u32> for Timelock {
fn from(n: u32) -> Self {
log::warn!("Choose a Timelock type explicitly via constructors");

if n < Timelock::LOCKTIME_THRESHOLD {
Self::BlockHeight(n)
} else {
Self::Timestamp(n)
}
}
}

/// Possible networks on which the loan contract may be deployed.
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
enum Chain {
Expand Down Expand Up @@ -1584,3 +1640,25 @@ mod constant_tests {
assert_eq!(actual, expected);
}
}

#[cfg(test)]
mod timelock_tests {
use super::{NotABlockHeight, NotATimestamp, Timelock};
use proptest::prelude::*;

proptest! {
#[test]
fn locktime_under_threshold_is_blocktime(n in 0u32..=Timelock::LOCKTIME_THRESHOLD) {
prop_assert_eq!(Timelock::new_block_height(n), Ok(Timelock::BlockHeight(n)));
prop_assert_eq!(Timelock::new_timestamp(n), Err(NotATimestamp(n)));
}
}

proptest! {
#[test]
fn locktime_over_threshold_is_timestamp(n in Timelock::LOCKTIME_THRESHOLD..=u32::MAX) {
prop_assert_eq!(Timelock::new_timestamp(n), Ok(Timelock::Timestamp(n)));
prop_assert_eq!(Timelock::new_block_height(n), Err(NotABlockHeight(n)));
}
}
}
10 changes: 5 additions & 5 deletions tests/loan_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
use std::time::SystemTime;

use anyhow::{Context, Result};
use baru::loan::{Borrower0, CollateralContract, Lender0};
use baru::loan::{Borrower0, CollateralContract, Lender0, Timelock};
use baru::oracle;
use elements::bitcoin::Amount;
use elements::secp256k1_zkp::SECP256K1;
Expand Down Expand Up @@ -72,7 +72,7 @@ async fn borrow_and_repay() {
(lender, address)
};

let timelock = 10;
let timelock = Timelock::new_block_height(10).unwrap();
let principal_amount = Amount::from_btc(38_000.0).unwrap();
let principal_inputs = wallet.coin_select(principal_amount, usdt_asset_id).unwrap();
let repayment_amount = principal_amount + Amount::from_btc(1_000.0).unwrap();
Expand Down Expand Up @@ -208,7 +208,7 @@ async fn lend_and_liquidate() {
(lender, address)
};

let timelock = 10;
let timelock = Timelock::new_block_height(10).unwrap();
let principal_amount = Amount::from_btc(38_000.0).unwrap();
let principal_inputs = wallet.coin_select(principal_amount, usdt_asset_id).unwrap();
let repayment_amount = principal_amount + Amount::from_btc(1_000.0).unwrap();
Expand Down Expand Up @@ -321,7 +321,7 @@ async fn lend_and_dynamic_liquidate() {
(lender, address)
};

let timelock = 10;
let timelock = Timelock::new_block_height(10).unwrap();
let principal_amount = Amount::from_btc(38_000.0).unwrap();
let principal_inputs = wallet.coin_select(principal_amount, usdt_asset_id).unwrap();
let repayment_amount = principal_amount + Amount::from_btc(1_000.0).unwrap();
Expand Down Expand Up @@ -561,7 +561,7 @@ async fn can_run_protocol_with_principal_change_outputs() {
(lender, address)
};

let timelock = 10;
let timelock = Timelock::new_block_height(10).unwrap();
let principal_amount = Amount::from_btc(38_000.0).unwrap();
let principal_inputs = wallet
.coin_select(
Expand Down

0 comments on commit c27ab0b

Please sign in to comment.