Merge branch '3.0' into feat/3.0/optimizeConstants

This commit is contained in:
Amir Bandeali 2019-08-15 15:20:48 -07:00
commit 7ac30c5153
11 changed files with 2101 additions and 171 deletions

View File

@ -25,6 +25,8 @@ import "../src/LibFillResults.sol";
contract TestLibFillResults {
using LibFillResults for *;
function calculateFillResults(
LibOrder.Order memory order,
uint256 takerAssetFilledAmount

File diff suppressed because it is too large Load Diff

View File

@ -1,72 +1,157 @@
import { addressUtils, blockchainTests, constants, describe, expect } from '@0x/contracts-test-utils';
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { blockchainTests, constants, describe, expect, hexRandom } from '@0x/contracts-test-utils';
import { eip712Utils, orderHashUtils } from '@0x/order-utils';
import { Order } from '@0x/types';
import { BigNumber, signTypedDataUtils } from '@0x/utils';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { artifacts, TestLibOrderContract } from '../src';
blockchainTests('LibOrder', env => {
let libOrderContract: TestLibOrderContract;
let order: Order;
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
const randomHash = () => hexRandom(constants.WORD_LENGTH);
const randomUint256 = () => new BigNumber(randomHash());
const randomAssetData = () => hexRandom(36);
const EMPTY_ORDER: Order = {
domain: {
verifyingContractAddress: constants.NULL_ADDRESS,
chainId: 0,
},
senderAddress: constants.NULL_ADDRESS,
makerAddress: constants.NULL_ADDRESS,
takerAddress: constants.NULL_ADDRESS,
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
makerAssetAmount: constants.ZERO_AMOUNT,
takerAssetAmount: constants.ZERO_AMOUNT,
makerAssetData: constants.NULL_BYTES,
takerAssetData: constants.NULL_BYTES,
makerFeeAssetData: constants.NULL_BYTES,
takerFeeAssetData: constants.NULL_BYTES,
salt: constants.ZERO_AMOUNT,
feeRecipientAddress: constants.NULL_ADDRESS,
expirationTimeSeconds: constants.ZERO_AMOUNT,
};
before(async () => {
libOrderContract = await TestLibOrderContract.deployFrom0xArtifactAsync(
artifacts.TestLibOrder,
env.provider,
env.txDefaults,
);
const domain = {
verifyingContractAddress: libOrderContract.address,
chainId: 1,
};
order = {
...constants.STATIC_ORDER_PARAMS,
makerAddress: addressUtils.generatePseudoRandomAddress(),
takerAddress: addressUtils.generatePseudoRandomAddress(),
senderAddress: addressUtils.generatePseudoRandomAddress(),
feeRecipientAddress: addressUtils.generatePseudoRandomAddress(),
makerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()),
takerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()),
makerFeeAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()),
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()),
salt: new BigNumber(0),
expirationTimeSeconds: new BigNumber(0),
domain,
};
});
describe('LibOrder', () => {
describe('getOrderHash', () => {
it('should return the correct orderHash', async () => {
const domainHash = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...order.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
}),
);
const orderHashHex = await libOrderContract.getTypedDataHash.callAsync(order, domainHash);
expect(orderHashUtils.getOrderHashHex(order)).to.be.equal(orderHashHex);
/**
* Tests the `getTypedDataHash()` function against a reference hash.
*/
async function testGetTypedDataHashAsync(order: Order): Promise<void> {
const expectedHash = orderHashUtils.getOrderHashHex(order);
const domainHash = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...order.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
}),
);
const actualHash = await libOrderContract.getTypedDataHash.callAsync(order, domainHash);
expect(actualHash).to.be.eq(expectedHash);
}
describe('getTypedDataHash', () => {
it('should correctly hash an empty order', async () => {
await testGetTypedDataHashAsync({
...EMPTY_ORDER,
domain: {
...EMPTY_ORDER.domain,
verifyingContractAddress: libOrderContract.address,
},
});
it('orderHash should differ if the domain hash is different', async () => {
const domainHash1 = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...order.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
}),
);
const domainHash2 = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...order.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
chainId: 1337,
}),
);
const orderHashHex1 = await libOrderContract.getTypedDataHash.callAsync(order, domainHash1);
const orderHashHex2 = await libOrderContract.getTypedDataHash.callAsync(order, domainHash2);
expect(orderHashHex1).to.be.not.equal(orderHashHex2);
});
it('should correctly hash a non-empty order', async () => {
await testGetTypedDataHashAsync({
domain: {
verifyingContractAddress: libOrderContract.address,
chainId: 1337,
},
senderAddress: randomAddress(),
makerAddress: randomAddress(),
takerAddress: randomAddress(),
makerFee: randomUint256(),
takerFee: randomUint256(),
makerAssetAmount: randomUint256(),
takerAssetAmount: randomUint256(),
makerAssetData: randomAssetData(),
takerAssetData: randomAssetData(),
makerFeeAssetData: randomAssetData(),
takerFeeAssetData: randomAssetData(),
salt: randomUint256(),
feeRecipientAddress: randomAddress(),
expirationTimeSeconds: randomUint256(),
});
});
it('orderHash should differ if the domain hash is different', async () => {
const domainHash1 = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...EMPTY_ORDER.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
}),
);
const domainHash2 = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...EMPTY_ORDER.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
chainId: 1337,
}),
);
const orderHashHex1 = await libOrderContract.getTypedDataHash.callAsync(EMPTY_ORDER, domainHash1);
const orderHashHex2 = await libOrderContract.getTypedDataHash.callAsync(EMPTY_ORDER, domainHash2);
expect(orderHashHex1).to.be.not.equal(orderHashHex2);
});
});
/**
* Tests the `getStructHash()` function against a reference hash.
*/
async function testGetStructHashAsync(order: Order): Promise<void> {
const typedData = eip712Utils.createOrderTypedData(order);
const expectedHash = ethUtil.bufferToHex(signTypedDataUtils.generateTypedDataHashWithoutDomain(typedData));
const actualHash = await libOrderContract.getStructHash.callAsync(order);
expect(actualHash).to.be.eq(expectedHash);
}
describe('getStructHash', () => {
it('should correctly hash an empty order', async () => {
await testGetStructHashAsync(EMPTY_ORDER);
});
it('should correctly hash a non-empty order', async () => {
await testGetStructHashAsync({
// The domain is not used in this test, so it's okay if it is left empty.
domain: {
verifyingContractAddress: constants.NULL_ADDRESS,
chainId: 0,
},
senderAddress: randomAddress(),
makerAddress: randomAddress(),
takerAddress: randomAddress(),
makerFee: randomUint256(),
takerFee: randomUint256(),
makerAssetAmount: randomUint256(),
takerAssetAmount: randomUint256(),
makerAssetData: randomAssetData(),
takerAssetData: randomAssetData(),
makerFeeAssetData: randomAssetData(),
takerFeeAssetData: randomAssetData(),
salt: randomUint256(),
feeRecipientAddress: randomAddress(),
expirationTimeSeconds: randomUint256(),
});
});
});

View File

@ -1,74 +1,132 @@
import { addressUtils, blockchainTests, constants, describe, expect } from '@0x/contracts-test-utils';
import { transactionHashUtils } from '@0x/order-utils';
import { blockchainTests, constants, describe, expect, hexRandom } from '@0x/contracts-test-utils';
import { eip712Utils, transactionHashUtils } from '@0x/order-utils';
import { ZeroExTransaction } from '@0x/types';
import { BigNumber, signTypedDataUtils } from '@0x/utils';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { artifacts, TestLibZeroExTransactionContract } from '../src';
blockchainTests('LibZeroExTransaction', env => {
let libZeroExTransactionContract: TestLibZeroExTransactionContract;
let zeroExTransaction: ZeroExTransaction;
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
const randomHash = () => hexRandom(constants.WORD_LENGTH);
const randomUint256 = () => new BigNumber(randomHash());
const randomAssetData = () => hexRandom(36);
const EMPTY_TRANSACTION: ZeroExTransaction = {
salt: constants.ZERO_AMOUNT,
expirationTimeSeconds: constants.ZERO_AMOUNT,
signerAddress: constants.NULL_ADDRESS,
data: constants.NULL_BYTES,
domain: {
verifyingContractAddress: constants.NULL_ADDRESS,
chainId: 0,
},
};
before(async () => {
libZeroExTransactionContract = await TestLibZeroExTransactionContract.deployFrom0xArtifactAsync(
artifacts.TestLibZeroExTransaction,
env.provider,
env.txDefaults,
);
const domain = {
verifyingContractAddress: libZeroExTransactionContract.address,
chainId: 1,
};
zeroExTransaction = {
signerAddress: addressUtils.generatePseudoRandomAddress(),
salt: new BigNumber(0),
expirationTimeSeconds: new BigNumber(0),
data: constants.NULL_BYTES,
domain,
};
});
describe('LibZeroExTransaction', () => {
describe('getTransactionHash', () => {
it('should return the correct transactionHash', async () => {
const domainHash = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...zeroExTransaction.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
}),
);
const orderHashHex = await libZeroExTransactionContract.getTypedDataHash.callAsync(
zeroExTransaction,
domainHash,
);
expect(transactionHashUtils.getTransactionHashHex(zeroExTransaction)).to.be.equal(orderHashHex);
/**
* Tests the `getTypedDataHash()` function against a reference hash.
*/
async function testGetTypedDataHashAsync(transaction: ZeroExTransaction): Promise<void> {
const expectedHash = transactionHashUtils.getTransactionHashHex(transaction);
const domainHash = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...transaction.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
}),
);
const actualHash = await libZeroExTransactionContract.getTypedDataHash.callAsync(transaction, domainHash);
expect(actualHash).to.be.eq(expectedHash);
}
describe('getTypedDataHash', () => {
it('should correctly hash an empty transaction', async () => {
await testGetTypedDataHashAsync({
...EMPTY_TRANSACTION,
domain: {
...EMPTY_TRANSACTION.domain,
verifyingContractAddress: libZeroExTransactionContract.address,
},
});
it('transactionHash should differ if the domain hash is different', async () => {
const domainHash1 = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...zeroExTransaction.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
}),
);
const domainHash2 = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...zeroExTransaction.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
chainId: 1337,
}),
);
const transactionHashHex1 = await libZeroExTransactionContract.getTypedDataHash.callAsync(
zeroExTransaction,
domainHash1,
);
const transactionHashHex2 = await libZeroExTransactionContract.getTypedDataHash.callAsync(
zeroExTransaction,
domainHash2,
);
expect(transactionHashHex1).to.be.not.equal(transactionHashHex2);
});
it('should correctly hash a non-empty transaction', async () => {
await testGetTypedDataHashAsync({
salt: randomUint256(),
expirationTimeSeconds: randomUint256(),
signerAddress: randomAddress(),
data: randomAssetData(),
domain: {
...EMPTY_TRANSACTION.domain,
verifyingContractAddress: libZeroExTransactionContract.address,
},
});
});
it('transactionHash should differ if the domain hash is different', async () => {
const domainHash1 = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...EMPTY_TRANSACTION.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
}),
);
const domainHash2 = ethUtil.bufferToHex(
signTypedDataUtils.generateDomainHash({
...EMPTY_TRANSACTION.domain,
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
chainId: 1337,
}),
);
const transactionHashHex1 = await libZeroExTransactionContract.getTypedDataHash.callAsync(
EMPTY_TRANSACTION,
domainHash1,
);
const transactionHashHex2 = await libZeroExTransactionContract.getTypedDataHash.callAsync(
EMPTY_TRANSACTION,
domainHash2,
);
expect(transactionHashHex1).to.be.not.equal(transactionHashHex2);
});
});
/**
* Tests the `getStructHash()` function against a reference hash.
*/
async function testGetStructHashAsync(transaction: ZeroExTransaction): Promise<void> {
const typedData = eip712Utils.createZeroExTransactionTypedData(transaction);
const expectedHash = ethUtil.bufferToHex(signTypedDataUtils.generateTypedDataHashWithoutDomain(typedData));
const actualHash = await libZeroExTransactionContract.getStructHash.callAsync(transaction);
expect(actualHash).to.be.eq(expectedHash);
}
describe('getStructHash', () => {
it('should correctly hash an empty transaction', async () => {
await testGetStructHashAsync(EMPTY_TRANSACTION);
});
it('should correctly hash a non-empty transaction', async () => {
await testGetStructHashAsync({
salt: randomUint256(),
expirationTimeSeconds: randomUint256(),
signerAddress: randomAddress(),
data: randomAssetData(),
// The domain is not used in this test, so it's okay if it is left empty.
domain: {
verifyingContractAddress: constants.NULL_ADDRESS,
chainId: 0,
},
});
});
});

View File

@ -424,7 +424,7 @@ contract MixinMatchOrders is
address takerAddress,
LibFillResults.MatchedFillResults memory matchedFillResults
)
private
internal
{
address leftFeeRecipientAddress = leftOrder.feeRecipientAddress;
address rightFeeRecipientAddress = rightOrder.feeRecipientAddress;

View File

@ -41,6 +41,21 @@ contract TestExchangeInternals is
Exchange(chainId)
{}
function assertValidMatch(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder
)
public
view
{
_assertValidMatch(
leftOrder,
rightOrder,
getOrderInfo(leftOrder),
getOrderInfo(rightOrder)
);
}
/// @dev Call `_updateFilledState()` but first set `filled[order]` to
/// `orderTakerAssetFilledAmount`.
function testUpdateFilledState(
@ -73,6 +88,26 @@ contract TestExchangeInternals is
_settleOrder(orderHash, order, takerAddress, fillResults);
}
function settleMatchOrders(
bytes32 leftOrderHash,
bytes32 rightOrderHash,
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
address takerAddress,
LibFillResults.MatchedFillResults memory matchedFillResults
)
public
{
_settleMatchedOrders(
leftOrderHash,
rightOrderHash,
leftOrder,
rightOrder,
takerAddress,
matchedFillResults
);
}
/// @dev Overidden to only log arguments so we can test `_settleOrder()`.
function _dispatchTransferFrom(
bytes32 orderHash,

View File

@ -271,7 +271,7 @@ describe('AssetProxyDispatcher', () => {
return expect(tx).to.revertWith(expectedError);
});
it('should should revert with the correct error when assetData length < 4 bytes', async () => {
it('should revert with the correct error when assetData length < 4 bytes', async () => {
await assetProxyDispatcher.registerAssetProxy.awaitTransactionSuccessAsync(erc20Proxy.address, {
from: owner,
});

View File

@ -1,12 +1,14 @@
import { ReferenceFunctions as LibReferenceFunctions } from '@0x/contracts-exchange-libs';
import { blockchainTests, constants, expect, hexRandom, LogDecoder } from '@0x/contracts-test-utils';
import { OrderWithoutDomain as Order } from '@0x/types';
import { ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils';
import { Order, OrderWithoutDomain } from '@0x/types';
import { BigNumber, SafeMathRevertErrors } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { LogWithDecodedArgs } from 'ethereum-types';
import * as _ from 'lodash';
import {
artifacts,
ReferenceFunctions,
TestExchangeInternalsContract,
TestExchangeInternalsDispatchTransferFromCalledEventArgs,
TestExchangeInternalsFillEventArgs,
@ -23,7 +25,9 @@ blockchainTests('Exchange core internal functions', env => {
let senderAddress: string;
before(async () => {
[senderAddress] = await env.getAccountAddressesAsync();
const accounts = await env.getAccountAddressesAsync();
senderAddress = accounts[0];
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
artifacts.TestExchangeInternals,
env.provider,
@ -33,6 +37,110 @@ blockchainTests('Exchange core internal functions', env => {
logDecoder = new LogDecoder(env.web3Wrapper, artifacts);
});
blockchainTests('assertValidMatch', () => {
const ORDER_DEFAULTS = {
senderAddress: randomAddress(),
makerAddress: randomAddress(),
takerAddress: randomAddress(),
makerFee: ONE_ETHER.times(0.001),
takerFee: ONE_ETHER.times(0.003),
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER.times(0.5),
makerAssetData: randomAssetData(),
takerAssetData: randomAssetData(),
makerFeeAssetData: randomAssetData(),
takerFeeAssetData: randomAssetData(),
salt: new BigNumber(_.random(0, 1e8)),
feeRecipientAddress: randomAddress(),
expirationTimeSeconds: new BigNumber(_.random(0, 1e8)),
domain: {
verifyingContractAddress: constants.NULL_ADDRESS,
chainId: 1337, // The chain id for the isolated exchange
},
};
function makeOrder(details?: Partial<Order>): Order {
return _.assign({}, ORDER_DEFAULTS, details);
}
before(async () => {
ORDER_DEFAULTS.domain.verifyingContractAddress = testExchange.address;
});
it('should revert if the maker asset multiplication should overflow', async () => {
const leftOrder = makeOrder({
makerAssetAmount: constants.MAX_UINT256,
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18),
});
const rightOrder = makeOrder({
makerAssetAmount: constants.MAX_UINT256_ROOT,
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18),
});
const expectedError = new SafeMathRevertErrors.SafeMathError(
SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow,
leftOrder.makerAssetAmount,
rightOrder.makerAssetAmount,
);
return expect(testExchange.assertValidMatch.callAsync(leftOrder, rightOrder)).to.revertWith(expectedError);
});
it('should revert if the taker asset multiplication should overflow', async () => {
const leftOrder = makeOrder({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18),
takerAssetAmount: constants.MAX_UINT256,
});
const rightOrder = makeOrder({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18),
takerAssetAmount: constants.MAX_UINT256_ROOT,
});
const expectedError = new SafeMathRevertErrors.SafeMathError(
SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow,
leftOrder.takerAssetAmount,
rightOrder.takerAssetAmount,
);
return expect(testExchange.assertValidMatch.callAsync(leftOrder, rightOrder)).to.revertWith(expectedError);
});
it('should revert if the prices of the left order is less than the price of the right order', async () => {
const leftOrder = makeOrder({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(49, 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18),
});
const rightOrder = makeOrder({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18),
});
const orderHashHexLeft = orderHashUtils.getOrderHashHex(leftOrder);
const orderHashHexRight = orderHashUtils.getOrderHashHex(rightOrder);
const expectedError = new ExchangeRevertErrors.NegativeSpreadError(orderHashHexLeft, orderHashHexRight);
return expect(testExchange.assertValidMatch.callAsync(leftOrder, rightOrder)).to.revertWith(expectedError);
});
it('should succeed if the prices of the left and right orders are equal', async () => {
const leftOrder = makeOrder({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18),
});
const rightOrder = makeOrder({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18),
});
return expect(testExchange.assertValidMatch.callAsync(leftOrder, rightOrder)).to.be.fulfilled('');
});
it('should succeed if the price of the left order is higher than the price of the right', async () => {
const leftOrder = makeOrder({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18),
});
const rightOrder = makeOrder({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18),
});
return expect(testExchange.assertValidMatch.callAsync(leftOrder, rightOrder)).to.be.fulfilled('');
});
});
blockchainTests.resets('updateFilledState', async () => {
const ORDER_DEFAULTS = {
senderAddress: randomAddress(),
@ -51,18 +159,18 @@ blockchainTests('Exchange core internal functions', env => {
expirationTimeSeconds: new BigNumber(_.random(0, 1e8)),
};
function makeOrder(details?: Partial<Order>): Order {
function makeOrder(details?: Partial<OrderWithoutDomain>): OrderWithoutDomain {
return _.assign({}, ORDER_DEFAULTS, details);
}
async function testUpdateFilledStateAsync(
order: Order,
order: OrderWithoutDomain,
orderTakerAssetFilledAmount: BigNumber,
takerAddress: string,
takerAssetFillAmount: BigNumber,
): Promise<void> {
const orderHash = randomHash();
const fillResults = ReferenceFunctions.calculateFillResults(order, takerAssetFillAmount);
const fillResults = LibReferenceFunctions.calculateFillResults(order, takerAssetFillAmount);
const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount);
// CAll `testUpdateFilledState()`, which will set the `filled`
// state for this order to `orderTakerAssetFilledAmount` before
@ -198,5 +306,305 @@ blockchainTests('Exchange core internal functions', env => {
expect(logs[3].args.amount).to.bignumber.eq(fillResults.makerFeePaid);
});
});
blockchainTests('settleMatchOrders', () => {
const getOrder = () => {
return {
senderAddress: randomAddress(),
makerAddress: randomAddress(),
takerAddress: randomAddress(),
makerFee: ONE_ETHER.times(0.001),
takerFee: ONE_ETHER.times(0.003),
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER.times(0.5),
makerAssetData: randomAssetData(),
takerAssetData: randomAssetData(),
makerFeeAssetData: randomAssetData(),
takerFeeAssetData: randomAssetData(),
salt: new BigNumber(_.random(0, 1e8)),
feeRecipientAddress: randomAddress(),
expirationTimeSeconds: new BigNumber(_.random(0, 1e8)),
};
};
it('should revert if the taker fee paid fields addition overflow and left.feeRecipient == right.feeRecipient && left.takerFeeAssetData == right.takerFeeAssetData', async () => {
// Get the arguments for the call to `settleMatchOrders()`.
const leftOrder = getOrder();
const rightOrder = getOrder();
const leftOrderHash = randomHash();
const rightOrderHash = randomHash();
const takerAddress = randomAddress();
const matchedFillResults = {
left: {
makerAssetFilledAmount: ONE_ETHER.times(2),
takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: constants.MAX_UINT256,
},
right: {
takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: constants.MAX_UINT256_ROOT,
},
profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2),
};
// Set the fee recipient addresses and the taker fee asset data fields to be the same
rightOrder.feeRecipientAddress = leftOrder.feeRecipientAddress;
rightOrder.takerFeeAssetData = leftOrder.takerFeeAssetData;
// The expected error that should be thrown by the function.
const expectedError = new SafeMathRevertErrors.SafeMathError(
SafeMathRevertErrors.SafeMathErrorCodes.Uint256AdditionOverflow,
matchedFillResults.left.takerFeePaid,
matchedFillResults.right.takerFeePaid,
);
// Ensure that the call to `settleMatchOrders()` fails with the expected error.
const tx = testExchange.settleMatchOrders.sendTransactionAsync(
leftOrderHash,
rightOrderHash,
leftOrder,
rightOrder,
takerAddress,
matchedFillResults,
);
return expect(tx).to.revertWith(expectedError);
});
it('should succeed if the taker fee paid fields addition overflow and left.feeRecipient != right.feeRecipient || left.takerFeeAssetData != right.takerFeeAssetData', async () => {
// Get the arguments for the call to `settleMatchOrders()`.
const leftOrder = getOrder();
const rightOrder = getOrder();
const leftOrderHash = randomHash();
const rightOrderHash = randomHash();
const takerAddress = randomAddress();
const matchedFillResults = {
left: {
makerAssetFilledAmount: ONE_ETHER.times(2),
takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: constants.MAX_UINT256,
},
right: {
takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: constants.MAX_UINT256_ROOT,
},
profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2),
};
// The call to `settleMatchOrders()` should be successful.
return expect(
testExchange.settleMatchOrders.sendTransactionAsync(
leftOrderHash,
rightOrderHash,
leftOrder,
rightOrder,
takerAddress,
matchedFillResults,
),
).to.be.fulfilled('');
});
it('calls `_dispatchTransferFrom()` to collect fees from the left order when left.feeRecipient == right.feeRecipient && left.takerFeeAssetData == right.takerFeeAssetData', async () => {
const leftOrder = getOrder();
const rightOrder = getOrder();
const leftOrderHash = randomHash();
const rightOrderHash = randomHash();
const takerAddress = randomAddress();
const matchedFillResults = {
left: {
makerAssetFilledAmount: ONE_ETHER.times(2),
takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: ONE_ETHER.times(0.025),
},
right: {
takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: ONE_ETHER.times(0.05),
},
profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2),
};
// Set the fee recipient addresses and the taker fee asset data fields to be the same
rightOrder.feeRecipientAddress = leftOrder.feeRecipientAddress;
rightOrder.takerFeeAssetData = leftOrder.takerFeeAssetData;
// Call settleMatchOrders and collect the logs
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await testExchange.settleMatchOrders.sendTransactionAsync(
leftOrderHash,
rightOrderHash,
leftOrder,
rightOrder,
takerAddress,
matchedFillResults,
),
);
const logs = receipt.logs as Array<
LogWithDecodedArgs<TestExchangeInternalsDispatchTransferFromCalledEventArgs>
>;
// Ensure that the logs have the correct lengths and names
expect(logs.length).to.be.eq(7);
expect(_.every(logs, log => log.event === 'DispatchTransferFromCalled')).to.be.true();
// Right maker asset -> left maker
expect(logs[0].args.orderHash).to.be.eq(rightOrderHash);
expect(logs[0].args.assetData).to.be.eq(rightOrder.makerAssetData);
expect(logs[0].args.from).to.be.eq(rightOrder.makerAddress);
expect(logs[0].args.to).to.be.eq(leftOrder.makerAddress);
expect(logs[0].args.amount).bignumber.to.be.eq(matchedFillResults.left.takerAssetFilledAmount);
// Left maker asset -> right maker
expect(logs[1].args.orderHash).to.be.eq(leftOrderHash);
expect(logs[1].args.assetData).to.be.eq(leftOrder.makerAssetData);
expect(logs[1].args.from).to.be.eq(leftOrder.makerAddress);
expect(logs[1].args.to).to.be.eq(rightOrder.makerAddress);
expect(logs[1].args.amount).bignumber.to.be.eq(matchedFillResults.right.takerAssetFilledAmount);
// Right maker fee -> right fee recipient
expect(logs[2].args.orderHash).to.be.eq(rightOrderHash);
expect(logs[2].args.assetData).to.be.eq(rightOrder.makerFeeAssetData);
expect(logs[2].args.from).to.be.eq(rightOrder.makerAddress);
expect(logs[2].args.to).to.be.eq(rightOrder.feeRecipientAddress);
expect(logs[2].args.amount).bignumber.to.be.eq(matchedFillResults.right.makerFeePaid);
// Left maker fee -> left fee recipient
expect(logs[3].args.orderHash).to.be.eq(leftOrderHash);
expect(logs[3].args.assetData).to.be.eq(leftOrder.makerFeeAssetData);
expect(logs[3].args.from).to.be.eq(leftOrder.makerAddress);
expect(logs[3].args.to).to.be.eq(leftOrder.feeRecipientAddress);
expect(logs[3].args.amount).bignumber.to.be.eq(matchedFillResults.left.makerFeePaid);
// Left maker -> taker profit
expect(logs[4].args.orderHash).to.be.eq(leftOrderHash);
expect(logs[4].args.assetData).to.be.eq(leftOrder.makerAssetData);
expect(logs[4].args.from).to.be.eq(leftOrder.makerAddress);
expect(logs[4].args.to).to.be.eq(takerAddress);
expect(logs[4].args.amount).bignumber.to.be.eq(matchedFillResults.profitInLeftMakerAsset);
// right maker -> taker profit
expect(logs[5].args.orderHash).to.be.eq(rightOrderHash);
expect(logs[5].args.assetData).to.be.eq(rightOrder.makerAssetData);
expect(logs[5].args.from).to.be.eq(rightOrder.makerAddress);
expect(logs[5].args.to).to.be.eq(takerAddress);
expect(logs[5].args.amount).bignumber.to.be.eq(matchedFillResults.profitInRightMakerAsset);
// taker fees -> fee recipient
expect(logs[6].args.orderHash).to.be.eq(leftOrderHash);
expect(logs[6].args.assetData).to.be.eq(leftOrder.takerFeeAssetData);
expect(logs[6].args.from).to.be.eq(takerAddress);
expect(logs[6].args.to).to.be.eq(leftOrder.feeRecipientAddress);
expect(logs[6].args.amount).bignumber.to.be.eq(ONE_ETHER.times(0.075));
});
it('calls `_dispatchTransferFrom()` from in the right order when the fee recipients and taker fee asset data are not the same', async () => {
const leftOrder = getOrder();
const rightOrder = getOrder();
const leftOrderHash = randomHash();
const rightOrderHash = randomHash();
const takerAddress = randomAddress();
const matchedFillResults = {
left: {
makerAssetFilledAmount: ONE_ETHER.times(2),
takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: ONE_ETHER.times(0.025),
},
right: {
takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: ONE_ETHER.times(0.05),
},
profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2),
};
// Call settleMatchOrders and collect the logs
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await testExchange.settleMatchOrders.sendTransactionAsync(
leftOrderHash,
rightOrderHash,
leftOrder,
rightOrder,
takerAddress,
matchedFillResults,
),
);
const logs = receipt.logs as Array<
LogWithDecodedArgs<TestExchangeInternalsDispatchTransferFromCalledEventArgs>
>;
// Ensure that the logs have the correct lengths and names
expect(logs.length).to.be.eq(8);
expect(_.every(logs, log => log.event === 'DispatchTransferFromCalled')).to.be.true();
// Right maker asset -> left maker
expect(logs[0].args.orderHash).to.be.eq(rightOrderHash);
expect(logs[0].args.assetData).to.be.eq(rightOrder.makerAssetData);
expect(logs[0].args.from).to.be.eq(rightOrder.makerAddress);
expect(logs[0].args.to).to.be.eq(leftOrder.makerAddress);
expect(logs[0].args.amount).bignumber.to.be.eq(matchedFillResults.left.takerAssetFilledAmount);
// Left maker asset -> right maker
expect(logs[1].args.orderHash).to.be.eq(leftOrderHash);
expect(logs[1].args.assetData).to.be.eq(leftOrder.makerAssetData);
expect(logs[1].args.from).to.be.eq(leftOrder.makerAddress);
expect(logs[1].args.to).to.be.eq(rightOrder.makerAddress);
expect(logs[1].args.amount).bignumber.to.be.eq(matchedFillResults.right.takerAssetFilledAmount);
// Right maker fee -> right fee recipient
expect(logs[2].args.orderHash).to.be.eq(rightOrderHash);
expect(logs[2].args.assetData).to.be.eq(rightOrder.makerFeeAssetData);
expect(logs[2].args.from).to.be.eq(rightOrder.makerAddress);
expect(logs[2].args.to).to.be.eq(rightOrder.feeRecipientAddress);
expect(logs[2].args.amount).bignumber.to.be.eq(matchedFillResults.right.makerFeePaid);
// Left maker fee -> left fee recipient
expect(logs[3].args.orderHash).to.be.eq(leftOrderHash);
expect(logs[3].args.assetData).to.be.eq(leftOrder.makerFeeAssetData);
expect(logs[3].args.from).to.be.eq(leftOrder.makerAddress);
expect(logs[3].args.to).to.be.eq(leftOrder.feeRecipientAddress);
expect(logs[3].args.amount).bignumber.to.be.eq(matchedFillResults.left.makerFeePaid);
// Left maker -> taker profit
expect(logs[4].args.orderHash).to.be.eq(leftOrderHash);
expect(logs[4].args.assetData).to.be.eq(leftOrder.makerAssetData);
expect(logs[4].args.from).to.be.eq(leftOrder.makerAddress);
expect(logs[4].args.to).to.be.eq(takerAddress);
expect(logs[4].args.amount).bignumber.to.be.eq(matchedFillResults.profitInLeftMakerAsset);
// right maker -> taker profit
expect(logs[5].args.orderHash).to.be.eq(rightOrderHash);
expect(logs[5].args.assetData).to.be.eq(rightOrder.makerAssetData);
expect(logs[5].args.from).to.be.eq(rightOrder.makerAddress);
expect(logs[5].args.to).to.be.eq(takerAddress);
expect(logs[5].args.amount).bignumber.to.be.eq(matchedFillResults.profitInRightMakerAsset);
// Right taker fee -> right fee recipient
expect(logs[6].args.orderHash).to.be.eq(rightOrderHash);
expect(logs[6].args.assetData).to.be.eq(rightOrder.takerFeeAssetData);
expect(logs[6].args.from).to.be.eq(takerAddress);
expect(logs[6].args.to).to.be.eq(rightOrder.feeRecipientAddress);
expect(logs[6].args.amount).bignumber.to.be.eq(matchedFillResults.right.takerFeePaid);
// Right taker fee -> right fee recipient
expect(logs[7].args.orderHash).to.be.eq(leftOrderHash);
expect(logs[7].args.assetData).to.be.eq(leftOrder.takerFeeAssetData);
expect(logs[7].args.from).to.be.eq(takerAddress);
expect(logs[7].args.to).to.be.eq(leftOrder.feeRecipientAddress);
expect(logs[7].args.amount).bignumber.to.be.eq(matchedFillResults.left.takerFeePaid);
});
});
});
// tslint:disable-line:max-file-line-count

View File

@ -1336,7 +1336,7 @@ describe('matchOrders', () => {
});
});
describe('matchOrdersWithMaximalFill', () => {
it('Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorCeil but fail isRoundingErrorFloor', async () => {
it('should transfer correct amounts when right order is fully filled and values pass isRoundingErrorCeil but fail isRoundingErrorFloor', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAddress: makerAddressLeft,

View File

@ -11,55 +11,6 @@ chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
/**
* Tests a specific instance of EIP712 domain hashing.
* @param lib The LibEIP712 contract to call.
* @param name The name of the domain.
* @param version The version of the domain.
* @param chainId The chain id of the domain.
* @param verifyingContractAddress The verifying contract address of the domain.
*/
async function testHashEIP712DomainAsync(
lib: TestLibEIP712Contract,
name: string,
version: string,
chainId: number,
verifyingContractAddress: string,
): Promise<void> {
const expectedHash = signTypedDataUtils.generateDomainHash({
name,
version,
chainId,
verifyingContractAddress,
});
const actualHash = await lib.externalHashEIP712DomainSeperator.callAsync(
name,
version,
new BigNumber(chainId),
verifyingContractAddress,
);
expect(actualHash).to.be.eq(hexConcat(expectedHash));
}
/**
* Tests a specific instance of EIP712 message hashing.
* @param lib The LibEIP712 contract to call.
* @param domainHash The hash of the EIP712 domain of this instance.
* @param hashStruct The hash of the struct of this instance.
*/
async function testHashEIP712MessageAsync(
lib: TestLibEIP712Contract,
domainHash: string,
hashStruct: string,
): Promise<void> {
const input = '0x1901'.concat(
domainHash.slice(2, domainHash.length).concat(hashStruct.slice(2, hashStruct.length)),
);
const expectedHash = '0x'.concat(ethUtil.sha3(input).toString('hex'));
const actualHash = await lib.externalHashEIP712Message.callAsync(domainHash, hashStruct);
expect(actualHash).to.be.eq(expectedHash);
}
describe('LibEIP712', () => {
let lib: TestLibEIP712Contract;
@ -73,28 +24,78 @@ describe('LibEIP712', () => {
await blockchainLifecycle.revertAsync();
});
/**
* Tests a specific instance of EIP712 domain hashing.
* @param lib The LibEIP712 contract to call.
* @param name The name of the domain.
* @param version The version of the domain.
* @param chainId The chain id of the domain.
* @param verifyingContractAddress The verifying contract address of the domain.
*/
async function testHashEIP712DomainAsync(
name: string,
version: string,
chainId: number,
verifyingContractAddress: string,
): Promise<void> {
const expectedHash = signTypedDataUtils.generateDomainHash({
name,
version,
chainId,
verifyingContractAddress,
});
const actualHash = await lib.externalHashEIP712DomainSeperator.callAsync(
name,
version,
new BigNumber(chainId),
verifyingContractAddress,
);
expect(actualHash).to.be.eq(hexConcat(expectedHash));
}
describe('_hashEIP712Domain', async () => {
it('should correctly hash empty input', async () => {
await testHashEIP712DomainAsync(lib, '', '', 0, constants.NULL_ADDRESS);
await testHashEIP712DomainAsync('', '', 0, constants.NULL_ADDRESS);
});
it('should correctly hash non-empty input', async () => {
await testHashEIP712DomainAsync(lib, '_hashEIP712Domain', '1.0', 62, lib.address);
await testHashEIP712DomainAsync('_hashEIP712Domain', '1.0', 62, lib.address);
});
it('should correctly hash non-empty input', async () => {
await testHashEIP712DomainAsync(lib, '_hashEIP712Domain', '2.0', 0, lib.address);
await testHashEIP712DomainAsync('_hashEIP712Domain', '2.0', 0, lib.address);
});
});
/**
* Tests a specific instance of EIP712 message hashing.
* @param lib The LibEIP712 contract to call.
* @param domainHash The hash of the EIP712 domain of this instance.
* @param hashStruct The hash of the struct of this instance.
*/
async function testHashEIP712MessageAsync(domainHash: string, hashStruct: string): Promise<void> {
// Remove the hex prefix from the domain hash and the hash struct
const unprefixedDomainHash = domainHash.slice(2, domainHash.length);
const unprefixedHashStruct = hashStruct.slice(2, hashStruct.length);
// Hash the provided input to get the expected hash
const input = '0x1901'.concat(unprefixedDomainHash.concat(unprefixedHashStruct));
const expectedHash = '0x'.concat(ethUtil.sha3(input).toString('hex'));
// Get the actual hash by calling the smart contract
const actualHash = await lib.externalHashEIP712Message.callAsync(domainHash, hashStruct);
// Verify that the actual hash matches the expected hash
expect(actualHash).to.be.eq(expectedHash);
}
describe('_hashEIP712Message', () => {
it('should correctly hash empty input', async () => {
await testHashEIP712MessageAsync(lib, constants.NULL_BYTES32, constants.NULL_BYTES32);
await testHashEIP712MessageAsync(constants.NULL_BYTES32, constants.NULL_BYTES32);
});
it('should correctly hash non-empty input', async () => {
await testHashEIP712MessageAsync(
lib,
'0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6', // keccak256(abi.encode(1))
'0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace', // keccak256(abi.encode(2))
);
@ -102,7 +103,6 @@ describe('LibEIP712', () => {
it('should correctly hash non-empty input', async () => {
await testHashEIP712MessageAsync(
lib,
'0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace', // keccak256(abi.encode(2))
'0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b', // keccak256(abi.encode(3))
);

View File

@ -20,6 +20,15 @@ export const signTypedDataUtils = {
]),
);
},
/**
* Generates the EIP712 Typed Data hash for a typed data object without using the domain field. This
* makes hashing easier for non-EIP712 data.
* @param typedData An object that conforms to the EIP712TypedData interface
* @return A Buffer containing the hash of the typed data.
*/
generateTypedDataHashWithoutDomain(typedData: EIP712TypedData): Buffer {
return signTypedDataUtils._structHash(typedData.primaryType, typedData.message, typedData.types);
},
/**
* Generates the hash of a EIP712 Domain with the default schema
* @param domain An EIP712 domain with the default schema containing a name, version, chain id,