mzhu25 dcce8276b8
Add decoders for broker and stop-limit data (#2484)
* Add decoders for broker and stop-limit data

* update changelogs

* Address comments
2020-02-14 17:38:43 -08:00

742 lines
32 KiB
TypeScript

import {
artifacts as BrokerArtifacts,
BrokerContract,
decodeBrokerAssetData,
decodePropertyData,
encodeBrokerAssetData,
encodePropertyData,
GodsUnchainedValidatorContract,
TestGodsUnchainedContract,
} from '@0x/contracts-broker';
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { ExchangeFunctionName, ExchangeRevertErrors } from '@0x/contracts-exchange';
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
import { assetDataUtils } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import { Actor } from '../framework/actors/base';
import { Maker } from '../framework/actors/maker';
import { Taker } from '../framework/actors/taker';
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
import { LocalBalanceStore } from '../framework/balances/local_balance_store';
import { DeploymentManager } from '../framework/deployment_manager';
blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
let deployment: DeploymentManager;
let balanceStore: BlockchainBalanceStore;
let initialBalances: LocalBalanceStore;
let maker: Maker;
let taker: Taker;
let affiliate: Actor;
let broker: BrokerContract;
let godsUnchained: TestGodsUnchainedContract;
let validator: GodsUnchainedValidatorContract;
let godsUnchainedTokenIds: BigNumber[];
const makerSpecifiedProto = new BigNumber(1337);
const makerSpecifiedQuality = new BigNumber(25);
before(async () => {
deployment = await DeploymentManager.deployAsync(env, {
numErc20TokensToDeploy: 1,
numErc721TokensToDeploy: 0,
numErc1155TokensToDeploy: 0,
});
const [makerToken] = deployment.tokens.erc20;
godsUnchained = await TestGodsUnchainedContract.deployFrom0xArtifactAsync(
BrokerArtifacts.TestGodsUnchained,
env.provider,
env.txDefaults,
BrokerArtifacts,
'Gods Unchained Cards',
'GU',
);
validator = await GodsUnchainedValidatorContract.deployFrom0xArtifactAsync(
BrokerArtifacts.GodsUnchainedValidator,
env.provider,
env.txDefaults,
BrokerArtifacts,
godsUnchained.address,
);
broker = await BrokerContract.deployFrom0xArtifactAsync(
BrokerArtifacts.Broker,
env.provider,
env.txDefaults,
BrokerArtifacts,
deployment.exchange.address,
deployment.tokens.weth.address,
);
const takerAssetData = encodeBrokerAssetData(broker.address, godsUnchained.address, validator.address, {
proto: makerSpecifiedProto,
quality: makerSpecifiedQuality,
});
const orderConfig = {
feeRecipientAddress: constants.NULL_ADDRESS,
makerAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
takerAssetData,
takerAssetAmount: new BigNumber(2), // buy 2 cards
makerFeeAssetData: constants.NULL_BYTES,
takerFeeAssetData: constants.NULL_BYTES,
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
};
maker = new Maker({
name: 'Maker',
deployment,
orderConfig,
});
taker = new Taker({ name: 'Taker', deployment });
affiliate = new Actor({ name: 'Affiliate', deployment });
// Set balances and allowances
await maker.configureERC20TokenAsync(makerToken);
godsUnchainedTokenIds = await taker.configureERC721TokenAsync(
new DummyERC721TokenContract(godsUnchained.address, env.provider),
broker.address,
5,
);
await maker.configureERC20TokenAsync(deployment.tokens.weth);
const tokenOwners = {
Maker: maker.address,
Taker: taker.address,
Affiliate: affiliate.address,
Broker: broker.address,
StakingProxy: deployment.staking.stakingProxy.address,
};
const tokenContracts = {
erc20: { makerToken, WETH: deployment.tokens.weth },
erc721: { GodsUnchained: new DummyERC721TokenContract(godsUnchained.address, env.provider) },
};
const tokenIds = {
erc721: { [godsUnchained.address]: godsUnchainedTokenIds },
};
balanceStore = new BlockchainBalanceStore(tokenOwners, tokenContracts, tokenIds);
await balanceStore.updateBalancesAsync();
initialBalances = LocalBalanceStore.create(balanceStore);
});
after(async () => {
Actor.reset();
});
function simulateBrokerFills(
brokeredAssets: BigNumber[],
orders: SignedOrder[],
takerAssetFillAmounts: BigNumber[],
receipt: TransactionReceiptWithDecodedLogs,
): LocalBalanceStore {
const expectedBalances = LocalBalanceStore.create(initialBalances);
// Transaction gas cost
expectedBalances.burnGas(receipt.from, DeploymentManager.gasPrice.times(receipt.gasUsed));
// Taker -> Maker
for (const brokeredAsset of brokeredAssets) {
const erc721AssetData = assetDataUtils.encodeERC721AssetData(godsUnchained.address, brokeredAsset);
expectedBalances.transferAsset(taker.address, maker.address, new BigNumber(1), erc721AssetData);
}
// Maker -> Taker
for (const [i, order] of orders.entries()) {
const amount = ReferenceFunctions.safeGetPartialAmountFloor(
takerAssetFillAmounts[i],
order.takerAssetAmount,
order.makerAssetAmount,
);
expectedBalances.transferAsset(maker.address, taker.address, amount, order.makerAssetData);
}
// Protocol fee
expectedBalances.sendEth(
receipt.from,
deployment.staking.stakingProxy.address,
DeploymentManager.protocolFee.times(orders.length),
);
expectedBalances.wrapEth(
deployment.staking.stakingProxy.address,
deployment.tokens.weth.address,
DeploymentManager.protocolFee.times(orders.length),
);
return expectedBalances;
}
describe('brokerTrade', () => {
let order: SignedOrder;
before(async () => {
// The first two cards will satisfy the maker-specified proto and quality
await godsUnchained
.setTokenProperties(godsUnchainedTokenIds[0], makerSpecifiedProto, makerSpecifiedQuality)
.awaitTransactionSuccessAsync();
await godsUnchained
.setTokenProperties(godsUnchainedTokenIds[1], makerSpecifiedProto, makerSpecifiedQuality)
.awaitTransactionSuccessAsync();
order = await maker.signOrderAsync();
});
for (const fnName of constants.SINGLE_FILL_FN_NAMES) {
it(`${fnName} with one valid asset`, async () => {
const receipt = await broker
.brokerTrade(
[godsUnchainedTokenIds[0]],
order,
new BigNumber(1),
order.signature,
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee,
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
[godsUnchainedTokenIds[0]],
[order],
[new BigNumber(1)],
receipt,
);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
it(`${fnName} with two valid assets`, async () => {
const receipt = await broker
.brokerTrade(
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[1]],
order,
new BigNumber(2),
order.signature,
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee,
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[1]],
[order],
[new BigNumber(2)],
receipt,
);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
it(`${fnName} with one invalid asset`, async () => {
const tx = broker
.brokerTrade(
[godsUnchainedTokenIds[2]],
order,
new BigNumber(1),
order.signature,
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee,
gasPrice: DeploymentManager.gasPrice,
});
return expect(tx).to.revertWith(new ExchangeRevertErrors.AssetProxyTransferError());
});
it(`${fnName} with one valid asset, one invalid asset`, async () => {
const tx = broker
.brokerTrade(
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[2]], // valid, invalid
order,
new BigNumber(2),
order.signature,
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee,
gasPrice: DeploymentManager.gasPrice,
});
return expect(tx).to.revertWith(new ExchangeRevertErrors.AssetProxyTransferError());
});
it(`${fnName} with too few assets`, async () => {
const tx = broker
.brokerTrade(
[godsUnchainedTokenIds[0]], // One asset provided
order,
new BigNumber(2), // But two are required for the fill
order.signature,
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee,
gasPrice: DeploymentManager.gasPrice,
});
return expect(tx).to.revertWith(new ExchangeRevertErrors.AssetProxyTransferError());
});
it(`${fnName} with same asset twice`, async () => {
const tx = broker
.brokerTrade(
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[0]],
order,
new BigNumber(2),
order.signature,
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee,
gasPrice: DeploymentManager.gasPrice,
});
return expect(tx).to.revertWith(new ExchangeRevertErrors.AssetProxyTransferError());
});
it(`${fnName} with excess assets`, async () => {
const receipt = await broker
.brokerTrade(
godsUnchainedTokenIds,
order,
new BigNumber(2),
order.signature,
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee,
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[1]], // 3rd card isn't transferred
[order],
[new BigNumber(2)],
receipt,
);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
}
describe('ETH/WETH behavior', () => {
it(`Reverts if insufficient ETH is provided`, async () => {
const tx = broker
.brokerTrade(
[godsUnchainedTokenIds[0]],
order,
new BigNumber(1),
order.signature,
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.minus(1),
gasPrice: DeploymentManager.gasPrice,
});
return expect(tx).to.revertWith(new ExchangeRevertErrors.PayProtocolFeeError());
});
it(`Refunds sender if excess ETH is provided`, async () => {
const receipt = await broker
.brokerTrade(
[godsUnchainedTokenIds[0]],
order,
new BigNumber(1),
order.signature,
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.plus(1), // 1 wei gets refunded
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
[godsUnchainedTokenIds[0]],
[order],
[new BigNumber(1)],
receipt,
);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
it(`Pays a single ETH affiliate fee and refunds excess ETH`, async () => {
const affiliateFee = new BigNumber(100);
const receipt = await broker
.brokerTrade(
[godsUnchainedTokenIds[0]],
order,
new BigNumber(1),
order.signature,
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
[affiliateFee],
[affiliate.address],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.plus(affiliateFee).plus(1),
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
[godsUnchainedTokenIds[0]],
[order],
[new BigNumber(1)],
receipt,
);
expectedBalances.sendEth(receipt.from, affiliate.address, affiliateFee);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
it(`Pays a multiple ETH affiliate fees and refunds excess ETH`, async () => {
const affiliateFee = new BigNumber(100);
const receipt = await broker
.brokerTrade(
[godsUnchainedTokenIds[0]],
order,
new BigNumber(1),
order.signature,
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
[affiliateFee, affiliateFee],
[affiliate.address, maker.address],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.plus(affiliateFee.times(2)).plus(1),
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
[godsUnchainedTokenIds[0]],
[order],
[new BigNumber(1)],
receipt,
);
expectedBalances.sendEth(receipt.from, affiliate.address, affiliateFee);
expectedBalances.sendEth(receipt.from, maker.address, affiliateFee);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
it(`Taker can fill an order with a WETH takerFee`, async () => {
const wethAssetData = assetDataUtils.encodeERC20AssetData(deployment.tokens.weth.address);
const takerFee = new BigNumber(100);
const takerFeeOrder = await maker.signOrderAsync({
feeRecipientAddress: affiliate.address,
takerFeeAssetData: wethAssetData,
takerFee,
});
const receipt = await broker
.brokerTrade(
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[1]],
takerFeeOrder,
new BigNumber(2),
takerFeeOrder.signature,
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: takerFee.plus(DeploymentManager.protocolFee),
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[1]],
[takerFeeOrder],
[new BigNumber(2)],
receipt,
);
expectedBalances.wrapEth(taker.address, deployment.tokens.weth.address, takerFee);
expectedBalances.transferAsset(taker.address, affiliate.address, takerFee, wethAssetData);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
it(`Taker can fill a vanilla (not property-based) order through the Broker if takerAssetData = WETH`, async () => {
const wethAssetData = assetDataUtils.encodeERC20AssetData(deployment.tokens.weth.address);
const takerAssetAmount = constants.ONE_ETHER.dividedToIntegerBy(2);
const wethOrder = await maker.signOrderAsync({
takerAssetData: wethAssetData,
takerAssetAmount,
});
const receipt = await broker
.brokerTrade(
[],
wethOrder,
takerAssetAmount,
wethOrder.signature,
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: takerAssetAmount.plus(DeploymentManager.protocolFee),
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = LocalBalanceStore.create(initialBalances);
expectedBalances.wrapEth(
taker.address,
deployment.tokens.weth.address,
takerAssetAmount.plus(DeploymentManager.protocolFee),
);
expectedBalances.simulateFills([wethOrder], taker.address, receipt, deployment);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
});
});
describe('batchBrokerTrade', () => {
let orders: SignedOrder[];
before(async () => {
// Two orders specifying different protos/qualities
const firstOrderProto = makerSpecifiedProto;
const firstOrderQuality = makerSpecifiedQuality;
const secondOrderProto = new BigNumber(42);
const secondOrderQuality = new BigNumber(7);
// First two cards satisfy the proto/quality of the first order
await godsUnchained
.setTokenProperties(godsUnchainedTokenIds[0], firstOrderProto, firstOrderQuality)
.awaitTransactionSuccessAsync();
await godsUnchained
.setTokenProperties(godsUnchainedTokenIds[1], firstOrderProto, firstOrderQuality)
.awaitTransactionSuccessAsync();
// Next two cards satisfy the proto/quality of the second order
await godsUnchained
.setTokenProperties(godsUnchainedTokenIds[2], secondOrderProto, secondOrderQuality)
.awaitTransactionSuccessAsync();
await godsUnchained
.setTokenProperties(godsUnchainedTokenIds[3], secondOrderProto, secondOrderQuality)
.awaitTransactionSuccessAsync();
orders = [
await maker.signOrderAsync({
takerAssetData: encodeBrokerAssetData(broker.address, godsUnchained.address, validator.address, {
proto: firstOrderProto,
quality: firstOrderQuality,
}),
}),
await maker.signOrderAsync({
takerAssetData: encodeBrokerAssetData(broker.address, godsUnchained.address, validator.address, {
proto: secondOrderProto,
quality: secondOrderQuality,
}),
}),
];
});
for (const fnName of constants.BATCH_FILL_FN_NAMES) {
it(`${fnName} with one order, one valid asset`, async () => {
const receipt = await broker
.batchBrokerTrade(
[godsUnchainedTokenIds[0]],
[orders[0]],
[new BigNumber(1)],
[orders[0].signature],
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee,
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
[godsUnchainedTokenIds[0]],
[orders[0]],
[new BigNumber(1)],
receipt,
);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
it(`${fnName} with two orders, one valid asset each`, async () => {
const receipt = await broker
.batchBrokerTrade(
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[2]], // valid for 1st order, valid for 2nd
orders,
[new BigNumber(1), new BigNumber(1)],
[orders[0].signature, orders[1].signature],
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.times(2),
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[2]],
orders,
[new BigNumber(1), new BigNumber(1)],
receipt,
);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
it(`${fnName} with two orders, two valid assets each`, async () => {
const receipt = await broker
.batchBrokerTrade(
godsUnchainedTokenIds.slice(0, 4),
orders,
[new BigNumber(2), new BigNumber(2)],
[orders[0].signature, orders[1].signature],
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.times(2),
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
godsUnchainedTokenIds.slice(0, 4),
orders,
[new BigNumber(2), new BigNumber(2)],
receipt,
);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
it(`${fnName} with two orders, two valid assets each + excess asset`, async () => {
const receipt = await broker
.batchBrokerTrade(
godsUnchainedTokenIds,
orders,
[new BigNumber(2), new BigNumber(2)],
[orders[0].signature, orders[1].signature],
deployment.exchange.getSelector(fnName),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.times(2),
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
godsUnchainedTokenIds.slice(0, 4), // 5th card isn't transferred
orders,
[new BigNumber(2), new BigNumber(2)],
receipt,
);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
}
it(`batchFillOrders reverts on invalid asset`, async () => {
const tx = broker
.batchBrokerTrade(
[...godsUnchainedTokenIds.slice(0, 3), godsUnchainedTokenIds[4]], // Last card isn't valid for 2nd order
orders,
[new BigNumber(2), new BigNumber(2)],
[orders[0].signature, orders[1].signature],
deployment.exchange.getSelector(ExchangeFunctionName.BatchFillOrders),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.times(2),
gasPrice: DeploymentManager.gasPrice,
});
return expect(tx).to.revertWith(new ExchangeRevertErrors.AssetProxyTransferError());
});
it(`batchFillOrKillOrders reverts on invalid asset`, async () => {
const tx = broker
.batchBrokerTrade(
[...godsUnchainedTokenIds.slice(0, 3), godsUnchainedTokenIds[4]], // Last card isn't valid for 2nd order
orders,
[new BigNumber(2), new BigNumber(2)],
[orders[0].signature, orders[1].signature],
deployment.exchange.getSelector(ExchangeFunctionName.BatchFillOrKillOrders),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.times(2),
gasPrice: DeploymentManager.gasPrice,
});
return expect(tx).to.revertWith(new ExchangeRevertErrors.AssetProxyTransferError());
});
it(`batchFillOrdersNoThrow catches revert on invalid asset`, async () => {
const receipt = await broker
.batchBrokerTrade(
[...godsUnchainedTokenIds.slice(0, 3), godsUnchainedTokenIds[4]], // Last card isn't valid for 2nd order
orders,
[new BigNumber(2), new BigNumber(2)],
[orders[0].signature, orders[1].signature],
deployment.exchange.getSelector(ExchangeFunctionName.BatchFillOrdersNoThrow),
[],
[],
)
.awaitTransactionSuccessAsync({
from: taker.address,
value: DeploymentManager.protocolFee.times(2),
gasPrice: DeploymentManager.gasPrice,
});
const expectedBalances = simulateBrokerFills(
godsUnchainedTokenIds.slice(0, 2), // First order gets filled
[orders[0]],
[new BigNumber(2)],
receipt,
);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
});
});
describe('Data encoding/decoding tools', () => {
const MAX_UINT8 = 2 ** 8 - 1;
const MAX_UINT16 = 2 ** 16 - 1;
it('correctly decodes property data', async () => {
const properties = {
proto: getRandomInteger(0, MAX_UINT16),
quality: getRandomInteger(0, MAX_UINT8),
};
const encoded = encodePropertyData(properties);
const decoded = decodePropertyData(encoded);
expect(decoded.proto).to.bignumber.equal(properties.proto);
expect(decoded.quality).to.bignumber.equal(properties.quality);
});
it('correctly decodes broker asset data', async () => {
const properties = {
proto: getRandomInteger(0, MAX_UINT16),
quality: getRandomInteger(0, MAX_UINT8),
};
const encoded = encodeBrokerAssetData(randomAddress(), randomAddress(), randomAddress(), properties);
const decoded = decodeBrokerAssetData(encoded);
expect(decoded.proto).to.bignumber.equal(properties.proto);
expect(decoded.quality).to.bignumber.equal(properties.quality);
});
});
}); // tslint:disable-line:max-file-line-count