Add unit tests for StaticCallProxy

This commit is contained in:
Amir Bandeali 2019-06-09 19:20:47 -07:00
parent f51c4f9617
commit 0c8bb2e675
4 changed files with 263 additions and 3 deletions

View File

@ -121,7 +121,7 @@ describe('ERC1155Proxy', () => {
}),
);
});
it('should have an id of 0x9645780d', async () => {
it('should have an id of 0xa7cb5fb7', async () => {
const proxyId = await erc1155Proxy.getProxyId.callAsync();
const expectedProxyId = AssetProxyId.ERC1155;
expect(proxyId).to.equal(expectedProxyId);
@ -1572,7 +1572,7 @@ describe('ERC1155Proxy', () => {
// execute transfer
await expectTransactionFailedAsync(
erc1155ProxyWrapper.transferFromRawAsync(badTxData, authorized),
RevertReason.InvalidAssetData,
RevertReason.InvalidAssetDataEnd,
);
});
it('should revert if length of assetData, excluding the selector, is not a multiple of 32', async () => {

View File

@ -0,0 +1,255 @@
import {
chaiSetup,
constants,
expectTransactionFailedAsync,
expectTransactionFailedWithoutReasonAsync,
provider,
txDefaults,
web3Wrapper,
} from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { assetDataUtils } from '@0x/order-utils';
import { AssetProxyId, RevertReason } from '@0x/types';
import { AbiEncoder, BigNumber } from '@0x/utils';
import * as chai from 'chai';
import * as ethUtil from 'ethereumjs-util';
import { artifacts, IAssetProxyContract, StaticCallProxyContract, TestStaticCallTargetContract } from '../src';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('StaticCallProxy', () => {
const amount = constants.ZERO_AMOUNT;
let fromAddress: string;
let toAddress: string;
let staticCallProxy: IAssetProxyContract;
let staticCallTarget: TestStaticCallTargetContract;
before(async () => {
await blockchainLifecycle.startAsync();
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
[fromAddress, toAddress] = accounts.slice(0, 2);
const staticCallProxyWithoutTransferFrom = await StaticCallProxyContract.deployFrom0xArtifactAsync(
artifacts.StaticCallProxy,
provider,
txDefaults,
);
staticCallProxy = new IAssetProxyContract(
artifacts.IAssetProxy.compilerOutput.abi,
staticCallProxyWithoutTransferFrom.address,
provider,
txDefaults,
);
staticCallTarget = await TestStaticCallTargetContract.deployFrom0xArtifactAsync(
artifacts.TestStaticCallTarget,
provider,
txDefaults,
);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('general', () => {
it('should revert if undefined function is called', async () => {
const undefinedSelector = '0x01020304';
await expectTransactionFailedWithoutReasonAsync(
web3Wrapper.sendTransactionAsync({
from: fromAddress,
to: staticCallProxy.address,
value: constants.ZERO_AMOUNT,
data: undefinedSelector,
}),
);
});
it('should have an id of 0xc339d10a', async () => {
const proxyId = await staticCallProxy.getProxyId.callAsync();
const expectedProxyId = AssetProxyId.StaticCall;
expect(proxyId).to.equal(expectedProxyId);
});
});
describe('transferFrom', () => {
it('should revert if assetData lies outside the bounds of calldata', async () => {
const staticCallData = staticCallTarget.noInputFunction.getABIEncodedTransactionData();
const expectedResultHash = constants.KECCAK256_NULL;
const assetData = assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
);
const txData = staticCallProxy.transferFrom.getABIEncodedTransactionData(
assetData,
fromAddress,
toAddress,
amount,
);
const offsetToAssetData = '0000000000000000000000000000000000000000000000000000000000000080';
const txDataEndBuffer = ethUtil.toBuffer((txData.length - 2) / 2 - 4);
const paddedTxDataEndBuffer = ethUtil.setLengthLeft(txDataEndBuffer, 32);
const invalidOffsetToAssetData = ethUtil.bufferToHex(paddedTxDataEndBuffer).slice(2);
const newAssetData = '0000000000000000000000000000000000000000000000000000000000000304';
const badTxData = `${txData.replace(offsetToAssetData, invalidOffsetToAssetData)}${newAssetData}`;
await expectTransactionFailedAsync(
web3Wrapper.sendTransactionAsync({
to: staticCallProxy.address,
from: fromAddress,
data: badTxData,
}),
RevertReason.InvalidAssetDataEnd,
);
});
it('should revert if the length of assetData, excluding the proxyId, is not a multiple of 32', async () => {
const staticCallData = staticCallTarget.noInputFunction.getABIEncodedTransactionData();
const expectedResultHash = constants.KECCAK256_NULL;
const assetData = `${assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
)}01`;
await expectTransactionFailedAsync(
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
RevertReason.InvalidAssetDataLength,
);
});
it('should revert if the length of assetData is less than 100 bytes', async () => {
const staticCallData = constants.NULL_BYTES;
const expectedResultHash = constants.KECCAK256_NULL;
const assetData = assetDataUtils
.encodeStaticCallAssetData(staticCallTarget.address, staticCallData, expectedResultHash)
.slice(0, -128);
const assetDataByteLen = (assetData.length - 2) / 2;
expect((assetDataByteLen - 4) % 32).to.equal(0);
await expectTransactionFailedAsync(
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
RevertReason.InvalidAssetDataLength,
);
});
it('should revert if the offset to `staticCallData` points to outside of assetData', async () => {
const staticCallData = staticCallTarget.noInputFunction.getABIEncodedTransactionData();
const expectedResultHash = constants.KECCAK256_NULL;
const assetData = assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
);
const offsetToStaticCallData = '0000000000000000000000000000000000000000000000000000000000000060';
const assetDataEndBuffer = ethUtil.toBuffer((assetData.length - 2) / 2 - 4);
const paddedAssetDataEndBuffer = ethUtil.setLengthLeft(assetDataEndBuffer, 32);
const invalidOffsetToStaticCallData = ethUtil.bufferToHex(paddedAssetDataEndBuffer).slice(2);
const newStaticCallData = '0000000000000000000000000000000000000000000000000000000000000304';
const badAssetData = `${assetData.replace(
offsetToStaticCallData,
invalidOffsetToStaticCallData,
)}${newStaticCallData}`;
await expectTransactionFailedAsync(
staticCallProxy.transferFrom.sendTransactionAsync(badAssetData, fromAddress, toAddress, amount),
RevertReason.InvalidStaticCallDataOffset,
);
});
it('should revert if the callTarget attempts to write to state', async () => {
const staticCallData = staticCallTarget.updateState.getABIEncodedTransactionData();
const expectedResultHash = constants.KECCAK256_NULL;
const assetData = assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
);
await expectTransactionFailedWithoutReasonAsync(
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
);
});
it('should revert with data provided by the callTarget if the staticcall reverts', async () => {
const staticCallData = staticCallTarget.assertEvenNumber.getABIEncodedTransactionData(new BigNumber(1));
const expectedResultHash = constants.KECCAK256_NULL;
const assetData = assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
);
await expectTransactionFailedAsync(
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
RevertReason.TargetNotEven,
);
});
it('should revert if the hash of the output is different than expected expected', async () => {
const staticCallData = staticCallTarget.isOddNumber.getABIEncodedTransactionData(new BigNumber(0));
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
const expectedResultHash = ethUtil.bufferToHex(ethUtil.sha3(trueAsBuffer));
const assetData = assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
);
await expectTransactionFailedAsync(
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
RevertReason.UnexpectedStaticCallResult,
);
});
it('should be successful if a function call with no inputs is successful', async () => {
const staticCallData = staticCallTarget.noInputFunction.getABIEncodedTransactionData();
const expectedResultHash = constants.KECCAK256_NULL;
const assetData = assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
);
await staticCallProxy.transferFrom.awaitTransactionSuccessAsync(assetData, fromAddress, toAddress, amount);
});
it('should be successful if a function call with one static input returns the correct value', async () => {
const staticCallData = staticCallTarget.isOddNumber.getABIEncodedTransactionData(new BigNumber(1));
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
const expectedResultHash = ethUtil.bufferToHex(ethUtil.sha3(trueAsBuffer));
const assetData = assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
);
await staticCallProxy.transferFrom.awaitTransactionSuccessAsync(assetData, fromAddress, toAddress, amount);
});
it('should be successful if a function with one dynamic input is successful', async () => {
const dynamicInput = '0x0102030405060708';
const staticCallData = staticCallTarget.dynamicInputFunction.getABIEncodedTransactionData(dynamicInput);
const expectedResultHash = constants.KECCAK256_NULL;
const assetData = assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
);
await staticCallProxy.transferFrom.awaitTransactionSuccessAsync(assetData, fromAddress, toAddress, amount);
});
it('should be successful if a function call returns a complex type', async () => {
const a = new BigNumber(1);
const b = new BigNumber(2);
const staticCallData = staticCallTarget.returnComplexType.getABIEncodedTransactionData(a, b);
const abiEncoder = new AbiEncoder.DynamicBytes({
name: '',
type: 'bytes',
});
const aHex = '0000000000000000000000000000000000000000000000000000000000000001';
const bHex = '0000000000000000000000000000000000000000000000000000000000000002';
const expectedResults = `${staticCallTarget.address}${aHex}${bHex}`;
const offset = '0000000000000000000000000000000000000000000000000000000000000020';
const encodedExpectedResultWithOffset = `0x${offset}${abiEncoder.encode(expectedResults).slice(2)}`;
const expectedResultHash = ethUtil.bufferToHex(
ethUtil.sha3(ethUtil.toBuffer(encodedExpectedResultWithOffset)),
);
const assetData = assetDataUtils.encodeStaticCallAssetData(
staticCallTarget.address,
staticCallData,
expectedResultHash,
);
await staticCallProxy.transferFrom.awaitTransactionSuccessAsync(assetData, fromAddress, toAddress, amount);
});
});
});

View File

@ -70,4 +70,5 @@ export const constants = {
'CANCEL_ORDERS_UP_TO',
'SET_SIGNATURE_VALIDATOR_APPROVAL',
],
KECCAK256_NULL: ethUtil.addHexPrefix(ethUtil.bufferToHex(ethUtil.SHA3_NULL)),
};

View File

@ -291,7 +291,7 @@ export enum RevertReason {
AuctionExpired = 'AUCTION_EXPIRED',
AuctionNotStarted = 'AUCTION_NOT_STARTED',
AuctionInvalidBeginTime = 'INVALID_BEGIN_TIME',
InvalidAssetData = 'INVALID_ASSET_DATA',
InvalidAssetDataEnd = 'INVALID_ASSET_DATA_END',
// Balance Threshold Filter
InvalidOrBlockedExchangeSelector = 'INVALID_OR_BLOCKED_EXCHANGE_SELECTOR',
BalanceQueryFailed = 'BALANCE_QUERY_FAILED',
@ -317,6 +317,10 @@ export enum RevertReason {
InvalidValuesOffset = 'INVALID_VALUES_OFFSET',
InvalidDataOffset = 'INVALID_DATA_OFFSET',
InvalidAssetDataLength = 'INVALID_ASSET_DATA_LENGTH',
// StaticCall
InvalidStaticCallDataOffset = 'INVALID_STATIC_CALL_DATA_OFFSET',
TargetNotEven = 'TARGET_NOT_EVEN',
UnexpectedStaticCallResult = 'UNEXPECTED_STATIC_CALL_RESULT',
}
export enum StatusCodes {