* `@0x/contract-wrappers`: Regenerate wrappers using updated abi-gen Update tools deps * update deps to hopefully fix doc gen
246 lines
13 KiB
TypeScript
246 lines
13 KiB
TypeScript
import {
|
|
chaiSetup,
|
|
constants,
|
|
expectTransactionFailedWithoutReasonAsync,
|
|
provider,
|
|
txDefaults,
|
|
web3Wrapper,
|
|
} from '@0x/contracts-test-utils';
|
|
import { BlockchainLifecycle } from '@0x/dev-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 } from './artifacts';
|
|
|
|
import {
|
|
IAssetDataContract,
|
|
IAssetProxyContract,
|
|
StaticCallProxyContract,
|
|
TestStaticCallTargetContract,
|
|
} from './wrappers';
|
|
|
|
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 assetDataInterface: IAssetDataContract;
|
|
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,
|
|
artifacts,
|
|
);
|
|
assetDataInterface = new IAssetDataContract(constants.NULL_ADDRESS, provider);
|
|
staticCallProxy = new IAssetProxyContract(
|
|
staticCallProxyWithoutTransferFrom.address,
|
|
provider,
|
|
txDefaults,
|
|
{},
|
|
StaticCallProxyContract.deployedBytecode,
|
|
);
|
|
staticCallTarget = await TestStaticCallTargetContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestStaticCallTarget,
|
|
provider,
|
|
txDefaults,
|
|
artifacts,
|
|
);
|
|
});
|
|
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 = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
const txData = staticCallProxy
|
|
.transferFrom(assetData, fromAddress, toAddress, amount)
|
|
.getABIEncodedTransactionData();
|
|
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 expectTransactionFailedWithoutReasonAsync(
|
|
web3Wrapper.sendTransactionAsync({
|
|
to: staticCallProxy.address,
|
|
from: fromAddress,
|
|
data: badTxData,
|
|
}),
|
|
);
|
|
});
|
|
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 = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData()
|
|
.slice(0, -128);
|
|
const assetDataByteLen = (assetData.length - 2) / 2;
|
|
expect((assetDataByteLen - 4) % 32).to.equal(0);
|
|
await expectTransactionFailedWithoutReasonAsync(
|
|
staticCallProxy.transferFrom(assetData, fromAddress, toAddress, amount).sendTransactionAsync(),
|
|
);
|
|
});
|
|
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 = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
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 expectTransactionFailedWithoutReasonAsync(
|
|
staticCallProxy.transferFrom(badAssetData, fromAddress, toAddress, amount).sendTransactionAsync(),
|
|
);
|
|
});
|
|
it('should revert if the callTarget attempts to write to state', async () => {
|
|
const staticCallData = staticCallTarget.updateState().getABIEncodedTransactionData();
|
|
const expectedResultHash = constants.KECCAK256_NULL;
|
|
const assetData = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
await expectTransactionFailedWithoutReasonAsync(
|
|
staticCallProxy.transferFrom(assetData, fromAddress, toAddress, amount).sendTransactionAsync(),
|
|
);
|
|
});
|
|
it('should revert with data provided by the callTarget if the staticcall reverts', async () => {
|
|
const staticCallData = staticCallTarget.assertEvenNumber(new BigNumber(1)).getABIEncodedTransactionData();
|
|
const expectedResultHash = constants.KECCAK256_NULL;
|
|
const assetData = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
return expect(
|
|
staticCallProxy.transferFrom(assetData, fromAddress, toAddress, amount).awaitTransactionSuccessAsync(),
|
|
).to.revertWith(RevertReason.TargetNotEven);
|
|
});
|
|
it('should revert if the hash of the output is different than expected expected', async () => {
|
|
const staticCallData = staticCallTarget.isOddNumber(new BigNumber(0)).getABIEncodedTransactionData();
|
|
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
|
|
const expectedResultHash = ethUtil.bufferToHex(ethUtil.keccak256(trueAsBuffer));
|
|
const assetData = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
return expect(
|
|
staticCallProxy.transferFrom(assetData, fromAddress, toAddress, amount).awaitTransactionSuccessAsync(),
|
|
).to.revertWith(RevertReason.UnexpectedStaticCallResult);
|
|
});
|
|
it('should be successful if a function call with no inputs and no outputs is successful', async () => {
|
|
const staticCallData = staticCallTarget.noInputFunction().getABIEncodedTransactionData();
|
|
const expectedResultHash = constants.KECCAK256_NULL;
|
|
const assetData = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
await staticCallProxy
|
|
.transferFrom(assetData, fromAddress, toAddress, amount)
|
|
.awaitTransactionSuccessAsync();
|
|
});
|
|
it('should be successful if the staticCallTarget is not a contract and no return value is expected', async () => {
|
|
const staticCallData = '0x0102030405060708';
|
|
const expectedResultHash = constants.KECCAK256_NULL;
|
|
const assetData = assetDataInterface
|
|
.StaticCall(toAddress, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
await staticCallProxy
|
|
.transferFrom(assetData, fromAddress, toAddress, amount)
|
|
.awaitTransactionSuccessAsync();
|
|
});
|
|
it('should be successful if a function call with one static input returns the correct value', async () => {
|
|
const staticCallData = staticCallTarget.isOddNumber(new BigNumber(1)).getABIEncodedTransactionData();
|
|
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
|
|
const expectedResultHash = ethUtil.bufferToHex(ethUtil.keccak256(trueAsBuffer));
|
|
const assetData = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
await staticCallProxy
|
|
.transferFrom(assetData, fromAddress, toAddress, amount)
|
|
.awaitTransactionSuccessAsync();
|
|
});
|
|
it('should be successful if a function with one dynamic input is successful', async () => {
|
|
const dynamicInput = '0x0102030405060708';
|
|
const staticCallData = staticCallTarget.dynamicInputFunction(dynamicInput).getABIEncodedTransactionData();
|
|
const expectedResultHash = constants.KECCAK256_NULL;
|
|
const assetData = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
await staticCallProxy
|
|
.transferFrom(assetData, fromAddress, toAddress, amount)
|
|
.awaitTransactionSuccessAsync();
|
|
});
|
|
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(a, b).getABIEncodedTransactionData();
|
|
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.keccak256(ethUtil.toBuffer(encodedExpectedResultWithOffset)),
|
|
);
|
|
const assetData = assetDataInterface
|
|
.StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
|
|
.getABIEncodedTransactionData();
|
|
await staticCallProxy
|
|
.transferFrom(assetData, fromAddress, toAddress, amount)
|
|
.awaitTransactionSuccessAsync();
|
|
});
|
|
});
|
|
});
|