import { MetaTransaction, MetaTransactionFields, MetaTransactionV2, MetaTransactionV2Fields, OtcOrder, Signature, } from '@0x/protocol-utils'; import { BigNumber } from '@0x/utils'; import { expect } from 'chai'; import { DataSource } from 'typeorm'; import * as uuid from 'uuid'; import { EXECUTE_META_TRANSACTION_EIP_712_TYPES, ONE_MINUTE_MS, ZERO } from '../src/core/constants'; import { feeToStoredFee, storedFeeToFee } from '../src/core/fee_utils'; import { MetaTransactionSubmissionEntityConstructorOpts } from '../src/entities/MetaTransactionSubmissionEntity'; import { RfqmV2TransactionSubmissionEntityConstructorOpts } from '../src/entities/RfqmV2TransactionSubmissionEntity'; import { RfqmJobStatus, RfqmTransactionSubmissionStatus, RfqmTransactionSubmissionType } from '../src/entities/types'; import { ExecuteMetaTransactionApproval, Fee, GaslessApprovalTypes } from '../src/core/types'; import { otcOrderToStoredOtcOrder, RfqmDbUtils, storedOtcOrderToOtcOrder } from '../src/utils/rfqm_db_utils'; import { MOCK_FEE, MOCK_META_TRANSACTION, MOCK_META_TRANSACTION_V2 } from './constants'; import { setupDependenciesAsync, TeardownDependenciesFunctionHandle } from './test_utils/deployment'; import { initDbDataSourceAsync } from './test_utils/initDbDataSourceAsync'; import { MetaTransactionV2SubmissionEntityConstructorOpts } from '../src/entities/MetaTransactionV2SubmissionEntity'; let dbUtils: RfqmDbUtils; const createdAt = new Date(); // it's expired if it's over 9000 const expiry = new BigNumber(9000); const chainId = 1; const makerUri = 'https://marketmaking.over9000.io'; const fee: Fee = { token: '0xatoken', amount: new BigNumber(5), type: 'fixed', }; const otcOrderNonce = new BigNumber(1637085289); const otcOrder = new OtcOrder({ txOrigin: '0x0000000000000000000000000000000000000000', taker: '0x1111111111111111111111111111111111111111', maker: '0x2222222222222222222222222222222222222222', makerToken: '0x3333333333333333333333333333333333333333', takerToken: '0x4444444444444444444444444444444444444444', expiryAndNonce: OtcOrder.encodeExpiryAndNonce(expiry, ZERO, otcOrderNonce), chainId, verifyingContract: '0x0000000000000000000000000000000000000000', }); const otcOrderHash = otcOrder.getHash(); const takerSignature: Signature = { v: 27, r: '0xd00d00', s: '0xcaca', signatureType: 1, }; const approval: ExecuteMetaTransactionApproval = { kind: GaslessApprovalTypes.ExecuteMetaTransaction, eip712: { types: { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'verifyingContract', type: 'address' }, { name: 'salt', type: 'bytes32' }, ], ...EXECUTE_META_TRANSACTION_EIP_712_TYPES, }, primaryType: 'MetaTransaction', domain: { name: 'Balancer (PoS)', version: '1', verifyingContract: '0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3', salt: '0x0000000000000000000000000000000000000000000000000000000000000089', }, message: { nonce: 1, from: '0x1111111111111111111111111111111111111111', functionSignature: '0x095ea7b3000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', }, }, }; // tx properties const transactionHash = '0x5678'; const from = '0xanRfqmWorker'; const to = '0xexchangeProxyAddress'; const gasPrice = new BigNumber('100'); const gasUsed = null; const blockMined = null; const nonce = 0; // meta-transaction const inputToken = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; const outputToken = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; const inputTokenAmount = new BigNumber(100); const minOutputTokenAmount = new BigNumber(99); function creatMockMetaTransaction(opts: Partial = {}): MetaTransaction { return new MetaTransaction({ ...MOCK_META_TRANSACTION, ...opts, }); } function createMockMetaTransactionV2(opts: Partial = {}): MetaTransactionV2 { return new MetaTransactionV2({ ...MOCK_META_TRANSACTION_V2, ...opts, }); } jest.setTimeout(ONE_MINUTE_MS * 3); let teardownDependencies: TeardownDependenciesFunctionHandle; let dataSource: DataSource; describe('RFQM Database', () => { beforeAll(async () => { teardownDependencies = await setupDependenciesAsync(['postgres']); dataSource = await initDbDataSourceAsync(); dbUtils = new RfqmDbUtils(dataSource); }); afterAll(async () => { if (!teardownDependencies()) { throw new Error('Failed to tear down dependencies'); } }); afterEach(async () => { await dataSource.query('TRUNCATE TABLE rfqm_quotes CASCADE;'); await dataSource.query('TRUNCATE TABLE rfqm_jobs CASCADE;'); await dataSource.query('TRUNCATE TABLE rfqm_transaction_submissions CASCADE;'); await dataSource.query('TRUNCATE TABLE rfqm_v2_quotes CASCADE;'); await dataSource.query('TRUNCATE TABLE rfqm_v2_jobs CASCADE;'); await dataSource.query('TRUNCATE TABLE rfqm_v2_transaction_submissions CASCADE;'); await dataSource.query('TRUNCATE TABLE meta_transaction_submissions CASCADE;'); await dataSource.query('TRUNCATE TABLE meta_transaction_jobs CASCADE;'); await dataSource.query('TRUNCATE TABLE meta_transaction_v2_submissions CASCADE;'); await dataSource.query('TRUNCATE TABLE meta_transaction_v2_jobs CASCADE;'); }); describe('RFQm v2 tables', () => { it('should be able to write to and read from the rfqm_v2_quote table', async () => { await dbUtils.writeV2QuoteAsync({ chainId, makerUri, isUnwrap: false, order: otcOrderToStoredOtcOrder(otcOrder), orderHash: otcOrderHash, fee: feeToStoredFee(fee), takerSpecifiedSide: 'takerToken', workflow: 'rfqm', }); const storedQuote = await dbUtils.findV2QuoteByOrderHashAsync(otcOrderHash); // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-non-null-asserted-optional-chain expect(otcOrder).to.deep.eq(storedOtcOrderToOtcOrder(storedQuote?.order!)); // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-non-null-asserted-optional-chain expect(fee).to.deep.eq(storedFeeToFee(storedQuote?.fee!)); expect(storedQuote?.takerSpecifiedSide).to.equal('takerToken'); }); it('should be able to write, update, and read the rfqm_v2_job table', async () => { // Write await dbUtils.writeV2JobAsync({ approval, chainId, status: RfqmJobStatus.PendingProcessing, expiry: otcOrder.expiry, makerUri, isUnwrap: false, order: otcOrderToStoredOtcOrder(otcOrder), takerSignature, orderHash: otcOrderHash, fee: feeToStoredFee(fee), takerSpecifiedSide: 'makerToken', workflow: 'rfqm', takerAddress: otcOrder.taker, takerToken: otcOrder.takerToken, }); // First Read const storedJob = await dbUtils.findV2JobByOrderHashAsync(otcOrderHash); // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-non-null-asserted-optional-chain expect(storedOtcOrderToOtcOrder(storedJob?.order!)).to.deep.eq(otcOrder); // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-non-null-asserted-optional-chain expect(storedFeeToFee(storedJob?.fee!)).to.deep.eq(fee); expect(storedJob?.status).to.equal(RfqmJobStatus.PendingProcessing); expect(storedJob?.takerSignature).to.deep.eq(takerSignature); expect(storedJob?.approval).to.deep.eq(approval); // Update await dbUtils.updateV2JobAsync(otcOrderHash, true, { status: RfqmJobStatus.SucceededConfirmed }); // Second Read const updatedJob = await dbUtils.findV2JobByOrderHashAsync(otcOrderHash); // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-non-null-asserted-optional-chain expect(storedOtcOrderToOtcOrder(updatedJob?.order!)).to.deep.eq(otcOrder); // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-non-null-asserted-optional-chain expect(storedFeeToFee(updatedJob?.fee!)).to.deep.eq(fee); expect(updatedJob?.status).to.equal(RfqmJobStatus.SucceededConfirmed); expect(updatedJob?.takerSpecifiedSide).to.equal('makerToken'); }); it('should be able to find by status across the rfqm_v2_job table', async () => { // Write job with failed status await dbUtils.writeV2JobAsync({ chainId, status: RfqmJobStatus.FailedEthCallFailed, expiry: otcOrder.expiry, makerUri, isUnwrap: false, order: otcOrderToStoredOtcOrder(otcOrder), orderHash: otcOrderHash, fee: feeToStoredFee(fee), takerSpecifiedSide: 'makerToken', workflow: 'rfqm', takerAddress: otcOrder.taker, takerToken: otcOrder.takerToken, }); // Get jobs with that status const storedJobs = await dbUtils.findV2JobsWithStatusesAsync([RfqmJobStatus.FailedEthCallFailed]); expect(storedJobs.length).to.equal(1); // Confirm correctness const storedJob = storedJobs[0]; // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-non-null-asserted-optional-chain expect(storedOtcOrderToOtcOrder(storedJob?.order!)).to.deep.eq(otcOrder); // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-non-null-asserted-optional-chain expect(storedFeeToFee(storedJob?.fee!)).to.deep.eq(fee); expect(storedJob?.status).to.equal(RfqmJobStatus.FailedEthCallFailed); }); it('should be able to write to and read from the last_look_rejection_cooldowns table', async () => { const makerId = 'makerId1'; const nowMs = Date.now(); const startTime = new Date(nowMs); const endTime = new Date(nowMs + ONE_MINUTE_MS); await dbUtils.writeV2LastLookRejectionCooldownAsync( makerId, chainId, otcOrder.makerToken, otcOrder.takerToken, startTime, endTime, otcOrderHash, ); const storedCooldown = await dbUtils.findV2LastLookRejectionCooldownAsync( makerId, chainId, otcOrder.makerToken, otcOrder.takerToken, startTime, ); expect(storedCooldown?.endTime).to.deep.eq(endTime); expect(storedCooldown?.orderHash).to.deep.eq(otcOrderHash); }); it('should be able to write, update, and read the rfqm_v2_transaction_submission table', async () => { // Write const rfqmTransactionSubmissionEntityOpts: RfqmV2TransactionSubmissionEntityConstructorOpts = { transactionHash, orderHash: otcOrderHash, createdAt, from, to, gasPrice, gasUsed, blockMined, nonce, status: RfqmTransactionSubmissionStatus.Submitted, type: RfqmTransactionSubmissionType.Trade, }; await dbUtils.writeV2TransactionSubmissionAsync(rfqmTransactionSubmissionEntityOpts); // First Read const transactionSubmissions = await dbUtils.findV2TransactionSubmissionsByOrderHashAsync(otcOrderHash); expect(transactionSubmissions.length).to.equal(1); const transactionSubmission = transactionSubmissions[0]; expect(transactionSubmission.transactionHash).to.equal(transactionHash); expect(transactionSubmission.status).to.equal(RfqmTransactionSubmissionStatus.Submitted); // Update await dbUtils.updateV2TransactionSubmissionsAsync([ { ...transactionSubmission, status: RfqmTransactionSubmissionStatus.SucceededConfirmed, }, ]); // Second Read const updatedTransactionSubmissions = await dbUtils.findV2TransactionSubmissionsByOrderHashAsync( otcOrderHash, ); expect(updatedTransactionSubmissions.length).to.equal(1); const updatedTransactionSubmission = updatedTransactionSubmissions[0]; expect(updatedTransactionSubmission.transactionHash).to.equal(transactionHash); expect(updatedTransactionSubmission.status).to.equal(RfqmTransactionSubmissionStatus.SucceededConfirmed); }); it('should not run into duplicate key issues if attempting to write to the same hash', async () => { // Write const rfqmTransactionSubmissionEntityOpts: RfqmV2TransactionSubmissionEntityConstructorOpts = { transactionHash, orderHash: otcOrderHash, createdAt, from, to, gasPrice, gasUsed, blockMined, nonce, status: RfqmTransactionSubmissionStatus.Submitted, type: RfqmTransactionSubmissionType.Trade, }; await dbUtils.writeV2TransactionSubmissionAsync(rfqmTransactionSubmissionEntityOpts); // Write again - should not error await dbUtils.writeV2TransactionSubmissionAsync(rfqmTransactionSubmissionEntityOpts); // Read const transactionSubmissions = await dbUtils.findV2TransactionSubmissionsByOrderHashAsync(otcOrderHash); expect(transactionSubmissions.length).to.equal(1); const transactionSubmission = transactionSubmissions[0]; expect(transactionSubmission.transactionHash).to.equal(transactionHash); expect(transactionSubmission.status).to.equal(RfqmTransactionSubmissionStatus.Submitted); }); }); describe('meta transaction tables', () => { it('should be able to write to, update, and read from the `meta_transaction_jobs` table', async () => { const metaTransaction = creatMockMetaTransaction(); const metaTransactionHash = metaTransaction.getHash(); // Write const savedJob = await dbUtils.writeMetaTransactionJobAsync({ approval, chainId: 1, expiry: new BigNumber(2), fee: MOCK_FEE, inputToken, inputTokenAmount, integratorId: 'integrator', metaTransaction, metaTransactionHash, minOutputTokenAmount, outputToken, takerAddress: '0xaddress', takerSignature, }); expect(savedJob.id).to.not.equal(null); // Read const job = await dbUtils.findMetaTransactionJobByMetaTransactionHashAsync(metaTransactionHash); if (!job) { throw new Error('job should exist'); } expect(job.metaTransaction).to.eql(metaTransaction); expect(job.fee).to.eql(MOCK_FEE); expect(job.status).to.eql(RfqmJobStatus.PendingEnqueued); expect(job.approval).to.eql(approval); expect(job.workerAddress).to.eql(null); // Update job.chainId = 1; await dbUtils.updateRfqmJobAsync(job); // Read const updatedJob = await dbUtils.findMetaTransactionJobByIdAsync(job.id); if (!updatedJob) { throw new Error('job should exist'); } expect(updatedJob.metaTransaction).to.eql(metaTransaction); expect(updatedJob.fee).to.eql(MOCK_FEE); expect(updatedJob.status).to.eql(RfqmJobStatus.PendingEnqueued); expect(updatedJob.approval).to.eql(approval); expect(updatedJob.workerAddress).to.eql(null); expect(updatedJob.chainId).to.eql(1); }); it('should be able to find by status across the `meta_transaction_jobs` table', async () => { const metaTransaction = creatMockMetaTransaction(); const metaTransactionHash = metaTransaction.getHash(); // Write const savedJob = await dbUtils.writeMetaTransactionJobAsync({ approval, chainId: 1, expiry: new BigNumber(2), fee: MOCK_FEE, inputToken, inputTokenAmount, integratorId: 'integrator', metaTransaction, metaTransactionHash, minOutputTokenAmount, outputToken, takerAddress: '0xaddress', takerSignature, status: RfqmJobStatus.FailedExpired, }); expect(savedJob.id).to.not.equal(null); // Read const jobs = await dbUtils.findMetaTransactionJobsWithStatusesAsync([RfqmJobStatus.FailedExpired]); expect(jobs.length).to.equal(1); expect(jobs[0].metaTransaction).to.eql(metaTransaction); expect(jobs[0].fee).to.eql(MOCK_FEE); expect(jobs[0].status).to.eql(RfqmJobStatus.FailedExpired); expect(jobs[0].approval).to.eql(approval); expect(jobs[0].workerAddress).to.eql(null); }); it('should be able to find unsolved meta transaction jobs in the `meta_transaction_jobs` table', async () => { const mockMetaTransaction1 = creatMockMetaTransaction(); const savedJob = await dbUtils.writeMetaTransactionJobAsync({ approval, chainId: 1, expiry: new BigNumber(2), fee: MOCK_FEE, id: '1', integratorId: 'integrator', inputToken, inputTokenAmount, metaTransaction: mockMetaTransaction1, metaTransactionHash: mockMetaTransaction1.getHash(), minOutputTokenAmount, outputToken, takerAddress: '0xaddress', takerSignature, status: RfqmJobStatus.PendingEnqueued, }); expect(savedJob.id).to.not.equal(null); const mockMetaTransaction2 = creatMockMetaTransaction({ signer: '0xabcdef2' }); await dbUtils.writeMetaTransactionJobAsync({ approval, chainId: 2, expiry: new BigNumber(2), fee: MOCK_FEE, inputToken, inputTokenAmount, integratorId: 'integrator', metaTransaction: mockMetaTransaction2, metaTransactionHash: mockMetaTransaction2.getHash(), minOutputTokenAmount, outputToken, takerAddress: '0xaddress', takerSignature, status: RfqmJobStatus.PendingProcessing, workerAddress: '0xworkerAddress', }); const mockMetaTransaction3 = creatMockMetaTransaction({ signer: '0xabcdef3' }); await dbUtils.writeMetaTransactionJobAsync({ approval, chainId: 3, expiry: new BigNumber(2), fee: MOCK_FEE, inputToken, inputTokenAmount, integratorId: 'integrator', metaTransaction: mockMetaTransaction3, metaTransactionHash: mockMetaTransaction3.getHash(), minOutputTokenAmount, outputToken, takerAddress: '0xaddress', takerSignature, status: RfqmJobStatus.FailedExpired, }); const jobs = await dbUtils.findUnresolvedMetaTransactionJobsAsync('0xworkerAddress', 2); expect(jobs.length).to.equal(1); expect(jobs[0].status).to.eql(RfqmJobStatus.PendingProcessing); }); it('should be able to write, update, and read the `meta_transaction_submissions` table', async () => { const metaTransactionJobId = uuid.v4(); // Write const metaTransactionSubmissionEntityOpts: MetaTransactionSubmissionEntityConstructorOpts = { from, metaTransactionJobId, nonce, to, transactionHash, type: RfqmTransactionSubmissionType.Trade, status: RfqmTransactionSubmissionStatus.SucceededUnconfirmed, }; const savedSubmission = await dbUtils.writeMetaTransactionSubmissionAsync( metaTransactionSubmissionEntityOpts, ); expect(savedSubmission.id).not.equal(null); // First Read let transactionSubmissions = await dbUtils.findMetaTransactionSubmissionsByTransactionHashAsync( transactionHash, RfqmTransactionSubmissionType.Trade, ); expect(transactionSubmissions.length).to.equal(1); let transactionSubmission = transactionSubmissions[0]; expect(transactionSubmission.transactionHash).to.equal(transactionHash); expect(transactionSubmission.status).to.equal(RfqmTransactionSubmissionStatus.SucceededUnconfirmed); // Update await dbUtils.updateRfqmTransactionSubmissionsAsync([ { ...transactionSubmission, status: RfqmTransactionSubmissionStatus.SucceededConfirmed, }, ]); // Second Read const updatedTransactionSubmissionOrNull = await dbUtils.findMetaTransactionSubmissionByIdAsync( transactionSubmission.id, ); if (!updatedTransactionSubmissionOrNull) { expect.fail('result should not be null'); } // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(updatedTransactionSubmissionOrNull!.transactionHash).to.equal(transactionHash); // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(updatedTransactionSubmissionOrNull!.status).to.equal( RfqmTransactionSubmissionStatus.SucceededConfirmed, ); // Third read transactionSubmissions = await dbUtils.findMetaTransactionSubmissionsByJobIdAsync(metaTransactionJobId); expect(transactionSubmissions.length).to.equal(1); transactionSubmission = transactionSubmissions[0]; expect(transactionSubmission.transactionHash).to.equal(transactionHash); expect(transactionSubmission.status).to.equal(RfqmTransactionSubmissionStatus.SucceededConfirmed); }); }); describe('meta transaction v2 tables', () => { it('should be able to write to, update, and read from the `meta_transaction_v2_jobs` table', async () => { const metaTransaction = createMockMetaTransactionV2(); const metaTransactionHash = metaTransaction.getHash(); // Write const savedJob = await dbUtils.writeMetaTransactionV2JobAsync({ approval, calledFunction: 'transformERC20', chainId: 1, expiry: new BigNumber(2), inputToken, inputTokenAmount, integratorId: 'integrator', metaTransaction, metaTransactionHash, minOutputTokenAmount, outputToken, takerAddress: '0xaddress', takerSignature, tokens: [inputToken, outputToken], }); expect(savedJob.id).to.not.equal(null); // Read const job = await dbUtils.findMetaTransactionV2JobByMetaTransactionHashAsync(metaTransactionHash); if (!job) { throw new Error('job should exist'); } expect(job.metaTransaction).to.eql(metaTransaction); expect(job.status).to.eql(RfqmJobStatus.PendingEnqueued); expect(job.approval).to.eql(approval); expect(job.workerAddress).to.eql(null); expect(job.tokens).to.eql([inputToken, outputToken]); // Update job.chainId = 1; await dbUtils.updateRfqmJobAsync(job); // Read const updatedJob = await dbUtils.findMetaTransactionV2JobByIdAsync(job.id); if (!updatedJob) { throw new Error('job should exist'); } expect(updatedJob.metaTransaction).to.eql(metaTransaction); expect(updatedJob.status).to.eql(RfqmJobStatus.PendingEnqueued); expect(updatedJob.approval).to.eql(approval); expect(updatedJob.workerAddress).to.eql(null); expect(updatedJob.chainId).to.eql(1); expect(job.tokens).to.eql([inputToken, outputToken]); }); it('should be able to find by status across the `meta_transaction_v2_jobs` table', async () => { const metaTransaction = createMockMetaTransactionV2(); const metaTransactionHash = metaTransaction.getHash(); // Write const savedJob = await dbUtils.writeMetaTransactionV2JobAsync({ approval, calledFunction: 'transformERC20', chainId: 1, expiry: new BigNumber(2), inputToken, inputTokenAmount, integratorId: 'integrator', metaTransaction, metaTransactionHash, minOutputTokenAmount, outputToken, status: RfqmJobStatus.FailedExpired, takerAddress: '0xaddress', takerSignature, tokens: [inputToken, outputToken], }); expect(savedJob.id).to.not.equal(null); // Read const jobs = await dbUtils.findMetaTransactionV2JobsWithStatusesAsync([RfqmJobStatus.FailedExpired]); expect(jobs.length).to.equal(1); expect(jobs[0].metaTransaction).to.eql(metaTransaction); expect(jobs[0].status).to.eql(RfqmJobStatus.FailedExpired); expect(jobs[0].approval).to.eql(approval); expect(jobs[0].workerAddress).to.eql(null); expect(jobs[0].tokens).to.eql([inputToken, outputToken]); }); it('should be able to find unsolved meta transaction jobs in the `meta_transaction_v2_jobs` table', async () => { const metaTransaction1 = createMockMetaTransactionV2(); const savedJob = await dbUtils.writeMetaTransactionV2JobAsync({ approval, calledFunction: 'transformERC20', chainId: 1, expiry: new BigNumber(2), inputToken, inputTokenAmount, integratorId: 'integrator', metaTransaction: metaTransaction1, metaTransactionHash: metaTransaction1.getHash(), minOutputTokenAmount, outputToken, status: RfqmJobStatus.PendingEnqueued, takerAddress: '0xaddress', takerSignature, tokens: [inputToken, outputToken], }); expect(savedJob.id).to.not.equal(null); const metaTransaction2 = createMockMetaTransactionV2({ signer: '0xabcdef2' }); await dbUtils.writeMetaTransactionV2JobAsync({ approval, calledFunction: 'transformERC20', chainId: 2, expiry: new BigNumber(2), inputToken, inputTokenAmount, integratorId: 'integrator', metaTransaction: metaTransaction2, metaTransactionHash: metaTransaction2.getHash(), minOutputTokenAmount, outputToken, status: RfqmJobStatus.PendingProcessing, workerAddress: '0xworkerAddress', takerAddress: '0xaddress', takerSignature, tokens: [inputToken, outputToken], }); const metaTransaction3 = createMockMetaTransactionV2({ signer: '0xabcdef3' }); await dbUtils.writeMetaTransactionV2JobAsync({ approval, calledFunction: 'transformERC20', chainId: 3, expiry: new BigNumber(2), inputToken, inputTokenAmount, integratorId: 'integrator', metaTransaction: metaTransaction3, metaTransactionHash: metaTransaction3.getHash(), minOutputTokenAmount, outputToken, status: RfqmJobStatus.FailedExpired, takerAddress: '0xaddress', takerSignature, tokens: [inputToken, outputToken], }); const jobs = await dbUtils.findUnresolvedMetaTransactionV2JobsAsync('0xworkerAddress', 2); expect(jobs.length).to.equal(1); expect(jobs[0].status).to.eql(RfqmJobStatus.PendingProcessing); }); it('should be able to write, update, and read the `meta_transaction_v2_submissions` table', async () => { const metaTransactionV2JobId = uuid.v4(); // Write const metaTransactionSubmissionEntityOpts: MetaTransactionV2SubmissionEntityConstructorOpts = { from, metaTransactionV2JobId, nonce, to, transactionHash, type: RfqmTransactionSubmissionType.Trade, status: RfqmTransactionSubmissionStatus.SucceededUnconfirmed, }; const savedSubmission = await dbUtils.writeMetaTransactionV2SubmissionAsync( metaTransactionSubmissionEntityOpts, ); expect(savedSubmission.id).not.equal(null); // First Read let transactionSubmissions = await dbUtils.findMetaTransactionV2SubmissionsByTransactionHashAsync( transactionHash, RfqmTransactionSubmissionType.Trade, ); expect(transactionSubmissions.length).to.equal(1); let transactionSubmission = transactionSubmissions[0]; expect(transactionSubmission.transactionHash).to.equal(transactionHash); expect(transactionSubmission.status).to.equal(RfqmTransactionSubmissionStatus.SucceededUnconfirmed); // Update await dbUtils.updateRfqmTransactionSubmissionsAsync([ { ...transactionSubmission, status: RfqmTransactionSubmissionStatus.SucceededConfirmed, }, ]); // Second Read const updatedTransactionSubmissionOrNull = await dbUtils.findMetaTransactionV2SubmissionByIdAsync( transactionSubmission.id, ); if (!updatedTransactionSubmissionOrNull) { expect.fail('result should not be null'); } // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(updatedTransactionSubmissionOrNull!.transactionHash).to.equal(transactionHash); // $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(updatedTransactionSubmissionOrNull!.status).to.equal( RfqmTransactionSubmissionStatus.SucceededConfirmed, ); // Third read transactionSubmissions = await dbUtils.findMetaTransactionV2SubmissionsByJobIdAsync(metaTransactionV2JobId); expect(transactionSubmissions.length).to.equal(1); transactionSubmission = transactionSubmissions[0]; expect(transactionSubmission.transactionHash).to.equal(transactionHash); expect(transactionSubmission.status).to.equal(RfqmTransactionSubmissionStatus.SucceededConfirmed); }); }); });