Files
protocol/apps-node/rfq-api/test/rfqm_db_test.ts

767 lines
33 KiB
TypeScript

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<MetaTransactionFields> = {}): MetaTransaction {
return new MetaTransaction({
...MOCK_META_TRANSACTION,
...opts,
});
}
function createMockMetaTransactionV2(opts: Partial<MetaTransactionV2Fields> = {}): 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);
});
});
});