@0x:contracts-exchange Added protocol fees to fillOrders and matchOrders

This commit is contained in:
Alex Towle
2019-08-22 22:50:18 -07:00
parent 3a4e72bb08
commit 7f17033ce3
36 changed files with 346 additions and 721 deletions

View File

@@ -52,12 +52,10 @@ library LibFillResults {
/// @dev Calculates amounts filled and fees paid by maker and taker.
/// @param order to be filled.
/// @param takerAssetFilledAmount Amount of takerAsset that will be filled.
/// @param protocolFeeMultiplier The multiplier used to calculate protocol fees.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function calculateFillResults(
LibOrder.Order memory order,
uint256 takerAssetFilledAmount,
uint256 protocolFeeMultiplier
uint256 takerAssetFilledAmount
)
internal
view
@@ -81,9 +79,6 @@ library LibFillResults {
order.takerFee
);
// Compute the protocol fee for a single fill.
fillResults.protocolFeePaid = tx.gasprice.safeMul(protocolFeeMultiplier);
return fillResults;
}
@@ -95,7 +90,6 @@ library LibFillResults {
/// @param rightOrder Second order to match.
/// @param leftOrderTakerAssetFilledAmount Amount of left order already filled.
/// @param rightOrderTakerAssetFilledAmount Amount of right order already filled.
/// @param protocolFeeMultiplier The multiplier used to calculate protocol fees.
/// @param shouldMaximallyFillOrders A value that indicates whether or not this calculation should use
/// the maximal fill order matching strategy.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
@@ -104,7 +98,6 @@ library LibFillResults {
LibOrder.Order memory rightOrder,
uint256 leftOrderTakerAssetFilledAmount,
uint256 rightOrderTakerAssetFilledAmount,
uint256 protocolFeeMultiplier,
bool shouldMaximallyFillOrders
)
internal
@@ -170,11 +163,6 @@ library LibFillResults {
rightOrder.takerFee
);
// Compute the protocol fees
uint256 protocolFee = tx.gasprice.safeMul(protocolFeeMultiplier);
matchedFillResults.left.protocolFeePaid = protocolFee;
matchedFillResults.right.protocolFeePaid = protocolFee;
// Return fill results
return matchedFillResults;
}

View File

@@ -29,14 +29,13 @@ contract TestLibFillResults {
function calculateFillResults(
LibOrder.Order memory order,
uint256 takerAssetFilledAmount,
uint256 protocolFeeMultiplier
uint256 takerAssetFilledAmount
)
public
view
returns (LibFillResults.FillResults memory fillResults)
{
fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount, protocolFeeMultiplier);
fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount);
return fillResults;
}
@@ -45,7 +44,6 @@ contract TestLibFillResults {
LibOrder.Order memory rightOrder,
uint256 leftOrderTakerAssetFilledAmount,
uint256 rightOrderTakerAssetFilledAmount,
uint256 protocolFeeMultiplier,
bool shouldMaximallyFillOrders
)
public
@@ -57,7 +55,6 @@ contract TestLibFillResults {
rightOrder,
leftOrderTakerAssetFilledAmount,
rightOrderTakerAssetFilledAmount,
protocolFeeMultiplier,
shouldMaximallyFillOrders
);
return matchedFillResults;

View File

@@ -7,7 +7,7 @@
"evmVersion": "constantinople",
"optimizer": {
"enabled": true,
"runs": 1000000,
"runs": 15000,
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
},
"outputSelection": {

View File

@@ -16,7 +16,7 @@
*/
pragma solidity ^0.5.5;
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "../src/interfaces/IExchange.sol";
@@ -89,7 +89,7 @@ contract ExchangeWrapper is
)
public
payable
refund
refundFinalBalance
{
address takerAddress = msg.sender;

View File

@@ -16,7 +16,7 @@
*/
pragma solidity ^0.5.5;
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "../src/interfaces/IExchange.sol";
@@ -104,7 +104,7 @@ contract Whitelist is
)
public
payable
refund
refundFinalBalance
{
address takerAddress = msg.sender;

View File

@@ -35,12 +35,12 @@ import "./MixinSignatureValidator.sol";
contract MixinExchangeCore is
IExchangeCore,
Refundable,
LibEIP712ExchangeDomain,
IExchangeCore,
MixinAssetProxyDispatcher,
MixinSignatureValidator,
MixinProtocolFees
MixinProtocolFees,
MixinSignatureValidator
{
using LibOrder for LibOrder.Order;
using LibSafeMath for uint256;
@@ -103,7 +103,7 @@ contract MixinExchangeCore is
public
payable
nonReentrant
refund
refundFinalBalance
returns (LibFillResults.FillResults memory fillResults)
{
fillResults = _fillOrder(
@@ -217,10 +217,18 @@ contract MixinExchangeCore is
uint256 takerAssetFilledAmount = LibSafeMath.min256(takerAssetFillAmount, remainingTakerAssetAmount);
// Compute proportional fill amounts
fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount, protocolFeeMultiplier);
fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount);
bytes32 orderHash = orderInfo.orderHash;
// Settle order
_settleOrder(
orderHash,
order,
takerAddress,
fillResults
);
// Update exchange internal state
_updateFilledState(
order,
@@ -230,14 +238,6 @@ contract MixinExchangeCore is
fillResults
);
// Settle order
_settleOrder(
orderHash,
order,
takerAddress,
fillResults
);
return fillResults;
}
@@ -462,25 +462,26 @@ contract MixinExchangeCore is
);
// Transfer protocol fee -> staking if the fee should be paid
if (staking != address(0)) {
// If sufficient ether was sent to the contract, the protocol fee should be paid in ETH.
// Otherwise the fee should be paid in WETH.
uint256 protocolFee = fillResults.protocolFeePaid;
if (address(this).balance >= protocolFee) {
IStaking(staking).payProtocolFee.value(protocolFee)(order.makerAddress);
} else {
// Transfer the Weth
_dispatchTransferFrom(
orderHash,
WETH_ASSET_DATA,
takerAddress,
staking,
protocolFee
);
address feeCollector = protocolFeeCollector;
if (feeCollector != address(0)) {
// Create a stack variable to hold the value that will be sent so that the gas optimization of
// only having one call statement can be implemented.
uint256 valuePaid = 0;
// Attribute the protocol fee to the maker
IStaking(staking).recordProtocolFee(order.makerAddress, protocolFee);
// Calculate the protocol fee that should be paid and populate the `protocolFeePaid` field in `fillResults`.
// It's worth noting that we leave this calculation until now so that work isn't wasted if a fee collector
// is not registered in the exchange.
uint256 protocolFee = tx.gasprice.safeMul(protocolFeeMultiplier);
fillResults.protocolFeePaid = protocolFee;
// If sufficient ether was sent to the contract, the protocol fee should be paid in ETH.
// Otherwise the fee should be paid in WETH. Since the exchange doesn't actually handle
// this case, it will just forward the procotolFee in ether in case 1 and will send zero
// value in case 2.
if (address(this).balance >= protocolFee) {
valuePaid = protocolFee;
}
IStaking(feeCollector).payProtocolFee.value(valuePaid)(order.makerAddress, takerAddress, protocolFee);
}
}
}

View File

@@ -47,7 +47,7 @@ contract MixinMatchOrders is
public
payable
nonReentrant
refund
refundFinalBalance
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults)
{
return _batchMatchOrders(
@@ -77,7 +77,7 @@ contract MixinMatchOrders is
public
payable
nonReentrant
refund
refundFinalBalance
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults)
{
return _batchMatchOrders(
@@ -107,7 +107,7 @@ contract MixinMatchOrders is
public
payable
nonReentrant
refund
refundFinalBalance
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
return _matchOrders(
@@ -137,7 +137,7 @@ contract MixinMatchOrders is
public
payable
nonReentrant
refund
refundFinalBalance
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
return _matchOrders(
@@ -385,10 +385,19 @@ contract MixinMatchOrders is
rightOrder,
leftOrderInfo.orderTakerAssetFilledAmount,
rightOrderInfo.orderTakerAssetFilledAmount,
protocolFeeMultiplier,
shouldMaximallyFillOrders
);
// Settle matched orders. Succeeds or throws.
_settleMatchedOrders(
leftOrderInfo.orderHash,
rightOrderInfo.orderHash,
leftOrder,
rightOrder,
takerAddress,
matchedFillResults
);
// Update exchange state
_updateFilledState(
leftOrder,
@@ -405,16 +414,6 @@ contract MixinMatchOrders is
matchedFillResults.right
);
// Settle matched orders. Succeeds or throws.
_settleMatchedOrders(
leftOrderInfo.orderHash,
rightOrderInfo.orderHash,
leftOrder,
rightOrder,
takerAddress,
matchedFillResults
);
return matchedFillResults;
}
@@ -490,39 +489,27 @@ contract MixinMatchOrders is
matchedFillResults.profitInRightMakerAsset
);
// Pay protocol fees
if (staking != address(0)) {
// matchedFillResults.left.protocolFeePaid == matchedFillResults.right.protocolFeePaid
// so we only use matchedFillResults.left.protocolFeePaid as a gas optimization.
uint256 protocolFee = matchedFillResults.left.protocolFeePaid;
// Pay the protocol fees if there is a registered `protocolFeeCollector` address.
address feeCollector = protocolFeeCollector;
if (feeCollector != address(0)) {
// Calculate the protocol fee that should be paid and populate the `protocolFeePaid` field in the left and
// right `fillResults` of `matchedFillResults`. It's worth noting that we leave this calculation until now
// so that work isn't wasted if a fee collector is not registered in the exchange.
uint256 protocolFee = tx.gasprice.safeMul(protocolFeeMultiplier);
matchedFillResults.left.protocolFeePaid = protocolFee;
matchedFillResults.right.protocolFeePaid = protocolFee;
// Construct an array of makers and fee amounts so that the staking contract will only need to be called once.
address[] memory makers = new address[](2);
makers[0] = leftOrder.makerAddress;
makers[1] = rightOrder.makerAddress;
uint256[] memory fees = new uint256[](2);
fees[0] = protocolFee;
fees[1] = protocolFee;
// Create a stack variable for the value that will be sent to the feeCollector when `payProtocolFee` is called.
// This allows a gas optimization where the `leftOrder.makerAddress` only needs be loaded onto the stack once AND
// a stack variable does not need to be allocated for the call.
uint256 valuePaid = 0;
// If sufficient ether was sent to the contract, the protocol fee should be paid in ETH.
// Otherwise the fee should be paid in WETH.
// Pay the left order's protocol fee.
if (address(this).balance >= 2 * protocolFee) {
// Forward the protocol fees
IStaking(staking).batchPayProtocolFees.value(2 * protocolFee)(makers, fees);
} else {
// Transfer the weth from the takerAddress.
// Note: `_dispatchTransferFrom` is only called once as a gas optimization.
_dispatchTransferFrom(
leftOrderHash,
WETH_ASSET_DATA,
takerAddress,
staking,
2 * protocolFee
);
// Attribute the protocol fees to the maker addresses
IStaking(staking).batchRecordProtocolFees(makers, fees);
valuePaid = 2 * protocolFee;
}
IStaking(feeCollector).payProtocolFee.value(valuePaid)(leftOrder.makerAddress, takerAddress, protocolFee);
IStaking(feeCollector).payProtocolFee.value(valuePaid)(rightOrder.makerAddress, takerAddress, protocolFee);
}
// Settle taker fees.

View File

@@ -1,67 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "./interfaces/IStakingManager.sol";
contract MixinStakingManager is
IStakingManager,
Ownable
{
// The protocol fee multiplier -- the owner can update this field.
uint256 public protocolFeeMultiplier;
// The address of the registered staking contract -- the owner can update this field.
address public staking;
// The address of the wrapped ether contract -- the owner can update this field.
address public weth;
/// @dev Allows the owner to update the protocol fee multiplier.
/// @param updatedProtocolFeeMultiplier The updated protocol fee multiplier.
function updateProtocolFeeMultiplier(uint256 updatedProtocolFeeMultiplier)
external
onlyOwner()
{
emit UpdatedProtocolFeeMultiplier(protocolFeeMultiplier, updatedProtocolFeeMultiplier);
protocolFeeMultiplier = updatedProtocolFeeMultiplier;
}
/// @dev Allows the owner to update the staking address.
/// @param updatedStaking The updated staking contract address.
function updateStakingAddress(address updatedStaking)
external
onlyOwner()
{
emit UpdatedStakingAddress(staking, updatedStaking);
staking = updatedStaking;
}
/// @dev Allows the owner to update the WETH address.
/// @param updatedWeth The updated WETH contract address.
function updateWethAddress(address updatedWeth)
external
onlyOwner()
{
emit UpdatedWethAddress(weth, updatedWeth);
weth = updatedWeth;
}
}

View File

@@ -53,7 +53,7 @@ contract MixinTransactions is
)
public
payable
refund
disableRefundUntilEnd
returns (bytes memory)
{
return _executeTransaction(transaction, signature);
@@ -69,7 +69,7 @@ contract MixinTransactions is
)
public
payable
refund
disableRefundUntilEnd
returns (bytes[] memory)
{
uint256 length = transactions.length;

View File

@@ -47,7 +47,7 @@ contract MixinTransferSimulator is
uint256 length = assetData.length;
for (uint256 i = 0; i != length; i++) {
_dispatchTransferFrom(
// The index is passed in as `orderHash` so that a failed transfer can be quickly identified when catching the error
// The index is passed in as `orderHash` so that a failed transfer can be quickly identified when catching the error
bytes32(i),
assetData[i],
fromAddresses[i],

View File

@@ -48,7 +48,7 @@ contract MixinWrapperFunctions is
public
payable
nonReentrant
refund
refundFinalBalance
returns (LibFillResults.FillResults memory fillResults)
{
fillResults = _fillOrKillOrder(
@@ -59,6 +59,10 @@ contract MixinWrapperFunctions is
return fillResults;
}
/// Note: This function only needs `refundFinalBalance` modifier because ether will not
// be returned in the event that the delegatecall fails. This said, there is no
// reason to invoke `disableRefundUntilEnd` because it is cheaper to use this modifier
// and the inner refund will not affect the logic of this call.
/// @dev Fills the input order.
/// Returns a null FillResults instance if the transaction would otherwise revert.
/// @param order Order struct containing order specifications.
@@ -72,7 +76,7 @@ contract MixinWrapperFunctions is
)
public
payable
refund
refundFinalBalance
returns (LibFillResults.FillResults memory fillResults)
{
// ABI encode calldata for `fillOrder`
@@ -85,7 +89,7 @@ contract MixinWrapperFunctions is
(bool didSucceed, bytes memory returnData) = address(this).delegatecall(fillOrderCalldata);
if (didSucceed) {
assert(returnData.length == 128);
assert(returnData.length == 160);
fillResults = abi.decode(returnData, (LibFillResults.FillResults));
}
// fillResults values will be 0 by default if call was unsuccessful
@@ -105,7 +109,7 @@ contract MixinWrapperFunctions is
public
payable
nonReentrant
refund
disableRefundUntilEnd
returns (LibFillResults.FillResults[] memory fillResults)
{
uint256 ordersLength = orders.length;
@@ -133,7 +137,7 @@ contract MixinWrapperFunctions is
public
payable
nonReentrant
refund
disableRefundUntilEnd
returns (LibFillResults.FillResults[] memory fillResults)
{
uint256 ordersLength = orders.length;
@@ -160,7 +164,7 @@ contract MixinWrapperFunctions is
)
public
payable
refund
// disableRefundUntilEnd
returns (LibFillResults.FillResults[] memory fillResults)
{
uint256 ordersLength = orders.length;
@@ -187,7 +191,7 @@ contract MixinWrapperFunctions is
)
public
payable
refund
disableRefundUntilEnd
returns (LibFillResults.FillResults memory fillResults)
{
bytes memory takerAssetData = orders[0].takerAssetData;
@@ -233,7 +237,7 @@ contract MixinWrapperFunctions is
)
public
payable
refund
disableRefundUntilEnd
returns (LibFillResults.FillResults memory fillResults)
{
bytes memory makerAssetData = orders[0].makerAssetData;
@@ -333,22 +337,6 @@ contract MixinWrapperFunctions is
}
}
/// @dev Fetches information for all passed in orders.
/// @param orders Array of order specifications.
/// @return Array of OrderInfo instances that correspond to each order.
function getOrdersInfo(LibOrder.Order[] memory orders)
public
view
returns (LibOrder.OrderInfo[] memory)
{
uint256 ordersLength = orders.length;
LibOrder.OrderInfo[] memory ordersInfo = new LibOrder.OrderInfo[](ordersLength);
for (uint256 i = 0; i != ordersLength; i++) {
ordersInfo[i] = getOrderInfo(orders[i]);
}
return ordersInfo;
}
/// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled.
/// @param order Order struct containing order specifications.
/// @param takerAssetFillAmount Desired amount of takerAsset to sell.

View File

@@ -25,6 +25,9 @@ import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
contract IExchangeCore {
// keccak256("Fill(address,address,bytes32,address,address,uint256,uint256,uint256,uint256,uint256,bool,bytes,bytes,bytes,bytes)")
bytes32 internal constant FILL_EVENT_TOPIC = 0x266de417a663e51231ccdf89b2794cea06fde5e2c433d76473160b32d31fd867;
// Fill event is emitted whenever an order is filled.
event Fill(
address indexed makerAddress, // Address that created the order.

View File

@@ -21,6 +21,9 @@ pragma solidity ^0.5.9;
contract IProtocolFees {
// The proxy id of the weth asset proxy.
bytes internal constant WETH_ASSET_DATA = hex"f47261b0";
// Logs updates to the protocol fee multiplier.
event UpdatedProtocolFeeMultiplier(uint256 oldProtocolFeeMultiplier, uint256 updatedProtocolFeeMultiplier);

View File

@@ -154,12 +154,4 @@ contract IWrapperFunctions {
/// @param orders Array of order specifications.
function batchCancelOrders(LibOrder.Order[] memory orders)
public;
/// @dev Fetches information for all passed in orders
/// @param orders Array of order specifications.
/// @return Array of OrderInfo instances that correspond to each order.
function getOrdersInfo(LibOrder.Order[] memory orders)
public
view
returns (LibOrder.OrderInfo[] memory);
}

View File

@@ -92,6 +92,7 @@ contract TestWrapperFunctions is
fillResults.takerAssetFilledAmount = order.takerAssetAmount;
fillResults.makerFeePaid = order.makerFee;
fillResults.takerFeePaid = order.takerFee;
fillResults.protocolFeePaid = protocolFeeMultiplier;
}
/// @dev Overridden to only log arguments and revert with certain inputs.

View File

@@ -27,7 +27,6 @@ import * as MixinExchangeCore from '../generated-artifacts/MixinExchangeCore.jso
import * as MixinMatchOrders from '../generated-artifacts/MixinMatchOrders.json';
import * as MixinProtocolFees from '../generated-artifacts/MixinProtocolFees.json';
import * as MixinSignatureValidator from '../generated-artifacts/MixinSignatureValidator.json';
import * as MixinStakingManager from '../generated-artifacts/MixinStakingManager.json';
import * as MixinTransactions from '../generated-artifacts/MixinTransactions.json';
import * as MixinTransferSimulator from '../generated-artifacts/MixinTransferSimulator.json';
import * as MixinWrapperFunctions from '../generated-artifacts/MixinWrapperFunctions.json';
@@ -49,7 +48,6 @@ export const artifacts = {
MixinMatchOrders: MixinMatchOrders as ContractArtifact,
MixinProtocolFees: MixinProtocolFees as ContractArtifact,
MixinSignatureValidator: MixinSignatureValidator as ContractArtifact,
MixinStakingManager: MixinStakingManager as ContractArtifact,
MixinTransactions: MixinTransactions as ContractArtifact,
MixinTransferSimulator: MixinTransferSimulator as ContractArtifact,
MixinWrapperFunctions: MixinWrapperFunctions as ContractArtifact,

View File

@@ -25,7 +25,6 @@ export * from '../generated-wrappers/mixin_exchange_core';
export * from '../generated-wrappers/mixin_match_orders';
export * from '../generated-wrappers/mixin_protocol_fees';
export * from '../generated-wrappers/mixin_signature_validator';
export * from '../generated-wrappers/mixin_staking_manager';
export * from '../generated-wrappers/mixin_transactions';
export * from '../generated-wrappers/mixin_transfer_simulator';
export * from '../generated-wrappers/mixin_wrapper_functions';

View File

@@ -17,7 +17,7 @@ import {
Web3ProviderEngine,
} from '@0x/contracts-test-utils';
import { orderHashUtils } from '@0x/order-utils';
import { AssetProxyId, FillResults, SignedOrder } from '@0x/types';
import { FillResults, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { TransactionReceiptWithDecodedLogs, ZeroExProvider } from 'ethereum-types';
@@ -33,6 +33,8 @@ export class FillOrderWrapper {
private readonly _blockchainBalanceStore: BlockchainBalanceStore;
private readonly _web3Wrapper: Web3Wrapper;
// FIXME - Punting on these tests for now since no staking contract will be registered. This
// should be revisited when the protocol fee testing has been unit tested well.
/**
* Simulates matching two orders by transferring amounts defined in
* `transferAmounts` and returns the results.
@@ -47,23 +49,18 @@ export class FillOrderWrapper {
takerAddress: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
initBalanceStore: BalanceStore,
stakingOpts: {
gasPrice: BigNumber;
messageValue: BigNumber;
protocolFeeMultiplier: BigNumber;
stakingAddress: string;
wethAddress: string;
},
// stakingOpts: {
// gasPrice: BigNumber;
// messageValue: BigNumber;
// protocolFeeMultiplier: BigNumber;
// stakingAddress: string;
// wethAddress: string;
// },
): [FillResults, FillEventArgs, BalanceStore] {
const balanceStore = LocalBalanceStore.create(initBalanceStore);
const takerAssetFillAmount =
opts.takerAssetFillAmount !== undefined ? opts.takerAssetFillAmount : signedOrder.takerAssetAmount;
const fillResults = LibReferenceFunctions.calculateFillResults(
signedOrder,
takerAssetFillAmount,
stakingOpts.protocolFeeMultiplier,
stakingOpts.gasPrice,
);
const fillResults = LibReferenceFunctions.calculateFillResults(signedOrder, takerAssetFillAmount);
const fillEvent = FillOrderWrapper.simulateFillEvent(signedOrder, takerAddress, fillResults);
// Taker -> Maker
balanceStore.transferAsset(
@@ -93,18 +90,20 @@ export class FillOrderWrapper {
fillResults.makerFeePaid,
signedOrder.makerFeeAssetData,
);
if (stakingOpts.messageValue.isGreaterThanOrEqualTo(fillResults.protocolFeePaid)) {
// Pay the protocol fee in ETH.
balanceStore.transferAsset(takerAddress, stakingOpts.stakingAddress, fillResults.protocolFeePaid, '');
} else {
// Pay the protocol fee in WETH.
balanceStore.transferAsset(
takerAddress,
stakingOpts.stakingAddress,
fillResults.protocolFeePaid,
AssetProxyId.ERC20,
);
}
// FIXME - Punting on these tests for now since no staking contract will be registered. This
// should be revisited when the protocol fee testing has been unit tested well.
// if (stakingOpts.messageValue.isGreaterThanOrEqualTo(fillResults.protocolFeePaid)) {
// // Pay the protocol fee in ETH.
// balanceStore.transferAsset(takerAddress, stakingOpts.stakingAddress, fillResults.protocolFeePaid, '');
// } else {
// // Pay the protocol fee in WETH.
// balanceStore.transferAsset(
// takerAddress,
// stakingOpts.stakingAddress,
// fillResults.protocolFeePaid,
// AssetProxyId.ERC20,
// );
// }
return [fillResults, fillEvent, balanceStore];
}
@@ -168,6 +167,8 @@ export class FillOrderWrapper {
return this._blockchainBalanceStore;
}
// FIXME - Punting on these tests for now since no staking contract will be registered. This
// should be revisited when the protocol fee testing has been unit tested well.
/**
* Fills an order and asserts the effects. This includes
* 1. The order info (via `getOrderInfo`)

View File

@@ -589,11 +589,11 @@ blockchainTests.resets('Exchange core', () => {
});
it('should cancel only orders with a orderEpoch less than existing orderEpoch', async () => {
// Cancel all transactions with a orderEpoch less than 1
// Cancel all transactions with a orderEpoch less than 2
const orderEpoch = new BigNumber(1);
await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
// Create 3 orders with orderEpoch values: 0,1,2,3
// Create 4 orders with orderEpoch values: 0,1,2,3
// Since we cancelled with orderEpoch=1, orders with orderEpoch<=1 will not be processed
erc20Balances = await erc20Wrapper.getBalancesAsync();
const signedOrders = [

View File

@@ -164,26 +164,22 @@ blockchainTests('Exchange core internal functions', env => {
return _.assign({}, ORDER_DEFAULTS, details);
}
// FIXME - This test definitely needs to be updated
async function testUpdateFilledStateAsync(
order: OrderWithoutDomain,
orderTakerAssetFilledAmount: BigNumber,
takerAddress: string,
takerAssetFillAmount: BigNumber,
protocolFeeMultiplier: BigNumber,
gasPrice: BigNumber,
isProtocolFeePaidInEth: boolean,
// protocolFeeMultiplier: BigNumber,
// gasPrice: BigNumber,
// isProtocolFeePaidInEth: boolean,
): Promise<void> {
const orderHash = randomHash();
const fillResults = LibReferenceFunctions.calculateFillResults(
order,
takerAssetFillAmount,
protocolFeeMultiplier,
gasPrice,
);
const fillResults = LibReferenceFunctions.calculateFillResults(order, takerAssetFillAmount);
const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount);
const opts = isProtocolFeePaidInEth
? { value: fillResults.protocolFeePaid }
: { value: constants.ZERO_AMOUNT };
// const opts = isProtocolFeePaidInEth
// ? { value: fillResults.protocolFeePaid }
// : { value: constants.ZERO_AMOUNT };
// CAll `testUpdateFilledState()`, which will set the `filled`
// state for this order to `orderTakerAssetFilledAmount` before
// calling `_updateFilledState()`.
@@ -194,7 +190,7 @@ blockchainTests('Exchange core internal functions', env => {
orderHash,
orderTakerAssetFilledAmount,
fillResults,
opts,
// opts, // FIXME
),
);
// Grab the new `filled` state for this order.
@@ -214,8 +210,8 @@ blockchainTests('Exchange core internal functions', env => {
expect(fillEvent.args.takerAssetFilledAmount).to.bignumber.eq(fillResults.takerAssetFilledAmount);
expect(fillEvent.args.makerFeePaid).to.bignumber.eq(fillResults.makerFeePaid);
expect(fillEvent.args.takerFeePaid).to.bignumber.eq(fillResults.takerFeePaid);
expect(fillEvent.args.protocolFeePaid).to.bignumber.eq(fillResults.protocolFeePaid);
expect(fillEvent.args.isProtocolFeePaidInEth).to.eq(isProtocolFeePaidInEth);
// expect(fillEvent.args.protocolFeePaid).to.bignumber.eq(fillResults.protocolFeePaid);
// expect(fillEvent.args.isProtocolFeePaidInEth).to.eq(isProtocolFeePaidInEth);
expect(fillEvent.args.makerAssetData).to.eq(order.makerAssetData);
expect(fillEvent.args.takerAssetData).to.eq(order.takerAssetData);
expect(fillEvent.args.makerFeeAssetData).to.eq(order.makerFeeAssetData);
@@ -229,9 +225,6 @@ blockchainTests('Exchange core internal functions', env => {
order.takerAssetAmount.times(0.1),
randomAddress(),
order.takerAssetAmount.times(0.25),
new BigNumber(150000),
new BigNumber(100000),
true,
);
});
@@ -242,9 +235,6 @@ blockchainTests('Exchange core internal functions', env => {
order.takerAssetAmount.times(0.1),
randomAddress(),
order.takerAssetAmount.times(0.25),
new BigNumber(100000),
new BigNumber(150000),
true,
);
});
@@ -252,11 +242,13 @@ blockchainTests('Exchange core internal functions', env => {
const order = makeOrder();
const orderTakerAssetFilledAmount = constants.MAX_UINT256.dividedToIntegerBy(2);
const takerAssetFillAmount = constants.MAX_UINT256.dividedToIntegerBy(2).plus(2);
// FIXME
const fillResults = {
makerAssetFilledAmount: constants.ZERO_AMOUNT,
takerAssetFilledAmount: takerAssetFillAmount,
makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: constants.ZERO_AMOUNT,
};
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow,
@@ -297,11 +289,13 @@ blockchainTests('Exchange core internal functions', env => {
const order = DEFAULT_ORDER;
const orderHash = randomHash();
const takerAddress = randomAddress();
// FIXME
const fillResults = {
makerAssetFilledAmount: ONE_ETHER.times(2),
takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: ONE_ETHER.times(0.025),
protocolFeePaid: constants.ZERO_AMOUNT,
};
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await testExchange.settleOrder.sendTransactionAsync(orderHash, order, takerAddress, fillResults),
@@ -371,12 +365,14 @@ blockchainTests('Exchange core internal functions', env => {
takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: constants.MAX_UINT256,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
right: {
takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: constants.MAX_UINT256_ROOT,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2),
@@ -418,12 +414,14 @@ blockchainTests('Exchange core internal functions', env => {
takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: constants.MAX_UINT256,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
right: {
takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: constants.MAX_UINT256_ROOT,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2),
@@ -454,12 +452,14 @@ blockchainTests('Exchange core internal functions', env => {
takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: ONE_ETHER.times(0.025),
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
right: {
takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: ONE_ETHER.times(0.05),
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2),
@@ -550,12 +550,14 @@ blockchainTests('Exchange core internal functions', env => {
takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: ONE_ETHER.times(0.025),
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
right: {
takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: ONE_ETHER.times(0.05),
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2),

View File

@@ -5,8 +5,6 @@ import { OrderWithoutDomain as Order } from '@0x/types';
import { BigNumber, SafeMathRevertErrors } from '@0x/utils';
import * as _ from 'lodash';
import { calculateFillResults } from '../src/reference_functions';
describe('Reference functions', () => {
const ONE_ETHER = constants.ONE_ETHER;
const EMPTY_ORDER: Order = {
@@ -44,7 +42,9 @@ describe('Reference functions', () => {
takerAssetFilledAmount,
order.makerAssetAmount,
);
return expect(() => calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
return expect(() => LibReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount)).to.throw(
expectedError.message,
);
});
it('reverts if computing `fillResults.makerFeePaid` overflows', () => {
@@ -65,7 +65,7 @@ describe('Reference functions', () => {
makerAssetFilledAmount,
order.makerFee,
);
return expect(() => calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
return expect(() => LibReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
});
it('reverts if computing `fillResults.takerFeePaid` overflows', () => {
@@ -81,7 +81,7 @@ describe('Reference functions', () => {
takerAssetFilledAmount,
order.takerFee,
);
return expect(() => calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
return expect(() => LibReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
});
it('reverts if `order.makerAssetAmount` is 0', () => {
@@ -91,7 +91,7 @@ describe('Reference functions', () => {
});
const takerAssetFilledAmount = ONE_ETHER;
const expectedError = new LibMathRevertErrors.DivisionByZeroError();
return expect(() => calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
return expect(() => LibReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
});
it('reverts if `order.takerAssetAmount` is 0', () => {
@@ -101,7 +101,7 @@ describe('Reference functions', () => {
});
const takerAssetFilledAmount = ONE_ETHER;
const expectedError = new LibMathRevertErrors.DivisionByZeroError();
return expect(() => calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
return expect(() => LibReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
});
it('reverts if there is a rounding error computing `makerAsssetFilledAmount`', () => {
@@ -115,7 +115,7 @@ describe('Reference functions', () => {
order.takerAssetAmount,
order.makerAssetAmount,
);
return expect(() => calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
return expect(() => LibReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
});
it('reverts if there is a rounding error computing `makerFeePaid`', () => {
@@ -135,7 +135,7 @@ describe('Reference functions', () => {
order.makerAssetAmount,
order.makerFee,
);
return expect(() => calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
return expect(() => LibReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
});
it('reverts if there is a rounding error computing `takerFeePaid`', () => {
@@ -155,7 +155,7 @@ describe('Reference functions', () => {
order.makerAssetAmount,
order.takerFee,
);
return expect(() => calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
return expect(() => LibReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount)).to.throw(expectedError.message);
});
});
});

View File

@@ -1,143 +0,0 @@
import { blockchainTests, constants, expect, LogDecoder } from '@0x/contracts-test-utils';
import { BigNumber, OwnableRevertErrors } from '@0x/utils';
import { LogWithDecodedArgs } from 'ethereum-types';
import {
artifacts,
ExchangeContract,
ExchangeUpdatedProtocolFeeMultiplierEventArgs,
ExchangeUpdatedStakingAddressEventArgs,
ExchangeUpdatedWethAddressEventArgs,
} from '../src';
blockchainTests.resets('MixinStakingManager', env => {
let accounts: string[];
let exchange: ExchangeContract;
let logDecoder: LogDecoder;
let nonOwner: string;
let owner: string;
let staking: string;
let weth: string;
// The protocolFeeMultiplier that will be used to test the update functions.
const protocolFeeMultiplier = new BigNumber(15000);
before(async () => {
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
owner = accounts[0];
nonOwner = accounts[1];
staking = accounts[2];
weth = accounts[3];
// Update the from address of the txDefaults. This is the address that will become the owner.
env.txDefaults.from = owner;
// Deploy the exchange contract.
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
env.provider,
env.txDefaults,
{},
new BigNumber(1337),
);
// Configure the log decoder
logDecoder = new LogDecoder(env.web3Wrapper, artifacts);
});
blockchainTests.resets('updateProtocolFeeMultiplier', () => {
it('should revert if msg.sender != owner', async () => {
const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner);
// Ensure that the transaction reverts with the expected rich error.
const tx = exchange.updateStakingAddress.sendTransactionAsync(staking, {
from: nonOwner,
});
return expect(tx).to.revertWith(expectedError);
});
it('should succeed and emit an UpdatedProtocolFeeMultiplier event if msg.sender == owner', async () => {
// Call the `updateProtocolFeeMultiplier()` function and get the receipt.
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await exchange.updateProtocolFeeMultiplier.sendTransactionAsync(protocolFeeMultiplier, {
from: owner,
}),
);
// Verify that the staking address was actually updated to the correct address.
const updated = await exchange.protocolFeeMultiplier.callAsync();
expect(updated).bignumber.to.be.eq(protocolFeeMultiplier);
// Ensure that the correct `UpdatedStakingAddress` event was logged.
// tslint:disable:no-unnecessary-type-assertion
const updatedEvent = receipt.logs[0] as LogWithDecodedArgs<ExchangeUpdatedProtocolFeeMultiplierEventArgs>;
expect(updatedEvent.event).to.be.eq('UpdatedProtocolFeeMultiplier');
expect(updatedEvent.args.oldProtocolFeeMultiplier).bignumber.to.be.eq(constants.ZERO_AMOUNT);
expect(updatedEvent.args.updatedProtocolFeeMultiplier).bignumber.to.be.eq(protocolFeeMultiplier);
});
});
blockchainTests.resets('updateStakingAddress', () => {
it('should revert if msg.sender != owner', async () => {
const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner);
// Ensure that the transaction reverts with the expected rich error.
const tx = exchange.updateStakingAddress.sendTransactionAsync(staking, {
from: nonOwner,
});
return expect(tx).to.revertWith(expectedError);
});
it('should succeed and emit an UpdatedStakingAddress event if msg.sender == owner', async () => {
// Call the `updateStakingAddress()` function and get the receipt.
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await exchange.updateStakingAddress.sendTransactionAsync(staking, {
from: owner,
}),
);
// Verify that the staking address was actually updated to the correct address.
const updated = await exchange.staking.callAsync();
expect(updated).to.be.eq(staking);
// Ensure that the correct `UpdatedStakingAddress` event was logged.
// tslint:disable:no-unnecessary-type-assertion
const updatedEvent = receipt.logs[0] as LogWithDecodedArgs<ExchangeUpdatedStakingAddressEventArgs>;
expect(updatedEvent.event).to.be.eq('UpdatedStakingAddress');
expect(updatedEvent.args.oldStaking).to.be.eq(constants.NULL_ADDRESS);
expect(updatedEvent.args.updatedStaking).to.be.eq(staking);
});
});
blockchainTests.resets('updateWethAddress', () => {
it('should revert if msg.sender != owner', async () => {
const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner);
// Ensure that the transaction reverts with the expected rich error.
const tx = exchange.updateWethAddress.sendTransactionAsync(weth, {
from: nonOwner,
});
return expect(tx).to.revertWith(expectedError);
});
it('should succeed and emit an UpdatedStakingAddress event if msg.sender == owner', async () => {
// Call the `updateWethAddress()` function and get the receipt.
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await exchange.updateWethAddress.sendTransactionAsync(weth, {
from: owner,
}),
);
// Verify that the staking address was actually updated to the correct address.
const updated = await exchange.weth.callAsync();
expect(updated).to.be.eq(weth);
// Ensure that the correct `UpdatedStakingAddress` event was logged.
// tslint:disable:no-unnecessary-type-assertion
const updatedEvent = receipt.logs[0] as LogWithDecodedArgs<ExchangeUpdatedWethAddressEventArgs>;
expect(updatedEvent.event).to.be.eq('UpdatedWethAddress');
expect(updatedEvent.args.oldWeth).to.be.eq(constants.NULL_ADDRESS);
expect(updatedEvent.args.updatedWeth).to.be.eq(weth);
});
});
});

View File

@@ -1037,13 +1037,14 @@ blockchainTests.resets('Exchange transactions', env => {
const cancelTransaction = await makerTransactionFactory.newSignedTransactionAsync({
data: cancelData,
});
await exchangeWrapperContract.cancelOrdersUpTo.awaitTransactionSuccessAsync(
targetOrderEpoch,
cancelTransaction.salt,
cancelTransaction.expirationTimeSeconds,
cancelTransaction.signature,
// { from: makerAddress },
{ from: makerAddress },
constants.AWAIT_TRANSACTION_MINED_MS,
);
const takerAssetFillAmount = signedOrder.takerAssetAmount;

View File

@@ -43,35 +43,35 @@ export class ExchangeWrapper {
public async fillOrKillOrderAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
opts: { takerAssetFillAmount?: BigNumber; gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const txReceipt = await this._exchange.fillOrKillOrder.awaitTransactionSuccessAsync(
params.order,
params.takerAssetFillAmount,
params.signature,
{ from },
{ from, gasPrice: opts.gasPrice },
);
return txReceipt;
}
public async fillOrderNoThrowAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {},
opts: { takerAssetFillAmount?: BigNumber; gas?: number; gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const txReceipt = await this._exchange.fillOrderNoThrow.awaitTransactionSuccessAsync(
params.order,
params.takerAssetFillAmount,
params.signature,
{ from, gas: opts.gas },
{ from, gas: opts.gas, gasPrice: opts.gasPrice },
);
return txReceipt;
}
public async batchFillOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[] } = {},
opts: { takerAssetFillAmounts?: BigNumber[]; gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
return this._exchange.batchFillOrders.awaitTransactionSuccessAsync(
orders,
@@ -79,13 +79,13 @@ export class ExchangeWrapper {
? orders.map(signedOrder => signedOrder.takerAssetAmount)
: opts.takerAssetFillAmounts,
orders.map(signedOrder => signedOrder.signature),
{ from },
{ from, gasPrice: opts.gasPrice },
);
}
public async batchFillOrKillOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[] } = {},
opts: { takerAssetFillAmounts?: BigNumber[]; gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
return this._exchange.batchFillOrKillOrders.awaitTransactionSuccessAsync(
orders,
@@ -93,13 +93,13 @@ export class ExchangeWrapper {
? orders.map(signedOrder => signedOrder.takerAssetAmount)
: opts.takerAssetFillAmounts,
orders.map(signedOrder => signedOrder.signature),
{ from },
{ from, gasPrice: opts.gasPrice },
);
}
public async batchFillOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {},
opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number; gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
return this._exchange.batchFillOrdersNoThrow.awaitTransactionSuccessAsync(
orders,
@@ -107,25 +107,25 @@ export class ExchangeWrapper {
? orders.map(signedOrder => signedOrder.takerAssetAmount)
: opts.takerAssetFillAmounts,
orders.map(signedOrder => signedOrder.signature),
{ from, gas: opts.gas },
{ from, gas: opts.gas, gasPrice: opts.gasPrice },
);
}
public async marketSellOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmount: BigNumber; gas?: number },
opts: { takerAssetFillAmount: BigNumber; gas?: number; gasPrice?: BigNumber },
): Promise<TransactionReceiptWithDecodedLogs> {
return this._exchange.marketSellOrdersNoThrow.awaitTransactionSuccessAsync(
orders,
opts.takerAssetFillAmount,
orders.map(signedOrder => signedOrder.signature),
{ from, gas: opts.gas },
{ from, gas: opts.gas, gasPrice: opts.gasPrice },
);
}
public async marketBuyOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { makerAssetFillAmount: BigNumber; gas?: number },
opts: { makerAssetFillAmount: BigNumber; gas?: number; gasPrice?: BigNumber },
): Promise<TransactionReceiptWithDecodedLogs> {
return this._exchange.marketBuyOrdersNoThrow.awaitTransactionSuccessAsync(
orders,
@@ -180,22 +180,23 @@ export class ExchangeWrapper {
public async executeTransactionAsync(
signedTransaction: SignedZeroExTransaction,
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
return this._exchange.executeTransaction.awaitTransactionSuccessAsync(
signedTransaction,
signedTransaction.signature,
{
from,
},
{ from, gasPrice: opts.gasPrice },
);
}
public async batchExecuteTransactionsAsync(
signedTransactions: SignedZeroExTransaction[],
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const signatures = signedTransactions.map(signedTransaction => signedTransaction.signature);
return this._exchange.batchExecuteTransactions.awaitTransactionSuccessAsync(signedTransactions, signatures, {
from,
gasPrice: opts.gasPrice,
});
}
public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise<BigNumber> {
@@ -214,14 +215,11 @@ export class ExchangeWrapper {
const orderInfo = await this._exchange.getOrderInfo.callAsync(signedOrder);
return orderInfo;
}
public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise<OrderInfo[]> {
const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[];
return ordersInfo;
}
public async batchMatchOrdersAsync(
signedOrdersLeft: SignedOrder[],
signedOrdersRight: SignedOrder[],
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createBatchMatchOrders(signedOrdersLeft, signedOrdersRight);
return this._exchange.batchMatchOrders.awaitTransactionSuccessAsync(
@@ -229,25 +227,27 @@ export class ExchangeWrapper {
params.rightOrders,
params.leftSignatures,
params.rightSignatures,
{ from },
{ from, gasPrice: opts.gasPrice },
);
}
public async batchMatchOrdersRawAsync(
params: BatchMatchOrder,
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
return this._exchange.batchMatchOrders.awaitTransactionSuccessAsync(
params.leftOrders,
params.rightOrders,
params.leftSignatures,
params.rightSignatures,
{ from },
{ from, gasPrice: opts.gasPrice },
);
}
public async getBatchMatchOrdersResultsAsync(
signedOrdersLeft: SignedOrder[],
signedOrdersRight: SignedOrder[],
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<BatchMatchedFillResults> {
const params = orderUtils.createBatchMatchOrders(signedOrdersLeft, signedOrdersRight);
const batchMatchedFillResults = await this._exchange.batchMatchOrders.callAsync(
@@ -255,7 +255,7 @@ export class ExchangeWrapper {
params.rightOrders,
params.leftSignatures,
params.rightSignatures,
{ from },
{ from, gasPrice: opts.gasPrice },
);
return batchMatchedFillResults;
}
@@ -263,6 +263,7 @@ export class ExchangeWrapper {
signedOrdersLeft: SignedOrder[],
signedOrdersRight: SignedOrder[],
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createBatchMatchOrders(signedOrdersLeft, signedOrdersRight);
return this._exchange.batchMatchOrdersWithMaximalFill.awaitTransactionSuccessAsync(
@@ -270,25 +271,27 @@ export class ExchangeWrapper {
params.rightOrders,
params.leftSignatures,
params.rightSignatures,
{ from },
{ from, gasPrice: opts.gasPrice },
);
}
public async batchMatchOrdersWithMaximalFillRawAsync(
params: BatchMatchOrder,
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
return this._exchange.batchMatchOrdersWithMaximalFill.awaitTransactionSuccessAsync(
params.leftOrders,
params.rightOrders,
params.leftSignatures,
params.rightSignatures,
{ from },
{ from, gasPrice: opts.gasPrice },
);
}
public async getBatchMatchOrdersWithMaximalFillResultsAsync(
signedOrdersLeft: SignedOrder[],
signedOrdersRight: SignedOrder[],
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<BatchMatchedFillResults> {
const params = orderUtils.createBatchMatchOrders(signedOrdersLeft, signedOrdersRight);
const batchMatchedFillResults = await this._exchange.batchMatchOrdersWithMaximalFill.callAsync(
@@ -296,7 +299,7 @@ export class ExchangeWrapper {
params.rightOrders,
params.leftSignatures,
params.rightSignatures,
{ from },
{ from, gasPrice: opts.gasPrice },
);
return batchMatchedFillResults;
}
@@ -304,6 +307,7 @@ export class ExchangeWrapper {
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
const txReceipt = await this._exchange.matchOrders.awaitTransactionSuccessAsync(
@@ -311,7 +315,7 @@ export class ExchangeWrapper {
params.right,
params.leftSignature,
params.rightSignature,
{ from },
{ from, gasPrice: opts.gasPrice },
);
return txReceipt;
}
@@ -319,6 +323,7 @@ export class ExchangeWrapper {
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<MatchedFillResults> {
const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
const matchedFillResults = await this._exchange.matchOrders.callAsync(
@@ -326,7 +331,7 @@ export class ExchangeWrapper {
params.right,
params.leftSignature,
params.rightSignature,
{ from },
{ from, gasPrice: opts.gasPrice },
);
return matchedFillResults;
}
@@ -334,6 +339,7 @@ export class ExchangeWrapper {
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
from: string,
opts: { gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
return this._exchange.matchOrdersWithMaximalFill.awaitTransactionSuccessAsync(
@@ -341,13 +347,14 @@ export class ExchangeWrapper {
params.right,
params.leftSignature,
params.rightSignature,
{ from },
{ from, gasPrice: opts.gasPrice },
);
}
public async getMatchOrdersWithMaximalFillResultsAsync(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
from: string,
opts: { gasPrice?: BigNumber },
): Promise<MatchedFillResults> {
const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
const matchedFillResults = await this._exchange.matchOrdersWithMaximalFill.callAsync(
@@ -355,21 +362,21 @@ export class ExchangeWrapper {
params.right,
params.leftSignature,
params.rightSignature,
{ from },
{ from, gasPrice: opts.gasPrice },
);
return matchedFillResults;
}
public async getFillOrderResultsAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
opts: { takerAssetFillAmount?: BigNumber; gasPrice?: BigNumber } = {},
): Promise<FillResults> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const fillResults = await this._exchange.fillOrder.callAsync(
params.order,
params.takerAssetFillAmount,
params.signature,
{ from },
{ from, gasPrice: opts.gasPrice },
);
return fillResults;
}

View File

@@ -5,7 +5,7 @@ import {
ERC721Wrapper,
MultiAssetProxyContract,
} from '@0x/contracts-asset-proxy';
import { constants, expect, orderUtils, signingUtils } from '@0x/contracts-test-utils';
import { constants, expect, LogDecoder, orderUtils, signingUtils } from '@0x/contracts-test-utils';
import { BalanceAndProxyAllowanceLazyStore, ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils';
import { FillResults, Order, SignatureType, SignedOrder } from '@0x/types';
import { BigNumber, errorUtils, providerUtils, RevertError, StringRevertError } from '@0x/utils';
@@ -40,6 +40,7 @@ const EMPTY_FILL_RESULTS = {
makerAssetFilledAmount: constants.ZERO_AMOUNT,
makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: constants.ZERO_AMOUNT,
};
enum TestOutlook {
@@ -48,6 +49,7 @@ enum TestOutlook {
Failure,
}
// FIXME - Really punting on this for now. It's possible that this won't need to be changed.
/**
* Instantiates a new instance of FillOrderCombinatorialUtils. Since this method has some
* required async setup, a factory method is required.
@@ -114,6 +116,8 @@ export async function fillOrderCombinatorialUtilsFactoryAsync(
{},
new BigNumber(chainId),
);
const logDecoder = new LogDecoder(web3Wrapper, artifacts);
const exchangeWrapper = new ExchangeWrapper(exchangeContract, provider);
await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, ownerAddress);
await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, ownerAddress);
@@ -202,6 +206,7 @@ export async function fillOrderCombinatorialUtilsFactoryAsync(
takerAddress,
exchangeWrapper,
assetWrapper,
logDecoder,
);
return fillOrderCombinatorialUtils;
}
@@ -215,6 +220,7 @@ export class FillOrderCombinatorialUtils {
public takerAddress: string;
public exchangeWrapper: ExchangeWrapper;
public assetWrapper: AssetWrapper;
public logDecoder: LogDecoder;
public balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher;
public static generateFillOrderCombinations(): FillScenario[] {
@@ -445,6 +451,7 @@ export class FillOrderCombinatorialUtils {
takerAddress: string,
exchangeWrapper: ExchangeWrapper,
assetWrapper: AssetWrapper,
logDecoder: LogDecoder,
) {
this.provider = provider;
this.orderFactory = orderFactory;
@@ -454,6 +461,7 @@ export class FillOrderCombinatorialUtils {
this.takerAddress = takerAddress;
this.exchangeWrapper = exchangeWrapper;
this.assetWrapper = assetWrapper;
this.logDecoder = logDecoder;
this.balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(assetWrapper);
}
@@ -643,7 +651,7 @@ export class FillOrderCombinatorialUtils {
);
expect(exchangeLogs.length).to.be.equal(1, 'logs length');
// tslint:disable-next-line:no-unnecessary-type-assertion
const log = txReceipt.logs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>;
const log = exchangeLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>;
expect(log.args.makerAddress, 'log.args.makerAddress').to.be.equal(makerAddress);
expect(log.args.takerAddress, 'log.args.takerAddress').to.be.equal(this.takerAddress);
expect(log.args.feeRecipientAddress, 'log.args.feeRecipientAddress').to.be.equal(feeRecipient);

View File

@@ -20,6 +20,7 @@ export enum FillOrderError {
TransferFailed = 'TRANSFER_FAILED',
}
// FIXME - Punting on protocol fees for now
/**
* Simplified fill order simulator.
*/
@@ -121,6 +122,7 @@ export class FillOrderSimulator {
makerAssetFilledAmount: makerAssetFillAmount,
makerFeePaid,
takerFeePaid,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
};
}
}

View File

@@ -1,5 +1,5 @@
import { ERC1155ProxyWrapper, ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
import { ERC1155HoldingsByOwner, expect, OrderStatus } from '@0x/contracts-test-utils';
import { constants, ERC1155HoldingsByOwner, expect, OrderStatus } from '@0x/contracts-test-utils';
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { AssetProxyId, BatchMatchedFillResults, FillResults, MatchedFillResults, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
@@ -235,6 +235,7 @@ export class MatchOrderTester {
return expectedBatchMatchResults;
}
// FIXME - Punting on protocol fees until later
/**
* Matches two complementary orders and asserts results.
* @param orders The matched orders and filled states.
@@ -266,6 +267,7 @@ export class MatchOrderTester {
orders.leftOrder,
orders.rightOrder,
takerAddress,
{}, // FIXME
);
transactionReceipt = await this._executeMatchOrdersWithMaximalFillAsync(
orders.leftOrder,
@@ -1198,12 +1200,14 @@ function convertToMatchResults(result: MatchResults): MatchedFillResults {
takerAssetFilledAmount: result.fills[0].takerAssetFilledAmount,
makerFeePaid: result.fills[0].makerFeePaid,
takerFeePaid: result.fills[0].takerFeePaid,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
right: {
makerAssetFilledAmount: result.fills[1].makerAssetFilledAmount,
takerAssetFilledAmount: result.fills[1].takerAssetFilledAmount,
makerFeePaid: result.fills[1].makerFeePaid,
takerFeePaid: result.fills[1].takerFeePaid,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
},
profitInLeftMakerAsset,
profitInRightMakerAsset,
@@ -1222,6 +1226,7 @@ function convertToFillResults(result: FillEventArgs): FillResults {
takerAssetFilledAmount: result.takerAssetFilledAmount,
makerFeePaid: result.makerFeePaid,
takerFeePaid: result.takerFeePaid,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
};
return fillResults;
}

View File

@@ -8,7 +8,6 @@ import {
ERC20BalancesByOwner,
expect,
getLatestBlockTimestampAsync,
increaseTimeAndMineBlockAsync,
OrderFactory,
} from '@0x/contracts-test-utils';
import { assetDataUtils, ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils';
@@ -48,11 +47,15 @@ blockchainTests.resets('Exchange wrappers', env => {
let defaultTakerAssetAddress: string;
let defaultFeeAssetAddress: string;
const DEFAULT_GAS_PRICE = new BigNumber(2);
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(150);
const nullFillResults: FillResults = {
makerAssetFilledAmount: constants.ZERO_AMOUNT,
takerAssetFilledAmount: constants.ZERO_AMOUNT,
makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: constants.ZERO_AMOUNT,
};
before(async () => {
@@ -81,10 +84,16 @@ blockchainTests.resets('Exchange wrappers', env => {
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
env.provider,
env.txDefaults,
{ ...env.txDefaults, from: owner },
{},
new BigNumber(chainId),
);
// Set the protocol fee multiplier of the exchange
await exchange.updateProtocolFeeMultiplier.awaitTransactionSuccessAsync(PROTOCOL_FEE_MULTIPLIER, {
from: owner,
});
exchangeWrapper = new ExchangeWrapper(exchange, env.provider);
await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
@@ -492,11 +501,14 @@ blockchainTests.resets('Exchange wrappers', env => {
.dividedToIntegerBy(signedOrder.makerAssetAmount);
takerAssetFillAmounts.push(takerAssetFillAmount);
// FIXME - Punting on these tests for now since no staking contract will be registered. This
// should be revisited when the protocol fee testing has been unit tested well.
expectedFillResults.push({
takerAssetFilledAmount: takerAssetFillAmount,
makerAssetFilledAmount,
makerFeePaid: makerFee,
takerFeePaid: takerFee,
protocolFeePaid: constants.ZERO_AMOUNT,
});
erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
@@ -522,11 +534,13 @@ blockchainTests.resets('Exchange wrappers', env => {
].plus(makerFee.plus(takerFee));
});
// FIXME - Punting on these tests for now since no staking contract will be registered. This
// should be revisited when the protocol fee testing has been unit tested well.
const fillResults = await exchange.batchFillOrders.callAsync(
signedOrders,
takerAssetFillAmounts,
signedOrders.map(signedOrder => signedOrder.signature),
{ from: takerAddress },
{ from: takerAddress, gasPrice: DEFAULT_GAS_PRICE },
);
await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, {
takerAssetFillAmounts,
@@ -559,11 +573,14 @@ blockchainTests.resets('Exchange wrappers', env => {
.dividedToIntegerBy(signedOrder.makerAssetAmount);
takerAssetFillAmounts.push(takerAssetFillAmount);
// FIXME - Punting on these tests for now since no staking contract will be registered. This
// should be revisited when the protocol fee testing has been unit tested well.
expectedFillResults.push({
takerAssetFilledAmount: takerAssetFillAmount,
makerAssetFilledAmount,
makerFeePaid: makerFee,
takerFeePaid: takerFee,
protocolFeePaid: constants.ZERO_AMOUNT,
});
erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
@@ -642,11 +659,14 @@ blockchainTests.resets('Exchange wrappers', env => {
.dividedToIntegerBy(signedOrder.makerAssetAmount);
takerAssetFillAmounts.push(takerAssetFillAmount);
// FIXME - Punting on these tests for now since no staking contract will be registered. This
// should be revisited when the protocol fee testing has been unit tested well.
expectedFillResults.push({
takerAssetFilledAmount: takerAssetFillAmount,
makerAssetFilledAmount,
makerFeePaid: makerFee,
takerFeePaid: takerFee,
protocolFeePaid: constants.ZERO_AMOUNT,
});
erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
@@ -712,11 +732,14 @@ blockchainTests.resets('Exchange wrappers', env => {
.dividedToIntegerBy(signedOrder.makerAssetAmount);
takerAssetFillAmounts.push(takerAssetFillAmount);
// FIXME - Punting on these tests for now since no staking contract will be registered. This
// should be revisited when the protocol fee testing has been unit tested well.
expectedFillResults.push({
takerAssetFilledAmount: takerAssetFillAmount,
makerAssetFilledAmount,
makerFeePaid: makerFee,
takerFeePaid: takerFee,
protocolFeePaid: constants.ZERO_AMOUNT,
});
erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
@@ -853,6 +876,7 @@ blockchainTests.resets('Exchange wrappers', env => {
takerAssetFilledAmount: signedOrder.takerAssetAmount,
makerFeePaid: signedOrder.makerFee,
takerFeePaid: signedOrder.takerFee,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME - This is what is being used now.
}))
.reduce(
(totalFillResults, currentFillResults) => ({
@@ -864,6 +888,7 @@ blockchainTests.resets('Exchange wrappers', env => {
),
makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid),
takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid),
protocolFeePaid: totalFillResults.protocolFeePaid.plus(currentFillResults.protocolFeePaid),
}),
nullFillResults,
);
@@ -923,6 +948,7 @@ blockchainTests.resets('Exchange wrappers', env => {
takerAssetFilledAmount: signedOrder.takerAssetAmount,
makerFeePaid: signedOrder.makerFee,
takerFeePaid: signedOrder.takerFee,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}))
.reduce(
(totalFillResults, currentFillResults) => ({
@@ -934,6 +960,7 @@ blockchainTests.resets('Exchange wrappers', env => {
),
makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid),
takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid),
protocolFeePaid: totalFillResults.protocolFeePaid.plus(currentFillResults.protocolFeePaid),
}),
nullFillResults,
);
@@ -1037,6 +1064,7 @@ blockchainTests.resets('Exchange wrappers', env => {
takerAssetFilledAmount: signedOrder.takerAssetAmount,
makerFeePaid: signedOrder.makerFee,
takerFeePaid: signedOrder.takerFee,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}))
.reduce(
(totalFillResults, currentFillResults) => ({
@@ -1048,6 +1076,7 @@ blockchainTests.resets('Exchange wrappers', env => {
),
makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid),
takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid),
protocolFeePaid: totalFillResults.protocolFeePaid.plus(currentFillResults.protocolFeePaid),
}),
nullFillResults,
);
@@ -1108,6 +1137,7 @@ blockchainTests.resets('Exchange wrappers', env => {
takerAssetFilledAmount: signedOrder.takerAssetAmount,
makerFeePaid: signedOrder.makerFee,
takerFeePaid: signedOrder.takerFee,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}))
.reduce(
(totalFillResults, currentFillResults) => ({
@@ -1119,6 +1149,7 @@ blockchainTests.resets('Exchange wrappers', env => {
),
makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid),
takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid),
protocolFeePaid: totalFillResults.protocolFeePaid.plus(currentFillResults.protocolFeePaid),
}),
nullFillResults,
);
@@ -1151,189 +1182,5 @@ blockchainTests.resets('Exchange wrappers', env => {
});
});
});
describe('getOrdersInfo', () => {
beforeEach(async () => {
signedOrders = [
await orderFactory.newSignedOrderAsync(),
await orderFactory.newSignedOrderAsync(),
await orderFactory.newSignedOrderAsync(),
];
});
it('should get the correct information for multiple unfilled orders', async () => {
const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders);
expect(ordersInfo.length).to.be.equal(3);
_.forEach(signedOrders, (signedOrder, index) => {
const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
const expectedTakerAssetFilledAmount = new BigNumber(0);
const expectedOrderStatus = OrderStatus.Fillable;
const orderInfo = ordersInfo[index];
expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount);
expect(orderInfo.orderStatus).to.equal(expectedOrderStatus);
});
});
it('should get the correct information for multiple partially filled orders', async () => {
const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2));
await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts });
const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders);
expect(ordersInfo.length).to.be.equal(3);
_.forEach(signedOrders, (signedOrder, index) => {
const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2);
const expectedOrderStatus = OrderStatus.Fillable;
const orderInfo = ordersInfo[index];
expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount);
expect(orderInfo.orderStatus).to.equal(expectedOrderStatus);
});
});
it('should get the correct information for multiple fully filled orders', async () => {
await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress);
const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders);
expect(ordersInfo.length).to.be.equal(3);
_.forEach(signedOrders, (signedOrder, index) => {
const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount;
const expectedOrderStatus = OrderStatus.FullyFilled;
const orderInfo = ordersInfo[index];
expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount);
expect(orderInfo.orderStatus).to.equal(expectedOrderStatus);
});
});
it('should get the correct information for multiple cancelled and unfilled orders', async () => {
await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress);
const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders);
expect(ordersInfo.length).to.be.equal(3);
_.forEach(signedOrders, (signedOrder, index) => {
const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
const expectedTakerAssetFilledAmount = new BigNumber(0);
const expectedOrderStatus = OrderStatus.Cancelled;
const orderInfo = ordersInfo[index];
expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount);
expect(orderInfo.orderStatus).to.equal(expectedOrderStatus);
});
});
it('should get the correct information for multiple cancelled and partially filled orders', async () => {
const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2));
await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts });
await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress);
const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders);
expect(ordersInfo.length).to.be.equal(3);
_.forEach(signedOrders, (signedOrder, index) => {
const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2);
const expectedOrderStatus = OrderStatus.Cancelled;
const orderInfo = ordersInfo[index];
expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount);
expect(orderInfo.orderStatus).to.equal(expectedOrderStatus);
});
});
it('should get the correct information for multiple expired and unfilled orders', async () => {
const currentTimestamp = await getLatestBlockTimestampAsync();
const timeUntilExpiration = signedOrders[0].expirationTimeSeconds.minus(currentTimestamp).toNumber();
await increaseTimeAndMineBlockAsync(timeUntilExpiration);
const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders);
expect(ordersInfo.length).to.be.equal(3);
_.forEach(signedOrders, (signedOrder, index) => {
const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
const expectedTakerAssetFilledAmount = new BigNumber(0);
const expectedOrderStatus = OrderStatus.Expired;
const orderInfo = ordersInfo[index];
expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount);
expect(orderInfo.orderStatus).to.equal(expectedOrderStatus);
});
});
it('should get the correct information for multiple expired and partially filled orders', async () => {
const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2));
await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts });
const currentTimestamp = await getLatestBlockTimestampAsync();
const timeUntilExpiration = signedOrders[0].expirationTimeSeconds.minus(currentTimestamp).toNumber();
await increaseTimeAndMineBlockAsync(timeUntilExpiration);
const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders);
expect(ordersInfo.length).to.be.equal(3);
_.forEach(signedOrders, (signedOrder, index) => {
const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2);
const expectedOrderStatus = OrderStatus.Expired;
const orderInfo = ordersInfo[index];
expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount);
expect(orderInfo.orderStatus).to.equal(expectedOrderStatus);
});
});
it('should get the correct information for a mix of unfilled, partially filled, fully filled, cancelled, and expired orders', async () => {
const unfilledOrder = await orderFactory.newSignedOrderAsync();
const partiallyFilledOrder = await orderFactory.newSignedOrderAsync();
await exchangeWrapper.fillOrderAsync(partiallyFilledOrder, takerAddress, {
takerAssetFillAmount: partiallyFilledOrder.takerAssetAmount.div(2),
});
const fullyFilledOrder = await orderFactory.newSignedOrderAsync();
await exchangeWrapper.fillOrderAsync(fullyFilledOrder, takerAddress);
const cancelledOrder = await orderFactory.newSignedOrderAsync();
await exchangeWrapper.cancelOrderAsync(cancelledOrder, makerAddress);
const currentTimestamp = await getLatestBlockTimestampAsync();
const expiredOrder = await orderFactory.newSignedOrderAsync({
expirationTimeSeconds: new BigNumber(currentTimestamp),
});
signedOrders = [unfilledOrder, partiallyFilledOrder, fullyFilledOrder, cancelledOrder, expiredOrder];
const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders);
expect(ordersInfo.length).to.be.equal(5);
const expectedUnfilledOrderHash = orderHashUtils.getOrderHashHex(unfilledOrder);
const expectedUnfilledTakerAssetFilledAmount = new BigNumber(0);
const expectedUnfilledOrderStatus = OrderStatus.Fillable;
const unfilledOrderInfo = ordersInfo[0];
expect(unfilledOrderInfo.orderHash).to.be.equal(expectedUnfilledOrderHash);
expect(unfilledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(
expectedUnfilledTakerAssetFilledAmount,
);
expect(unfilledOrderInfo.orderStatus).to.be.equal(expectedUnfilledOrderStatus);
const expectedPartialOrderHash = orderHashUtils.getOrderHashHex(partiallyFilledOrder);
const expectedPartialTakerAssetFilledAmount = partiallyFilledOrder.takerAssetAmount.div(2);
const expectedPartialOrderStatus = OrderStatus.Fillable;
const partialOrderInfo = ordersInfo[1];
expect(partialOrderInfo.orderHash).to.be.equal(expectedPartialOrderHash);
expect(partialOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(
expectedPartialTakerAssetFilledAmount,
);
expect(partialOrderInfo.orderStatus).to.be.equal(expectedPartialOrderStatus);
const expectedFilledOrderHash = orderHashUtils.getOrderHashHex(fullyFilledOrder);
const expectedFilledTakerAssetFilledAmount = fullyFilledOrder.takerAssetAmount;
const expectedFilledOrderStatus = OrderStatus.FullyFilled;
const filledOrderInfo = ordersInfo[2];
expect(filledOrderInfo.orderHash).to.be.equal(expectedFilledOrderHash);
expect(filledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(
expectedFilledTakerAssetFilledAmount,
);
expect(filledOrderInfo.orderStatus).to.be.equal(expectedFilledOrderStatus);
const expectedCancelledOrderHash = orderHashUtils.getOrderHashHex(cancelledOrder);
const expectedCancelledTakerAssetFilledAmount = new BigNumber(0);
const expectedCancelledOrderStatus = OrderStatus.Cancelled;
const cancelledOrderInfo = ordersInfo[3];
expect(cancelledOrderInfo.orderHash).to.be.equal(expectedCancelledOrderHash);
expect(cancelledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(
expectedCancelledTakerAssetFilledAmount,
);
expect(cancelledOrderInfo.orderStatus).to.be.equal(expectedCancelledOrderStatus);
const expectedExpiredOrderHash = orderHashUtils.getOrderHashHex(expiredOrder);
const expectedExpiredTakerAssetFilledAmount = new BigNumber(0);
const expectedExpiredOrderStatus = OrderStatus.Expired;
const expiredOrderInfo = ordersInfo[4];
expect(expiredOrderInfo.orderHash).to.be.equal(expectedExpiredOrderHash);
expect(expiredOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(
expectedExpiredTakerAssetFilledAmount,
);
expect(expiredOrderInfo.orderStatus).to.be.equal(expectedExpiredOrderStatus);
});
});
});
}); // tslint:disable-line:max-file-line-count

View File

@@ -28,6 +28,7 @@ blockchainTests('Exchange wrapper functions unit tests.', env => {
const { ONE_ETHER, MAX_UINT256 } = constants;
const { addFillResults, getPartialAmountFloor } = LibReferenceFunctions;
const { safeSub } = UtilReferenceFunctions;
const protocolFeeMultiplier = new BigNumber(150000);
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
const randomAssetData = () => hexRandom(34);
const randomAmount = (maxAmount: BigNumber = ONE_ETHER) => maxAmount.times(_.random(0, 100, true).toFixed(12));
@@ -40,20 +41,30 @@ blockchainTests('Exchange wrapper functions unit tests.', env => {
takerAssetFilledAmount: constants.ZERO_AMOUNT,
makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: constants.ZERO_AMOUNT,
};
let testContract: TestWrapperFunctionsContract;
let txHelper: TransactionHelper;
let owner: string;
let senderAddress: string;
before(async () => {
[senderAddress] = await env.getAccountAddressesAsync();
[owner, senderAddress] = await env.getAccountAddressesAsync();
txHelper = new TransactionHelper(env.web3Wrapper, artifacts);
testContract = await TestWrapperFunctionsContract.deployFrom0xArtifactAsync(
artifacts.TestWrapperFunctions,
env.provider,
env.txDefaults,
{
...env.txDefaults,
from: owner,
},
{},
);
// Set the protocol fee multiplier.
await testContract.updateProtocolFeeMultiplier.awaitTransactionSuccessAsync(protocolFeeMultiplier, {
from: owner,
});
});
function randomOrder(fields?: Partial<Order>): Order {
@@ -96,6 +107,7 @@ blockchainTests('Exchange wrapper functions unit tests.', env => {
takerAssetFilledAmount: order.takerAssetAmount,
makerFeePaid: order.makerFee,
takerFeePaid: order.takerFee,
protocolFeePaid: protocolFeeMultiplier,
};
}
@@ -1309,50 +1321,5 @@ blockchainTests('Exchange wrapper functions unit tests.', env => {
return expect(tx).to.revertWith(expectedError);
});
});
describe('getOrdersInfo', () => {
// Computes the expected (fake) order info generated by the `TestWrapperFunctions` contract.
function getExpectedOrderInfo(order: Order): OrderInfo {
const MAX_ORDER_STATUS = OrderStatus.Cancelled as number;
return {
orderHash: getExpectedOrderHash(order),
// Lower uint128 of `order.salt` is the `orderTakerAssetFilledAmount`.
orderTakerAssetFilledAmount: order.salt.mod(new BigNumber(2).pow(128)),
// High byte of `order.salt` is the `orderStatus`.
orderStatus:
order.salt.dividedToIntegerBy(new BigNumber(2).pow(248)).toNumber() % (MAX_ORDER_STATUS + 1),
};
}
it('works with no orders', async () => {
const infos = await testContract.getOrdersInfo.callAsync([]);
expect(infos.length).to.eq(0);
});
it('works with one order', async () => {
const orders = [randomOrder()];
const expectedResult = orders.map(getExpectedOrderInfo);
const actualResult = await testContract.getOrdersInfo.callAsync(orders);
expect(actualResult).to.deep.eq(expectedResult);
});
it('works with many orders', async () => {
const NUM_ORDERS = 16;
const orders = _.times(NUM_ORDERS, () => randomOrder());
const expectedResult = orders.map(getExpectedOrderInfo);
const actualResult = await testContract.getOrdersInfo.callAsync(orders);
expect(actualResult).to.deep.eq(expectedResult);
});
it('works with duplicate orders', async () => {
const NUM_UNIQUE_ORDERS = 4;
const CLONE_COUNT = 2;
const uniqueOrders = _.times(NUM_UNIQUE_ORDERS, () => randomOrder());
const orders = _.shuffle(_.flatten(_.times(CLONE_COUNT, () => uniqueOrders)));
const expectedResult = orders.map(getExpectedOrderInfo);
const actualResult = await testContract.getOrdersInfo.callAsync(orders);
expect(actualResult).to.deep.eq(expectedResult);
});
});
});
// tslint:disable-next-line: max-file-line-count

View File

@@ -25,7 +25,6 @@
"generated-artifacts/MixinMatchOrders.json",
"generated-artifacts/MixinProtocolFees.json",
"generated-artifacts/MixinSignatureValidator.json",
"generated-artifacts/MixinStakingManager.json",
"generated-artifacts/MixinTransactions.json",
"generated-artifacts/MixinTransferSimulator.json",
"generated-artifacts/MixinWrapperFunctions.json",

View File

@@ -21,37 +21,15 @@ pragma solidity ^0.5.9;
interface IStaking {
/// @dev Pays several protocols fee in ETH.
/// @param makers The addresses of the order makers.
/// @param fees The fee amounts paid by each of the makers.
function batchPayProtocolFees(
address[] calldata makers,
uint256[] calldata fees
)
external
payable;
/// @dev Pays a protocol fee in ETH.
/// @param makerAddress The address of the order's maker.
function payProtocolFee(address makerAddress)
/// @param payerAddress The address that is responsible for paying the protocol fee.
/// @param protocolFeePaid The amount of protocol fees that should be paid.
function payProtocolFee(
address makerAddress,
address payerAddress,
uint256 protocolFeePaid
)
external
payable;
/// @dev Records several protocol fees that were paid in WETH.
/// @param makers The addresses of the order makers.
/// @param fees The fee amounts paid by each of the makers.
function batchRecordProtocolFees(
address[] calldata makers,
uint256[] calldata fees
)
external;
/// @dev Records a protocol fee that was paid in WETH.
/// @param makerAddress The address of the order's maker.
/// @param fee The fee amount that was paid by the maker.
function recordProtocolFee(
address makerAddress,
uint256 fee
)
external;
}

View File

@@ -27,7 +27,7 @@ contract Refundable {
modifier refundFinalBalance {
_;
if (!shouldNotRefund) {
msg.sender.transfer(address(this).balance);
refundNonzeroBalance();
}
}
@@ -38,7 +38,16 @@ contract Refundable {
shouldNotRefund = true;
_;
shouldNotRefund = false;
msg.sender.transfer(address(this).balance);
refundNonzeroBalance();
}
}
function refundNonzeroBalance()
internal
{
uint256 balance = address(this).balance;
if (balance > 0) {
msg.sender.transfer(balance);
}
}
}

View File

@@ -24,6 +24,13 @@ import "../src/Refundable.sol";
contract TestRefundable is
Refundable
{
function refundNonzeroBalanceExternal()
external
payable
{
refundNonzeroBalance();
}
function setShouldNotRefund(bool shouldNotRefundNew)
external
{

View File

@@ -24,10 +24,33 @@ import "./TestRefundable.sol";
contract TestRefundableReceiver {
/// @dev A payable fallback function is necessary to receive refunds from the `TestRefundable` contract.
/// This function ensures that zero value is not sent to the contract, which tests the feature of
/// of the `refundNonzeroBalance` that doesn't transfer if the balance is zero.
function ()
external
payable
{} // solhint-disable-line no-empty-blocks
{
// Ensure that a value of zero was not transferred to the contract.
require(msg.value != 0, "Zero value should not be sent to this contract.");
}
/// @dev This function tests the behavior of the `refundNonzeroBalance` function by checking whether or
/// not the `callCounter` state variable changes after the `refundNonzeroBalance` is called.
/// @param testRefundable The TestRefundable that should be tested against.
function testRefundNonzeroBalance(TestRefundable testRefundable)
external
payable
{
// Call `refundNonzeroBalance()` and forward all of the eth sent to the contract.
testRefundable.refundNonzeroBalanceExternal.value(msg.value)();
// If the value sent was nonzero, a check that a refund was received will be executed. Otherwise, the fallback
// function contains a check that will fail in the event that a value of zero was sent to the contract.
if (msg.value > 0) {
// Ensure that a full refund was provided to this contract.
require(address(this).balance == msg.value, "A full refund was not provided by `refundNonzeroBalance`");
}
}
/// @dev This function tests the behavior to a simple call to `refundFinalBalanceFunction`. This
/// test will verify that the correct refund was provided after the call (depending on whether

View File

@@ -1,4 +1,4 @@
import { blockchainTests } from '@0x/contracts-test-utils';
import { blockchainTests, constants } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
@@ -8,6 +8,9 @@ blockchainTests('Refundable', env => {
let refundable: TestRefundableContract;
let receiver: TestRefundableReceiverContract;
const ONE_HUNDRED = new BigNumber(100);
const ONE_THOUSAND = new BigNumber(1000);
before(async () => {
// Create the refundable contract.
refundable = await TestRefundableContract.deployFrom0xArtifactAsync(
@@ -26,13 +29,32 @@ blockchainTests('Refundable', env => {
);
});
// The contents of these typescript tests is not adequate to understand the assertions that are made during
// these calls. For a more accurate picture, checkout out "./contracts/test/TestRefundableReceiver.sol". Specifically,
// the function `testRefundNonzeroBalance()` is used in this test suite.
blockchainTests.resets('refundNonzeroBalance', () => {
it('should not send a refund when no value is sent', async () => {
// Send 100 wei to the refundable contract that should be refunded.
await receiver.testRefundNonzeroBalance.awaitTransactionSuccessAsync(refundable.address, {
value: constants.ZERO_AMOUNT,
});
});
it('should send a full refund when nonzero value is sent', async () => {
// Send 100 wei to the refundable contract that should be refunded.
await receiver.testRefundNonzeroBalance.awaitTransactionSuccessAsync(refundable.address, {
value: ONE_HUNDRED,
});
});
});
// The contents of these typescript tests is not adequate to understand the assertions that are made during
// these calls. For a more accurate picture, checkout out "./contracts/test/TestRefundableReceiver.sol".
blockchainTests.resets('refundFinalBalance', async () => {
blockchainTests.resets('refundFinalBalance', () => {
it('should fully refund the sender when `shouldNotRefund` is false', async () => {
// Send 100 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testRefundFinalBalance.awaitTransactionSuccessAsync(refundable.address, false, {
value: new BigNumber(100),
value: ONE_HUNDRED,
});
});
@@ -40,7 +62,7 @@ blockchainTests('Refundable', env => {
it('should fully refund the sender when `shouldNotRefund` is false for two calls in a row', async () => {
// Send 100 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testRefundFinalBalance.awaitTransactionSuccessAsync(refundable.address, false, {
value: new BigNumber(100),
value: ONE_HUNDRED,
});
// Send 1000 wei to the refundable contract that should be refunded to the receiver contract.
@@ -59,11 +81,11 @@ blockchainTests('Refundable', env => {
// The contents of these typescript tests is not adequate to understand the assertions that are made during
// these calls. For a more accurate picture, checkout out "./contracts/test/TestRefundableReceiver.sol".
blockchainTests.resets('disableRefundUntilEnd', async () => {
blockchainTests.resets('disableRefundUntilEnd', () => {
it('should fully refund the sender when `shouldNotRefund` is false', async () => {
// Send 100 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, {
value: new BigNumber(100),
value: ONE_HUNDRED,
});
});
@@ -71,47 +93,47 @@ blockchainTests('Refundable', env => {
it('should fully refund the sender when `shouldNotRefund` is false for two calls in a row', async () => {
// Send 100 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, {
value: new BigNumber(100),
value: ONE_HUNDRED,
});
// Send 1000 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, {
value: new BigNumber(1000),
value: ONE_THOUSAND,
});
});
it('should not refund the sender if `shouldNotRefund` is true', async () => {
/// Send 100 wei to the refundable contract that should not be refunded.
await receiver.testDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, {
value: new BigNumber(100),
value: ONE_HUNDRED,
});
});
it('should disable the `disableRefundUntilEnd` modifier and refund when `shouldNotRefund` is false', async () => {
/// Send 100 wei to the refundable contract that should be refunded.
await receiver.testNestedDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, {
value: new BigNumber(100),
value: ONE_HUNDRED,
});
});
it('should disable the `refundFinalBalance` modifier and send no refund when `shouldNotRefund` is true', async () => {
/// Send 100 wei to the refundable contract that should not be refunded.
await receiver.testNestedDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, true, {
value: new BigNumber(100),
value: ONE_HUNDRED,
});
});
it('should disable the `refundFinalBalance` modifier and refund when `shouldNotRefund` is false', async () => {
/// Send 100 wei to the refundable contract that should be refunded.
await receiver.testMixedRefunds.awaitTransactionSuccessAsync(refundable.address, false, {
value: new BigNumber(100),
value: ONE_HUNDRED,
});
});
it('should disable the `refundFinalBalance` modifier and send no refund when `shouldNotRefund` is true', async () => {
/// Send 100 wei to the refundable contract that should not be refunded.
await receiver.testMixedRefunds.awaitTransactionSuccessAsync(refundable.address, true, {
value: new BigNumber(100),
value: ONE_HUNDRED,
});
});
});