-
Notifications
You must be signed in to change notification settings - Fork 465
@0x/contracts-extensions
: Maximum gas price contract
#2511
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
|
||
Copyright 2019 ZeroEx Intl. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
|
||
*/ | ||
|
||
pragma solidity ^0.5.16; | ||
pragma experimental ABIEncoderV2; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we actually need to use ABIv2 here. I'd just run tests with/without to see if there are any gas differences. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like without is slightly cheaper for the parametrized version |
||
|
||
|
||
contract MaximumGasPrice { | ||
// 20 Gwei | ||
uint256 constant private DEFAULT_MAX_GAS_PRICE = 20 * (10 ** 9); | ||
|
||
/// @dev Checks that the current transaction's gas price is less than | ||
/// the default maximum value of 20 Gwei. | ||
function checkGasPrice() | ||
external | ||
view | ||
{ | ||
require( | ||
tx.gasprice <= DEFAULT_MAX_GAS_PRICE, | ||
"MaximumGasPrice/GAS_PRICE_EXCEEDS_20_GWEI" | ||
); | ||
} | ||
|
||
/// @dev Checks that the current transaction's gas price is less than | ||
/// the specified maximum value. | ||
/// @param data Encodes the maximum gas price. | ||
function checkGasPrice(bytes calldata data) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just take a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. didn't realize that would work 🙈 |
||
external | ||
view | ||
{ | ||
(uint256 maxGasPrice) = abi.decode(data, (uint256)); | ||
require( | ||
tx.gasprice <= maxGasPrice, | ||
"MaximumGasPrice/GAS_PRICE_EXCEEDS_MAXIMUM" | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,3 +29,4 @@ export { | |
TupleDataItem, | ||
StateMutability, | ||
} from 'ethereum-types'; | ||
export * from './max_gas_price_utils'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for writing this helper 🙌 |
||
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<string>(staticCallData)); | ||
return maxGasPrice; | ||
} catch (e) { | ||
defaultGasPriceEncoder.strictDecode(staticCallData); | ||
return TWENTY_GWEI; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀