@node-dlc/messaging
Version:
DLC Messaging Protocol
315 lines (263 loc) • 10.2 kB
text/typescript
import { BufferReader, BufferWriter } from '@node-dlc/bufio';
import { MessageType } from '../MessageType';
import { getTlv } from '../serialize/getTlv';
import {
ContractDescriptor,
ContractDescriptorV0JSON,
ContractDescriptorV1,
ContractDescriptorV1JSON,
} from './ContractDescriptor';
import { DlcMessage, IDlcMessage } from './DlcMessage';
import { DigitDecompositionEventDescriptorV0 } from './EventDescriptor';
import { OracleEventV0 } from './OracleEventV0';
import { OracleInfoV0, OracleInfoV0JSON } from './OracleInfoV0';
export abstract class ContractInfo extends DlcMessage {
public static deserialize(buf: Buffer): ContractInfoV0 | ContractInfoV1 {
const reader = new BufferReader(buf);
const type = Number(reader.readBigSize());
switch (type) {
case MessageType.ContractInfoV0:
return ContractInfoV0.deserialize(buf);
case MessageType.ContractInfoV1:
return ContractInfoV1.deserialize(buf);
default:
throw new Error(
`Contract info TLV type must be ContractInfoV0 or ContractInfoV1`,
);
}
}
public abstract type: number;
public abstract length: bigint;
public abstract totalCollateral: bigint;
public abstract validate(): void;
public abstract toJSON(): IContractInfoV0JSON | IContractInfoV1JSON;
public abstract serialize(): Buffer;
}
/**
* ContractInfo V0 contains information about a contract's outcomes,
* their corresponding payouts, and the oracles to be used.
*/
export class ContractInfoV0 implements IDlcMessage {
public static type = MessageType.ContractInfoV0;
/**
* Deserializes an contract_info_v0 message
* @param buf
*/
public static deserialize(buf: Buffer): ContractInfoV0 {
const instance = new ContractInfoV0();
const reader = new BufferReader(buf);
reader.readBigSize(); // read type
instance.length = reader.readBigSize();
instance.totalCollateral = reader.readUInt64BE();
instance.contractDescriptor = ContractDescriptor.deserialize(
getTlv(reader),
);
instance.oracleInfo = OracleInfoV0.deserialize(getTlv(reader));
return instance;
}
/**
* The type for contract_info_v0 message. contract_info_v0 = 55342
*/
public type = ContractInfoV0.type;
public length: bigint;
public totalCollateral: bigint;
public contractDescriptor: ContractDescriptor;
public oracleInfo: OracleInfoV0;
/**
* Validates correctness of all fields in the message
* https://github.com/discreetlogcontracts/dlcspecs/blob/master/Messaging.md#the-contract_info-type
* @throws Will throw an error if validation fails
*/
public validate(): void {
this.oracleInfo.validate();
switch (this.contractDescriptor.type) {
case MessageType.ContractDescriptorV1:
// eslint-disable-next-line no-case-declarations
const contractDescriptor = this
.contractDescriptor as ContractDescriptorV1;
contractDescriptor.validate();
// roundingmod should not be greater than totalCollateral
for (const interval of contractDescriptor.roundingIntervals.intervals) {
if (interval.roundingMod > this.totalCollateral) {
throw new Error(
`Rounding modulus ${interval.roundingMod} is greater than total collateral ${this.totalCollateral}`,
);
}
}
switch (this.oracleInfo.announcement.oracleEvent.eventDescriptor.type) {
case MessageType.DigitDecompositionEventDescriptorV0:
// eslint-disable-next-line no-case-declarations
const eventDescriptor = this.oracleInfo.announcement.oracleEvent
.eventDescriptor as DigitDecompositionEventDescriptorV0;
if (eventDescriptor.nbDigits !== contractDescriptor.numDigits)
throw new Error(
'DigitDecompositionEventDescriptorV0 and ContractDescriptorV1 must have the same numDigits',
);
// Sanity check. Shouldn't be hit since there are other validations which should catch this in OracleEventV0.
// eslint-disable-next-line no-case-declarations
const oracleEvent = this.oracleInfo.announcement
.oracleEvent as OracleEventV0;
if (
oracleEvent.oracleNonces.length !== contractDescriptor.numDigits
) {
throw new Error(
'oracleEvent.oracleNonces.length and contractDescriptor.numDigits must be the same',
);
}
break;
default:
throw new Error(
'Only ContractDescriptorV1 can be used with DigitDecompositionEventDescriptor',
);
}
}
}
/**
* Converts contract_info_v0 to JSON
*/
public toJSON(): IContractInfoV0JSON {
return {
type: this.type,
totalCollateral: Number(this.totalCollateral),
contractDescriptor: this.contractDescriptor.toJSON(),
oracleInfo: this.oracleInfo.toJSON(),
};
}
/**
* Serializes the contract_info_v0 message into a Buffer
*/
public serialize(): Buffer {
const writer = new BufferWriter();
writer.writeBigSize(this.type);
const dataWriter = new BufferWriter();
dataWriter.writeUInt64BE(this.totalCollateral);
dataWriter.writeBytes(this.contractDescriptor.serialize());
dataWriter.writeBytes(this.oracleInfo.serialize());
writer.writeBigSize(dataWriter.size);
writer.writeBytes(dataWriter.toBuffer());
return writer.toBuffer();
}
}
/**
* ContractInfo V1 contains information about a contract's outcomes,
* their corresponding payouts, and the oracles to be used.
*/
export class ContractInfoV1 implements IDlcMessage {
public static type = MessageType.ContractInfoV1;
/**
* Deserializes an contract_info_v0 message
* @param buf
*/
public static deserialize(buf: Buffer): ContractInfoV1 {
const instance = new ContractInfoV1();
const reader = new BufferReader(buf);
reader.readBigSize(); // read type
instance.length = reader.readBigSize();
instance.totalCollateral = reader.readUInt64BE();
reader.readBigSize(); // read num_disjoint_events
while (!reader.eof) {
const contractDescriptor = ContractDescriptor.deserialize(getTlv(reader));
const oracleInfo = OracleInfoV0.deserialize(getTlv(reader));
instance.contractOraclePairs.push({ contractDescriptor, oracleInfo });
}
return instance;
}
/**
* The type for contract_info_v1 message. contract_info_v0 = 55342
*/
public type = ContractInfoV1.type;
public length: bigint;
public totalCollateral: bigint;
public contractOraclePairs: IContractOraclePair[] = [];
/**
* Validates correctness of all fields in the message
* https://github.com/discreetlogcontracts/dlcspecs/blob/master/Messaging.md#the-contract_info-type
* @throws Will throw an error if validation fails
*/
public validate(): void {
this.contractOraclePairs.forEach((oraclePair) => {
oraclePair.oracleInfo.validate();
switch (oraclePair.contractDescriptor.type) {
case MessageType.ContractDescriptorV1:
// eslint-disable-next-line no-case-declarations
const contractDescriptor = oraclePair.contractDescriptor as ContractDescriptorV1;
contractDescriptor.validate();
switch (
oraclePair.oracleInfo.announcement.oracleEvent.eventDescriptor.type
) {
case MessageType.DigitDecompositionEventDescriptorV0:
// eslint-disable-next-line no-case-declarations
const eventDescriptor = oraclePair.oracleInfo.announcement
.oracleEvent
.eventDescriptor as DigitDecompositionEventDescriptorV0;
if (eventDescriptor.nbDigits !== contractDescriptor.numDigits)
throw new Error(
'DigitDecompositionEventDescriptorV0 and ContractDescriptorV1 must have the same numDigits',
);
// eslint-disable-next-line no-case-declarations
const oracleEvent = oraclePair.oracleInfo.announcement
.oracleEvent as OracleEventV0;
if (
oracleEvent.oracleNonces.length !== contractDescriptor.numDigits
) {
throw new Error(
'oracleEvent.oracleNonces.length and contractDescriptor.numDigits must be the same',
);
}
}
}
});
}
/**
* Converts contract_info_v1 to JSON
*/
public toJSON(): IContractInfoV1JSON {
return {
type: this.type,
totalCollateral: Number(this.totalCollateral),
contractOraclePairs: this.contractOraclePairs.map((oraclePairs) => {
return {
contractDescriptor: oraclePairs.contractDescriptor.toJSON(),
oracleInfo: oraclePairs.oracleInfo.toJSON(),
};
}),
};
}
/**
* Serializes the contract_info_v1 message into a Buffer
*/
public serialize(): Buffer {
const writer = new BufferWriter();
writer.writeBigSize(this.type);
const dataWriter = new BufferWriter();
dataWriter.writeUInt64BE(this.totalCollateral);
dataWriter.writeBigSize(this.contractOraclePairs.length);
for (const contractOraclePair of this.contractOraclePairs) {
const { contractDescriptor, oracleInfo } = contractOraclePair;
dataWriter.writeBytes(contractDescriptor.serialize());
dataWriter.writeBytes(oracleInfo.serialize());
}
writer.writeBigSize(dataWriter.size);
writer.writeBytes(dataWriter.toBuffer());
return writer.toBuffer();
}
}
interface IContractOraclePair {
contractDescriptor: ContractDescriptor;
oracleInfo: OracleInfoV0;
}
interface IContractOraclePairJSON {
contractDescriptor: ContractDescriptorV0JSON | ContractDescriptorV1JSON;
oracleInfo: OracleInfoV0JSON;
}
export interface IContractInfoV0JSON {
type: number;
totalCollateral: number;
contractDescriptor: ContractDescriptorV0JSON | ContractDescriptorV1JSON;
oracleInfo: OracleInfoV0JSON;
}
export interface IContractInfoV1JSON {
type: number;
totalCollateral: number;
contractOraclePairs: IContractOraclePairJSON[];
}