@covenance/dlc
Version:
Crypto and Bitcoin functions for Covenance DLC implementation
141 lines (121 loc) • 4.85 kB
text/typescript
import { expect } from 'chai';
import { Transaction, Address, PrivateKey, Script, Networks, PublicKey } from '../../src/btc';
import { createLiquidationCets, createDlcInitTx, selectLiquidationOutcomes } from '../../src/cet/transactions';
import { OracleEvent, LoanConfig } from '../../src/cet/types';
import { Point, utils } from '../../src/crypto/secp256k1';
import { EventOutcomeHash, PrivKey, PubKey } from '../../src/crypto/types';
import { sha256 } from '../../src';
describe('createLiquidationCets', () => {
let borrowerKey: PrivateKey;
let lenderKey: PrivateKey;
let borrowerPubKey: PublicKey;
let borrowerAddress: Address;
let lenderAddress: Address;
let borrowerDlcPrivateKey: PrivKey;
let borrowerDlcPubKey: PubKey;
let lenderDlcPrivateKey: PrivKey;
let lenderDlcPubKey: PubKey;
let dlcUtxo: any;
let config: LoanConfig;
before(() => {
// Create test keys and addresses
borrowerKey = new PrivateKey();
lenderKey = new PrivateKey();
borrowerPubKey = borrowerKey.toPublicKey();
borrowerAddress = new Address(borrowerPubKey, Networks.testnet, 'witnesspubkeyhash');
lenderAddress = new Address(lenderKey.toPublicKey(), Networks.testnet, 'witnesspubkeyhash');
borrowerDlcPrivateKey = utils.randomPrivateKey();
borrowerDlcPubKey = Point.fromPrivateKey(borrowerDlcPrivateKey);
lenderDlcPrivateKey = utils.randomPrivateKey();
lenderDlcPubKey = Point.fromPrivateKey(lenderDlcPrivateKey);
// Create DLC init transaction
const borrowedAmountUsd = 50_000;
const liquidationThreshold = 0.8;
const annualInterestRate = 0.1;
const collateralAmount = 0.8; // in BTC
const inputAmount = collateralAmount * 2;
const collateralUtxos = [{
txId: 'a'.repeat(64),
outputIndex: 0,
satoshis: inputAmount * 100000000,
script: (Script as any).buildWitnessV0Out(borrowerAddress)
}];
const dlcInitTx = createDlcInitTx(
collateralUtxos,
collateralAmount * 100000000,
borrowerDlcPubKey,
lenderDlcPubKey,
borrowerAddress
);
dlcUtxo = dlcInitTx.dlcUtxo;
config = {
collateralAmount,
annualInterestRate,
liquidationThreshold,
borrowedAmount: borrowedAmountUsd,
penaltyPercentage: 0.1
};
});
// Helper function to create a series of events with increasing prices
const createEvents = async (
count: number,
startTime: number = (Date.now() / 1000) | 0,
btcPriceRange: readonly [number, number] = [50000, 150000],
step = 500
): Promise<OracleEvent[]> => {
const [min, max] = btcPriceRange;
const n = ((max - min) / step) | 0;
const events = new Array<OracleEvent>(count);
for (let i = 0; i < count; i++) {
const sigs = new Array<Point>(n);
for (let j = 0; j < n; j++) sigs[j] = Point.fromPrivateKey(utils.randomPrivateKey());
const outcomeHashes = new Array<EventOutcomeHash>(n);
for (let j = 0; j < n; j++) {
outcomeHashes[j] = await sha256(new Uint8Array([i, j]));
}
events[i] = {
id: `event${i}`,
timestamp: startTime + i * 60 * 10,
outcomeSignaturePoints: sigs,
outcomePrices: Array.from({ length: n }, (_, j) => min + j * step),
outcomeHashes: outcomeHashes
};
}
return events;
};
it('should create valid CETs for many events', async function() {
this.timeout(10000);
const events = await createEvents(100);
const outcomes = selectLiquidationOutcomes(events, config);
const oracleCets = createLiquidationCets(
config,
dlcUtxo,
borrowerAddress,
lenderAddress,
outcomes
);
// Verify transaction structure
expect(oracleCets).to.have.lengthOf(100);
// Verify each CET
oracleCets.forEach((oracleCet) => {
const cet = oracleCet.cetTx;
expect(cet).to.be.instanceOf(Transaction);
expect(cet.inputs.length).to.equal(1);
expect(cet.outputs.length).to.equal(2);
expect(cet.inputs[0].prevTxId.toString('hex')).to.equal(dlcUtxo.txId);
expect(cet.inputs[0].outputIndex).to.equal(dlcUtxo.outputIndex);
expect(cet.outputs[0].script.toHex()).to.include('0014');
expect(cet.outputs[1].script.toHex()).to.include('0014');
expect(oracleCet.lenderAmount).to.be.greaterThan(0);
expect(oracleCet.borrowerAmount).to.be.greaterThan(0);
expect(oracleCet.lenderAmount + oracleCet.borrowerAmount).to.equal(config.collateralAmount);
});
});
it('should throw error when no suitable liquidation price is found', async () => {
// Create event with all prices above liquidation price
const events = await createEvents(1, 0, [120000, 130000]); // High BTC price range
expect(() => {
selectLiquidationOutcomes(events, config);
}).to.throw(/^No grid price/);
});
});