ABI encoding, fast

Related tags

fast-abi
Overview

fast-abi

Encodes and decodes abi data, fast.

Usage

const RUST_ENCODER = new FastABI(ABI as MethodAbi[]);
const callData = RUST_ENCODER.encodeInput('sampleSellsFromUniswapV2', [...values]);
// 0x.....

// Decode the output of a method call
const output = RUST_ENCODER.decodeOutput('sampleSellsFromUniswapV2', callData);
// {
//   router: '0x6b175474e89094c44da98b954eedeac495271d0f',
//   path: [ '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ],
//   takerTokenAmounts: [ 1, 2, 3 ]
// }

Perf

    Uniswap ABI
      13 input encode
        ✓ ZeroEx - optimized (2579ms)
        p25: 0.223583ms, p50: 0.22825ms, p99: 1.734083ms, p100: 5.321458ms
        ✓ ZeroEx - no optimize (893ms)
        p25: 0.077166ms, p50: 0.077792ms, p99: 0.098417ms, p100: 2.595542ms
        ✓ fast-abi (335ms)
        p25: 0.027958ms, p50: 0.028292ms, p99: 0.032583ms, p100: 2.886333ms

      13 input decode
        ✓ ZeroEx (1399ms)
        p25: 0.112125ms, p50: 0.113542ms, p99: 0.219583ms, p100: 63.167208ms
        ✓ fast-abi (392ms)
        p25: 0.02975ms, p50: 0.030708ms, p99: 0.046667ms, p100: 4.391958ms

      13 output decode
        ✓ ZeroEx (1138ms)
        p25: 0.100583ms, p50: 0.101458ms, p99: 0.126875ms, p100: 4.380667ms
        ✓ fast-abi (327ms)
        p25: 0.023709ms, p50: 0.024625ms, p99: 0.049875ms, p100: 8.044291ms

      130 input encode
        ✓ ZeroEx - optimized (15317ms)
        p25: 1.374375ms, p50: 1.398958ms, p99: 3.235333ms, p100: 9.725875ms
        ✓ ZeroEx - no optimize (6272ms)
        p25: 0.552209ms, p50: 0.560416ms, p99: 1.711958ms, p100: 3.264542ms
        ✓ fast-abi (1569ms)
        p25: 0.140291ms, p50: 0.142292ms, p99: 0.1875ms, p100: 10.919333ms

      130 input decode
        ✓ ZeroEx (10277ms)
        p25: 0.8775ms, p50: 0.891917ms, p99: 3.149708ms, p100: 211.279542ms
        ✓ fast-abi (2381ms)
        p25: 0.189791ms, p50: 0.192541ms, p99: 0.367417ms, p100: 10.084375ms

      130 output decode
        ✓ ZeroEx (9743ms)
        p25: 0.872875ms, p50: 0.886042ms, p99: 3.282917ms, p100: 6.403833ms
        ✓ fast-abi (2244ms)
        p25: 0.177959ms, p50: 0.183959ms, p99: 0.230166ms, p100: 6.963125ms
Issues
  • Simplify a bit and improve error handling

    Simplify a bit and improve error handling

    I wrote this mostly to learn more about neon. Have not tested it.

    opened by Recmo 1
  • always encode bignumbers as integers

    always encode bignumbers as integers

    opened by dorothy-zbornak 0
  • Perf

    Perf

    import { expect } from '@0x/contracts-test-utils';
    import { AbiEncoder, BigNumber, NULL_ADDRESS } from '@0x/utils';
    import { MethodAbi } from 'ethereum-types';
    import { utils } from 'ethers';
    // import { utils as ethers5Utils } from 'ethers5';
    import _ = require('lodash');
    import { performance, PerformanceObserver } from 'perf_hooks';
    
    const percentile = require('percentile');
    // tslint:disable-next-line: no-implicit-dependencies
    import { FastABI } from 'fast-abi';
    
    import { ZERO_AMOUNT } from '../../src/utils/market_operation_utils/constants';
    import { getSampleAmounts } from '../../src/utils/market_operation_utils/sampler';
    
    describe.only('Encoder perf', () => {
        const UNISWAP_V2_SELL_ABI: MethodAbi = {
            inputs: [
                {
                    internalType: 'address',
                    name: 'router',
                    type: 'address',
                },
                {
                    internalType: 'address[]',
                    name: 'path',
                    type: 'address[]',
                },
                {
                    internalType: 'uint256[]',
                    name: 'takerTokenAmounts',
                    type: 'uint256[]',
                },
            ],
            name: 'sampleSellsFromUniswapV2',
            outputs: [
                {
                    internalType: 'uint256[]',
                    name: 'makerTokenAmounts',
                    type: 'uint256[]',
                },
            ],
            stateMutability: 'view',
            type: 'function',
        };
    
        const KYBER_TUPLE_ABI: MethodAbi = {
            inputs: [
                {
                    name: 'opts',
                    type: 'tuple',
                    components: [
                        {
                            name: 'reserveOffset',
                            type: 'uint256',
                        },
                        {
                            name: 'hintHandler',
                            type: 'address',
                        },
                        {
                            name: 'networkProxy',
                            type: 'address',
                        },
                        {
                            name: 'weth',
                            type: 'address',
                        },
                        {
                            name: 'hint',
                            type: 'bytes',
                        },
                    ],
                },
                {
                    name: 'takerToken',
                    type: 'address',
                },
                {
                    name: 'makerToken',
                    type: 'address',
                },
                {
                    name: 'takerTokenAmounts',
                    type: 'uint256[]',
                },
            ],
            name: 'sampleSellsFromKyberNetwork',
            outputs: [
                {
                    name: 'reserveId',
                    type: 'bytes32',
                },
                {
                    name: 'hint',
                    type: 'bytes',
                },
                {
                    name: 'makerTokenAmounts',
                    type: 'uint256[]',
                },
            ],
            stateMutability: 'view',
            type: 'function',
        };
    
        const BATCH_CALL_ABI: MethodAbi = {
            inputs: [
                {
                    name: 'callDatas',
                    type: 'bytes[]',
                },
            ],
            name: 'batchCall',
            outputs: [
                {
                    name: 'callResults',
                    type: 'tuple[]',
                    components: [
                        {
                            name: 'data',
                            type: 'bytes',
                        },
                        {
                            name: 'success',
                            type: 'bool',
                        },
                    ],
                },
            ],
            stateMutability: 'view',
            type: 'function',
        };
    
        const RUST_ENCODER = new FastABI([UNISWAP_V2_SELL_ABI, BATCH_CALL_ABI, KYBER_TUPLE_ABI], { BigNumber });
    
        // tslint:disable: custom-no-magic-numbers
        const RUNS = 10000;
        // const RUNS = 1;
        const ADDRESS_1 = '0x6b175474e89094c44da98b954eedeac495271d0f';
        const ADDRESS_2 = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
        let summary: { p25: string; p50: string; p99: string; p100: string };
        const perf = (fn: () => void): any => {
            const wrapped = performance.timerify(fn);
            const resultsMs: number[] = [];
            const obs = new PerformanceObserver(list => resultsMs.push(list.getEntries()[0].duration));
            obs.observe({ entryTypes: ['function'] });
            _.times(RUNS, () => wrapped());
            obs.disconnect();
            summary = {
                p25: percentile(25, resultsMs),
                p50: percentile(50, resultsMs),
                p99: percentile(99, resultsMs),
                p100: percentile(100, resultsMs),
            };
        };
    
        before(() => {
            console.log('Runs:', RUNS);
        });
        beforeEach(() => {
            summary = { p25: '0', p50: '0', p99: '0', p100: '0' };
        });
        afterEach(() => {
            const { p25, p50, p99, p100 } = summary;
            console.log(`p25: ${p25}ms, p50: ${p50}ms, p99: ${p99}ms, p100: ${p100}ms\n`);
        });
    
        const TIMEOUT = 360000;
        const ZERO_EX_ENCODER = new AbiEncoder.Method(UNISWAP_V2_SELL_ABI);
        const ZERO_EX_UNOPTIMIZED: AbiEncoder.EncodingRules = { shouldOptimize: false, shouldAnnotate: false };
        const ZERO_EX_OPTIMIZED: AbiEncoder.EncodingRules = { shouldOptimize: true, shouldAnnotate: false };
    
        const ETHERS_INTERFACE = new utils.Interface([UNISWAP_V2_SELL_ABI]);
        const ETHERS_ENCODER = ETHERS_INTERFACE.functions[UNISWAP_V2_SELL_ABI.name];
    
        // const ETHERS_5_INTERFACE = new ethers5Utils.Interface([UNISWAP_V2_SELL_ABI]);
    
        describe.only('hello', () => {
            it('node', () => {
                const f = () => 'hello world';
                perf(f);
            });
            it.only('rust', () => {
                const f = () => FastABI.ping();
                perf(f);
            });
            it.only('rust - encode', () => {
                const params = [ADDRESS_1, [ADDRESS_2], [1, 2, 3]];
                const output = RUST_ENCODER.encodeInput('sampleSellsFromUniswapV2', params);
            });
        });
    
        describe.only('Tuple ABI', () => {
            const ZERO_EX_TUPLE = new AbiEncoder.Method(KYBER_TUPLE_ABI);
            const params = [
                {
                    reserveOffset: ZERO_AMOUNT,
                    hintHandler: NULL_ADDRESS,
                    networkProxy: NULL_ADDRESS,
                    weth: NULL_ADDRESS,
                    hint: '0x',
                },
                ADDRESS_1,
                ADDRESS_2,
                getSampleAmounts(new BigNumber(100e6), 13, 1.03),
            ];
            const encoded = ZERO_EX_TUPLE.encode(params, ZERO_EX_UNOPTIMIZED);
            describe('encode', () => {
                it('rust', () => {
                    const f = () => RUST_ENCODER.encodeInput('sampleSellsFromKyberNetwork', params);
                    expect(f()).to.eq(encoded);
                    perf(f);
                });
                it('zeroex - optimized', () => {
                    const f = () => ZERO_EX_TUPLE.encode(params, ZERO_EX_OPTIMIZED);
                    perf(f);
                });
                it('zeroex - unoptimized', () => {
                    const f = () => ZERO_EX_TUPLE.encode(params, ZERO_EX_UNOPTIMIZED);
                    perf(f);
                });
            });
    
            describe('decode', () => {
                it('rust', () => {
                    const f = () => RUST_ENCODER.decodeInput('sampleSellsFromKyberNetwork', encoded);
                });
            });
        });
    
        describe('Uniswap ABI', () => {
            [13, 130].forEach(numSamples => {
                describe(`${numSamples} input encode`, () => {
                    const params: [string, string[], BigNumber[]] = [
                        ADDRESS_1, // router
                        [ADDRESS_1, ADDRESS_2, ADDRESS_1], // path
                        getSampleAmounts(new BigNumber(100e6), numSamples, 1.03),
                    ];
    
                    it.only('ZeroEx - optimized', () => {
                        const f = () => ZERO_EX_ENCODER.encode(params, ZERO_EX_OPTIMIZED);
                        perf(f);
                    }).timeout(TIMEOUT);
    
                    it.only('ZeroEx - no optimize', () => {
                        const f = () => ZERO_EX_ENCODER.encode(params, ZERO_EX_UNOPTIMIZED);
                        perf(f);
                    }).timeout(TIMEOUT);
    
                    it('ZeroEx - BigInt - optimize', () => {
                        const amounts = params[2].map(n => BigInt(n.toString()));
                        const f = () => ZERO_EX_ENCODER.encode([params[0], params[1], amounts], ZERO_EX_OPTIMIZED);
                        perf(f);
                    }).timeout(TIMEOUT);
    
                    it('ZeroEx - BigInt - no optimize', () => {
                        const amounts = params[2].map(n => BigInt(n.toString()));
                        const f = () => ZERO_EX_ENCODER.encode([params[0], params[1], amounts], ZERO_EX_UNOPTIMIZED);
                        perf(f);
                    }).timeout(TIMEOUT);
    
                    it.only('rust', () => {
                        const f = () => RUST_ENCODER.encodeInput('sampleSellsFromUniswapV2', params);
                        perf(f);
                        expect(f()).to.eq(ZERO_EX_ENCODER.encode(params, ZERO_EX_UNOPTIMIZED));
                    }).timeout(TIMEOUT);
    
                    it.skip('Ethers', () => {
                        const f = () => ETHERS_ENCODER.encode(params);
                        perf(f);
                    });
                    // it.skip('Ethers 5', () => {
                    //     const f = () => ETHERS_5_INTERFACE.encodeFunctionData(UNISWAP_V2_SELL_ABI.name, params);
                    //     perf(f);
                    // }).timeout(TIMEOUT);
                });
    
                describe.only(`${numSamples} input decode`, () => {
                    const params: [string, string[], BigNumber[]] = [
                        ADDRESS_1, // router
                        [ADDRESS_1, ADDRESS_2, ADDRESS_1], // path
                        getSampleAmounts(new BigNumber(100e6), numSamples, 1.03),
                    ];
                    const data = ZERO_EX_ENCODER.encode(params, ZERO_EX_UNOPTIMIZED);
                    it('ZeroEx', () => {
                        const f = () => ZERO_EX_ENCODER.decode(data);
                        perf(f);
                    }).timeout(TIMEOUT);
    
                    it('rust', () => {
                        const f = () => RUST_ENCODER.decodeInput('sampleSellsFromUniswapV2', data);
                        perf(f);
                    }).timeout(TIMEOUT);
                });
    
                describe.only(`${numSamples} output decode`, () => {
                    const params = getSampleAmounts(new BigNumber(100e6), numSamples, 1.03);
                    const data = ZERO_EX_ENCODER.encodeReturnValues([params], ZERO_EX_UNOPTIMIZED);
                    it('ZeroEx', () => {
                        const f = () => ZERO_EX_ENCODER.decodeReturnValues(data);
                        perf(f);
                    }).timeout(TIMEOUT);
    
                    it('rust', () => {
                        const f = () => RUST_ENCODER.decodeOutput('sampleSellsFromUniswapV2', data);
                        perf(f);
                    }).timeout(TIMEOUT);
                });
            });
        });
    
        describe('BatchCall ABI', () => {
            [10, 50, 100].forEach(numSamples => {
                describe.only(`${numSamples} batchCall`, () => {
                    const callParams: [string, string[], BigNumber[]] = [
                        ADDRESS_1, // router
                        [ADDRESS_1, ADDRESS_2, ADDRESS_1], // path
                        getSampleAmounts(new BigNumber(100e6), 13, 1.03),
                    ];
    
                    const encodedBatchCall = ZERO_EX_ENCODER.encode(callParams, ZERO_EX_UNOPTIMIZED);
                    const params = _.times(numSamples, () => encodedBatchCall);
    
                    const BATCH_CALL_ENCODER = new AbiEncoder.Method(BATCH_CALL_ABI);
    
                    it('rust', () => {
                        const f = () => RUST_ENCODER.encodeInput('batchCall', [params]);
                        expect(f()).to.eq(BATCH_CALL_ENCODER.encode([params], ZERO_EX_UNOPTIMIZED));
                        perf(f);
                    }).timeout(TIMEOUT);
    
                    it('ZeroEx - optimized', () => {
                        const f = () => BATCH_CALL_ENCODER.encode([params], ZERO_EX_OPTIMIZED);
                        perf(f);
                    }).timeout(TIMEOUT);
    
                    it('ZeroEx - no optimize', () => {
                        const f = () => BATCH_CALL_ENCODER.encode([params], ZERO_EX_UNOPTIMIZED);
                        perf(f);
                    }).timeout(TIMEOUT);
                });
            });
        });
    });
    
    
    opened by dekz 0
Owner
0x
The Protocol for Trading Tokens
0x
Safe interop between Rust and C++

CXX — safe FFI between Rust and C++ This library provides a safe mechanism for calling C++ code from Rust and Rust code from C++, not subject to the m

David Tolnay 2.9k Jun 14, 2021
A project for generating C bindings from Rust code

cbindgen   Read the full user docs here! cbindgen creates C/C++11 headers for Rust libraries which expose a public C API. While you could do this by h

Ryan Hunt 1k Jun 8, 2021
A Rust crate for automatically generating C header files from Rust source file.

Please be aware that this crate is no longer actively maintained, please look into the much more feature rich cbindgen instead. rusty-cheddar rusty-ch

Sean Marshallsay 188 May 24, 2021
Safe Rust bridge for creating Erlang NIF functions

Rustler Documentation | Getting Started | Example Rustler is a library for writing Erlang NIFs in safe Rust code. That means there should be no ways t

Rusterlium 2.8k Jun 15, 2021
Rust bindings for the Python interpreter

PyO3 Rust bindings for Python. This includes running and interacting with Python code from a Rust binary, as well as writing native Python modules. Us

PyO3 4.3k Jun 13, 2021
Rust bindings for writing safe and fast native Node.js modules.

Rust bindings for writing safe and fast native Node.js modules. Getting started Once you have the platform dependencies installed, getting started is

The Neon Project 5.5k Jun 12, 2021
The JavaScript runtime that aims for productivity and ease

Byte Byte is a easy and productive runtime for Javascript . It makes making complex programs simple and easy-to-scale with its large and fast Rust API

Byte 32 Jun 16, 2021
High-level Rust bindings to Perl XS API

Perl XS for Rust High-level Rust bindings to Perl XS API. Example xs! { package Array::Sum; sub sum_array(ctx, array: AV) { array.iter().map(|

Vickenty Fesunov 54 Oct 27, 2020
Native Ruby extensions written in Rust

Ruru (Rust + Ruby) Native Ruby extensions in Rust Documentation Website Have you ever considered rewriting some parts of your slow Ruby application? J

Dmitry Gritsay 776 Jun 14, 2021
WebAssembly implementation from scratch in Safe Rust with zero dependencies

wain wain is a WebAssembly INterpreter written in Rust from scratch with zero dependencies. An implementation of WebAssembly. Features: No unsafe code

Linda_pp 171 Jun 6, 2021
A minimalist and safe ECS library for rust!

The full ECS (Entity-Component-System) library. Support an Open Source Developer! ♥️ Composed of two smaller libraries: world_dispatcher: the System p

Joël Lupien 101 Jun 4, 2021