UNPKG

@node-dlc/core

Version:
1,125 lines (970 loc) 40 kB
import { Value } from '@node-dlc/bitcoin'; import { ContractDescriptorV1, ContractInfoV0, DigitDecompositionEventDescriptorV0, DlcOffer, OracleAnnouncement, OracleEvent, OracleInfoV0, PayoutFunctionV0, PolynomialPayoutCurvePiece, } from '@node-dlc/messaging'; import { BitcoinNetworks } from 'bitcoin-networks'; import { expect } from 'chai'; import { buildCustomStrategyOrderOffer, buildRoundingIntervalsFromIntervals, DlcParty, dustThreshold, getFinalizerByCount, LinearPayout, roundDownToNearestMultiplier, roundUpToNearestMultiplier, } from '../../../lib'; import { getCsoInfoFromContractInfo, getCsoInfoFromOffer, validateCsoPayoutFunction, } from '../../../lib/dlc/finance/CsoInfo'; const buildOracleAnnouncement = (oracleDigits: number, expiry: Date) => { const eventDescriptor = new DigitDecompositionEventDescriptorV0(); eventDescriptor.base = 2; eventDescriptor.isSigned = false; eventDescriptor.unit = 'bits'; eventDescriptor.precision = 0; eventDescriptor.nbDigits = oracleDigits; const oracleEvent = new OracleEvent(); oracleEvent.eventMaturityEpoch = Math.floor(expiry.getTime() / 1000); oracleEvent.eventDescriptor = eventDescriptor; const oracleAnnouncement = new OracleAnnouncement(); oracleAnnouncement.oracleEvent = oracleEvent; return oracleAnnouncement; }; const buildCsoDlcOfferFixture = ( oracleDigits: number, expiry: Date, payoutFunction: PayoutFunctionV0, totalCollateral: bigint, offerCollateral: bigint, ): DlcOffer => { const oracleAnnouncement = buildOracleAnnouncement(oracleDigits, expiry); const oracleInfo = new OracleInfoV0(); oracleInfo.announcement = oracleAnnouncement; const contractDescriptor = new ContractDescriptorV1(); contractDescriptor.numDigits = oracleDigits; contractDescriptor.payoutFunction = payoutFunction; const contractInfo = new ContractInfoV0(); contractInfo.totalCollateral = totalCollateral; contractInfo.contractDescriptor = contractDescriptor; contractInfo.oracleInfo = oracleInfo; const dlcOffer = new DlcOffer(); // Set all required properties following DlcOffer.spec.ts pattern dlcOffer.contractFlags = Buffer.from('00', 'hex'); dlcOffer.chainHash = Buffer.from( '06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', 'hex', ); dlcOffer.temporaryContractId = Buffer.from( '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 'hex', ); dlcOffer.fundingPubkey = Buffer.from( '0327efea09ff4dfb13230e887cbab8821d5cc249c7ff28668c6633ff9f4b4c08e3', 'hex', ); dlcOffer.payoutSpk = Buffer.from( '00142bbdec425007dc360523b0294d2c64d2213af498', 'hex', ); dlcOffer.payoutSerialId = BigInt(11555292); dlcOffer.changeSpk = Buffer.from( '0014afa16f949f3055f38bd3a73312bed00b61558884', 'hex', ); dlcOffer.changeSerialId = BigInt(2008045); dlcOffer.fundOutputSerialId = BigInt(5411962); dlcOffer.feeRatePerVb = BigInt(1); dlcOffer.cetLocktime = 100; dlcOffer.refundLocktime = 200; dlcOffer.contractInfo = contractInfo; dlcOffer.offerCollateral = offerCollateral; return dlcOffer; }; describe('CsoInfo', () => { const minPayout = 60000000n; const maxPayout = 100000000n; const startOutcome = 800000n; const endOutcome = 1200000n; const offerCollateralValue = 80000000n; const oracleBase = 2; const oracleDigits = 21; const expiry = new Date(1620014750000); const maxGain = Value.fromBitcoin(0.2); const maxLoss = Value.fromBitcoin(0.2); const maxGainForContractSize = Value.fromBitcoin(0.2); const maxLossForContractSize = Value.fromBitcoin(0.2); const contractSize = Value.fromBitcoin(1); const totalCollateral = Value.fromBitcoin(1); const offerCollateral = Value.fromBitcoin(0.8); const network = BitcoinNetworks.bitcoin; const highestPrecisionRounding = Value.fromSats(10000); const highPrecisionRounding = Value.fromSats(25000); const mediumPrecisionRounding = Value.fromSats(100000); const lowPrecisionRounding = Value.fromSats(200000); const oracleAnnouncement = OracleAnnouncement.deserialize( Buffer.from( 'fdd824fd0344340d5e431a385a8bb3819c0410c749b2a11ae5bebdd363e2dc23af057c6cbd3fd097fffa8e6beef46cb02389fb6ef102d3653a281dda5ff0d6e3f4d42a14df6830bbf19aa3a986ed4e5640240b507901d6e03d6bbd71a281ed356a145516c655fdd822fd02de0015b4ced0696e0c7b8636b816e8742944cfd652f3366d659694add4d186da20f06e1750ce25afb96aee7aedbc5a3981b7e62a22baf768f94fe0ffd455cb6ccb4da1a07fadc76aada50705e583aa77b664f1217212d4537f9d5398630716cd26e5fcdba1a300388860a9f5feed894ca9d5caf9daffaca23f53fd46071f69007b286050e798293f7524223f1882667d5e282be7b09faada3179496630053a060c1af1d2c80d212dc871bf56a846a6b70cfca17b47d2fcb86e57cef165215f2a526cc40215c3c6002fbeb5f0a9d545379004fd108a8a5ee96ac0bd33d1495c609724e6554fe1bf90fe92e679df8883df6df9173e8c476b5e83493f55bf2e20e9c85acd21f7f710a9a380233fc28254077fb751c4ffbd5124639421cc05ae2e3886bbc29177c02ad5ab569b5a89c4dd5da2e131ab41ac4ce2347f5425230b8822198cd805047b1a35fa00cd3877fdc902857293f94fcad3f0e418448b0756e99ce6cc46d1a4cf8673823a89819a9bd9d4c3506975d37835a5310a1bee056887124b6d0fc0ae8974371e1e12e13d68f844878bd9fa51bfdb2455f6bb6c93e49998b37e4fa2b01ff31c665b16313015311007d624fe77df48ac69d8475ca06500d618fbff38ab6f50575d4b0fd3efe4fd6e561797070ec365ecda8a62e6dcb993b8eff16a2e5af7ad7b93cb6a028cdab351251e368c4f188d620ad86fa43886ee57f91be033feab89cd381c486b9cb9c102ae2d3d3cc15068ecb5e0822e8f5ff5a2faef2598308ec40acc4f122023722e8e6758034f98bb45136117154cb7690a2c04b9504a747ce3f69be7d29299462181fcf721d795233aacf010309af5b1709309cb5c2ceae7926ea0868b1c8a2573554284f86310d1df7ed82e6be49d417ec494da9f15f9fac757bdee7cb49c3e7fdcfabc28801bde8f90b90cf3eb37784bb91d9c35662b5f00fdd80a0e00020004626974730000000000152561746f6d69632d656e67696e652d6d6f6e74686c792d323646454232342d32364150523234', 'hex', ), ); describe('CsoInfo from linear curve message', () => { const { payoutFunction } = LinearPayout.buildPayoutFunction( minPayout, maxPayout, startOutcome, endOutcome, oracleBase, oracleDigits, ); const dlcOffer = buildCsoDlcOfferFixture( oracleDigits, expiry, payoutFunction, maxPayout, offerCollateralValue, ); const contractInfo = dlcOffer.contractInfo; it('should get correct CsoInfo from ContractInfo', () => { const csoInfo = getCsoInfoFromContractInfo( contractInfo, 'neither', Value.fromSats(0), undefined, 'v0', ); expect(csoInfo).to.deep.equal({ normalizedMaxGain: maxGain, normalizedMaxLoss: maxLoss, maxGainForContractSize, maxLossForContractSize, minPayout, maxPayout, contractSize, offerCollateral, totalCollateral, expiry, }); }); it('should get correct CsoInfo from ContractInfo', () => { const csoInfo = getCsoInfoFromOffer(dlcOffer, 'v0'); expect(csoInfo).to.deep.equal({ normalizedMaxGain: maxGain, normalizedMaxLoss: maxLoss, maxGainForContractSize, maxLossForContractSize, minPayout, maxPayout, contractSize, offerCollateral, totalCollateral, expiry, }); }); it('should throw if offerCollateralSatoshis does not match calculated offerCollateral', () => { const { payoutFunction } = LinearPayout.buildPayoutFunction( minPayout, maxPayout, startOutcome, endOutcome, oracleBase, oracleDigits, ); const dlcOffer = buildCsoDlcOfferFixture( oracleDigits, expiry, payoutFunction, maxPayout, offerCollateralValue, ); dlcOffer.offerCollateral -= BigInt(10000); expect(() => getCsoInfoFromOffer(dlcOffer)).to.throw(Error); }); it('should get correct CsoInfo from ContractInfo with fees shifted', () => { const contractSize = Value.fromBitcoin(1); const maxLoss = Value.fromBitcoin(0.2); const maxGain = Value.fromBitcoin(0.04); const feeRate = 10n; const highestPrecisionRounding = Value.fromSats(10000); const highPrecisionRounding = Value.fromSats(25000); const mediumPrecisionRounding = Value.fromSats(100000); const lowPrecisionRounding = Value.fromSats(200000); const roundingIntervals = buildRoundingIntervalsFromIntervals( contractSize, [ { beginInterval: 0n, rounding: lowPrecisionRounding }, { beginInterval: 750000n, rounding: mediumPrecisionRounding }, { beginInterval: 850000n, rounding: highPrecisionRounding }, { beginInterval: 950000n, rounding: highestPrecisionRounding }, ], ); const network = BitcoinNetworks.bitcoin; const shiftForFees: DlcParty = 'offeror'; const fees = Value.fromSats(10000); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, maxLoss, maxGain, feeRate, roundingIntervals, network, shiftForFees, fees, ); const csoInfoFromContractInfo = getCsoInfoFromContractInfo( csoOrderOffer.contractInfo, shiftForFees, fees, ); const csoInfoFromOffer = getCsoInfoFromOffer(csoOrderOffer); expect(csoInfoFromContractInfo.normalizedMaxGain.sats).to.equal( maxGain.sats, ); expect(csoInfoFromContractInfo.normalizedMaxLoss.sats).to.equal( maxLoss.sats, ); expect(csoInfoFromOffer.normalizedMaxGain.sats).to.equal(maxGain.sats); expect(csoInfoFromOffer.normalizedMaxLoss.sats).to.equal(maxLoss.sats); }); it('should get correct ContractInfo with smaller max gain', () => { const contractSize = Value.fromBitcoin(1); const maxLoss = Value.fromBitcoin(0.95); const maxGain = Value.fromBitcoin(0.005); const feeRate = 10n; const highestPrecisionRounding = Value.fromSats(10000); const highPrecisionRounding = Value.fromSats(25000); const mediumPrecisionRounding = Value.fromSats(100000); const lowPrecisionRounding = Value.fromSats(200000); const roundingIntervals = buildRoundingIntervalsFromIntervals( contractSize, [ { beginInterval: 0n, rounding: lowPrecisionRounding }, { beginInterval: 750000n, rounding: mediumPrecisionRounding }, { beginInterval: 850000n, rounding: highPrecisionRounding }, { beginInterval: 950000n, rounding: highestPrecisionRounding }, ], ); const network = BitcoinNetworks.bitcoin; const shiftForFees: DlcParty = 'offeror'; const fees = Value.fromSats(10000); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, maxLoss, maxGain, feeRate, roundingIntervals, network, shiftForFees, fees, ); const csoInfoFromContractInfo = getCsoInfoFromContractInfo( csoOrderOffer.contractInfo, shiftForFees, fees, ); const csoInfoFromOffer = getCsoInfoFromOffer(csoOrderOffer); expect(csoInfoFromContractInfo.normalizedMaxGain.sats).to.equal( maxGain.sats, ); expect(csoInfoFromContractInfo.normalizedMaxLoss.sats).to.equal( maxLoss.sats, ); expect(csoInfoFromOffer.normalizedMaxGain.sats).to.equal(maxGain.sats); expect(csoInfoFromOffer.normalizedMaxLoss.sats).to.equal(maxLoss.sats); }); it('should get correct CsoInfo from ContractInfo with fees shifted contract size 0.01', () => { const contractSize = Value.fromBitcoin(0.01); const maxLoss = Value.fromBitcoin(0.2); const maxGain = Value.fromBitcoin(0.04); const feeRate = 10n; const highestPrecisionRounding = Value.fromSats(10000); const highPrecisionRounding = Value.fromSats(25000); const mediumPrecisionRounding = Value.fromSats(100000); const lowPrecisionRounding = Value.fromSats(200000); const roundingIntervals = buildRoundingIntervalsFromIntervals( contractSize, [ { beginInterval: 0n, rounding: lowPrecisionRounding }, { beginInterval: 750000n, rounding: mediumPrecisionRounding }, { beginInterval: 850000n, rounding: highPrecisionRounding }, { beginInterval: 950000n, rounding: highestPrecisionRounding }, ], ); const network = BitcoinNetworks.bitcoin; const shiftForFees: DlcParty = 'neither'; const fees = Value.fromSats(0); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, maxLoss, maxGain, feeRate, roundingIntervals, network, shiftForFees, fees, ); const csoInfoFromContractInfo = getCsoInfoFromContractInfo( csoOrderOffer.contractInfo, shiftForFees, fees, ); const csoInfoFromOffer = getCsoInfoFromOffer(csoOrderOffer); expect(csoInfoFromContractInfo.normalizedMaxGain.sats).to.equal( maxGain.sats, ); expect(csoInfoFromContractInfo.normalizedMaxLoss.sats).to.equal( maxLoss.sats, ); expect(csoInfoFromOffer.normalizedMaxGain.sats).to.equal(maxGain.sats); expect(csoInfoFromOffer.normalizedMaxLoss.sats).to.equal(maxLoss.sats); }); const fees = [0, 1116, 29384, 34, 245, 11293, 2223, 10410]; const contractSizes = [0.01, 0.1, 0.5, 1, 2, 5, 10, 50]; contractSizes.forEach((contractSizeNum) => { fees.forEach((fee) => { it(`should get correct CsoInfo from ContractInfo with fees shifted contract size ${contractSizeNum} and fee ${fee}`, () => { const contractSize = Value.fromBitcoin(contractSizeNum); // const eventDescriptor = new DigitDecompositionEventDescriptorV0(); // eventDescriptor.base = 2; // eventDescriptor.isSigned = false; // eventDescriptor.unit = 'bits'; // eventDescriptor.precision = 0; // eventDescriptor.nbDigits = oracleDigits; // const oracleEvent = new OracleEventV0(); // oracleEvent.eventMaturityEpoch = Math.floor(expiry.getTime() / 1000); // oracleEvent.eventDescriptor = eventDescriptor; // const oracleAnnouncement = new OracleAnnouncementV0(); // oracleAnnouncement.oracleEvent = oracleEvent; const maxLoss = Value.fromBitcoin(0.2); const maxGain = Value.fromBitcoin(0.04); const feeRate = 4n; const highestPrecisionRounding = Value.fromSats(10000); const highPrecisionRounding = Value.fromSats(25000); const mediumPrecisionRounding = Value.fromSats(100000); const lowPrecisionRounding = Value.fromSats(200000); const roundingIntervals = buildRoundingIntervalsFromIntervals( contractSize, [ { beginInterval: 0n, rounding: lowPrecisionRounding }, { beginInterval: 750000n, rounding: mediumPrecisionRounding }, { beginInterval: 850000n, rounding: highPrecisionRounding }, { beginInterval: 950000n, rounding: highestPrecisionRounding }, ], ); const network = BitcoinNetworks.bitcoin; const shiftForFees: DlcParty = 'acceptor'; const fees = Value.fromSats(fee); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, maxLoss, maxGain, feeRate, roundingIntervals, network, shiftForFees, fees, ); const csoInfo = getCsoInfoFromOffer(csoOrderOffer); expect(csoInfo.normalizedMaxGain.sats).to.equal(maxGain.sats); expect(csoInfo.normalizedMaxLoss.sats).to.equal(maxLoss.sats); }); }); }); }); describe('validateCsoPayoutFunction', () => { it('should throw if event outcome is not an ascending line', () => { const startOutcome = 900000n; const endOutcome = 900000n; const { payoutFunction } = LinearPayout.buildPayoutFunction( minPayout, maxPayout, startOutcome, endOutcome, oracleBase, oracleDigits, ); expect(() => validateCsoPayoutFunction(payoutFunction)).to.throw(Error); }); }); it('should throw if maxLoss to maxGain is not an ascending line', () => { const minPayout = 100000000n; const maxPayout = 100000000n; const { payoutFunction } = LinearPayout.buildPayoutFunction( minPayout, maxPayout, startOutcome, endOutcome, oracleBase, oracleDigits, ); expect(() => validateCsoPayoutFunction(payoutFunction)).to.throw(Error); }); it('should throw if Payout Function point outcome payout not continuous', () => { const { payoutFunction } = LinearPayout.buildPayoutFunction( minPayout, maxPayout, startOutcome, endOutcome, oracleBase, oracleDigits, ); // Subtract 1 from outcomePayout to create gap in point outcome payout (payoutFunction.payoutFunctionPieces[0] .payoutCurvePiece as PolynomialPayoutCurvePiece).points[1].outcomePayout -= BigInt( 1, ); expect(() => validateCsoPayoutFunction(payoutFunction)).to.throw(Error); }); describe('CsoInfo collateral', () => { const oracleAnnouncement = OracleAnnouncement.deserialize( Buffer.from( 'fdd824fd0344340d5e431a385a8bb3819c0410c749b2a11ae5bebdd363e2dc23af057c6cbd3fd097fffa8e6beef46cb02389fb6ef102d3653a281dda5ff0d6e3f4d42a14df6830bbf19aa3a986ed4e5640240b507901d6e03d6bbd71a281ed356a145516c655fdd822fd02de0015b4ced0696e0c7b8636b816e8742944cfd652f3366d659694add4d186da20f06e1750ce25afb96aee7aedbc5a3981b7e62a22baf768f94fe0ffd455cb6ccb4da1a07fadc76aada50705e583aa77b664f1217212d4537f9d5398630716cd26e5fcdba1a300388860a9f5feed894ca9d5caf9daffaca23f53fd46071f69007b286050e798293f7524223f1882667d5e282be7b09faada3179496630053a060c1af1d2c80d212dc871bf56a846a6b70cfca17b47d2fcb86e57cef165215f2a526cc40215c3c6002fbeb5f0a9d545379004fd108a8a5ee96ac0bd33d1495c609724e6554fe1bf90fe92e679df8883df6df9173e8c476b5e83493f55bf2e20e9c85acd21f7f710a9a380233fc28254077fb751c4ffbd5124639421cc05ae2e3886bbc29177c02ad5ab569b5a89c4dd5da2e131ab41ac4ce2347f5425230b8822198cd805047b1a35fa00cd3877fdc902857293f94fcad3f0e418448b0756e99ce6cc46d1a4cf8673823a89819a9bd9d4c3506975d37835a5310a1bee056887124b6d0fc0ae8974371e1e12e13d68f844878bd9fa51bfdb2455f6bb6c93e49998b37e4fa2b01ff31c665b16313015311007d624fe77df48ac69d8475ca06500d618fbff38ab6f50575d4b0fd3efe4fd6e561797070ec365ecda8a62e6dcb993b8eff16a2e5af7ad7b93cb6a028cdab351251e368c4f188d620ad86fa43886ee57f91be033feab89cd381c486b9cb9c102ae2d3d3cc15068ecb5e0822e8f5ff5a2faef2598308ec40acc4f122023722e8e6758034f98bb45136117154cb7690a2c04b9504a747ce3f69be7d29299462181fcf721d795233aacf010309af5b1709309cb5c2ceae7926ea0868b1c8a2573554284f86310d1df7ed82e6be49d417ec494da9f15f9fac757bdee7cb49c3e7fdcfabc28801bde8f90b90cf3eb37784bb91d9c35662b5f00fdd80a0e00020004626974730000000000152561746f6d69632d656e67696e652d6d6f6e74686c792d323646454232342d32364150523234', 'hex', ), ); const roundingIntervals = buildRoundingIntervalsFromIntervals( contractSize, [ { beginInterval: 0n, rounding: lowPrecisionRounding }, { beginInterval: 750000n, rounding: mediumPrecisionRounding }, { beginInterval: 850000n, rounding: highPrecisionRounding }, { beginInterval: 950000n, rounding: highestPrecisionRounding }, ], ); it('should get cso info from offer with contract size greater than collateral', () => { const contractSize = Value.fromBitcoin(0.03); const collateral = Value.fromBitcoin(0.01); const numOfferInputs = 3; const numContracts = 1; const normalizedMaxGain = Value.fromBitcoin(0.005); const normalizedMaxLoss = Value.fromBitcoin(0.04); const feePerByte = BigInt(100); const shiftForFees: DlcParty = 'offeror'; // 'offeror pays network fees const fees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 1, numContracts) .offerFees, ); const skipValidation = false; const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, normalizedMaxLoss, normalizedMaxGain, feePerByte, roundingIntervals, network, shiftForFees, fees, collateral, numOfferInputs, numContracts, skipValidation, ); getCsoInfoFromOffer(csoOrderOffer, 'v1'); }); it('should get cso info from offer with contract size equal to collateral with both v0 and v1', () => { const contractSize = Value.fromBitcoin(0.01); const collateral = Value.fromBitcoin(0.01); const numOfferInputs = 3; const numContracts = 1; const normalizedMaxGain = Value.fromBitcoin(0.005); const normalizedMaxLoss = Value.fromBitcoin(0.04); const feePerByte = BigInt(100); const shiftForFees: DlcParty = 'offeror'; // 'offeror pays network fees const fees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 1, numContracts) .offerFees, ); const skipValidation = false; const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, normalizedMaxLoss, normalizedMaxGain, feePerByte, roundingIntervals, network, shiftForFees, fees, collateral, numOfferInputs, numContracts, skipValidation, ); getCsoInfoFromOffer(csoOrderOffer, 'v0'); getCsoInfoFromOffer(csoOrderOffer, 'v1'); }); describe('high leverage with high fees', () => { const contractSize = Value.fromBitcoin(0.36); const collateral = Value.fromBitcoin(0.02); const numOfferInputs = 5; const numContracts = 5; const normalizedMaxGain = Value.fromBitcoin(0.005); const normalizedMaxLoss = Value.fromBitcoin(0.04); const feePerByte = BigInt(450); const skipValidation = false; it('should build and decode shift for fees offeror', () => { const shiftForFees: DlcParty = 'offeror'; // 'offeror' pays network fees const fees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) .acceptFees, ); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, normalizedMaxLoss, normalizedMaxGain, feePerByte, roundingIntervals, network, shiftForFees, fees, collateral, numOfferInputs, numContracts, skipValidation, ); const { normalizedMaxGain: actualNormalizedMaxGain, normalizedMaxLoss: actualNormalizedMaxLoss, maxGainForContractSize: actualMaxGainForContractSize, maxLossForContractSize: actualMaxLossForContractSize, minPayout, maxPayout, contractSize: actualContractSize, offerCollateral: actualOfferCollateral, totalCollateral: actualTotalCollateral, } = getCsoInfoFromOffer(csoOrderOffer, 'v1'); // Fees are very high, so use dust threshold for max gain const offerFees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) .offerFees, ); const expectedMaxGainForContractSize_ = offerFees.addn( Value.fromSats(dustThreshold(BigInt(feePerByte))), ); const expectedMaxGainForContractSize = Value.fromSats( roundUpToNearestMultiplier( expectedMaxGainForContractSize_.sats, BigInt(100), ), ); const expectedMaxLossForContractSize = Value.fromSats( roundUpToNearestMultiplier( (normalizedMaxLoss.sats * contractSize.sats) / BigInt(1e8), BigInt(100), ), ); const expectedNormalizedMaxGain = Value.fromSats( roundDownToNearestMultiplier( (expectedMaxGainForContractSize.sats * BigInt(1e8)) / contractSize.sats, BigInt(100), ), ); expect(actualMaxGainForContractSize.sats).to.equal( expectedMaxGainForContractSize.sats, ); expect(actualMaxLossForContractSize.sats).to.equal( expectedMaxLossForContractSize.sats, ); expect(actualNormalizedMaxLoss.sats).to.equal(normalizedMaxLoss.sats); expect(actualNormalizedMaxGain.sats).to.equal( expectedNormalizedMaxGain.sats, ); // TODO: Fix issue with this line expect(minPayout).to.equal(BigInt(121200)); expect(maxPayout).to.equal(collateral.sats); expect(actualContractSize.sats).to.equal(contractSize.sats); expect(actualOfferCollateral.sats).to.equal( collateral.subn(expectedMaxGainForContractSize).sats, ); expect(actualTotalCollateral.sats).to.equal(collateral.sats); }); it('should build and decode shift for fees acceptor', () => { const shiftForFees: DlcParty = 'acceptor'; // 'acceptor' pays network fees const fees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) .offerFees, ); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, normalizedMaxLoss, normalizedMaxGain, feePerByte, roundingIntervals, network, shiftForFees, fees, collateral, numOfferInputs, numContracts, skipValidation, ); const { normalizedMaxGain: actualNormalizedMaxGain, normalizedMaxLoss: actualNormalizedMaxLoss, maxGainForContractSize: actualMaxGainForContractSize, maxLossForContractSize: actualMaxLossForContractSize, minPayout, maxPayout, contractSize: actualContractSize, offerCollateral: actualOfferCollateral, totalCollateral: actualTotalCollateral, } = getCsoInfoFromOffer(csoOrderOffer, 'v1'); // Fees are very high, so use dust threshold for max gain const offerFees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) .offerFees, ); const expectedMaxGainForContractSize_ = offerFees.addn( Value.fromSats(dustThreshold(BigInt(feePerByte))), ); const expectedMaxGainForContractSize = Value.fromSats( roundUpToNearestMultiplier( expectedMaxGainForContractSize_.sats, BigInt(100), ), ); const expectedMaxLossForContractSize = Value.fromSats( roundUpToNearestMultiplier( (normalizedMaxLoss.sats * contractSize.sats) / BigInt(1e8), BigInt(100), ), ); const expectedNormalizedMaxGain = Value.fromSats( roundDownToNearestMultiplier( (expectedMaxGainForContractSize.sats * BigInt(1e8)) / contractSize.sats, BigInt(100), ), ); expect(actualNormalizedMaxGain.sats).to.equal( expectedNormalizedMaxGain.sats, ); expect(actualNormalizedMaxLoss.sats).to.equal(normalizedMaxLoss.sats); expect(actualMaxGainForContractSize.sats).to.equal( expectedMaxGainForContractSize.sats, ); expect(actualMaxLossForContractSize.sats).to.equal( expectedMaxLossForContractSize.sats, ); expect(minPayout).to.equal(BigInt(121200)); expect(maxPayout).to.equal(collateral.sats); expect(actualContractSize.sats).to.equal(contractSize.sats); expect(actualOfferCollateral.sats).to.equal( collateral.subn(expectedMaxGainForContractSize).sats, ); expect(actualTotalCollateral.sats).to.equal(collateral.sats); }); it('should build and decode shift for fees neither', () => { const shiftForFees: DlcParty = 'neither'; // both parties pay their fair share for network fees const fees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) .offerFees, ); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, normalizedMaxLoss, normalizedMaxGain, feePerByte, roundingIntervals, network, shiftForFees, fees, collateral, numOfferInputs, numContracts, skipValidation, ); const { normalizedMaxGain: actualNormalizedMaxGain, normalizedMaxLoss: actualNormalizedMaxLoss, maxGainForContractSize: actualMaxGainForContractSize, maxLossForContractSize: actualMaxLossForContractSize, minPayout, maxPayout, contractSize: actualContractSize, offerCollateral: actualOfferCollateral, totalCollateral: actualTotalCollateral, } = getCsoInfoFromOffer(csoOrderOffer, 'v1'); // Fees are very high, so use dust threshold for max gain const offerFees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) .offerFees, ); const expectedMaxGainForContractSize_ = offerFees.addn( Value.fromSats(dustThreshold(BigInt(feePerByte))), ); const expectedMaxGainForContractSize = Value.fromSats( roundUpToNearestMultiplier( expectedMaxGainForContractSize_.sats, BigInt(100), ), ); const expectedMaxLossForContractSize = Value.fromSats( roundUpToNearestMultiplier( (normalizedMaxLoss.sats * contractSize.sats) / BigInt(1e8), BigInt(100), ), ); const expectedNormalizedMaxGain = Value.fromSats( roundDownToNearestMultiplier( (expectedMaxGainForContractSize.sats * BigInt(1e8)) / contractSize.sats, BigInt(100), ), ); expect(actualNormalizedMaxGain.sats).to.equal( expectedNormalizedMaxGain.sats, ); expect(actualNormalizedMaxLoss.sats).to.equal(normalizedMaxLoss.sats); expect(actualMaxGainForContractSize.sats).to.equal( expectedMaxGainForContractSize.sats, ); expect(actualMaxLossForContractSize.sats).to.equal( expectedMaxLossForContractSize.sats, ); expect(minPayout).to.equal(BigInt(121200)); expect(maxPayout).to.equal(collateral.sats); expect(actualContractSize.sats).to.equal(contractSize.sats); expect(actualOfferCollateral.sats).to.equal( collateral.subn(expectedMaxGainForContractSize).sats, ); expect(actualTotalCollateral.sats).to.equal(collateral.sats); }); }); describe('high leverage with low fees', () => { const contractSize = Value.fromBitcoin(0.36); const collateral = Value.fromBitcoin(0.02); const numOfferInputs = 5; const numContracts = 5; const normalizedMaxGain = Value.fromBitcoin(0.005); const normalizedMaxLoss = Value.fromBitcoin(0.04); const feePerByte = BigInt(50); const skipValidation = false; it('should build and decode shift for fees offeror', () => { const shiftForFees: DlcParty = 'offeror'; // 'offeror' pays network fees const fees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) .acceptFees, ); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, normalizedMaxLoss, normalizedMaxGain, feePerByte, roundingIntervals, network, shiftForFees, fees, collateral, numOfferInputs, numContracts, skipValidation, ); const { normalizedMaxGain: actualNormalizedMaxGain, normalizedMaxLoss: actualNormalizedMaxLoss, maxGainForContractSize: actualMaxGainForContractSize, maxLossForContractSize: actualMaxLossForContractSize, minPayout, maxPayout, contractSize: actualContractSize, offerCollateral: actualOfferCollateral, totalCollateral: actualTotalCollateral, } = getCsoInfoFromOffer(csoOrderOffer, 'v1'); const expectedMaxGainForContractSize = Value.fromSats( roundUpToNearestMultiplier( (normalizedMaxGain.sats * contractSize.sats) / BigInt(1e8), BigInt(100), ), ); const expectedMaxLossForContractSize = Value.fromSats( roundUpToNearestMultiplier( (normalizedMaxLoss.sats * contractSize.sats) / BigInt(1e8), BigInt(100), ), ); const expectedNormalizedMaxGain = Value.fromSats( roundUpToNearestMultiplier( (expectedMaxGainForContractSize.sats * BigInt(1e8)) / contractSize.sats, BigInt(100), ), ); expect(actualNormalizedMaxGain.sats).to.equal( expectedNormalizedMaxGain.sats, ); expect(actualNormalizedMaxLoss.sats).to.equal(normalizedMaxLoss.sats); expect(actualMaxGainForContractSize.sats).to.equal( expectedMaxGainForContractSize.sats, ); expect(actualMaxLossForContractSize.sats).to.equal( expectedMaxLossForContractSize.sats, ); expect(minPayout).to.equal(BigInt(380000)); expect(maxPayout).to.equal(collateral.sats); expect(actualContractSize.sats).to.equal(contractSize.sats); expect(actualOfferCollateral.sats).to.equal( collateral.subn(expectedMaxGainForContractSize).sats, ); expect(actualTotalCollateral.sats).to.equal(collateral.sats); }); it('should build and decode shift for fees acceptor', () => { const shiftForFees: DlcParty = 'acceptor'; // 'acceptor' pays network fees const fees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) .offerFees, ); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, normalizedMaxLoss, normalizedMaxGain, feePerByte, roundingIntervals, network, shiftForFees, fees, collateral, numOfferInputs, numContracts, skipValidation, ); const { normalizedMaxGain: actualNormalizedMaxGain, normalizedMaxLoss: actualNormalizedMaxLoss, maxGainForContractSize: actualMaxGainForContractSize, maxLossForContractSize: actualMaxLossForContractSize, minPayout, maxPayout, contractSize: actualContractSize, offerCollateral: actualOfferCollateral, totalCollateral: actualTotalCollateral, } = getCsoInfoFromOffer(csoOrderOffer, 'v1'); const expectedMaxGainForContractSize = Value.fromSats( roundUpToNearestMultiplier( (normalizedMaxGain.sats * contractSize.sats) / BigInt(1e8), BigInt(100), ), ); const expectedMaxLossForContractSize = Value.fromSats( roundUpToNearestMultiplier( (normalizedMaxLoss.sats * contractSize.sats) / BigInt(1e8), BigInt(100), ), ); const expectedNormalizedMaxGain = Value.fromSats( roundUpToNearestMultiplier( (expectedMaxGainForContractSize.sats * BigInt(1e8)) / contractSize.sats, BigInt(100), ), ); expect(actualNormalizedMaxGain.sats).to.equal( expectedNormalizedMaxGain.sats, ); expect(actualNormalizedMaxLoss.sats).to.equal(normalizedMaxLoss.sats); expect(actualMaxGainForContractSize.sats).to.equal( expectedMaxGainForContractSize.sats, ); expect(actualMaxLossForContractSize.sats).to.equal( expectedMaxLossForContractSize.sats, ); expect(minPayout).to.equal(BigInt(380000)); expect(maxPayout).to.equal(collateral.sats); expect(actualContractSize.sats).to.equal(contractSize.sats); expect(actualOfferCollateral.sats).to.equal( collateral.subn(expectedMaxGainForContractSize).sats, ); expect(actualTotalCollateral.sats).to.equal(collateral.sats); }); it('should build and decode shift for fees neither', () => { const shiftForFees: DlcParty = 'neither'; // both parties pay their fair share for network fees const fees = Value.fromSats( getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) .offerFees, ); const csoOrderOffer = buildCustomStrategyOrderOffer( oracleAnnouncement, contractSize, normalizedMaxLoss, normalizedMaxGain, feePerByte, roundingIntervals, network, shiftForFees, fees, collateral, numOfferInputs, numContracts, skipValidation, ); const { normalizedMaxGain: actualNormalizedMaxGain, normalizedMaxLoss: actualNormalizedMaxLoss, maxGainForContractSize: actualMaxGainForContractSize, maxLossForContractSize: actualMaxLossForContractSize, minPayout, maxPayout, contractSize: actualContractSize, offerCollateral: actualOfferCollateral, totalCollateral: actualTotalCollateral, } = getCsoInfoFromOffer(csoOrderOffer, 'v1'); const expectedMaxGainForContractSize = Value.fromSats( roundUpToNearestMultiplier( (normalizedMaxGain.sats * contractSize.sats) / BigInt(1e8), BigInt(100), ), ); const expectedMaxLossForContractSize = Value.fromSats( roundUpToNearestMultiplier( (normalizedMaxLoss.sats * contractSize.sats) / BigInt(1e8), BigInt(100), ), ); const expectedNormalizedMaxGain = Value.fromSats( roundUpToNearestMultiplier( (expectedMaxGainForContractSize.sats * BigInt(1e8)) / contractSize.sats, BigInt(100), ), ); expect(actualNormalizedMaxGain.sats).to.equal( expectedNormalizedMaxGain.sats, ); expect(actualNormalizedMaxLoss.sats).to.equal(normalizedMaxLoss.sats); expect(actualMaxGainForContractSize.sats).to.equal( expectedMaxGainForContractSize.sats, ); expect(actualMaxLossForContractSize.sats).to.equal( expectedMaxLossForContractSize.sats, ); expect(minPayout).to.equal(BigInt(380000)); expect(maxPayout).to.equal(collateral.sats); expect(actualContractSize.sats).to.equal(contractSize.sats); expect(actualOfferCollateral.sats).to.equal( collateral.subn(expectedMaxGainForContractSize).sats, ); expect(actualTotalCollateral.sats).to.equal(collateral.sats); }); }); }); });