Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🧪 mock get transaction receipt #231

Merged
merged 3 commits into from
May 19, 2023
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 90 additions & 111 deletions src/providers/test/json-rpc-provider/get-transaction-receipt.test.ts
Original file line number Diff line number Diff line change
@@ -1,123 +1,102 @@
import { ethers } from 'ethers';
import omit from 'just-omit';
import Web3 from 'web3';
import type web3core from 'web3-core';
import { JsonRpcProvider } from '../../../index';
import type { TransactionReceipt } from '../../../types/Transaction.types';
import * as unfetch from 'isomorphic-unfetch';
import { JsonRpcProvider, tinyBig } from '../../../index';
import { rpcUrls } from '../rpc-urls';
import { buildFetchInit, buildRPCPostBody } from '../../../classes/utils/fetchers';
import { mockOf } from '../mock-of';
import { hexToDecimal } from '../../../classes/utils/hex-to-decimal';

jest.mock('isomorphic-unfetch');
const rpcUrl = rpcUrls.mainnet;

describe('provider.getTransactionReceipt', () => {
function testTransactionReceiptEquality(
transactionReceipt1:
| ethers.providers.TransactionReceipt
| web3core.Transaction,
transactionReceipt2: TransactionReceipt,
) {
let typeCheckKeys: Array<string> = [];
let omittable1: Array<string> = [];
let omittable2: Array<string> = [];
if (
(transactionReceipt1 as ethers.providers.TransactionReceipt).confirmations
) {
// only ethers response has confirmations
// requires manually comparing values via bigNum conversion
typeCheckKeys = ['gasUsed', 'cumulativeGasUsed', 'effectiveGasPrice'];
omittable1 = typeCheckKeys;
omittable2 = typeCheckKeys;

typeCheckKeys.forEach((key) => {
expect((transactionReceipt1 as any)[key].toString()).toBe(
(transactionReceipt2 as any)[key].toString(),
);
});

expect(
Math.abs(
(transactionReceipt1 as ethers.providers.TransactionReceipt)
.confirmations - transactionReceipt2.confirmations,
),
).toBeLessThan(3);
} else {
typeCheckKeys = [
'cumulativeGasUsed',
'effectiveGasPrice',
'from',
'gasUsed',
'status',
'to',
'type',
];
omittable1 = typeCheckKeys;
omittable2 = ['byzantium', 'confirmations', ...typeCheckKeys];

typeCheckKeys.forEach((key) => {
switch (key) {
case 'cumulativeGasUsed':
case 'effectiveGasPrice':
case 'gasUsed':
expect((transactionReceipt1 as any)[key].toString()).toBe(
(transactionReceipt2 as any)[key].toString(),
);
break;
case 'from':
case 'to':
expect((transactionReceipt1 as any)[key]).toBe(
(transactionReceipt2 as any)[key].toLowerCase(),
);
break;
}
});
}

const omittedTransactionReceipt1 = omit(
transactionReceipt1,
omittable1 as any,
) as TransactionReceipt | web3core.TransactionReceipt;
const omittedTransactionReceipt2 = omit(
transactionReceipt2,
omittable2 as any,
) as TransactionReceipt | web3core.TransactionReceipt;

omittedTransactionReceipt1.logs = omittedTransactionReceipt1.logs.map(
(log) => omit(log, ['id', 'removed'] as any) as any,
);
const mockBlocksBetween = 10;
const mockReceiptResponse = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WOW, this will be so helpful to get merged. Big objects 🙌

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍

blockHash:
'0x876810a013dbcd140f6fd6048c1dc33abbb901f1f96b394c2fa63aef3cb40b5d',
blockNumber: 14578286,
contractAddress: '0xdfD9dE5f6FA60BD70636c0900752E93a6144AEd4',
cumulativeGasUsed: '0x7f110',
effectiveGasPrice: '0x7f110',
from: '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
gasUsed: '0x7f110',
logs: [
{
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
blockHash: "0x876810a013dbcd140f6fd6048c1dc33abbb901f1f96b394c2fa63aef3cb40b5d",
blockNumber: 14578286,
data: "0x0000000000000000000000000000000000000000000000000000000000000000",
logIndex: "0x1d",
removed: false,
topics: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
transactionHash: "0x9014ae6ef92464338355a79e5150e542ff9a83e2323318b21f40d6a3e65b4789",
transactionIndex: "0x1d"
},
],
logsBloom: '0x0000000000000',
status: "0x1",
to: '0xdfD9dE5f6FA60BD70636c0900752E93a6144AEd4',
transactionHash:
"0x9014ae6ef92464338355a79e5150e542ff9a83e2323318b21f40d6a3e65b4789",
transactionIndex: 29,
type: 2,
confirmations: mockBlocksBetween,
};
const mockRpcReceiptResponse = JSON.stringify({
jsonrpc: '2.0',
id: 1,
result: mockReceiptResponse,
});
const mockRpcBlockResponse = JSON.stringify({
jsonrpc: '2.0',
id: 1,
result: {
number: mockReceiptResponse.blockNumber + mockBlocksBetween - 1,
},
});
const mockReceipt = {
...mockReceiptResponse,
cumulativeGasUsed: tinyBig(mockReceiptResponse.cumulativeGasUsed),
effectiveGasPrice: tinyBig(mockReceiptResponse.effectiveGasPrice),
gasUsed: tinyBig(mockReceiptResponse.gasUsed),
status: Number(hexToDecimal(mockReceiptResponse.status)),
logs: mockReceiptResponse.logs.map(({ removed, ...log }) => ({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sleek and interesting, but I have no idea what this does.

Probably too tricky to throw into a JSON object like this. Perhaps we can name this a helpful const like "mockLogsWithoutWhatever" above the mockReceipt initialization then use it inside here?

My main misunderstanding is why we need to remove anything from the logs using this restructuring and a well-named variable above might be helpful

Copy link
Collaborator Author

@jtfirek jtfirek May 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to clean the log without using the cleanLog function, but I see how this confusing. I will just use the helper and add cleanLog to the list of helpers to test #228.

...log,
logIndex: Number(hexToDecimal(log.logIndex)),
transactionIndex: Number(hexToDecimal(log.transactionIndex)),
})),
byzantium: true,
};

expect(omittedTransactionReceipt1).toMatchObject(
omittedTransactionReceipt2,
);
}
it('should match web3.js', async () => {
describe('provider.getTransactionReceipt', () => {
it('should match mocked transaction receipt', async () => {
const transactionHash =
'0x9014ae6ef92464338355a79e5150e542ff9a83e2323318b21f40d6a3e65b4789';
const web3Provider = new Web3(rpcUrl);
const essentialEthProvider = new JsonRpcProvider(rpcUrl);
const [web3TransactionReceipt, essentialEthTransactionReceipt] =
await Promise.all([
web3Provider.eth.getTransactionReceipt(transactionHash),
essentialEthProvider.getTransactionReceipt(transactionHash),
]);
const provider = new JsonRpcProvider(rpcUrl);

testTransactionReceiptEquality(
web3TransactionReceipt as any,
essentialEthTransactionReceipt,
);
});
it('should match ethers', async () => {
const transactionHash =
'0x9014ae6ef92464338355a79e5150e542ff9a83e2323318b21f40d6a3e65b4789';
const ethersProvider = new ethers.providers.StaticJsonRpcProvider(rpcUrl);
const essentialEthProvider = new JsonRpcProvider(rpcUrl);
const [ethersTransactionReceipt, essentialEthTransactionReceipt] =
await Promise.all([
ethersProvider.getTransactionReceipt(transactionHash),
essentialEthProvider.getTransactionReceipt(transactionHash),
]);
mockOf(unfetch.default).mockResolvedValueOnce({
text: () => Promise.resolve(mockRpcReceiptResponse),
} as Response);
mockOf(unfetch.default).mockResolvedValueOnce({
text: () => Promise.resolve(mockRpcBlockResponse),
} as Response);

testTransactionReceiptEquality(
ethersTransactionReceipt,
essentialEthTransactionReceipt,
const spy = jest.spyOn(unfetch, 'default');
const transactionReceipt = await provider.getTransactionReceipt(transactionHash);
expect(spy).toHaveBeenCalledWith(
rpcUrl,
buildFetchInit(
buildRPCPostBody('eth_getTransactionReceipt', [transactionHash]),
),
);
expect(spy).toHaveBeenCalledWith(
rpcUrl,
buildFetchInit(
buildRPCPostBody('eth_getBlockByNumber', ['latest', false]),
),
);

expect(JSON.stringify(transactionReceipt)).toBe(JSON.stringify(mockReceipt));
});
});