Move LibTransactionDecoder to dev-utils package

This commit is contained in:
Amir Bandeali
2019-06-03 17:48:26 -07:00
parent 6b7cb13e9a
commit f68b8d82e0
16 changed files with 159 additions and 250 deletions

View File

@@ -22,5 +22,5 @@
}
}
},
"contracts": ["src/DevUtils.sol"]
"contracts": ["src/DevUtils.sol", "src/LibTransactionDecoder.sol"]
}

View File

@@ -20,7 +20,7 @@ pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "./OrderValidationUtils.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibTransactionDecoder.sol";
import "./LibTransactionDecoder.sol";
// solhint-disable no-empty-blocks

View File

@@ -0,0 +1,193 @@
/*
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.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
contract LibTransactionDecoder is
LibExchangeSelectors
{
using LibBytes for bytes;
/// @dev Decodes the call data for an Exchange contract method call.
/// @param transactionData ABI-encoded calldata for an Exchange
/// contract method call.
/// @return The name of the function called, and the parameters it was
/// given. For single-order fills and cancels, the arrays will have
/// just one element.
function decodeZeroExTransactionData(bytes memory transactionData)
public
pure
returns(
string memory functionName,
LibOrder.Order[] memory orders,
uint256[] memory takerAssetFillAmounts,
bytes[] memory signatures
)
{
bytes4 functionSelector = transactionData.readBytes4(0);
if (functionSelector == BATCH_CANCEL_ORDERS_SELECTOR) {
functionName = "batchCancelOrders";
} else if (functionSelector == BATCH_FILL_ORDERS_SELECTOR) {
functionName = "batchFillOrders";
} else if (functionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR) {
functionName = "batchFillOrdersNoThrow";
} else if (functionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR) {
functionName = "batchFillOrKillOrders";
} else if (functionSelector == CANCEL_ORDER_SELECTOR) {
functionName = "cancelOrder";
} else if (functionSelector == FILL_ORDER_SELECTOR) {
functionName = "fillOrder";
} else if (functionSelector == FILL_ORDER_NO_THROW_SELECTOR) {
functionName = "fillOrderNoThrow";
} else if (functionSelector == FILL_OR_KILL_ORDER_SELECTOR) {
functionName = "fillOrKillOrder";
} else if (functionSelector == MARKET_BUY_ORDERS_SELECTOR) {
functionName = "marketBuyOrders";
} else if (functionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR) {
functionName = "marketBuyOrdersNoThrow";
} else if (functionSelector == MARKET_SELL_ORDERS_SELECTOR) {
functionName = "marketSellOrders";
} else if (functionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR) {
functionName = "marketSellOrdersNoThrow";
} else if (functionSelector == MATCH_ORDERS_SELECTOR) {
functionName = "matchOrders";
} else if (
functionSelector == CANCEL_ORDERS_UP_TO_SELECTOR ||
functionSelector == EXECUTE_TRANSACTION_SELECTOR
// TODO: add new noThrow cancel functions when https://github.com/0xProject/ZEIPs/issues/35 is merged.
) {
revert("UNIMPLEMENTED");
} else {
revert("UNKNOWN_FUNCTION_SELECTOR");
}
if (functionSelector == BATCH_CANCEL_ORDERS_SELECTOR) {
// solhint-disable-next-line indent
orders = abi.decode(transactionData.slice(4, transactionData.length), (LibOrder.Order[]));
takerAssetFillAmounts = new uint256[](0);
signatures = new bytes[](0);
} else if (
functionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR ||
functionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR ||
functionSelector == BATCH_FILL_ORDERS_SELECTOR
) {
(orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForBatchFill(transactionData);
} else if (functionSelector == CANCEL_ORDER_SELECTOR) {
orders = new LibOrder.Order[](1);
orders[0] = abi.decode(transactionData.slice(4, transactionData.length), (LibOrder.Order));
takerAssetFillAmounts = new uint256[](0);
signatures = new bytes[](0);
} else if (
functionSelector == FILL_OR_KILL_ORDER_SELECTOR ||
functionSelector == FILL_ORDER_SELECTOR ||
functionSelector == FILL_ORDER_NO_THROW_SELECTOR
) {
(orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForSingleOrderFill(transactionData);
} else if (
functionSelector == MARKET_BUY_ORDERS_SELECTOR ||
functionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR ||
functionSelector == MARKET_SELL_ORDERS_SELECTOR ||
functionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR
) {
(orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForMarketFill(transactionData);
} else if (functionSelector == MATCH_ORDERS_SELECTOR) {
(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
bytes memory leftSignature,
bytes memory rightSignature
) = abi.decode(
transactionData.slice(4, transactionData.length),
(LibOrder.Order, LibOrder.Order, bytes, bytes)
);
orders = new LibOrder.Order[](2);
orders[0] = leftOrder;
orders[1] = rightOrder;
takerAssetFillAmounts = new uint256[](2);
takerAssetFillAmounts[0] = leftOrder.takerAssetAmount;
takerAssetFillAmounts[1] = rightOrder.takerAssetAmount;
signatures = new bytes[](2);
signatures[0] = leftSignature;
signatures[1] = rightSignature;
}
}
function _makeReturnValuesForSingleOrderFill(bytes memory transactionData)
private
pure
returns(
LibOrder.Order[] memory orders,
uint256[] memory takerAssetFillAmounts,
bytes[] memory signatures
)
{
orders = new LibOrder.Order[](1);
takerAssetFillAmounts = new uint256[](1);
signatures = new bytes[](1);
// solhint-disable-next-line indent
(orders[0], takerAssetFillAmounts[0], signatures[0]) = abi.decode(
transactionData.slice(4, transactionData.length),
(LibOrder.Order, uint256, bytes)
);
}
function _makeReturnValuesForBatchFill(bytes memory transactionData)
private
pure
returns(
LibOrder.Order[] memory orders,
uint256[] memory takerAssetFillAmounts,
bytes[] memory signatures
)
{
// solhint-disable-next-line indent
(orders, takerAssetFillAmounts, signatures) = abi.decode(
transactionData.slice(4, transactionData.length),
// solhint-disable-next-line indent
(LibOrder.Order[], uint256[], bytes[])
);
}
function _makeReturnValuesForMarketFill(bytes memory transactionData)
private
pure
returns(
LibOrder.Order[] memory orders,
uint256[] memory takerAssetFillAmounts,
bytes[] memory signatures
)
{
takerAssetFillAmounts = new uint256[](1);
// solhint-disable-next-line indent
(orders, takerAssetFillAmounts[0], signatures) = abi.decode(
transactionData.slice(4, transactionData.length),
// solhint-disable-next-line indent
(LibOrder.Order[], uint256, bytes[])
);
}
}

View File

@@ -34,7 +34,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"config": {
"abis": "./generated-artifacts/@(DevUtils).json",
"abis": "./generated-artifacts/@(DevUtils|LibTransactionDecoder).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {

View File

@@ -6,4 +6,8 @@
import { ContractArtifact } from 'ethereum-types';
import * as DevUtils from '../generated-artifacts/DevUtils.json';
export const artifacts = { DevUtils: DevUtils as ContractArtifact };
import * as LibTransactionDecoder from '../generated-artifacts/LibTransactionDecoder.json';
export const artifacts = {
DevUtils: DevUtils as ContractArtifact,
LibTransactionDecoder: LibTransactionDecoder as ContractArtifact,
};

View File

@@ -4,3 +4,4 @@
* -----------------------------------------------------------------------------
*/
export * from '../generated-wrappers/dev_utils';
export * from '../generated-wrappers/lib_transaction_decoder';

View File

@@ -0,0 +1,144 @@
import { artifacts as exchangeArtifacts, IExchangeContract } from '@0x/contracts-exchange';
import { chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import { artifacts, LibTransactionDecoderContract } from '../src';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const order = {
makerAddress: '0xe36ea790bc9d7ab70c55260c66d52b1eca985f84',
takerAddress: '0x0000000000000000000000000000000000000000',
feeRecipientAddress: '0x78dc5d2d739606d31509c31d654056a45185ecb6',
senderAddress: '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb',
makerAssetAmount: new BigNumber('100000000000000000000'),
takerAssetAmount: new BigNumber('200000000000000000000'),
makerFee: new BigNumber('1000000000000000000'),
takerFee: new BigNumber('1000000000000000000'),
expirationTimeSeconds: new BigNumber('1552396423'),
salt: new BigNumber('66097384406870180066678463045003379626790660770396923976862707230261946348951'),
makerAssetData: '0xf47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064',
takerAssetData: '0xf47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e3',
};
const takerAssetFillAmount = new BigNumber('100000000000000000000');
const signature =
'0x1ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03';
describe('LibTransactionDecoder', () => {
let libTxDecoder: LibTransactionDecoderContract;
const exchangeInterface = new IExchangeContract(
exchangeArtifacts.Exchange.compilerOutput.abi,
constants.NULL_ADDRESS,
provider,
txDefaults,
);
before(async () => {
await blockchainLifecycle.startAsync();
libTxDecoder = await LibTransactionDecoderContract.deployFrom0xArtifactAsync(
artifacts.LibTransactionDecoder,
provider,
txDefaults,
);
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
it('should decode an Exchange.batchCancelOrders() transaction', async () => {
const input = exchangeInterface.batchCancelOrders.getABIEncodedTransactionData([order, order]);
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
'batchCancelOrders',
[order, order],
[],
[],
]);
});
for (const func of ['batchFillOrders', 'batchFillOrdersNoThrow', 'batchFillOrKillOrders']) {
const input = (exchangeInterface as any)[func].getABIEncodedTransactionData(
[order, order],
[takerAssetFillAmount, takerAssetFillAmount],
[signature, signature],
);
it(`should decode an Exchange.${func}() transaction`, async () => {
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
func,
[order, order],
[takerAssetFillAmount, takerAssetFillAmount],
[signature, signature],
]);
});
}
it('should decode an Exchange.cancelOrder() transaction', async () => {
const input = exchangeInterface.cancelOrder.getABIEncodedTransactionData(order);
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
'cancelOrder',
[order],
[],
[],
]);
});
for (const func of ['fillOrder', 'fillOrderNoThrow', 'fillOrKillOrder']) {
const input = (exchangeInterface as any)[func].getABIEncodedTransactionData(
order,
takerAssetFillAmount,
signature,
);
it(`should decode an Exchange.${func}() transaction`, async () => {
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
func,
[order],
[takerAssetFillAmount],
[signature],
]);
});
}
for (const func of ['marketBuyOrders', 'marketBuyOrdersNoThrow', 'marketSellOrders', 'marketSellOrdersNoThrow']) {
const input = (exchangeInterface as any)[func].getABIEncodedTransactionData(
[order, order],
takerAssetFillAmount,
[signature, signature],
);
it(`should decode an Exchange.${func}() transaction`, async () => {
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
func,
[order, order],
[takerAssetFillAmount],
[signature, signature],
]);
});
}
it('should decode an Exchange.matchOrders() transaction', async () => {
const complementaryOrder = {
...order,
makerAddress: order.takerAddress,
takerAddress: order.makerAddress,
makerAssetData: order.takerAssetData,
takerAssetData: order.makerAssetData,
makerAssetAmount: order.takerAssetAmount,
takerAssetAmount: order.makerAssetAmount,
makerFee: order.takerFee,
takerFee: order.makerFee,
};
const input = exchangeInterface.matchOrders.getABIEncodedTransactionData(
order,
complementaryOrder,
signature,
signature,
);
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
'matchOrders',
[order, complementaryOrder],
[order.takerAssetAmount, complementaryOrder.takerAssetAmount],
[signature, signature],
]);
});
});

View File

@@ -16,7 +16,6 @@ import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import * as _ from 'lodash';
import { artifacts, DevUtilsContract } from '../src';
@@ -24,7 +23,7 @@ chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('DevUtils', () => {
describe('OrderValidationUtils', () => {
let makerAddress: string;
let takerAddress: string;
let owner: string;
@@ -56,7 +55,7 @@ describe('DevUtils', () => {
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
const usedAddresses = ([owner, makerAddress, takerAddress] = _.slice(accounts, 0, 3));
const usedAddresses = ([owner, makerAddress, takerAddress] = accounts.slice(0, 3));
const erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);

View File

@@ -2,6 +2,6 @@
"extends": "../../tsconfig",
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": ["generated-artifacts/DevUtils.json"],
"files": ["generated-artifacts/DevUtils.json", "generated-artifacts/LibTransactionDecoder.json"],
"exclude": ["./deploy/solc/solc_bin"]
}