Move all exchange-libs tests to separate files

This commit is contained in:
Amir Bandeali 2019-08-11 14:04:31 -07:00
parent 2e519b534d
commit 24eaf93db8
11 changed files with 448 additions and 20 deletions

View File

@ -29,6 +29,10 @@
"src/LibMath.sol",
"src/LibOrder.sol",
"src/LibZeroExTransaction.sol",
"test/TestLibs.sol"
"test/TestLibEIP712ExchangeDomain.sol",
"test/TestLibFillResults.sol",
"test/TestLibMath.sol",
"test/TestLibOrder.sol",
"test/TestLibZeroExTransaction.sol"
]
}

View File

@ -35,7 +35,7 @@
"generate-exchange-selectors": "node lib/scripts/generate-exchange-selectors.js ../../../exchange/generated-artifacts/Exchange.json ./contracts/src/LibExchangeSelectors.sol"
},
"config": {
"abis": "./generated-artifacts/@(LibEIP712ExchangeDomain|LibFillResults|LibMath|LibOrder|LibZeroExTransaction|TestLibs).json",
"abis": "./generated-artifacts/@(LibEIP712ExchangeDomain|LibFillResults|LibMath|LibOrder|LibZeroExTransaction|TestLibEIP712ExchangeDomain|TestLibFillResults|TestLibMath|TestLibOrder|TestLibZeroExTransaction).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {

View File

@ -10,12 +10,20 @@ import * as LibFillResults from '../generated-artifacts/LibFillResults.json';
import * as LibMath from '../generated-artifacts/LibMath.json';
import * as LibOrder from '../generated-artifacts/LibOrder.json';
import * as LibZeroExTransaction from '../generated-artifacts/LibZeroExTransaction.json';
import * as TestLibs from '../generated-artifacts/TestLibs.json';
import * as TestLibEIP712ExchangeDomain from '../generated-artifacts/TestLibEIP712ExchangeDomain.json';
import * as TestLibFillResults from '../generated-artifacts/TestLibFillResults.json';
import * as TestLibMath from '../generated-artifacts/TestLibMath.json';
import * as TestLibOrder from '../generated-artifacts/TestLibOrder.json';
import * as TestLibZeroExTransaction from '../generated-artifacts/TestLibZeroExTransaction.json';
export const artifacts = {
LibEIP712ExchangeDomain: LibEIP712ExchangeDomain as ContractArtifact,
LibFillResults: LibFillResults as ContractArtifact,
LibMath: LibMath as ContractArtifact,
LibOrder: LibOrder as ContractArtifact,
LibZeroExTransaction: LibZeroExTransaction as ContractArtifact,
TestLibs: TestLibs as ContractArtifact,
TestLibMath: TestLibMath as ContractArtifact,
TestLibOrder: TestLibOrder as ContractArtifact,
TestLibZeroExTransaction: TestLibZeroExTransaction as ContractArtifact,
TestLibFillResults: TestLibFillResults as ContractArtifact,
TestLibEIP712ExchangeDomain: TestLibEIP712ExchangeDomain as ContractArtifact,
};

View File

@ -1,6 +1,6 @@
import { ReferenceFunctions } from '@0x/contracts-utils';
import { LibMathRevertErrors } from '@0x/order-utils';
import { FillResults } from '@0x/types';
import { FillResults, OrderWithoutDomain } from '@0x/types';
import { BigNumber } from '@0x/utils';
const { safeAdd, safeSub, safeMul, safeDiv } = ReferenceFunctions;
@ -87,3 +87,22 @@ export function addFillResults(a: FillResults, b: FillResults): FillResults {
takerFeePaid: safeAdd(a.takerFeePaid, b.takerFeePaid),
};
}
/**
* Calculates amounts filled and fees paid by maker and taker.
*/
export function calculateFillResults(order: OrderWithoutDomain, takerAssetFilledAmount: BigNumber): FillResults {
const makerAssetFilledAmount = safeGetPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
const makerFeePaid = safeGetPartialAmountFloor(makerAssetFilledAmount, order.makerAssetAmount, order.makerFee);
const takerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee);
return {
makerAssetFilledAmount,
takerAssetFilledAmount,
makerFeePaid,
takerFeePaid,
};
}

View File

@ -8,4 +8,8 @@ export * from '../generated-wrappers/lib_fill_results';
export * from '../generated-wrappers/lib_math';
export * from '../generated-wrappers/lib_order';
export * from '../generated-wrappers/lib_zero_ex_transaction';
export * from '../generated-wrappers/test_libs';
export * from '../generated-wrappers/test_lib_e_i_p712_exchange_domain';
export * from '../generated-wrappers/test_lib_fill_results';
export * from '../generated-wrappers/test_lib_math';
export * from '../generated-wrappers/test_lib_order';
export * from '../generated-wrappers/test_lib_zero_ex_transaction';

View File

@ -1,23 +1,260 @@
import { blockchainTests, constants, describe, expect } from '@0x/contracts-test-utils';
import {
blockchainTests,
constants,
describe,
expect,
testCombinatoriallyWithReferenceFunc,
uint256Values,
} from '@0x/contracts-test-utils';
import { LibMathRevertErrors } from '@0x/order-utils';
import { FillResults, OrderWithoutDomain as Order } from '@0x/types';
import { BigNumber, SafeMathRevertErrors } from '@0x/utils';
import * as _ from 'lodash';
import { artifacts, ReferenceFunctions, TestLibsContract } from '../src';
import { artifacts, ReferenceFunctions, TestLibFillResultsContract } from '../src';
blockchainTests('LibFillResults', env => {
const CHAIN_ID = 1337;
const { ONE_ETHER, MAX_UINT256 } = constants;
let libsContract: TestLibsContract;
const EMPTY_ORDER: Order = {
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,
};
let libsContract: TestLibFillResultsContract;
before(async () => {
libsContract = await TestLibsContract.deployFrom0xArtifactAsync(
artifacts.TestLibs,
libsContract = await TestLibFillResultsContract.deployFrom0xArtifactAsync(
artifacts.TestLibFillResults,
env.provider,
env.txDefaults,
new BigNumber(CHAIN_ID),
);
});
describe('calculateFillResults', () => {
describe.optional('combinatorial tests', () => {
function makeOrder(
makerAssetAmount: BigNumber,
takerAssetAmount: BigNumber,
makerFee: BigNumber,
takerFee: BigNumber,
): Order {
return {
...EMPTY_ORDER,
makerAssetAmount,
takerAssetAmount,
makerFee,
takerFee,
};
}
async function referenceCalculateFillResultsAsync(
orderTakerAssetAmount: BigNumber,
takerAssetFilledAmount: BigNumber,
otherAmount: BigNumber,
): Promise<FillResults> {
// Note(albrow): Here we are re-using the same value (otherAmount)
// for order.makerAssetAmount, order.makerFee, and order.takerFee.
// This should be safe because they are never used with each other
// in any mathematical operation in either the reference TypeScript
// implementation or the Solidity implementation of
// calculateFillResults.
return ReferenceFunctions.calculateFillResults(
makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount),
takerAssetFilledAmount,
);
}
async function testCalculateFillResultsAsync(
orderTakerAssetAmount: BigNumber,
takerAssetFilledAmount: BigNumber,
otherAmount: BigNumber,
): Promise<FillResults> {
const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount);
return libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount);
}
testCombinatoriallyWithReferenceFunc(
'calculateFillResults',
referenceCalculateFillResultsAsync,
testCalculateFillResultsAsync,
[uint256Values, uint256Values, uint256Values],
);
});
describe('explicit tests', () => {
const MAX_UINT256_ROOT = constants.MAX_UINT256_ROOT;
function makeOrder(details?: Partial<Order>): Order {
return _.assign({}, EMPTY_ORDER, details);
}
it('matches the output of the reference function', async () => {
const order = makeOrder({
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER.times(2),
makerFee: ONE_ETHER.times(0.0023),
takerFee: ONE_ETHER.times(0.0025),
});
const takerAssetFilledAmount = ONE_ETHER.dividedToIntegerBy(3);
const expected = ReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount);
const actual = await libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount);
expect(actual).to.deep.eq(expected);
});
it('reverts if computing `fillResults.makerAssetFilledAmount` overflows', async () => {
// All values need to be large to ensure we don't trigger a RoundingError.
const order = makeOrder({
makerAssetAmount: MAX_UINT256_ROOT.times(2),
takerAssetAmount: MAX_UINT256_ROOT,
});
const takerAssetFilledAmount = MAX_UINT256_ROOT;
const expectedError = new SafeMathRevertErrors.SafeMathError(
SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow,
takerAssetFilledAmount,
order.makerAssetAmount,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
});
it('reverts if computing `fillResults.makerFeePaid` overflows', async () => {
// All values need to be large to ensure we don't trigger a RoundingError.
const order = makeOrder({
makerAssetAmount: MAX_UINT256_ROOT,
takerAssetAmount: MAX_UINT256_ROOT,
makerFee: MAX_UINT256_ROOT.times(11),
});
const takerAssetFilledAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10);
const makerAssetFilledAmount = ReferenceFunctions.getPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
const expectedError = new SafeMathRevertErrors.SafeMathError(
SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow,
makerAssetFilledAmount,
order.makerFee,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
});
it('reverts if computing `fillResults.takerFeePaid` overflows', async () => {
// All values need to be large to ensure we don't trigger a RoundingError.
const order = makeOrder({
makerAssetAmount: MAX_UINT256_ROOT,
takerAssetAmount: MAX_UINT256_ROOT,
takerFee: MAX_UINT256_ROOT.times(11),
});
const takerAssetFilledAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10);
const expectedError = new SafeMathRevertErrors.SafeMathError(
SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow,
takerAssetFilledAmount,
order.takerFee,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
});
it('reverts if `order.makerAssetAmount` is 0', async () => {
const order = makeOrder({
makerAssetAmount: constants.ZERO_AMOUNT,
takerAssetAmount: ONE_ETHER,
});
const takerAssetFilledAmount = ONE_ETHER;
const expectedError = new LibMathRevertErrors.DivisionByZeroError();
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
});
it('reverts if `order.takerAssetAmount` is 0', async () => {
const order = makeOrder({
makerAssetAmount: ONE_ETHER,
takerAssetAmount: constants.ZERO_AMOUNT,
});
const takerAssetFilledAmount = ONE_ETHER;
const expectedError = new LibMathRevertErrors.DivisionByZeroError();
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
});
it('reverts if there is a rounding error computing `makerAsssetFilledAmount`', async () => {
const order = makeOrder({
makerAssetAmount: new BigNumber(100),
takerAssetAmount: ONE_ETHER,
});
const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3);
const expectedError = new LibMathRevertErrors.RoundingError(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
});
it('reverts if there is a rounding error computing `makerFeePaid`', async () => {
const order = makeOrder({
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER,
makerFee: new BigNumber(100),
});
const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3);
const makerAssetFilledAmount = ReferenceFunctions.getPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
const expectedError = new LibMathRevertErrors.RoundingError(
makerAssetFilledAmount,
order.makerAssetAmount,
order.makerFee,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
});
it('reverts if there is a rounding error computing `takerFeePaid`', async () => {
const order = makeOrder({
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER,
takerFee: new BigNumber(100),
});
const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3);
const makerAssetFilledAmount = ReferenceFunctions.getPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
const expectedError = new LibMathRevertErrors.RoundingError(
makerAssetFilledAmount,
order.makerAssetAmount,
order.takerFee,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
});
});
});
describe('addFillResults', () => {
describe('explicit tests', () => {
const DEFAULT_FILL_RESULTS = [

View File

@ -9,19 +9,17 @@ import {
import { LibMathRevertErrors } from '@0x/order-utils';
import { BigNumber, SafeMathRevertErrors } from '@0x/utils';
import { artifacts, ReferenceFunctions, TestLibsContract } from '../src';
import { artifacts, ReferenceFunctions, TestLibMathContract } from '../src';
blockchainTests('LibMath', env => {
const CHAIN_ID = 1337;
const { ONE_ETHER, MAX_UINT256, MAX_UINT256_ROOT, ZERO_AMOUNT } = constants;
let libsContract: TestLibsContract;
let libsContract: TestLibMathContract;
before(async () => {
libsContract = await TestLibsContract.deployFrom0xArtifactAsync(
artifacts.TestLibs,
libsContract = await TestLibMathContract.deployFrom0xArtifactAsync(
artifacts.TestLibMath,
env.provider,
env.txDefaults,
new BigNumber(CHAIN_ID),
);
});

View File

@ -0,0 +1,75 @@
import { addressUtils, blockchainTests, constants, describe, expect } from '@0x/contracts-test-utils';
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { Order } from '@0x/types';
import { BigNumber, providerUtils, signTypedDataUtils } from '@0x/utils';
import * as ethUtil from 'ethereumjs-util';
import { artifacts, TestLibOrderContract } from '../src';
blockchainTests('LibOrder', env => {
let libOrderContract: TestLibOrderContract;
let order: Order;
let chainId: number;
before(async () => {
libOrderContract = await TestLibOrderContract.deployFrom0xArtifactAsync(
artifacts.TestLibOrder,
env.provider,
env.txDefaults,
);
chainId = await providerUtils.getChainIdAsync(env.provider);
const domain = {
verifyingContractAddress: libOrderContract.address,
chainId,
};
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.getOrderHash.callAsync(order, domainHash);
expect(orderHashUtils.getOrderHashHex(order)).to.be.equal(orderHashHex);
});
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.getOrderHash.callAsync(order, domainHash1);
const orderHashHex2 = await libOrderContract.getOrderHash.callAsync(order, domainHash2);
expect(orderHashHex1).to.be.not.equal(orderHashHex2);
});
});
});
});

View File

@ -0,0 +1,77 @@
import { addressUtils, blockchainTests, constants, describe, expect } from '@0x/contracts-test-utils';
import { transactionHashUtils } from '@0x/order-utils';
import { ZeroExTransaction } from '@0x/types';
import { BigNumber, providerUtils, signTypedDataUtils } from '@0x/utils';
import * as ethUtil from 'ethereumjs-util';
import { artifacts, TestLibZeroExTransactionContract } from '../src';
blockchainTests('LibZeroExTransaction', env => {
let libZeroExTransactionContract: TestLibZeroExTransactionContract;
let zeroExTransaction: ZeroExTransaction;
let chainId: number;
before(async () => {
libZeroExTransactionContract = await TestLibZeroExTransactionContract.deployFrom0xArtifactAsync(
artifacts.TestLibZeroExTransaction,
env.provider,
env.txDefaults,
);
chainId = await providerUtils.getChainIdAsync(env.provider);
const domain = {
verifyingContractAddress: libZeroExTransactionContract.address,
chainId,
};
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.getZeroExTransactionHash.callAsync(
zeroExTransaction,
domainHash,
);
expect(transactionHashUtils.getTransactionHashHex(zeroExTransaction)).to.be.equal(orderHashHex);
});
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.getZeroExTransactionHash.callAsync(
zeroExTransaction,
domainHash1,
);
const transactionHashHex2 = await libZeroExTransactionContract.getZeroExTransactionHash.callAsync(
zeroExTransaction,
domainHash2,
);
expect(transactionHashHex1).to.be.not.equal(transactionHashHex2);
});
});
});
});

View File

@ -8,7 +8,11 @@
"generated-artifacts/LibMath.json",
"generated-artifacts/LibOrder.json",
"generated-artifacts/LibZeroExTransaction.json",
"generated-artifacts/TestLibs.json"
"generated-artifacts/TestLibEIP712ExchangeDomain.json",
"generated-artifacts/TestLibFillResults.json",
"generated-artifacts/TestLibMath.json",
"generated-artifacts/TestLibOrder.json",
"generated-artifacts/TestLibZeroExTransaction.json"
],
"exclude": ["./deploy/solc/solc_bin"]
}

View File

@ -66,4 +66,6 @@ export const constants = {
KECCAK256_NULL: ethUtil.addHexPrefix(ethUtil.bufferToHex(ethUtil.SHA3_NULL)),
MAX_UINT256_ROOT: new BigNumber('340282366920938463463374607431768211456'),
ONE_ETHER: new BigNumber(1e18),
EIP712_DOMAIN_NAME: '0x Protocol',
EIP712_DOMAIN_VERSION: '3.0.0',
};