moria v1-1: TEST18 categories and riftenlabs-defi 0.5.1
Switch the indexer off the local path dep onto crates.io 0.5.1 (75-byte loan, 3-byte principal). Point chipnet deploy constants at TEST18 and keep continuation UTXOs on partial redeem/repay.
This commit is contained in:
parent
90ad5def9a
commit
499275eaef
6 changed files with 254 additions and 41 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -2283,9 +2283,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "riftenlabs-defi"
|
name = "riftenlabs-defi"
|
||||||
version = "0.4.6"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cfcf89dd770556a74ecd88b6971e7fa151f58bcee5b1a400b836c485c668973b"
|
checksum = "e7541d5d2101a03925b9de89846c1e9a66bcdcdf6a4435a7bcf30bf294bb236f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitcoin_hashes",
|
"bitcoin_hashes",
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ serde_json = "1.0.133"
|
||||||
rocket = { version = "0.5.1", features = ["json"] }
|
rocket = { version = "0.5.1", features = ["json"] }
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
riftenlabs-defi = "0.4.6"
|
riftenlabs-defi = "0.5.1"
|
||||||
rocket_cors = "0.6.0"
|
rocket_cors = "0.6.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
stderrlog = "0.6.0"
|
stderrlog = "0.6.0"
|
||||||
|
|
|
||||||
|
|
@ -3,40 +3,41 @@
|
||||||
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
|
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
|
||||||
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
|
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
|
||||||
//! Per-network Moria v1-1 deployment constants.
|
//! Per-network Moria v1-1 TEST18 deployment constants.
|
||||||
//!
|
//!
|
||||||
//! Mainnet is unconfigured until a v1-1 deployment exists (fail-closed).
|
//! Chipnet is TEST18. Mainnet is unconfigured until a v1-1 deployment exists
|
||||||
|
//! (fail-closed).
|
||||||
|
|
||||||
use bitcoincash::{Network, TokenID};
|
use bitcoincash::{Network, TokenID};
|
||||||
use riftenlabs_defi::moria_v1_1::MoriaV11TokenIds;
|
use riftenlabs_defi::moria_v1_1::MoriaV11TokenIds;
|
||||||
|
|
||||||
/// Unique tail of the moria v1-1 redeem script. Partial scriptsig match —
|
/// Unique tail of the moria v1-1 TEST18 redeem script. Partial scriptsig match —
|
||||||
/// every moria method spends this body.
|
/// every moria method spends this body. Last 20 bytes of REDEEMSCRIPT_PROD.
|
||||||
const REDEEM_TAIL: [u8; 20] = [
|
const REDEEM_TAIL: [u8; 20] = [
|
||||||
0x51, 0xd2, 0x51, 0xcf, 0x88, 0x52, 0xd1, 0x00, 0x88, 0xc4, 0x53, 0x9c, 0x77, 0x77, 0x77, 0x68,
|
0xd2, 0x51, 0xcf, 0x88, 0x52, 0xd1, 0x00, 0x88, 0xc4, 0x53, 0x9d, 0x6d, 0x6d, 0x51, 0x68, 0x68,
|
||||||
0x68, 0x68, 0x68, 0x68,
|
0x68, 0x68, 0x68, 0x68,
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Chipnet v1-1 token categories and loan P2SH32, or `None` on other networks.
|
/// Chipnet TEST18 token categories and loan P2SH32, or `None` on other networks.
|
||||||
pub fn token_ids(network: Option<Network>) -> Option<MoriaV11TokenIds> {
|
pub fn token_ids(network: Option<Network>) -> Option<MoriaV11TokenIds> {
|
||||||
match network {
|
match network {
|
||||||
Some(Network::Chipnet) => Some(MoriaV11TokenIds {
|
Some(Network::Chipnet) => Some(MoriaV11TokenIds {
|
||||||
moria: "56d909e16cd3b4cecae685a7ca45baa292bf7550205c2ad59320a342c73754de"
|
moria: "1c6dc7c7ad3e7e37fa1c84e715cd22a28177f52a2c49853e6e0b638505c29637"
|
||||||
.parse::<TokenID>()
|
.parse::<TokenID>()
|
||||||
.expect("valid chipnet moria v1-1 token_id"),
|
.expect("valid chipnet TEST18 moria token_id"),
|
||||||
bp_oracle: "a0628bc21d10211a23f986fd12770e836bae43878db93db8b164ab8a5ab33c96"
|
bp_oracle: "640d2c75cb1d5d15dbaf26022b37661d3578485a6788736893c1331ed2384905"
|
||||||
.parse::<TokenID>()
|
.parse::<TokenID>()
|
||||||
.expect("valid chipnet bp_oracle v1-1 token_id"),
|
.expect("valid chipnet TEST18 bp_oracle token_id"),
|
||||||
pool: "a533b7e96435af18136b534b43e465dd759db3ac33e8490b9bfd254b8c27ae48"
|
pool: "8aaa8012b206f45b33472a55b5c3c328bae0eb33e4ee466f7f9cbade4750225c"
|
||||||
.parse::<TokenID>()
|
.parse::<TokenID>()
|
||||||
.expect("valid chipnet pool v1-1 token_id"),
|
.expect("valid chipnet TEST18 pool token_id"),
|
||||||
bar: "bc98b2733ca1c2070586cce1a4216418830ccd9b19ff77b5d43d47542bb3b50d"
|
bar: "779df3e91141f0ea414c477f61d08a3c55b69cd91eb58e42953d8c7eaec84bb9"
|
||||||
.parse::<TokenID>()
|
.parse::<TokenID>()
|
||||||
.expect("valid chipnet bar v1-1 token_id"),
|
.expect("valid chipnet TEST18 bar token_id"),
|
||||||
loan_locking_bytecode: hex::decode(
|
loan_locking_bytecode: hex::decode(
|
||||||
"aa20415812e068086c90686635a813d77b6c412427e574e89b16e79feeaa48cf0f0787",
|
"aa208bbc3b3f65b354e69a93175c70f3cbb619154ae2902878521c17edcbd83cc12f87",
|
||||||
)
|
)
|
||||||
.expect("valid chipnet loan locking bytecode"),
|
.expect("valid chipnet TEST18 loan locking bytecode"),
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -544,7 +544,7 @@ pub async fn index_moria_v11(
|
||||||
action.loan_commitment = Some(LoanCommitmentV11 {
|
action.loan_commitment = Some(LoanCommitmentV11 {
|
||||||
borrower_nft_hash: borrower,
|
borrower_nft_hash: borrower,
|
||||||
delegate_nft_hash: delegate,
|
delegate_nft_hash: delegate,
|
||||||
principal: principal as u16,
|
principal: principal as u32,
|
||||||
annual_interest_rate_bp: interest_rate as u16,
|
annual_interest_rate_bp: interest_rate as u16,
|
||||||
timestamp: loan_ts as u64,
|
timestamp: loan_ts as u64,
|
||||||
});
|
});
|
||||||
|
|
@ -565,7 +565,10 @@ pub async fn index_moria_v11(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create / replace continuing loan UTXO.
|
// Create / replace continuing loan UTXO. Refinance, borrow, retarget,
|
||||||
|
// add-collateral, and partial redeem/repay (continuation NFT via
|
||||||
|
// `new_loan_commitment` + `loan_output_index`) all land here. Full
|
||||||
|
// close has no continuation and the spent UTXO stays deleted.
|
||||||
if let Some(output_idx) = action.loan_output_index {
|
if let Some(output_idx) = action.loan_output_index {
|
||||||
if let Some(commitment) = action
|
if let Some(commitment) = action
|
||||||
.new_loan_commitment
|
.new_loan_commitment
|
||||||
|
|
@ -581,7 +584,6 @@ pub async fn index_moria_v11(
|
||||||
blockhash,
|
blockhash,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
// Policy lives on the allowance, revealed at first spend.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,8 @@ pub struct MoriaV11Entry {
|
||||||
pub action_type: &'static str,
|
pub action_type: &'static str,
|
||||||
pub borrower_hash: Option<String>,
|
pub borrower_hash: Option<String>,
|
||||||
pub delegate_hash: Option<String>,
|
pub delegate_hash: Option<String>,
|
||||||
/// Encoded principal in whole MUSD (× [`MUSD_BASE_UNITS_PER_MUSD`](riftenlabs_defi::moria_v1_1::MUSD_BASE_UNITS_PER_MUSD) for base units).
|
/// Whole-MUSD principal (`i64`, not `u16`; 128_000 fits). Multiply by
|
||||||
|
/// [`MUSD_BASE_UNITS_PER_MUSD`](riftenlabs_defi::moria_v1_1::MUSD_BASE_UNITS_PER_MUSD) for base units.
|
||||||
pub principal: Option<i64>,
|
pub principal: Option<i64>,
|
||||||
pub interest_rate: Option<i64>,
|
pub interest_rate: Option<i64>,
|
||||||
pub loan_timestamp: Option<i64>,
|
pub loan_timestamp: Option<i64>,
|
||||||
|
|
@ -71,7 +72,8 @@ pub struct MoriaV11LoanUtxo {
|
||||||
pub vout: i64,
|
pub vout: i64,
|
||||||
pub borrower_hash: String,
|
pub borrower_hash: String,
|
||||||
pub delegate_hash: String,
|
pub delegate_hash: String,
|
||||||
/// Encoded principal in whole MUSD (× [`MUSD_BASE_UNITS_PER_MUSD`](riftenlabs_defi::moria_v1_1::MUSD_BASE_UNITS_PER_MUSD) for base units).
|
/// Whole-MUSD principal (`i64`, not `u16`; 128_000 fits). Multiply by
|
||||||
|
/// [`MUSD_BASE_UNITS_PER_MUSD`](riftenlabs_defi::moria_v1_1::MUSD_BASE_UNITS_PER_MUSD) for base units.
|
||||||
pub principal: i64,
|
pub principal: i64,
|
||||||
pub interest_rate: i64,
|
pub interest_rate: i64,
|
||||||
pub loan_timestamp: i64,
|
pub loan_timestamp: i64,
|
||||||
|
|
@ -117,7 +119,8 @@ pub struct MoriaV11Crankable {
|
||||||
pub vout: i64,
|
pub vout: i64,
|
||||||
pub borrower_hash: String,
|
pub borrower_hash: String,
|
||||||
pub delegate_hash: String,
|
pub delegate_hash: String,
|
||||||
/// Encoded principal in whole MUSD (× [`MUSD_BASE_UNITS_PER_MUSD`](riftenlabs_defi::moria_v1_1::MUSD_BASE_UNITS_PER_MUSD) for base units).
|
/// Whole-MUSD principal (`i64`, not `u16`; 128_000 fits). Multiply by
|
||||||
|
/// [`MUSD_BASE_UNITS_PER_MUSD`](riftenlabs_defi::moria_v1_1::MUSD_BASE_UNITS_PER_MUSD) for base units.
|
||||||
pub principal: i64,
|
pub principal: i64,
|
||||||
pub interest_rate: i64,
|
pub interest_rate: i64,
|
||||||
pub loan_timestamp: i64,
|
pub loan_timestamp: i64,
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,17 @@ use riftenlabs_defi::moria_v1_1::{
|
||||||
crank_allowance_locking_bytecode, crank_hint_to_policy, decode_crank_hint,
|
crank_allowance_locking_bytecode, crank_hint_to_policy, decode_crank_hint,
|
||||||
encode_loan_commitment_v11, principal_to_base_units, CrankPolicy, LoanCommitmentV11,
|
encode_loan_commitment_v11, principal_to_base_units, CrankPolicy, LoanCommitmentV11,
|
||||||
MoriaV11TokenIds, ALLOWANCE_METHOD_CLOSE, ALLOWANCE_METHOD_CRANK, DEFAULT_DOWN_LEG_SECONDS,
|
MoriaV11TokenIds, ALLOWANCE_METHOD_CLOSE, ALLOWANCE_METHOD_CRANK, DEFAULT_DOWN_LEG_SECONDS,
|
||||||
LOAN_METHOD_RETARGET,
|
LOAN_METHOD_REDEEM, LOAN_METHOD_REPAY, LOAN_METHOD_RETARGET,
|
||||||
};
|
};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
/// Whole-MUSD → base units as i64 (DB/API). Scale lives only in defi `MUSD_BASE_UNITS_PER_MUSD`.
|
/// Whole-MUSD → base units as i64 (DB/API). Scale lives only in defi `MUSD_BASE_UNITS_PER_MUSD`.
|
||||||
fn principal_base_units(principal_musd: u16) -> i64 {
|
fn principal_base_units(principal_musd: u32) -> i64 {
|
||||||
principal_to_base_units(principal_musd) as i64
|
principal_to_base_units(principal_musd) as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
const MORIA_METHOD_BORROW: u8 = 2;
|
const MORIA_METHOD_BORROW: u8 = 2;
|
||||||
|
const MORIA_METHOD_REPAY: u8 = 0;
|
||||||
const MORIA_METHOD_RETARGET: u8 = 4;
|
const MORIA_METHOD_RETARGET: u8 = 4;
|
||||||
|
|
||||||
async fn setup(pool: SqlitePool) {
|
async fn setup(pool: SqlitePool) {
|
||||||
|
|
@ -46,7 +47,7 @@ fn token_ids() -> MoriaV11TokenIds {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_loan_lock() -> Vec<u8> {
|
fn test_loan_lock() -> Vec<u8> {
|
||||||
hex::decode("aa20415812e068086c90686635a813d77b6c412427e574e89b16e79feeaa48cf0f0787").unwrap()
|
hex::decode("aa208bbc3b3f65b354e69a93175c70f3cbb619154ae2902878521c17edcbd83cc12f87").unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn token_ids_with_loan_lock() -> MoriaV11TokenIds {
|
fn token_ids_with_loan_lock() -> MoriaV11TokenIds {
|
||||||
|
|
@ -194,7 +195,7 @@ fn make_tx(inputs: Vec<TxIn>, outputs: Vec<TxOut>) -> Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commitment(borrower: [u8; 32], delegate: [u8; 32], principal: u16, bp: u16, ts: u64) -> Vec<u8> {
|
fn commitment(borrower: [u8; 32], delegate: [u8; 32], principal: u32, bp: u16, ts: u64) -> Vec<u8> {
|
||||||
encode_loan_commitment_v11(&LoanCommitmentV11 {
|
encode_loan_commitment_v11(&LoanCommitmentV11 {
|
||||||
borrower_nft_hash: borrower,
|
borrower_nft_hash: borrower,
|
||||||
delegate_nft_hash: delegate,
|
delegate_nft_hash: delegate,
|
||||||
|
|
@ -508,10 +509,8 @@ async fn index_crank_updates_allowance_in_place() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let old = commitment(borrower(), delegate(), 1_000, 500, 1_700_000_000);
|
let old = commitment(borrower(), delegate(), 1_000, 500, 1_700_000_000);
|
||||||
let mut new = old.clone();
|
// 75-byte: rate @67, timestamp @69 (frozen head [0,67) includes principal).
|
||||||
new[66] = 0x20;
|
let new = commitment(borrower(), delegate(), 1_000, 0x0320, 1_700_000_016);
|
||||||
new[67] = 0x03;
|
|
||||||
new[68] = 0x20;
|
|
||||||
|
|
||||||
let interest = 15i64;
|
let interest = 15i64;
|
||||||
let allowance_remaining = 500i64;
|
let allowance_remaining = 500i64;
|
||||||
|
|
@ -667,18 +666,13 @@ async fn index_two_loan_retarget_batch() {
|
||||||
let bh = blockhash(0xCC);
|
let bh = blockhash(0xCC);
|
||||||
|
|
||||||
let old0 = commitment(borrower(), delegate(), 1_000, 500, 1_700_000_000);
|
let old0 = commitment(borrower(), delegate(), 1_000, 500, 1_700_000_000);
|
||||||
let mut new0 = old0.clone();
|
// 75-byte layout: rate @67 and timestamp @69 may change; frozen head [0,67).
|
||||||
new0[66] = 0xF4;
|
let new0 = commitment(borrower(), delegate(), 1_000, 500, 1_700_000_016);
|
||||||
new0[67] = 0x01;
|
|
||||||
new0[68] = 0x10;
|
|
||||||
|
|
||||||
let borrower2 = [0x33; 32];
|
let borrower2 = [0x33; 32];
|
||||||
let delegate2 = [0x44; 32];
|
let delegate2 = [0x44; 32];
|
||||||
let old1 = commitment(borrower2, delegate2, 2_000, 600, 1_700_000_000);
|
let old1 = commitment(borrower2, delegate2, 2_000, 600, 1_700_000_000);
|
||||||
let mut new1 = old1.clone();
|
let new1 = commitment(borrower2, delegate2, 2_000, 600, 1_700_000_016);
|
||||||
new1[66] = 0x58;
|
|
||||||
new1[67] = 0x02;
|
|
||||||
new1[68] = 0x10;
|
|
||||||
|
|
||||||
let seed_txid0 = Txid::from_byte_array([0xA0; 32]);
|
let seed_txid0 = Txid::from_byte_array([0xA0; 32]);
|
||||||
let seed_txid1 = Txid::from_byte_array([0xA1; 32]);
|
let seed_txid1 = Txid::from_byte_array([0xA1; 32]);
|
||||||
|
|
@ -1086,3 +1080,216 @@ async fn mempool_eviction_drops_unconfirmed_loan() {
|
||||||
assert!(get_active_loans(pool).await.unwrap().is_empty());
|
assert!(get_active_loans(pool).await.unwrap().is_empty());
|
||||||
assert!(get_unconfirmed_txids(pool).await.unwrap().is_empty());
|
assert!(get_unconfirmed_txids(pool).await.unwrap().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn partial_repay_tx(
|
||||||
|
ids: &MoriaV11TokenIds,
|
||||||
|
remaining: Vec<u8>,
|
||||||
|
remaining_sats: u64,
|
||||||
|
) -> Transaction {
|
||||||
|
make_tx(
|
||||||
|
vec![
|
||||||
|
txin(0x01, 0, unlock(MORIA_METHOD_REPAY, &[])),
|
||||||
|
txin(0x02, 0, unlock(0, &[])),
|
||||||
|
txin(0xA0, 2, unlock(LOAN_METHOD_REPAY, &[])),
|
||||||
|
txin(0xB0, 0, ScriptBuf::new()),
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
out(1_000, Some(minting(ids.moria, vec![0x02], 1_100_000))),
|
||||||
|
out(1_000, Some(nft(tid(0xD1), vec![0x11; 16]))),
|
||||||
|
out(1_000, Some(ft(ids.moria, 42))),
|
||||||
|
bare(1_000),
|
||||||
|
out(remaining_sats, Some(nft(ids.moria, remaining))),
|
||||||
|
bare(1_000),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn partial_redeem_tx(
|
||||||
|
ids: &MoriaV11TokenIds,
|
||||||
|
remaining: Vec<u8>,
|
||||||
|
remaining_sats: u64,
|
||||||
|
) -> Transaction {
|
||||||
|
make_tx(
|
||||||
|
vec![
|
||||||
|
txin(0x01, 0, unlock(MORIA_METHOD_REPAY, &[])),
|
||||||
|
txin(0x02, 0, unlock(0, &[])),
|
||||||
|
txin(0xA0, 2, unlock(LOAN_METHOD_REDEEM, &[])),
|
||||||
|
txin(0xB2, 0, ScriptBuf::new()),
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
out(1_000, Some(minting(ids.moria, vec![0x02], 1_050_000))),
|
||||||
|
out(1_000, Some(nft(tid(0xD1), vec![0x11; 16]))),
|
||||||
|
out(1_000, Some(ft(ids.moria, 77))),
|
||||||
|
out(1_000, Some(nft(ids.bp_oracle, vec![0x22; 26]))),
|
||||||
|
out(remaining_sats, Some(nft(ids.moria, remaining))),
|
||||||
|
bare(1_000),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn partial_repay_keeps_continuation_loan_utxo() {
|
||||||
|
let db = crate::utiltest::mock_db_pool(setup).await;
|
||||||
|
let pool = &db.moria_w;
|
||||||
|
let ids = token_ids();
|
||||||
|
let bh = blockhash(0xE1);
|
||||||
|
let seed = Txid::from_byte_array([0xA0; 32]);
|
||||||
|
|
||||||
|
insert_loan_utxo(
|
||||||
|
pool,
|
||||||
|
&seed,
|
||||||
|
2,
|
||||||
|
&LoanCommitmentV11 {
|
||||||
|
borrower_nft_hash: borrower(),
|
||||||
|
delegate_nft_hash: delegate(),
|
||||||
|
principal: 20_000,
|
||||||
|
annual_interest_rate_bp: 500,
|
||||||
|
timestamp: 1_700_000_000,
|
||||||
|
},
|
||||||
|
9_000_000,
|
||||||
|
&bh,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let remaining = commitment(borrower(), delegate(), 15_000, 500, 1_700_000_000);
|
||||||
|
let tx = partial_repay_tx(&ids, remaining, 6_000_000);
|
||||||
|
let n = index_moria_v11(pool, &[tx.clone()], &bh, 1_700_100_000, &ids)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(n, 1);
|
||||||
|
|
||||||
|
let history = get_loan_history(pool, &borrower()).await.unwrap();
|
||||||
|
assert_eq!(history.len(), 1);
|
||||||
|
assert_eq!(history[0].action_type, "repay");
|
||||||
|
assert_eq!(history[0].new_principal, Some(15_000));
|
||||||
|
|
||||||
|
let active = get_active_loans(pool).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
active.len(),
|
||||||
|
1,
|
||||||
|
"partial repay must keep the continuation UTXO"
|
||||||
|
);
|
||||||
|
assert_eq!(active[0].principal, 15_000);
|
||||||
|
assert_eq!(active[0].txid, tx.compute_txid().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn partial_redeem_keeps_continuation_loan_utxo() {
|
||||||
|
let db = crate::utiltest::mock_db_pool(setup).await;
|
||||||
|
let pool = &db.moria_w;
|
||||||
|
let ids = token_ids();
|
||||||
|
let bh = blockhash(0xE2);
|
||||||
|
let seed = Txid::from_byte_array([0xA0; 32]);
|
||||||
|
|
||||||
|
insert_loan_utxo(
|
||||||
|
pool,
|
||||||
|
&seed,
|
||||||
|
2,
|
||||||
|
&LoanCommitmentV11 {
|
||||||
|
borrower_nft_hash: borrower(),
|
||||||
|
delegate_nft_hash: delegate(),
|
||||||
|
principal: 20_000,
|
||||||
|
annual_interest_rate_bp: 500,
|
||||||
|
timestamp: 1_700_000_000,
|
||||||
|
},
|
||||||
|
9_000_000,
|
||||||
|
&bh,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let remaining = commitment(borrower(), delegate(), 10_000, 500, 1_700_000_000);
|
||||||
|
let tx = partial_redeem_tx(&ids, remaining, 4_000_000);
|
||||||
|
index_moria_v11(pool, &[tx.clone()], &bh, 1_700_100_000, &ids)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let history = get_loan_history(pool, &borrower()).await.unwrap();
|
||||||
|
assert_eq!(history[0].action_type, "redeem");
|
||||||
|
assert_eq!(history[0].new_principal, Some(10_000));
|
||||||
|
|
||||||
|
let active = get_active_loans(pool).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
active.len(),
|
||||||
|
1,
|
||||||
|
"partial redeem must keep the continuation UTXO"
|
||||||
|
);
|
||||||
|
assert_eq!(active[0].principal, 10_000);
|
||||||
|
assert_eq!(active[0].txid, tx.compute_txid().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn full_repay_drops_loan_utxo() {
|
||||||
|
let db = crate::utiltest::mock_db_pool(setup).await;
|
||||||
|
let pool = &db.moria_w;
|
||||||
|
let ids = token_ids();
|
||||||
|
let bh = blockhash(0xE3);
|
||||||
|
let seed = Txid::from_byte_array([0xA0; 32]);
|
||||||
|
|
||||||
|
insert_loan_utxo(
|
||||||
|
pool,
|
||||||
|
&seed,
|
||||||
|
2,
|
||||||
|
&LoanCommitmentV11 {
|
||||||
|
borrower_nft_hash: borrower(),
|
||||||
|
delegate_nft_hash: delegate(),
|
||||||
|
principal: 20_000,
|
||||||
|
annual_interest_rate_bp: 500,
|
||||||
|
timestamp: 1_700_000_000,
|
||||||
|
},
|
||||||
|
9_000_000,
|
||||||
|
&bh,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let tx = make_tx(
|
||||||
|
vec![
|
||||||
|
txin(0x01, 0, unlock(MORIA_METHOD_REPAY, &[])),
|
||||||
|
txin(0x02, 0, unlock(0, &[])),
|
||||||
|
txin(0xA0, 2, unlock(LOAN_METHOD_REPAY, &[])),
|
||||||
|
txin(0xB0, 0, ScriptBuf::new()),
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
out(1_000, Some(minting(ids.moria, vec![0x02], 1_100_000))),
|
||||||
|
out(1_000, Some(nft(tid(0xD1), vec![0x11; 16]))),
|
||||||
|
out(1_000, Some(ft(ids.moria, 42))),
|
||||||
|
bare(1_000),
|
||||||
|
bare(5_000_000),
|
||||||
|
bare(1_000),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
index_moria_v11(pool, &[tx], &bh, 1_700_100_000, &ids)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(get_active_loans(pool).await.unwrap().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn principal_128000_fits_loan_utxo() {
|
||||||
|
let db = crate::utiltest::mock_db_pool(setup).await;
|
||||||
|
let pool = &db.moria_w;
|
||||||
|
let bh = blockhash(0xE4);
|
||||||
|
insert_loan_utxo(
|
||||||
|
pool,
|
||||||
|
&Txid::from_byte_array([0xC8; 32]),
|
||||||
|
2,
|
||||||
|
&LoanCommitmentV11 {
|
||||||
|
borrower_nft_hash: borrower(),
|
||||||
|
delegate_nft_hash: delegate(),
|
||||||
|
principal: 128_000,
|
||||||
|
annual_interest_rate_bp: 500,
|
||||||
|
timestamp: 1_700_000_000,
|
||||||
|
},
|
||||||
|
50_000_000,
|
||||||
|
&bh,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let active = get_active_loans(pool).await.unwrap();
|
||||||
|
assert_eq!(active.len(), 1);
|
||||||
|
assert_eq!(active[0].principal, 128_000);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue