Skip to content

Commit

Permalink
🐛 fix: find outcome index hyperbola edge case
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewjablack committed Oct 16, 2023
1 parent e709b6d commit cdd194b
Show file tree
Hide file tree
Showing 4 changed files with 470 additions and 75 deletions.
50 changes: 36 additions & 14 deletions packages/bitcoin-dlc-provider/lib/BitcoinDlcProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1142,30 +1142,52 @@ Payout Group not found',
let index = 0;
let groupIndex = -1;
let groupLength = 0;
const payoutGroupFound = false;

for (const payoutGroup of payoutGroups) {
for (const [i, payoutGroup] of payoutGroups.entries()) {
if (payoutGroup.payout === roundedPayout) {
groupIndex = payoutGroup.groups.findIndex((group) => {
return group.every((msg, i) => msg === outcomesFormatted[i]);
});
if (groupIndex === -1)
throw Error(
'Failed to Find OutcomeIndex From HyperbolaPayoutCurvePiece. \
Payout Group found but incorrect group index',
);
index += groupIndex;
groupLength = payoutGroup.groups[groupIndex].length;
break;
if (groupIndex !== -1) {
index += groupIndex;
groupLength = payoutGroup.groups[groupIndex].length;
break;
}
} else if (payoutGroup.payout === BigInt(payout.toString())) {
// Edge case to account for case where payout is maximum payout for DLC
// But rounded payout does not round down
if (payoutGroups[i - 1].payout === roundedPayout) {
// Ensure that the previous payout group causes index to be incremented
index += payoutGroups[i - 1].groups.length;
}

groupIndex = payoutGroup.groups.findIndex((group) => {
return group.every((msg, i) => msg === outcomesFormatted[i]);
});
if (groupIndex !== -1) {
index += groupIndex;
groupLength = payoutGroup.groups[groupIndex].length;
break;
}
} else {
index += payoutGroup.groups.length;
}
}

if (groupIndex === -1)
throw Error(
'Failed to Find OutcomeIndex From HyperbolaPayoutCurvePiece. \
Payout Group not found',
);
if (groupIndex === -1) {
if (payoutGroupFound) {
throw Error(
'Failed to Find OutcomeIndex From HyperbolaPayoutCurvePiece. \
Payout Group found but incorrect group index',
);
} else {
throw Error(
'Failed to Find OutcomeIndex From HyperbolaPayoutCurvePiece. \
Payout Group not found',
);
}
}

return { index: payoutIndexOffset + index, groupLength };
}
Expand Down
272 changes: 271 additions & 1 deletion tests/integration/dlc/dlc.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'mocha';

import { Value } from '@node-dlc/bitcoin';
import {
CoveredCall,
DlcTxBuilder,
Expand Down Expand Up @@ -43,7 +44,9 @@ import { chains, getInput } from '../common';
import f from '../fixtures/blockchain.json';
import Oracle from '../models/Oracle';
import {
calculateNetworkFees,
generateContractInfo,
generateLongCallOffer,
generateOracleAttestation,
} from '../utils/contract';

Expand All @@ -55,7 +58,6 @@ const alice = chain.client;

const bob = chains.bitcoinWithJs2.client;
const carol = chains.bitcoinWithJs3.client;
const mm = chains.bitcoinWithJs4.client;

describe('bitcoin networks', () => {
it('have correct genesis block hashes', async () => {
Expand Down Expand Up @@ -595,6 +597,274 @@ describe('dlc provider', () => {
});
});

// Test to make sure payout groups find index works properly
describe('Finding payout index works for long calls', () => {
it('finds payout index correctly for payout that does not match rounded payout', async () => {
console.time('offer-get-time');
const aliceInput = await getInput(alice);
const bobInput = await getInput(bob);

oracle = new Oracle('olivia', 18);

const feePerByte = 5;

const LONG_OPTION_MAX_GAIN = Value.fromBitcoin(0.005);

const contractPrice = 0.0108;

const networkFees = Value.fromSats(
calculateNetworkFees(BigInt(feePerByte)),
);

const contractSize = 0.1;

console.log('networkFees.bitcoin', networkFees.bitcoin);

const maxGain = Value.fromBitcoin(
LONG_OPTION_MAX_GAIN.bitcoin * contractSize +
contractPrice * contractSize +
networkFees.bitcoin,
);
const premium = Value.fromBitcoin(
contractPrice * contractSize + networkFees.bitcoin,
);

const offer = generateLongCallOffer(
oracle,
18,
2,
'atomic-deribit-BTC-13OCT23',
29000,
maxGain,
premium,
feePerByte,
5000,
'bitcoin_regtest',
);

const cetLocktime = 1617170572;
const refundLocktime = 1617170573;

dlcOffer = await alice.dlc.createDlcOffer(
offer.contractInfo,
offer.offerCollateralSatoshis,
offer.feeRatePerVb,
cetLocktime,
refundLocktime,
[aliceInput],
);

console.timeEnd('offer-get-time');

console.time('accept-time');
const acceptDlcOfferResponse: AcceptDlcOfferResponse = await bob.dlc.acceptDlcOffer(
dlcOffer,
[bobInput],
);
dlcAccept = acceptDlcOfferResponse.dlcAccept;
dlcTransactions = acceptDlcOfferResponse.dlcTransactions;

const { dlcTransactions: dlcTxsFromMsgs } = await bob.dlc.createDlcTxs(
dlcOffer,
dlcAccept,
);

expect(
(dlcTransactions as DlcTransactionsV0).fundTx
.serialize()
.toString('hex'),
).to.equal(
(dlcTxsFromMsgs as DlcTransactionsV0).fundTx
.serialize()
.toString('hex'),
);
expect(
(dlcTransactions as DlcTransactionsV0).cets[5]
.serialize()
.toString('hex'),
).to.equal(
(dlcTxsFromMsgs as DlcTransactionsV0).cets[5]
.serialize()
.toString('hex'),
);

console.timeEnd('accept-time');

console.time('sign-time');
const signDlcAcceptResponse: SignDlcAcceptResponse = await alice.dlc.signDlcAccept(
dlcOffer,
dlcAccept,
);
dlcSign = signDlcAcceptResponse.dlcSign;
console.timeEnd('sign-time');

const fundTx = await bob.dlc.finalizeDlcSign(
dlcOffer,
dlcAccept,
dlcSign,
dlcTransactions,
);
const fundTxId = await bob.chain.sendRawTransaction(
fundTx.serialize().toString('hex'),
);

const outcome = 37000;
oracleAttestation = generateOracleAttestation(
outcome,
oracle,
oracleBase,
18,
'atomic-deribit-BTC-13OCT23',
);

const cet = await bob.dlc.execute(
dlcOffer,
dlcAccept,
dlcSign,
dlcTransactions,
oracleAttestation,
false,
);
const cetTxId = await bob.chain.sendRawTransaction(
cet.serialize().toString('hex'),
);
const cetTx = await alice.getMethod('getTransactionByHash')(cetTxId);
expect(cetTx._raw.vin.length).to.equal(1);
});

it('finds payout index correctly for payout that does match rounded payout', async () => {
console.time('offer-get-time');
const aliceInput = await getInput(alice);
const bobInput = await getInput(bob);

oracle = new Oracle('olivia', 18);

const feePerByte = 5;

const LONG_OPTION_MAX_GAIN = Value.fromBitcoin(0.005);

const contractPrice = 0.0108;

const networkFees = Value.fromSats(
calculateNetworkFees(BigInt(feePerByte)),
);

const contractSize = 0.1;

console.log('networkFees.bitcoin', networkFees.bitcoin);

const maxGain = Value.fromBitcoin(
LONG_OPTION_MAX_GAIN.bitcoin * contractSize +
contractPrice * contractSize,
);
const premium = Value.fromBitcoin(
contractPrice * contractSize + networkFees.bitcoin,
);

const offer = generateLongCallOffer(
oracle,
18,
2,
'atomic-deribit-BTC-13OCT23',
29000,
maxGain,
premium,
feePerByte,
5000,
'bitcoin_regtest',
);

const cetLocktime = 1617170572;
const refundLocktime = 1617170573;

dlcOffer = await alice.dlc.createDlcOffer(
offer.contractInfo,
offer.offerCollateralSatoshis,
offer.feeRatePerVb,
cetLocktime,
refundLocktime,
[aliceInput],
);

console.timeEnd('offer-get-time');

console.time('accept-time');
const acceptDlcOfferResponse: AcceptDlcOfferResponse = await bob.dlc.acceptDlcOffer(
dlcOffer,
[bobInput],
);
dlcAccept = acceptDlcOfferResponse.dlcAccept;
dlcTransactions = acceptDlcOfferResponse.dlcTransactions;

const { dlcTransactions: dlcTxsFromMsgs } = await bob.dlc.createDlcTxs(
dlcOffer,
dlcAccept,
);

expect(
(dlcTransactions as DlcTransactionsV0).fundTx
.serialize()
.toString('hex'),
).to.equal(
(dlcTxsFromMsgs as DlcTransactionsV0).fundTx
.serialize()
.toString('hex'),
);
expect(
(dlcTransactions as DlcTransactionsV0).cets[5]
.serialize()
.toString('hex'),
).to.equal(
(dlcTxsFromMsgs as DlcTransactionsV0).cets[5]
.serialize()
.toString('hex'),
);

console.timeEnd('accept-time');

console.time('sign-time');
const signDlcAcceptResponse: SignDlcAcceptResponse = await alice.dlc.signDlcAccept(
dlcOffer,
dlcAccept,
);
dlcSign = signDlcAcceptResponse.dlcSign;
console.timeEnd('sign-time');

const fundTx = await bob.dlc.finalizeDlcSign(
dlcOffer,
dlcAccept,
dlcSign,
dlcTransactions,
);
const fundTxId = await bob.chain.sendRawTransaction(
fundTx.serialize().toString('hex'),
);

const outcome = 38000;
oracleAttestation = generateOracleAttestation(
outcome,
oracle,
oracleBase,
18,
'atomic-deribit-BTC-13OCT23',
);

const cet = await bob.dlc.execute(
dlcOffer,
dlcAccept,
dlcSign,
dlcTransactions,
oracleAttestation,
false,
);
const cetTxId = await bob.chain.sendRawTransaction(
cet.serialize().toString('hex'),
);
const cetTx = await alice.getMethod('getTransactionByHash')(cetTxId);
expect(cetTx._raw.vin.length).to.equal(1);
});
});

/**
* Currently quickFindAddress only checked the first 5000 addresses
* This means DlcSign would fail if any addresses are > 5000
Expand Down
Loading

0 comments on commit cdd194b

Please sign in to comment.