Skip to content

Commit

Permalink
Merge pull request #67 from comit-network/timelock-model
Browse files Browse the repository at this point in the history
Force users to explicitly choose timelock types
  • Loading branch information
luckysori authored Aug 13, 2021
2 parents faf653e + c176658 commit 87b7677
Show file tree
Hide file tree
Showing 4 changed files with 92 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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ version = "0.3.0"
authors = ["CoBloX Team <[email protected]>"]
edition = "2018"
license-file = "LICENSE"
resolver = "2"
description = "Library to facilitate DeFi on Liquid"

[dependencies]
Expand All @@ -33,6 +34,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 @@ -929,7 +929,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 @@ -974,13 +974,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 @@ -1141,7 +1142,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 @@ -1292,7 +1293,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 @@ -1378,7 +1379,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 @@ -1508,6 +1509,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 @@ -1587,3 +1643,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 87b7677

Please sign in to comment.