Contract wrappers: Catch empty reverts on live networks (#2433)
* `@0x/utils`: Allow strict decoding of return values. * `@0x/base-contract`: Catch empty call reverts on live networks. `@0x/abi-gen`: Catch empty call reverts on live networks. * `@0x/contracts-integrations`: Add mainnet contract wrapper `callAsync()` revert behavior tests. * `@0x/contract-wrappers`: Regenerate wrappers to catch empty reverts on live networks. * Update CHANGELOGs * `@0x/contracts-integrations`: Fix solidity linter errors. * `@0x/abi-gen`: Regenerate test outputs. * `@0x/base-contract`: Update CHANGELOG. Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "2.2.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add mainnet contract wrapper `callAsync()` revert behavior tests.",
|
||||
"pr": 2433
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"changes": [
|
||||
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
|
||||
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.9;
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract TestContractWrapper {
|
||||
|
||||
uint256 constant public VALID_RETURN_VALUE = 0xf984f922a56ea9a20a32a32f0f60f2d216ff0c0a0d16c986a97a7f1897a6613b;
|
||||
|
||||
function throwStringRevert() external returns (uint256) {
|
||||
revert("ERROR");
|
||||
}
|
||||
|
||||
function throwEmptyRevert() external returns (uint256) {
|
||||
revert();
|
||||
}
|
||||
|
||||
function throwInvalidOpcode() external returns (uint256) {
|
||||
assembly {
|
||||
invalid()
|
||||
}
|
||||
}
|
||||
|
||||
function returnForcedEmpty() external returns (uint256) {
|
||||
assembly {
|
||||
return(0x60, 0)
|
||||
}
|
||||
}
|
||||
|
||||
function returnTruncated() external returns (uint256) {
|
||||
uint256 v = VALID_RETURN_VALUE;
|
||||
assembly {
|
||||
mstore(0x0, v)
|
||||
return(0x0, 16)
|
||||
}
|
||||
}
|
||||
|
||||
function returnEmpty() external { }
|
||||
|
||||
function returnValid() external returns (uint256) {
|
||||
return VALID_RETURN_VALUE;
|
||||
}
|
||||
}
|
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "TestFramework",
|
||||
"abis": "./test/generated-artifacts/@(TestDydxUser|TestEth2Dai|TestEth2DaiBridge|TestFramework|TestMainnetAggregatorFills|TestSignatureValidationWallet|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).json",
|
||||
"abis": "./test/generated-artifacts/@(TestContractWrapper|TestDydxUser|TestEth2Dai|TestEth2DaiBridge|TestFramework|TestMainnetAggregatorFills|TestSignatureValidationWallet|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
|
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as TestContractWrapper from '../test/generated-artifacts/TestContractWrapper.json';
|
||||
import * as TestDydxUser from '../test/generated-artifacts/TestDydxUser.json';
|
||||
import * as TestEth2Dai from '../test/generated-artifacts/TestEth2Dai.json';
|
||||
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
||||
@@ -15,6 +16,7 @@ import * as TestUniswapBridge from '../test/generated-artifacts/TestUniswapBridg
|
||||
import * as TestUniswapExchange from '../test/generated-artifacts/TestUniswapExchange.json';
|
||||
import * as TestUniswapExchangeFactory from '../test/generated-artifacts/TestUniswapExchangeFactory.json';
|
||||
export const artifacts = {
|
||||
TestContractWrapper: TestContractWrapper as ContractArtifact,
|
||||
TestDydxUser: TestDydxUser as ContractArtifact,
|
||||
TestEth2Dai: TestEth2Dai as ContractArtifact,
|
||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||
|
53
contracts/integrations/test/mainnet_contract_wrapper_test.ts
Normal file
53
contracts/integrations/test/mainnet_contract_wrapper_test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { TestContractWrapperContract } from './wrappers';
|
||||
|
||||
blockchainTests.live('Contract wrapper mainnet callAsync revert behavior tests', env => {
|
||||
// Mainnet address of the `TestContractWrapper` contract.
|
||||
const TEST_CONTRACT_ADDRESS = '0x3C120F51aa2360E6C7078dbc849591dd14F21405';
|
||||
const REVERT_STRING = 'ERROR';
|
||||
const VALID_RESULT = new BigNumber('0xf984f922a56ea9a20a32a32f0f60f2d216ff0c0a0d16c986a97a7f1897a6613b');
|
||||
let testContract: TestContractWrapperContract;
|
||||
|
||||
before(async () => {
|
||||
testContract = new TestContractWrapperContract(TEST_CONTRACT_ADDRESS, env.provider, env.txDefaults);
|
||||
});
|
||||
|
||||
describe('callAsync()', () => {
|
||||
it('can decode valid result', async () => {
|
||||
const result = await testContract.returnValid().callAsync();
|
||||
expect(result).to.bignumber.eq(VALID_RESULT);
|
||||
});
|
||||
|
||||
it('can decode an empty result', async () => {
|
||||
const result = await testContract.returnEmpty().callAsync();
|
||||
expect(result).to.eq(undefined);
|
||||
});
|
||||
|
||||
it('catches a string revert', async () => {
|
||||
const tx = testContract.throwStringRevert().callAsync();
|
||||
return expect(tx).to.revertWith(REVERT_STRING);
|
||||
});
|
||||
|
||||
it('catches an empty revert', async () => {
|
||||
const tx = testContract.throwEmptyRevert().callAsync();
|
||||
return expect(tx).to.be.rejectedWith('reverted with no data');
|
||||
});
|
||||
|
||||
it('catches invalid opcode', async () => {
|
||||
const tx = testContract.throwInvalidOpcode().callAsync();
|
||||
return expect(tx).to.be.rejectedWith('reverted with no data');
|
||||
});
|
||||
|
||||
it('catches a forced empty result', async () => {
|
||||
const tx = testContract.returnForcedEmpty().callAsync();
|
||||
return expect(tx).to.be.rejectedWith('reverted with no data');
|
||||
});
|
||||
|
||||
it('catches a truncated result', async () => {
|
||||
const tx = testContract.returnTruncated().callAsync();
|
||||
return expect(tx).to.be.rejectedWith('decode beyond the end of calldata');
|
||||
});
|
||||
});
|
||||
});
|
@@ -3,6 +3,7 @@
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/test_contract_wrapper';
|
||||
export * from '../test/generated-wrappers/test_dydx_user';
|
||||
export * from '../test/generated-wrappers/test_eth2_dai';
|
||||
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
||||
|
@@ -4,6 +4,7 @@
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"files": [
|
||||
"generated-artifacts/TestFramework.json",
|
||||
"test/generated-artifacts/TestContractWrapper.json",
|
||||
"test/generated-artifacts/TestDydxUser.json",
|
||||
"test/generated-artifacts/TestEth2Dai.json",
|
||||
"test/generated-artifacts/TestEth2DaiBridge.json",
|
||||
|
Reference in New Issue
Block a user