From 5db065644c03ac66aa6ea20bfc8ce0e4fa996982 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Tue, 3 Mar 2020 17:11:26 -0800 Subject: [PATCH] Add tooling and unit tests --- contracts/extensions/src/index.ts | 1 + .../extensions/src/max_gas_price_utils.ts | 44 ++++++++++++ .../extensions/test/max_gas_price_test.ts | 71 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 contracts/extensions/src/max_gas_price_utils.ts create mode 100644 contracts/extensions/test/max_gas_price_test.ts diff --git a/contracts/extensions/src/index.ts b/contracts/extensions/src/index.ts index 8615baa7c9..1e8731cfd1 100644 --- a/contracts/extensions/src/index.ts +++ b/contracts/extensions/src/index.ts @@ -29,3 +29,4 @@ export { TupleDataItem, StateMutability, } from 'ethereum-types'; +export * from './max_gas_price_utils'; diff --git a/contracts/extensions/src/max_gas_price_utils.ts b/contracts/extensions/src/max_gas_price_utils.ts new file mode 100644 index 0000000000..754c8d4069 --- /dev/null +++ b/contracts/extensions/src/max_gas_price_utils.ts @@ -0,0 +1,44 @@ +import { constants } from '@0x/contracts-test-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { StaticCallAssetData } from '@0x/types'; +import { AbiEncoder, BigNumber } from '@0x/utils'; + +const maxGasPriceEncoder = AbiEncoder.create([{ name: 'maxGasPrice', type: 'uint256' }]); +const customGasPriceEncoder = AbiEncoder.createMethod('checkGasPrice', [{ name: 'data', type: 'bytes' }]); +const defaultGasPriceEncoder = AbiEncoder.createMethod('checkGasPrice', []); + +const ONE_GWEI = new BigNumber(10 ** 9); +export const TWENTY_GWEI = ONE_GWEI.times(20); + +/** + * Encodes the given stop limit data parameters into StaticCall asset data so that it can be used + * in a 0x order. + */ +export function encodeMaxGasPriceStaticCallData(maxGasPriceContractAddress: string, maxGasPrice?: BigNumber): string { + const staticCallData = + maxGasPrice === undefined + ? defaultGasPriceEncoder.encode({}) + : customGasPriceEncoder.encode({ + data: maxGasPriceEncoder.encode({ maxGasPrice }), + }); + return assetDataUtils.encodeStaticCallAssetData( + maxGasPriceContractAddress, + staticCallData, + constants.KECCAK256_NULL, + ); +} + +/** + * Decodes the maxGasPrice StaticCall asset data. + */ +export function decodeMaxGasPriceStaticCallData(assetData: string): BigNumber { + // tslint:disable-next-line:no-unnecessary-type-assertion + const { staticCallData } = assetDataUtils.decodeAssetDataOrThrow(assetData) as StaticCallAssetData; + try { + const { maxGasPrice } = maxGasPriceEncoder.decode(customGasPriceEncoder.strictDecode(staticCallData)); + return maxGasPrice; + } catch (e) { + defaultGasPriceEncoder.strictDecode(staticCallData); + return TWENTY_GWEI; + } +} diff --git a/contracts/extensions/test/max_gas_price_test.ts b/contracts/extensions/test/max_gas_price_test.ts new file mode 100644 index 0000000000..3264532d84 --- /dev/null +++ b/contracts/extensions/test/max_gas_price_test.ts @@ -0,0 +1,71 @@ +import { blockchainTests, constants, expect, getRandomInteger } from '@0x/contracts-test-utils'; +import { AbiEncoder } from '@0x/utils'; + +import { + decodeMaxGasPriceStaticCallData, + encodeMaxGasPriceStaticCallData, + TWENTY_GWEI, +} from '../src/max_gas_price_utils'; + +import { artifacts } from './artifacts'; +import { MaximumGasPriceContract } from './wrappers'; + +blockchainTests.resets('MaximumGasPrice unit tests', env => { + let maxGasPriceContract: MaximumGasPriceContract; + const maxGasPriceEncoder = AbiEncoder.create([{ name: 'maxGasPrice', type: 'uint256' }]); + + before(async () => { + maxGasPriceContract = await MaximumGasPriceContract.deployFrom0xArtifactAsync( + artifacts.MaximumGasPrice, + env.provider, + env.txDefaults, + artifacts, + ); + }); + + describe('Contract functionality', () => { + it('does not revert if tx.gasprice < default maximum', async () => { + await maxGasPriceContract.checkGasPrice1().callAsync({ gasPrice: TWENTY_GWEI.minus(1) }); + }); + it('does not revert if tx.gasprice = default maximum', async () => { + await maxGasPriceContract.checkGasPrice1().callAsync({ gasPrice: TWENTY_GWEI }); + }); + it('reverts if tx.gasPrice > default maximum', async () => { + const tx = maxGasPriceContract.checkGasPrice1().callAsync({ gasPrice: TWENTY_GWEI.plus(1) }); + return expect(tx).to.revertWith('MaximumGasPrice/GAS_PRICE_EXCEEDS_20_GWEI'); + }); + it('does not revert if tx.gasprice < custom maximum', async () => { + const maxGasPrice = getRandomInteger(0, TWENTY_GWEI.times(2)); + await maxGasPriceContract + .checkGasPrice2(maxGasPriceEncoder.encode({ maxGasPrice })) + .callAsync({ gasPrice: maxGasPrice.minus(1) }); + }); + it('does not revert if tx.gasprice = default maximum', async () => { + const maxGasPrice = getRandomInteger(0, TWENTY_GWEI.times(2)); + await maxGasPriceContract + .checkGasPrice2(maxGasPriceEncoder.encode({ maxGasPrice })) + .callAsync({ gasPrice: maxGasPrice }); + }); + it('reverts if tx.gasPrice > default maximum', async () => { + const maxGasPrice = getRandomInteger(0, TWENTY_GWEI.times(2)); + const tx = maxGasPriceContract + .checkGasPrice2(maxGasPriceEncoder.encode({ maxGasPrice })) + .callAsync({ gasPrice: maxGasPrice.plus(1) }); + return expect(tx).to.revertWith('MaximumGasPrice/GAS_PRICE_EXCEEDS_MAXIMUM'); + }); + }); + + describe('Data encoding/decoding tools', () => { + it('correctly decodes default maximum gas price', async () => { + const encoded = encodeMaxGasPriceStaticCallData(maxGasPriceContract.address); + const decoded = decodeMaxGasPriceStaticCallData(encoded); + expect(decoded).to.bignumber.equal(TWENTY_GWEI); + }); + it('correctly decodes custom maximum gas price', async () => { + const maxGasPrice = getRandomInteger(0, constants.MAX_UINT256); + const encoded = encodeMaxGasPriceStaticCallData(maxGasPriceContract.address, maxGasPrice); + const decoded = decodeMaxGasPriceStaticCallData(encoded); + expect(decoded).to.bignumber.equal(maxGasPrice); + }); + }); +});