Skip to content

Commit

Permalink
Add a test (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhinodavid authored Feb 16, 2023
1 parent 20401db commit 7760d03
Show file tree
Hide file tree
Showing 9 changed files with 2,218 additions and 135 deletions.
19 changes: 15 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,22 @@ jobs:
with:
node-version: ${{ matrix.node-version }}

- name: yarn
run: yarn install
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cached-node-modules
with:
path: node_modules
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('yarn.lock') }}

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Build
run: yarn build

#- name: Test
# run: npm test
- name: Test
run: yarn test

#- name: Package
# run: yarn package
Expand Down
6 changes: 6 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['/node_modules/', '/lib/'],
};
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,27 @@
"install": "node-pre-gyp install --fallback-to-build=false || yarn build:rs:release",
"watch": "tsc -w -p tsconfig.json",
"package": "node-pre-gyp package",
"test": "jest",
"upload-binary": "yarn build && yarn node-pre-gyp package && yarn node-pre-gyp-github publish",
"prepack": "shx rm -rf bin"
},
"devDependencies": {
"@jest/globals": "^29.4.3",
"@mathquis/node-pre-gyp-github": "1.0.1",
"@types/jest": "^29.4.0",
"@types/mocha": "^5.2.7",
"@types/node": "12.12.54",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0",
"ethereum-types": "^3.4.1",
"ethers": "^6.0.4",
"jest": "^29.4.3",
"mocha": "^6.2.0",
"shx": "^0.3.3",
"ts-jest": "^29.0.5",
"tslint": "5.11.0",
"typescript": "4.2.2"
"typescript": "4.9.5"
},
"repository": {
"type": "git",
Expand Down
31 changes: 31 additions & 0 deletions src/__tests__/BalanceChecker.abi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
},
{
"constant": true,
"inputs": [
{
"name": "user",
"type": "address"
},
{
"name": "token",
"type": "address"
}
],
"name": "tokenBalance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]

57 changes: 57 additions & 0 deletions src/__tests__/fast_abi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as ethers from 'ethers';

import BalanceCheckerAbi from './BalanceChecker.abi.json';
import { BigNumber } from 'bignumber.js';
import { FastABI } from '..';
import { MethodAbi } from 'ethereum-types';
import util from 'util';

/**
* Tests FastABI by comparing the results of encoding/decoding with ethers.js.
*
* Test ABI is from https://github.com/wbobeirne/eth-balance-checker/blob/master/abis/BalanceChecker.abi.json
*/
describe('fastAbi', () => {
it('encodes inputs', () => {
const balanceCheckerEthersInterface = new ethers.Interface(BalanceCheckerAbi as MethodAbi[]);
const encodedFunctionData = balanceCheckerEthersInterface.encodeFunctionData('tokenBalance', [
'0x4Ea754349AcE5303c82f0d1D491041e042f2ad22',
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
]);

const balanceCheckerFastAbi = new FastABI(BalanceCheckerAbi as MethodAbi[]);
const encodedFunctionDataFast = balanceCheckerFastAbi.encodeInput('tokenBalance', [
'0x4Ea754349AcE5303c82f0d1D491041e042f2ad22',
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
]);

expect(encodedFunctionDataFast).toEqual(encodedFunctionData);
});

it('decodes inputs', () => {
// Caldata from 'encodes inputs' test
const calldata =
'0x1049334f0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const balanceCheckerEthersInterface = new ethers.Interface(BalanceCheckerAbi as MethodAbi[]);
const decodedFunctionDataEthers = balanceCheckerEthersInterface.decodeFunctionData('tokenBalance', calldata);

const balanceCheckerFastAbi = new FastABI(BalanceCheckerAbi as MethodAbi[]);
const decodedFunctionDataFast: string[] = balanceCheckerFastAbi.decodeInput('tokenBalance', calldata);

expect(decodedFunctionDataFast).toEqual(decodedFunctionDataEthers.map((a) => a.toLowerCase()));
});

it('decodes outputs', () => {
const result = '0x00000000000000000000000000000000000000000000000000eb01cd45901fac';
const balanceCheckerEthersInterface = new ethers.Interface(BalanceCheckerAbi as MethodAbi[]);
const decodedOutputEthers: bigint[] = balanceCheckerEthersInterface.decodeFunctionResult(
'tokenBalance',
result,
);

const balanceCheckerFastAbi = new FastABI(BalanceCheckerAbi as MethodAbi[]);
const decodedOutputFast: BigNumber = balanceCheckerFastAbi.decodeOutput('tokenBalance', result);

expect(decodedOutputFast.toNumber()).toEqual(Number(decodedOutputEthers[0]));
});
});
109 changes: 0 additions & 109 deletions src/fast_abi.ts

This file was deleted.

113 changes: 112 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,112 @@
export { FastABI } from './fast_abi';
import { DataItem, MethodAbi } from 'ethereum-types';
import { BigNumber } from 'bignumber.js';

const { coderNew, coderEncodeInput, coderDecodeInput, coderDecodeOutput } = require('../bin');

interface Opts {
BigNumber: any;
}

export class FastABI {
private readonly _coder: any;
private readonly _abi: MethodAbi[];
private readonly _opts: Opts;

constructor(abi: MethodAbi[], opts?: Opts) {
this._opts = { BigNumber: BigNumber, ...opts } || { BigNumber: BigNumber };
this._abi = abi;
this._coder = coderNew(JSON.stringify(abi));
}

public encodeInput(fnName: string, values: any[]): string {
const found = this._abi.filter((a) => a.name === fnName)[0];
const args = this._serializeArgsOut(values, found.inputs);
try {
const encoded = coderEncodeInput.call(this._coder, fnName, args);
return `0x${encoded}`;
} catch (e) {
throw new Error(`${(e as Error).message}.\nvalues=${JSON.stringify(values)}\nargs=${JSON.stringify(args)}`);
}
}

/**
* NOTE: does not produce checksummed values
*/
public decodeInput(fnName: string, output: string): any {
const found = this._abi.filter((a) => a.name === fnName)[0];
const decoded = coderDecodeInput.call(this._coder, fnName, output);
return this._deserializeResultsIn(found.inputs, decoded);
}

public decodeOutput(fnName: string, output: string): any {
const found = this._abi.filter((a) => a.name === fnName)[0];
const decoded = coderDecodeOutput.call(this._coder, fnName, output);
return this._deserializeResultsIn(found.outputs, decoded);
}

private _deserializeResultIn(abi: DataItem, value: any): any {
if (abi.type.indexOf('[]') !== -1) {
// Pop off the last [] and serialize each sub value
let type = abi.type.split('[]');
type.pop();
let newType = type.join('[]'); // e.g address[][] -> address[]
return (value as any[]).map((v) => this._deserializeResultIn({ ...abi, type: newType }, v));
}
if (abi.type.indexOf('int') !== -1) {
return new this._opts.BigNumber(value);
}
if (abi.type === 'tuple' && abi.components) {
const output: any = {};
for (const [i, c] of Object.entries(abi.components)) {
output[c.name] = this._deserializeResultIn(c, value[parseInt(i)]);
}
return output;
}
return value;
}

private _deserializeResultsIn(abis: DataItem[], values: any[]): any {
const output: any = [];
for (const [i, v] of Object.entries(abis)) {
output.push(this._deserializeResultIn(v, values[parseInt(i)]));
}
if (abis.length === 1) {
return output[0];
}
return output;
}

// Convert the javascript arguments into the FastAbi preferred arguments
private _serializeArgsOut(abis: DataItem[], args: any[]): any[] {
return abis.map((abi, i) => this._serializeArgOut(args[i], abi));
}

private _serializeArgOut(abi: DataItem, arg: any): any {
if (arg === undefined) {
throw new Error(`Encountered undefined argument`);
}

if (abi.type.indexOf('[]') !== -1) {
// Pop off the last [] and serialize each sub value
let type = abi.type.split('[]');
type.pop();
let newType = type.join('[]'); // e.g address[][] -> address[]
return (arg as any[]).map((v) => this._serializeArgOut({ ...abi, type: newType }, v));
}

// Convert from { b: 2, a: 1 } into a component ordered value array, [1,2]
if (abi.type === 'tuple' && abi.components) {
const output: any[] = [];
for (const [_i, c] of Object.entries(abi.components)) {
output.push(this._serializeArgOut(c, arg[c.name]));
}
return output;
}

if (this._opts.BigNumber.isBigNumber(arg)) {
return arg.toString(10);
}

return arg.toString();
}
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"outDir": "lib",
"rootDir": "src",
"resolveJsonModule": true,
"esModuleInterop": true,
"module": "commonjs",
"target": "es6",
"lib": ["es2017", "esnext.asynciterable", "es2018.promise"],
Expand Down
Loading

0 comments on commit 7760d03

Please sign in to comment.