diff --git a/src/models/methods/baseMethod.ts b/src/models/methods/baseMethod.ts index eb4bebca..d91c828f 100644 --- a/src/models/methods/baseMethod.ts +++ b/src/models/methods/baseMethod.ts @@ -1,4 +1,4 @@ -import { Request } from './../methods' +import { Request } from "."; export interface BaseRequest { id: number | string diff --git a/src/models/transactions/common.ts b/src/models/transactions/common.ts index fdbc3b11..fd414615 100644 --- a/src/models/transactions/common.ts +++ b/src/models/transactions/common.ts @@ -48,13 +48,17 @@ const isSigner = (signer: Signer): boolean => { && typeof signer.SigningPubKey === 'string' } -export interface CommonFields { +export interface GlobalFlags { + tfFullyCanonicalSig: boolean, +} + +export interface BaseTransaction { Account: string; TransactionType: string; Fee?: string; Sequence?: number; AccountTxnID?: string; - Flags?: number; + Flags?: number | GlobalFlags; LastLedgerSequence?: number; Memos?: Array<{ Memo: Memo }>; Signers?: Array; @@ -73,59 +77,59 @@ export interface CommonFields { * @returns - Void * @throws - When the common param is malformed. */ -export function verifyCommonFields(common: CommonFields): void { +export function verifyBaseTransaction(common: BaseTransaction): void { if (common.Account === undefined) - throw new ValidationError("CommonFields: missing field Account") + throw new ValidationError("BaseTransaction: missing field Account") if (typeof common.Account !== 'string') - throw new ValidationError("CommonFields: Account not string") + throw new ValidationError("BaseTransaction: Account not string") if (common.TransactionType === undefined) - throw new ValidationError("CommonFields: missing field TransactionType") + throw new ValidationError("BaseTransaction: missing field TransactionType") if (typeof common.TransactionType !== 'string') - throw new ValidationError("CommonFields: TransactionType not string") + throw new ValidationError("BaseTransaction: TransactionType not string") if (!transactionTypes.includes(common.TransactionType)) - throw new ValidationError("CommonFields: Unknown TransactionType") + throw new ValidationError("BaseTransaction: Unknown TransactionType") if (common.Fee !== undefined && typeof common.Fee !== 'string') - throw new ValidationError("CommonFields: invalid Fee") + throw new ValidationError("BaseTransaction: invalid Fee") if (common.Sequence !== undefined && typeof common.Sequence !== 'number') - throw new ValidationError("CommonFields: invalid Sequence") + throw new ValidationError("BaseTransaction: invalid Sequence") if (common.Flags !== undefined && typeof common.Flags !== 'number') - throw new ValidationError("CommonFields: invalid Flags") + throw new ValidationError("BaseTransaction: invalid Flags") if (common.AccountTxnID !== undefined && typeof common.AccountTxnID !== 'string') - throw new ValidationError("CommonFields: invalid AccountTxnID") + throw new ValidationError("BaseTransaction: invalid AccountTxnID") if (common.LastLedgerSequence !== undefined && typeof common.LastLedgerSequence !== 'number') - throw new ValidationError("CommonFields: invalid LastLedgerSequence") + throw new ValidationError("BaseTransaction: invalid LastLedgerSequence") if (common.Memos !== undefined && (common.Memos.length === 0 || !common.Memos.every(isMemo))) - throw new ValidationError("CommonFields: invalid Memos") + throw new ValidationError("BaseTransaction: invalid Memos") if (common.Signers !== undefined && (common.Signers.length === 0 || !common.Signers.every(isSigner))) - throw new ValidationError("CommonFields: invalid Signers") + throw new ValidationError("BaseTransaction: invalid Signers") if (common.SourceTag !== undefined && typeof common.SourceTag !== 'number') - throw new ValidationError("CommonFields: invalid SourceTag") + throw new ValidationError("BaseTransaction: invalid SourceTag") if (common.SigningPubKey !== undefined && typeof common.SigningPubKey !== 'string') - throw new ValidationError("CommonFields: invalid SigningPubKey") + throw new ValidationError("BaseTransaction: invalid SigningPubKey") if (common.TicketSequence !== undefined && typeof common.TicketSequence !== 'number') - throw new ValidationError("CommonFields: invalid TicketSequence") + throw new ValidationError("BaseTransaction: invalid TicketSequence") if (common.TxnSignature !== undefined && typeof common.TxnSignature !== 'string') - throw new ValidationError("CommonFields: invalid TxnSignature") + throw new ValidationError("BaseTransaction: invalid TxnSignature") } \ No newline at end of file diff --git a/src/models/transactions/index.ts b/src/models/transactions/index.ts index 34decccb..f1f45ab5 100644 --- a/src/models/transactions/index.ts +++ b/src/models/transactions/index.ts @@ -1,28 +1,2 @@ -import Metadata from "../common/metadata"; - - -export type Transaction = never // (For now) -// AccountSet -// | AccountDelete -// | CheckCancel -// | CheckCash -// | CheckCreate -// | DepositPreauth -// | EscrowCancel -// | EscrowCreate -// | EscrowFinish -// | OfferCancel -// | OfferCreate -// | PaymentTransaction -// | PaymentChannelClaim -// | PaymentChannelCreate -// | PaymentChannelFund -// | SetRegularKey -// | SignerListSet -// | TicketCreate -// | TrustSet - -export interface TransactionAndMetadata { - transaction: Transaction; - metadata: Metadata -} \ No newline at end of file +export * from './transaction' +export * from './offerCreate' diff --git a/src/models/transactions/offerCreate.ts b/src/models/transactions/offerCreate.ts new file mode 100644 index 00000000..a63caad2 --- /dev/null +++ b/src/models/transactions/offerCreate.ts @@ -0,0 +1,55 @@ +import { ValidationError } from "../../common/errors"; +import { Amount, IssuedCurrencyAmount } from "../common"; +import { BaseTransaction, GlobalFlags, verifyBaseTransaction } from "./common"; + +export interface OfferCreateFlags extends GlobalFlags { + tfPassive?: boolean; + tfImmediateOrCancel?: boolean; + tfFillOrKill?: boolean; + tfSell?: boolean; +} + +export interface OfferCreate extends BaseTransaction { + TransactionType: "OfferCreate"; + Flags?: number | OfferCreateFlags + Expiration?: number; + OfferSequence?: number; + TakerGets: Amount; + TakerPays: Amount; +} + +/** + * Verify the form and type of an OfferCreate at runtime. + * + * @param tx - An OfferCreate Transaction + * @returns - Void. + * @throws - When the OfferCreate is Malformed. + */ + export function verifyOfferCreate(tx: OfferCreate): void { + verifyBaseTransaction(tx) + + if (tx.TakerGets === undefined) + throw new ValidationError("OfferCreate: missing field TakerGets") + + if (tx.TakerPays === undefined) + throw new ValidationError("OfferCreate: missing field TakerPays") + + const isIssuedCurrency = (obj: IssuedCurrencyAmount): boolean => { + return Object.keys(obj).length === 3 + && typeof obj.value === 'string' + && typeof obj.issuer === 'string' + && typeof obj.currency === 'string' + } + + if (typeof tx.TakerGets !== 'string' && !isIssuedCurrency(tx.TakerGets)) + throw new ValidationError("OfferCreate: invalid TakerGets") + + if (typeof tx.TakerPays !== 'string' && !isIssuedCurrency(tx.TakerPays)) + throw new ValidationError("OfferCreate: invalid TakerPays") + + if (tx.Expiration !== undefined && typeof tx.Expiration !== 'number') + throw new ValidationError("OfferCreate: invalid Expiration") + + if (tx.OfferSequence !== undefined && typeof tx.OfferSequence !== 'number') + throw new ValidationError("OfferCreate: invalid OfferSequence") +} \ No newline at end of file diff --git a/src/models/transactions/transaction.ts b/src/models/transactions/transaction.ts new file mode 100644 index 00000000..c0cfc8f0 --- /dev/null +++ b/src/models/transactions/transaction.ts @@ -0,0 +1,29 @@ +import Metadata from "../common/metadata"; +import { OfferCreate } from "./offerCreate"; + + +export type Transaction = +// AccountSet +// | AccountDelete +// | CheckCancel +// | CheckCash +// | CheckCreate +// | DepositPreauth +// | EscrowCancel +// | EscrowCreate +// | EscrowFinish +// | OfferCancel + OfferCreate +// | PaymentTransaction +// | PaymentChannelClaim +// | PaymentChannelCreate +// | PaymentChannelFund +// | SetRegularKey +// | SignerListSet +// | TicketCreate +// | TrustSet + +export interface TransactionAndMetadata { + transaction: Transaction; + metadata: Metadata +} \ No newline at end of file diff --git a/test/models/baseTransaction.ts b/test/models/baseTransaction.ts index fbff4f5b..c8ceb7bd 100644 --- a/test/models/baseTransaction.ts +++ b/test/models/baseTransaction.ts @@ -1,5 +1,5 @@ import { ValidationError } from 'ripple-api/common/errors' -import { verifyCommonFields } from '../../src/models/transactions/common' +import { verifyBaseTransaction } from './../../src/models/transactions/common' import { assert } from 'chai' /** @@ -8,7 +8,7 @@ import { assert } from 'chai' * Providing runtime verification testing for each specific transaction type */ describe('Transaction Verification', function () { - it(`Verifies all optional CommonFields`, () => { + it(`Verifies all optional BaseTransaction`, () => { const txJson = { Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe", TransactionType: "Payment", @@ -49,16 +49,16 @@ describe('Transaction Verification', function () { TxnSignature: "3045022100C6708538AE5A697895937C758E99A595B57A16393F370F11B8D4C032E80B532002207776A8E85BB9FAF460A92113B9C60F170CD964196B1F084E0DAB65BAEC368B66" } - assert.doesNotThrow(() => verifyCommonFields(txJson)) + assert.doesNotThrow(() => verifyBaseTransaction(txJson)) }) - it(`Verifies only required CommonFields`, () => { + it(`Verifies only required BaseTransaction`, () => { const txJson = { Account: "r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe", TransactionType: "Payment", } - assert.doesNotThrow(() => verifyCommonFields(txJson)) + assert.doesNotThrow(() => verifyBaseTransaction(txJson)) }) it (`Handles invalid Fee`, () => { @@ -69,9 +69,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidFee), + () => verifyBaseTransaction(invalidFee), ValidationError, - "CommonFields: invalid Fee" + "BaseTransaction: invalid Fee" ) }) @@ -83,9 +83,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidSeq), + () => verifyBaseTransaction(invalidSeq), ValidationError, - "CommonFields: invalid Sequence" + "BaseTransaction: invalid Sequence" ) }) @@ -97,9 +97,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidID), + () => verifyBaseTransaction(invalidID), ValidationError, - "CommonFields: invalid AccountTxnID" + "BaseTransaction: invalid AccountTxnID" ) }) @@ -112,9 +112,9 @@ describe('Transaction Verification', function () { assert.throws( - () => verifyCommonFields(invalidFlags), + () => verifyBaseTransaction(invalidFlags), ValidationError, - "CommonFields: invalid Flags" + "BaseTransaction: invalid Flags" ) }) @@ -126,9 +126,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidLastLedgerSequence), + () => verifyBaseTransaction(invalidLastLedgerSequence), ValidationError, - "CommonFields: invalid LastLedgerSequence" + "BaseTransaction: invalid LastLedgerSequence" ) }) @@ -141,9 +141,9 @@ describe('Transaction Verification', function () { assert.throws( - () => verifyCommonFields(invalidSourceTag), + () => verifyBaseTransaction(invalidSourceTag), ValidationError, - "CommonFields: invalid SourceTag" + "BaseTransaction: invalid SourceTag" ) }) @@ -155,9 +155,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidSigningPubKey), + () => verifyBaseTransaction(invalidSigningPubKey), ValidationError, - "CommonFields: invalid SigningPubKey" + "BaseTransaction: invalid SigningPubKey" ) }) @@ -169,9 +169,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidTicketSequence), + () => verifyBaseTransaction(invalidTicketSequence), ValidationError, - "CommonFields: invalid TicketSequence" + "BaseTransaction: invalid TicketSequence" ) }) @@ -183,9 +183,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidTxnSignature), + () => verifyBaseTransaction(invalidTxnSignature), ValidationError, - "CommonFields: invalid TxnSignature" + "BaseTransaction: invalid TxnSignature" ) }) @@ -197,9 +197,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidSigners), + () => verifyBaseTransaction(invalidSigners), ValidationError, - "CommonFields: invalid Signers" + "BaseTransaction: invalid Signers" ) const invalidSigners2 = { @@ -213,9 +213,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidSigners2), + () => verifyBaseTransaction(invalidSigners2), ValidationError, - "CommonFields: invalid Signers" + "BaseTransaction: invalid Signers" ) }) @@ -232,9 +232,9 @@ describe('Transaction Verification', function () { } as any assert.throws( - () => verifyCommonFields(invalidMemo), + () => verifyBaseTransaction(invalidMemo), ValidationError, - "CommonFields: invalid Memos" + "BaseTransaction: invalid Memos" ) }) }) \ No newline at end of file diff --git a/test/models/offerCreate.ts b/test/models/offerCreate.ts new file mode 100644 index 00000000..d7dfdc36 --- /dev/null +++ b/test/models/offerCreate.ts @@ -0,0 +1,177 @@ +import { ValidationError } from 'ripple-api/common/errors' +import { verifyOfferCreate } from './../../src/models/transactions/offerCreate' +import { assert } from 'chai' + + +/** + * OfferCreate Transaction Verification Testing + * + * Providing runtime verification testing for each specific transaction type + */ +describe('OfferCreate Transaction Verification', function () { + it (`verifies valid OfferCreate`, () => { + const offer = { + Account: "r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W", + Fee: "10", + Flags: 0, + LastLedgerSequence: 65453019, + Sequence: 40949322, + SigningPubKey: "03C48299E57F5AE7C2BE1391B581D313F1967EA2301628C07AC412092FDC15BA22", + Expiration: 10, + OfferSequence: 12, + TakerGets: { + currency: "DSH", + issuer: "rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX", + value: "43.11584856965009" + }, + TakerPays: "12928290425", + TransactionType: "OfferCreate", + TxnSignature: "3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91", + } as any + + assert.doesNotThrow(() => verifyOfferCreate(offer)) + + const offer2 = { + Account: "r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W", + Fee: "10", + Flags: 0, + LastLedgerSequence: 65453019, + Sequence: 40949322, + SigningPubKey: "03C48299E57F5AE7C2BE1391B581D313F1967EA2301628C07AC412092FDC15BA22", + TakerGets: "12928290425", + TakerPays: { + currency: "DSH", + issuer: "rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX", + value: "43.11584856965009" + }, + TransactionType: "OfferCreate", + TxnSignature: "3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91", + } as any + + assert.doesNotThrow(() => verifyOfferCreate(offer2)) + + + const offer3 = { + Account: "r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W", + Fee: "10", + Flags: 0, + LastLedgerSequence: 65453019, + Sequence: 40949322, + SigningPubKey: "03C48299E57F5AE7C2BE1391B581D313F1967EA2301628C07AC412092FDC15BA22", + TakerGets: { + currency: "DSH", + issuer: "rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX", + value: "43.11584856965009" + }, + TakerPays: { + currency: "DSH", + issuer: "rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX", + value: "43.11584856965009" + }, + TransactionType: "OfferCreate", + TxnSignature: "3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91", + } as any + + assert.doesNotThrow(() => verifyOfferCreate(offer3)) + }) + + it (`throws w/ invalid Expiration`, () => { + const offer = { + Account: "r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W", + Fee: "10", + Flags: 0, + LastLedgerSequence: 65453019, + Sequence: 40949322, + SigningPubKey: "03C48299E57F5AE7C2BE1391B581D313F1967EA2301628C07AC412092FDC15BA22", + Expiration: "11", + TakerGets: "12928290425", + TakerPays: { + currency: "DSH", + issuer: "rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX", + value: "43.11584856965009" + }, + TransactionType: "OfferCreate", + TxnSignature: "3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91", + } as any + + assert.throws( + () => verifyOfferCreate(offer), + ValidationError, + "OfferCreate: invalid Expiration" + ) + }) + + it (`throws w/ invalid OfferSequence`, () => { + const offer = { + Account: "r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W", + Fee: "10", + Flags: 0, + LastLedgerSequence: 65453019, + Sequence: 40949322, + SigningPubKey: "03C48299E57F5AE7C2BE1391B581D313F1967EA2301628C07AC412092FDC15BA22", + OfferSequence: "11", + TakerGets: "12928290425", + TakerPays: { + currency: "DSH", + issuer: "rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX", + value: "43.11584856965009" + }, + TransactionType: "OfferCreate", + TxnSignature: "3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91", + } as any + + assert.throws( + () => verifyOfferCreate(offer), + ValidationError, + "OfferCreate: invalid OfferSequence" + ) + }) + + it (`throws w/ invalid TakerPays`, () => { + const offer = { + Account: "r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W", + Fee: "10", + Flags: 0, + LastLedgerSequence: 65453019, + Sequence: 40949322, + SigningPubKey: "03C48299E57F5AE7C2BE1391B581D313F1967EA2301628C07AC412092FDC15BA22", + OfferSequence: "11", + TakerGets: "12928290425", + TakerPays: 10, + TransactionType: "OfferCreate", + TxnSignature: "3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91", + } as any + + assert.throws( + () => verifyOfferCreate(offer), + ValidationError, + "OfferCreate: invalid TakerPays" + ) + }) + + it (`throws w/ invalid TakerGets`, () => { + const offer = { + Account: "r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W", + Fee: "10", + Flags: 0, + LastLedgerSequence: 65453019, + Sequence: 40949322, + SigningPubKey: "03C48299E57F5AE7C2BE1391B581D313F1967EA2301628C07AC412092FDC15BA22", + OfferSequence: "11", + TakerGets: 11, + TakerPays: { + currency: "DSH", + issuer: "rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX", + value: "43.11584856965009" + }, + TransactionType: "OfferCreate", + TxnSignature: "3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91", + } as any + + assert.throws( + () => verifyOfferCreate(offer), + ValidationError, + "OfferCreate: invalid TakerGets" + ) + }) +}) \ No newline at end of file