Merge branch '3.0' into feat/3.0/optimizeConstants
This commit is contained in:
commit
7ac30c5153
@ -25,6 +25,8 @@ import "../src/LibFillResults.sol";
|
|||||||
|
|
||||||
contract TestLibFillResults {
|
contract TestLibFillResults {
|
||||||
|
|
||||||
|
using LibFillResults for *;
|
||||||
|
|
||||||
function calculateFillResults(
|
function calculateFillResults(
|
||||||
LibOrder.Order memory order,
|
LibOrder.Order memory order,
|
||||||
uint256 takerAssetFilledAmount
|
uint256 takerAssetFilledAmount
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,43 +1,54 @@
|
|||||||
import { addressUtils, blockchainTests, constants, describe, expect } from '@0x/contracts-test-utils';
|
import { blockchainTests, constants, describe, expect, hexRandom } from '@0x/contracts-test-utils';
|
||||||
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
import { eip712Utils, orderHashUtils } from '@0x/order-utils';
|
||||||
import { Order } from '@0x/types';
|
import { Order } from '@0x/types';
|
||||||
import { BigNumber, signTypedDataUtils } from '@0x/utils';
|
import { BigNumber, signTypedDataUtils } from '@0x/utils';
|
||||||
import * as ethUtil from 'ethereumjs-util';
|
import * as ethUtil from 'ethereumjs-util';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { artifacts, TestLibOrderContract } from '../src';
|
import { artifacts, TestLibOrderContract } from '../src';
|
||||||
|
|
||||||
blockchainTests('LibOrder', env => {
|
blockchainTests('LibOrder', env => {
|
||||||
let libOrderContract: TestLibOrderContract;
|
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 () => {
|
before(async () => {
|
||||||
libOrderContract = await TestLibOrderContract.deployFrom0xArtifactAsync(
|
libOrderContract = await TestLibOrderContract.deployFrom0xArtifactAsync(
|
||||||
artifacts.TestLibOrder,
|
artifacts.TestLibOrder,
|
||||||
env.provider,
|
env.provider,
|
||||||
env.txDefaults,
|
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', () => {
|
* Tests the `getTypedDataHash()` function against a reference hash.
|
||||||
it('should return the correct orderHash', async () => {
|
*/
|
||||||
|
async function testGetTypedDataHashAsync(order: Order): Promise<void> {
|
||||||
|
const expectedHash = orderHashUtils.getOrderHashHex(order);
|
||||||
const domainHash = ethUtil.bufferToHex(
|
const domainHash = ethUtil.bufferToHex(
|
||||||
signTypedDataUtils.generateDomainHash({
|
signTypedDataUtils.generateDomainHash({
|
||||||
...order.domain,
|
...order.domain,
|
||||||
@ -45,29 +56,103 @@ blockchainTests('LibOrder', env => {
|
|||||||
version: constants.EIP712_DOMAIN_VERSION,
|
version: constants.EIP712_DOMAIN_VERSION,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const orderHashHex = await libOrderContract.getTypedDataHash.callAsync(order, domainHash);
|
const actualHash = await libOrderContract.getTypedDataHash.callAsync(order, domainHash);
|
||||||
expect(orderHashUtils.getOrderHashHex(order)).to.be.equal(orderHashHex);
|
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('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 () => {
|
it('orderHash should differ if the domain hash is different', async () => {
|
||||||
const domainHash1 = ethUtil.bufferToHex(
|
const domainHash1 = ethUtil.bufferToHex(
|
||||||
signTypedDataUtils.generateDomainHash({
|
signTypedDataUtils.generateDomainHash({
|
||||||
...order.domain,
|
...EMPTY_ORDER.domain,
|
||||||
name: constants.EIP712_DOMAIN_NAME,
|
name: constants.EIP712_DOMAIN_NAME,
|
||||||
version: constants.EIP712_DOMAIN_VERSION,
|
version: constants.EIP712_DOMAIN_VERSION,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const domainHash2 = ethUtil.bufferToHex(
|
const domainHash2 = ethUtil.bufferToHex(
|
||||||
signTypedDataUtils.generateDomainHash({
|
signTypedDataUtils.generateDomainHash({
|
||||||
...order.domain,
|
...EMPTY_ORDER.domain,
|
||||||
name: constants.EIP712_DOMAIN_NAME,
|
name: constants.EIP712_DOMAIN_NAME,
|
||||||
version: constants.EIP712_DOMAIN_VERSION,
|
version: constants.EIP712_DOMAIN_VERSION,
|
||||||
chainId: 1337,
|
chainId: 1337,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const orderHashHex1 = await libOrderContract.getTypedDataHash.callAsync(order, domainHash1);
|
const orderHashHex1 = await libOrderContract.getTypedDataHash.callAsync(EMPTY_ORDER, domainHash1);
|
||||||
const orderHashHex2 = await libOrderContract.getTypedDataHash.callAsync(order, domainHash2);
|
const orderHashHex2 = await libOrderContract.getTypedDataHash.callAsync(EMPTY_ORDER, domainHash2);
|
||||||
expect(orderHashHex1).to.be.not.equal(orderHashHex2);
|
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(),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,75 +1,133 @@
|
|||||||
import { addressUtils, blockchainTests, constants, describe, expect } from '@0x/contracts-test-utils';
|
import { blockchainTests, constants, describe, expect, hexRandom } from '@0x/contracts-test-utils';
|
||||||
import { transactionHashUtils } from '@0x/order-utils';
|
import { eip712Utils, transactionHashUtils } from '@0x/order-utils';
|
||||||
import { ZeroExTransaction } from '@0x/types';
|
import { ZeroExTransaction } from '@0x/types';
|
||||||
import { BigNumber, signTypedDataUtils } from '@0x/utils';
|
import { BigNumber, signTypedDataUtils } from '@0x/utils';
|
||||||
import * as ethUtil from 'ethereumjs-util';
|
import * as ethUtil from 'ethereumjs-util';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { artifacts, TestLibZeroExTransactionContract } from '../src';
|
import { artifacts, TestLibZeroExTransactionContract } from '../src';
|
||||||
|
|
||||||
blockchainTests('LibZeroExTransaction', env => {
|
blockchainTests('LibZeroExTransaction', env => {
|
||||||
let libZeroExTransactionContract: TestLibZeroExTransactionContract;
|
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 () => {
|
before(async () => {
|
||||||
libZeroExTransactionContract = await TestLibZeroExTransactionContract.deployFrom0xArtifactAsync(
|
libZeroExTransactionContract = await TestLibZeroExTransactionContract.deployFrom0xArtifactAsync(
|
||||||
artifacts.TestLibZeroExTransaction,
|
artifacts.TestLibZeroExTransaction,
|
||||||
env.provider,
|
env.provider,
|
||||||
env.txDefaults,
|
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', () => {
|
* Tests the `getTypedDataHash()` function against a reference hash.
|
||||||
it('should return the correct transactionHash', async () => {
|
*/
|
||||||
|
async function testGetTypedDataHashAsync(transaction: ZeroExTransaction): Promise<void> {
|
||||||
|
const expectedHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||||
const domainHash = ethUtil.bufferToHex(
|
const domainHash = ethUtil.bufferToHex(
|
||||||
signTypedDataUtils.generateDomainHash({
|
signTypedDataUtils.generateDomainHash({
|
||||||
...zeroExTransaction.domain,
|
...transaction.domain,
|
||||||
name: constants.EIP712_DOMAIN_NAME,
|
name: constants.EIP712_DOMAIN_NAME,
|
||||||
version: constants.EIP712_DOMAIN_VERSION,
|
version: constants.EIP712_DOMAIN_VERSION,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const orderHashHex = await libZeroExTransactionContract.getTypedDataHash.callAsync(
|
const actualHash = await libZeroExTransactionContract.getTypedDataHash.callAsync(transaction, domainHash);
|
||||||
zeroExTransaction,
|
expect(actualHash).to.be.eq(expectedHash);
|
||||||
domainHash,
|
}
|
||||||
);
|
|
||||||
expect(transactionHashUtils.getTransactionHashHex(zeroExTransaction)).to.be.equal(orderHashHex);
|
describe('getTypedDataHash', () => {
|
||||||
|
it('should correctly hash an empty transaction', async () => {
|
||||||
|
await testGetTypedDataHashAsync({
|
||||||
|
...EMPTY_TRANSACTION,
|
||||||
|
domain: {
|
||||||
|
...EMPTY_TRANSACTION.domain,
|
||||||
|
verifyingContractAddress: libZeroExTransactionContract.address,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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 () => {
|
it('transactionHash should differ if the domain hash is different', async () => {
|
||||||
const domainHash1 = ethUtil.bufferToHex(
|
const domainHash1 = ethUtil.bufferToHex(
|
||||||
signTypedDataUtils.generateDomainHash({
|
signTypedDataUtils.generateDomainHash({
|
||||||
...zeroExTransaction.domain,
|
...EMPTY_TRANSACTION.domain,
|
||||||
name: constants.EIP712_DOMAIN_NAME,
|
name: constants.EIP712_DOMAIN_NAME,
|
||||||
version: constants.EIP712_DOMAIN_VERSION,
|
version: constants.EIP712_DOMAIN_VERSION,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const domainHash2 = ethUtil.bufferToHex(
|
const domainHash2 = ethUtil.bufferToHex(
|
||||||
signTypedDataUtils.generateDomainHash({
|
signTypedDataUtils.generateDomainHash({
|
||||||
...zeroExTransaction.domain,
|
...EMPTY_TRANSACTION.domain,
|
||||||
name: constants.EIP712_DOMAIN_NAME,
|
name: constants.EIP712_DOMAIN_NAME,
|
||||||
version: constants.EIP712_DOMAIN_VERSION,
|
version: constants.EIP712_DOMAIN_VERSION,
|
||||||
chainId: 1337,
|
chainId: 1337,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const transactionHashHex1 = await libZeroExTransactionContract.getTypedDataHash.callAsync(
|
const transactionHashHex1 = await libZeroExTransactionContract.getTypedDataHash.callAsync(
|
||||||
zeroExTransaction,
|
EMPTY_TRANSACTION,
|
||||||
domainHash1,
|
domainHash1,
|
||||||
);
|
);
|
||||||
const transactionHashHex2 = await libZeroExTransactionContract.getTypedDataHash.callAsync(
|
const transactionHashHex2 = await libZeroExTransactionContract.getTypedDataHash.callAsync(
|
||||||
zeroExTransaction,
|
EMPTY_TRANSACTION,
|
||||||
domainHash2,
|
domainHash2,
|
||||||
);
|
);
|
||||||
expect(transactionHashHex1).to.be.not.equal(transactionHashHex2);
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -424,7 +424,7 @@ contract MixinMatchOrders is
|
|||||||
address takerAddress,
|
address takerAddress,
|
||||||
LibFillResults.MatchedFillResults memory matchedFillResults
|
LibFillResults.MatchedFillResults memory matchedFillResults
|
||||||
)
|
)
|
||||||
private
|
internal
|
||||||
{
|
{
|
||||||
address leftFeeRecipientAddress = leftOrder.feeRecipientAddress;
|
address leftFeeRecipientAddress = leftOrder.feeRecipientAddress;
|
||||||
address rightFeeRecipientAddress = rightOrder.feeRecipientAddress;
|
address rightFeeRecipientAddress = rightOrder.feeRecipientAddress;
|
||||||
|
@ -41,6 +41,21 @@ contract TestExchangeInternals is
|
|||||||
Exchange(chainId)
|
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
|
/// @dev Call `_updateFilledState()` but first set `filled[order]` to
|
||||||
/// `orderTakerAssetFilledAmount`.
|
/// `orderTakerAssetFilledAmount`.
|
||||||
function testUpdateFilledState(
|
function testUpdateFilledState(
|
||||||
@ -73,6 +88,26 @@ contract TestExchangeInternals is
|
|||||||
_settleOrder(orderHash, order, takerAddress, fillResults);
|
_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()`.
|
/// @dev Overidden to only log arguments so we can test `_settleOrder()`.
|
||||||
function _dispatchTransferFrom(
|
function _dispatchTransferFrom(
|
||||||
bytes32 orderHash,
|
bytes32 orderHash,
|
||||||
|
@ -271,7 +271,7 @@ describe('AssetProxyDispatcher', () => {
|
|||||||
return expect(tx).to.revertWith(expectedError);
|
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, {
|
await assetProxyDispatcher.registerAssetProxy.awaitTransactionSuccessAsync(erc20Proxy.address, {
|
||||||
from: owner,
|
from: owner,
|
||||||
});
|
});
|
||||||
|
@ -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 { 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 { BigNumber, SafeMathRevertErrors } from '@0x/utils';
|
||||||
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
artifacts,
|
artifacts,
|
||||||
ReferenceFunctions,
|
|
||||||
TestExchangeInternalsContract,
|
TestExchangeInternalsContract,
|
||||||
TestExchangeInternalsDispatchTransferFromCalledEventArgs,
|
TestExchangeInternalsDispatchTransferFromCalledEventArgs,
|
||||||
TestExchangeInternalsFillEventArgs,
|
TestExchangeInternalsFillEventArgs,
|
||||||
@ -23,7 +25,9 @@ blockchainTests('Exchange core internal functions', env => {
|
|||||||
let senderAddress: string;
|
let senderAddress: string;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
[senderAddress] = await env.getAccountAddressesAsync();
|
const accounts = await env.getAccountAddressesAsync();
|
||||||
|
senderAddress = accounts[0];
|
||||||
|
|
||||||
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
|
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
|
||||||
artifacts.TestExchangeInternals,
|
artifacts.TestExchangeInternals,
|
||||||
env.provider,
|
env.provider,
|
||||||
@ -33,6 +37,110 @@ blockchainTests('Exchange core internal functions', env => {
|
|||||||
logDecoder = new LogDecoder(env.web3Wrapper, artifacts);
|
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 () => {
|
blockchainTests.resets('updateFilledState', async () => {
|
||||||
const ORDER_DEFAULTS = {
|
const ORDER_DEFAULTS = {
|
||||||
senderAddress: randomAddress(),
|
senderAddress: randomAddress(),
|
||||||
@ -51,18 +159,18 @@ blockchainTests('Exchange core internal functions', env => {
|
|||||||
expirationTimeSeconds: new BigNumber(_.random(0, 1e8)),
|
expirationTimeSeconds: new BigNumber(_.random(0, 1e8)),
|
||||||
};
|
};
|
||||||
|
|
||||||
function makeOrder(details?: Partial<Order>): Order {
|
function makeOrder(details?: Partial<OrderWithoutDomain>): OrderWithoutDomain {
|
||||||
return _.assign({}, ORDER_DEFAULTS, details);
|
return _.assign({}, ORDER_DEFAULTS, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testUpdateFilledStateAsync(
|
async function testUpdateFilledStateAsync(
|
||||||
order: Order,
|
order: OrderWithoutDomain,
|
||||||
orderTakerAssetFilledAmount: BigNumber,
|
orderTakerAssetFilledAmount: BigNumber,
|
||||||
takerAddress: string,
|
takerAddress: string,
|
||||||
takerAssetFillAmount: BigNumber,
|
takerAssetFillAmount: BigNumber,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const orderHash = randomHash();
|
const orderHash = randomHash();
|
||||||
const fillResults = ReferenceFunctions.calculateFillResults(order, takerAssetFillAmount);
|
const fillResults = LibReferenceFunctions.calculateFillResults(order, takerAssetFillAmount);
|
||||||
const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount);
|
const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount);
|
||||||
// CAll `testUpdateFilledState()`, which will set the `filled`
|
// CAll `testUpdateFilledState()`, which will set the `filled`
|
||||||
// state for this order to `orderTakerAssetFilledAmount` before
|
// 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);
|
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
|
// tslint:disable-line:max-file-line-count
|
||||||
|
@ -1336,7 +1336,7 @@ describe('matchOrders', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('matchOrdersWithMaximalFill', () => {
|
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
|
// Create orders to match
|
||||||
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
|
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
|
||||||
makerAddress: makerAddressLeft,
|
makerAddress: makerAddressLeft,
|
||||||
|
@ -11,55 +11,6 @@ chaiSetup.configure();
|
|||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
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', () => {
|
describe('LibEIP712', () => {
|
||||||
let lib: TestLibEIP712Contract;
|
let lib: TestLibEIP712Contract;
|
||||||
|
|
||||||
@ -73,28 +24,78 @@ describe('LibEIP712', () => {
|
|||||||
await blockchainLifecycle.revertAsync();
|
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 () => {
|
describe('_hashEIP712Domain', async () => {
|
||||||
it('should correctly hash empty input', 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 () => {
|
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 () => {
|
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', () => {
|
describe('_hashEIP712Message', () => {
|
||||||
it('should correctly hash empty input', async () => {
|
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 () => {
|
it('should correctly hash non-empty input', async () => {
|
||||||
await testHashEIP712MessageAsync(
|
await testHashEIP712MessageAsync(
|
||||||
lib,
|
|
||||||
'0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6', // keccak256(abi.encode(1))
|
'0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6', // keccak256(abi.encode(1))
|
||||||
'0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace', // keccak256(abi.encode(2))
|
'0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace', // keccak256(abi.encode(2))
|
||||||
);
|
);
|
||||||
@ -102,7 +103,6 @@ describe('LibEIP712', () => {
|
|||||||
|
|
||||||
it('should correctly hash non-empty input', async () => {
|
it('should correctly hash non-empty input', async () => {
|
||||||
await testHashEIP712MessageAsync(
|
await testHashEIP712MessageAsync(
|
||||||
lib,
|
|
||||||
'0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace', // keccak256(abi.encode(2))
|
'0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace', // keccak256(abi.encode(2))
|
||||||
'0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b', // keccak256(abi.encode(3))
|
'0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b', // keccak256(abi.encode(3))
|
||||||
);
|
);
|
||||||
|
@ -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
|
* 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,
|
* @param domain An EIP712 domain with the default schema containing a name, version, chain id,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user