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.sha3(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.sha3(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.sha3(ethUtil.toBuffer(encodedExpectedResultWithOffset)),
 | |
|             );
 | |
|             const assetData = assetDataInterface
 | |
|                 .StaticCall(staticCallTarget.address, staticCallData, expectedResultHash)
 | |
|                 .getABIEncodedTransactionData();
 | |
|             await staticCallProxy
 | |
|                 .transferFrom(assetData, fromAddress, toAddress, amount)
 | |
|                 .awaitTransactionSuccessAsync();
 | |
|         });
 | |
|     });
 | |
| });
 |