742 lines
32 KiB
TypeScript
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
|