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

View File

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

View File

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

View File

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

View File

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

View File

@@ -35,12 +35,12 @@ import "./MixinSignatureValidator.sol";
contract MixinExchangeCore is contract MixinExchangeCore is
IExchangeCore,
Refundable, Refundable,
LibEIP712ExchangeDomain, LibEIP712ExchangeDomain,
IExchangeCore,
MixinAssetProxyDispatcher, MixinAssetProxyDispatcher,
MixinSignatureValidator, MixinProtocolFees,
MixinProtocolFees MixinSignatureValidator
{ {
using LibOrder for LibOrder.Order; using LibOrder for LibOrder.Order;
using LibSafeMath for uint256; using LibSafeMath for uint256;
@@ -103,7 +103,7 @@ contract MixinExchangeCore is
public public
payable payable
nonReentrant nonReentrant
refund refundFinalBalance
returns (LibFillResults.FillResults memory fillResults) returns (LibFillResults.FillResults memory fillResults)
{ {
fillResults = _fillOrder( fillResults = _fillOrder(
@@ -217,10 +217,18 @@ contract MixinExchangeCore is
uint256 takerAssetFilledAmount = LibSafeMath.min256(takerAssetFillAmount, remainingTakerAssetAmount); uint256 takerAssetFilledAmount = LibSafeMath.min256(takerAssetFillAmount, remainingTakerAssetAmount);
// Compute proportional fill amounts // Compute proportional fill amounts
fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount, protocolFeeMultiplier); fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount);
bytes32 orderHash = orderInfo.orderHash; bytes32 orderHash = orderInfo.orderHash;
// Settle order
_settleOrder(
orderHash,
order,
takerAddress,
fillResults
);
// Update exchange internal state // Update exchange internal state
_updateFilledState( _updateFilledState(
order, order,
@@ -230,14 +238,6 @@ contract MixinExchangeCore is
fillResults fillResults
); );
// Settle order
_settleOrder(
orderHash,
order,
takerAddress,
fillResults
);
return fillResults; return fillResults;
} }
@@ -462,25 +462,26 @@ contract MixinExchangeCore is
); );
// Transfer protocol fee -> staking if the fee should be paid // Transfer protocol fee -> staking if the fee should be paid
if (staking != address(0)) { address feeCollector = protocolFeeCollector;
// If sufficient ether was sent to the contract, the protocol fee should be paid in ETH. if (feeCollector != address(0)) {
// Otherwise the fee should be paid in WETH. // Create a stack variable to hold the value that will be sent so that the gas optimization of
uint256 protocolFee = fillResults.protocolFeePaid; // only having one call statement can be implemented.
if (address(this).balance >= protocolFee) { uint256 valuePaid = 0;
IStaking(staking).payProtocolFee.value(protocolFee)(order.makerAddress);
} else {
// Transfer the Weth
_dispatchTransferFrom(
orderHash,
WETH_ASSET_DATA,
takerAddress,
staking,
protocolFee
);
// Attribute the protocol fee to the maker // Calculate the protocol fee that should be paid and populate the `protocolFeePaid` field in `fillResults`.
IStaking(staking).recordProtocolFee(order.makerAddress, protocolFee); // 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 public
payable payable
nonReentrant nonReentrant
refund refundFinalBalance
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults) returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults)
{ {
return _batchMatchOrders( return _batchMatchOrders(
@@ -77,7 +77,7 @@ contract MixinMatchOrders is
public public
payable payable
nonReentrant nonReentrant
refund refundFinalBalance
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults) returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults)
{ {
return _batchMatchOrders( return _batchMatchOrders(
@@ -107,7 +107,7 @@ contract MixinMatchOrders is
public public
payable payable
nonReentrant nonReentrant
refund refundFinalBalance
returns (LibFillResults.MatchedFillResults memory matchedFillResults) returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{ {
return _matchOrders( return _matchOrders(
@@ -137,7 +137,7 @@ contract MixinMatchOrders is
public public
payable payable
nonReentrant nonReentrant
refund refundFinalBalance
returns (LibFillResults.MatchedFillResults memory matchedFillResults) returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{ {
return _matchOrders( return _matchOrders(
@@ -385,10 +385,19 @@ contract MixinMatchOrders is
rightOrder, rightOrder,
leftOrderInfo.orderTakerAssetFilledAmount, leftOrderInfo.orderTakerAssetFilledAmount,
rightOrderInfo.orderTakerAssetFilledAmount, rightOrderInfo.orderTakerAssetFilledAmount,
protocolFeeMultiplier,
shouldMaximallyFillOrders shouldMaximallyFillOrders
); );
// Settle matched orders. Succeeds or throws.
_settleMatchedOrders(
leftOrderInfo.orderHash,
rightOrderInfo.orderHash,
leftOrder,
rightOrder,
takerAddress,
matchedFillResults
);
// Update exchange state // Update exchange state
_updateFilledState( _updateFilledState(
leftOrder, leftOrder,
@@ -405,16 +414,6 @@ contract MixinMatchOrders is
matchedFillResults.right matchedFillResults.right
); );
// Settle matched orders. Succeeds or throws.
_settleMatchedOrders(
leftOrderInfo.orderHash,
rightOrderInfo.orderHash,
leftOrder,
rightOrder,
takerAddress,
matchedFillResults
);
return matchedFillResults; return matchedFillResults;
} }
@@ -490,39 +489,27 @@ contract MixinMatchOrders is
matchedFillResults.profitInRightMakerAsset matchedFillResults.profitInRightMakerAsset
); );
// Pay protocol fees // Pay the protocol fees if there is a registered `protocolFeeCollector` address.
if (staking != address(0)) { address feeCollector = protocolFeeCollector;
// matchedFillResults.left.protocolFeePaid == matchedFillResults.right.protocolFeePaid if (feeCollector != address(0)) {
// so we only use matchedFillResults.left.protocolFeePaid as a gas optimization. // Calculate the protocol fee that should be paid and populate the `protocolFeePaid` field in the left and
uint256 protocolFee = matchedFillResults.left.protocolFeePaid; // 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. // Create a stack variable for the value that will be sent to the feeCollector when `payProtocolFee` is called.
address[] memory makers = new address[](2); // This allows a gas optimization where the `leftOrder.makerAddress` only needs be loaded onto the stack once AND
makers[0] = leftOrder.makerAddress; // a stack variable does not need to be allocated for the call.
makers[1] = rightOrder.makerAddress; uint256 valuePaid = 0;
uint256[] memory fees = new uint256[](2);
fees[0] = protocolFee;
fees[1] = protocolFee;
// If sufficient ether was sent to the contract, the protocol fee should be paid in ETH. // Pay the left order's protocol fee.
// Otherwise the fee should be paid in WETH.
if (address(this).balance >= 2 * protocolFee) { if (address(this).balance >= 2 * protocolFee) {
// Forward the protocol fees valuePaid = 2 * protocolFee;
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);
} }
IStaking(feeCollector).payProtocolFee.value(valuePaid)(leftOrder.makerAddress, takerAddress, protocolFee);
IStaking(feeCollector).payProtocolFee.value(valuePaid)(rightOrder.makerAddress, takerAddress, protocolFee);
} }
// Settle taker fees. // 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 public
payable payable
refund disableRefundUntilEnd
returns (bytes memory) returns (bytes memory)
{ {
return _executeTransaction(transaction, signature); return _executeTransaction(transaction, signature);
@@ -69,7 +69,7 @@ contract MixinTransactions is
) )
public public
payable payable
refund disableRefundUntilEnd
returns (bytes[] memory) returns (bytes[] memory)
{ {
uint256 length = transactions.length; uint256 length = transactions.length;

View File

@@ -47,7 +47,7 @@ contract MixinTransferSimulator is
uint256 length = assetData.length; uint256 length = assetData.length;
for (uint256 i = 0; i != length; i++) { for (uint256 i = 0; i != length; i++) {
_dispatchTransferFrom( _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), bytes32(i),
assetData[i], assetData[i],
fromAddresses[i], fromAddresses[i],

View File

@@ -48,7 +48,7 @@ contract MixinWrapperFunctions is
public public
payable payable
nonReentrant nonReentrant
refund refundFinalBalance
returns (LibFillResults.FillResults memory fillResults) returns (LibFillResults.FillResults memory fillResults)
{ {
fillResults = _fillOrKillOrder( fillResults = _fillOrKillOrder(
@@ -59,6 +59,10 @@ contract MixinWrapperFunctions is
return fillResults; 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. /// @dev Fills the input order.
/// Returns a null FillResults instance if the transaction would otherwise revert. /// Returns a null FillResults instance if the transaction would otherwise revert.
/// @param order Order struct containing order specifications. /// @param order Order struct containing order specifications.
@@ -72,7 +76,7 @@ contract MixinWrapperFunctions is
) )
public public
payable payable
refund refundFinalBalance
returns (LibFillResults.FillResults memory fillResults) returns (LibFillResults.FillResults memory fillResults)
{ {
// ABI encode calldata for `fillOrder` // ABI encode calldata for `fillOrder`
@@ -85,7 +89,7 @@ contract MixinWrapperFunctions is
(bool didSucceed, bytes memory returnData) = address(this).delegatecall(fillOrderCalldata); (bool didSucceed, bytes memory returnData) = address(this).delegatecall(fillOrderCalldata);
if (didSucceed) { if (didSucceed) {
assert(returnData.length == 128); assert(returnData.length == 160);
fillResults = abi.decode(returnData, (LibFillResults.FillResults)); fillResults = abi.decode(returnData, (LibFillResults.FillResults));
} }
// fillResults values will be 0 by default if call was unsuccessful // fillResults values will be 0 by default if call was unsuccessful
@@ -105,7 +109,7 @@ contract MixinWrapperFunctions is
public public
payable payable
nonReentrant nonReentrant
refund disableRefundUntilEnd
returns (LibFillResults.FillResults[] memory fillResults) returns (LibFillResults.FillResults[] memory fillResults)
{ {
uint256 ordersLength = orders.length; uint256 ordersLength = orders.length;
@@ -133,7 +137,7 @@ contract MixinWrapperFunctions is
public public
payable payable
nonReentrant nonReentrant
refund disableRefundUntilEnd
returns (LibFillResults.FillResults[] memory fillResults) returns (LibFillResults.FillResults[] memory fillResults)
{ {
uint256 ordersLength = orders.length; uint256 ordersLength = orders.length;
@@ -160,7 +164,7 @@ contract MixinWrapperFunctions is
) )
public public
payable payable
refund // disableRefundUntilEnd
returns (LibFillResults.FillResults[] memory fillResults) returns (LibFillResults.FillResults[] memory fillResults)
{ {
uint256 ordersLength = orders.length; uint256 ordersLength = orders.length;
@@ -187,7 +191,7 @@ contract MixinWrapperFunctions is
) )
public public
payable payable
refund disableRefundUntilEnd
returns (LibFillResults.FillResults memory fillResults) returns (LibFillResults.FillResults memory fillResults)
{ {
bytes memory takerAssetData = orders[0].takerAssetData; bytes memory takerAssetData = orders[0].takerAssetData;
@@ -233,7 +237,7 @@ contract MixinWrapperFunctions is
) )
public public
payable payable
refund disableRefundUntilEnd
returns (LibFillResults.FillResults memory fillResults) returns (LibFillResults.FillResults memory fillResults)
{ {
bytes memory makerAssetData = orders[0].makerAssetData; 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. /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled.
/// @param order Order struct containing order specifications. /// @param order Order struct containing order specifications.
/// @param takerAssetFillAmount Desired amount of takerAsset to sell. /// @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 { 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. // Fill event is emitted whenever an order is filled.
event Fill( event Fill(
address indexed makerAddress, // Address that created the order. address indexed makerAddress, // Address that created the order.

View File

@@ -21,6 +21,9 @@ pragma solidity ^0.5.9;
contract IProtocolFees { 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. // Logs updates to the protocol fee multiplier.
event UpdatedProtocolFeeMultiplier(uint256 oldProtocolFeeMultiplier, uint256 updatedProtocolFeeMultiplier); event UpdatedProtocolFeeMultiplier(uint256 oldProtocolFeeMultiplier, uint256 updatedProtocolFeeMultiplier);

View File

@@ -154,12 +154,4 @@ contract IWrapperFunctions {
/// @param orders Array of order specifications. /// @param orders Array of order specifications.
function batchCancelOrders(LibOrder.Order[] memory orders) function batchCancelOrders(LibOrder.Order[] memory orders)
public; 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.takerAssetFilledAmount = order.takerAssetAmount;
fillResults.makerFeePaid = order.makerFee; fillResults.makerFeePaid = order.makerFee;
fillResults.takerFeePaid = order.takerFee; fillResults.takerFeePaid = order.takerFee;
fillResults.protocolFeePaid = protocolFeeMultiplier;
} }
/// @dev Overridden to only log arguments and revert with certain inputs. /// @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 MixinMatchOrders from '../generated-artifacts/MixinMatchOrders.json';
import * as MixinProtocolFees from '../generated-artifacts/MixinProtocolFees.json'; import * as MixinProtocolFees from '../generated-artifacts/MixinProtocolFees.json';
import * as MixinSignatureValidator from '../generated-artifacts/MixinSignatureValidator.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 MixinTransactions from '../generated-artifacts/MixinTransactions.json';
import * as MixinTransferSimulator from '../generated-artifacts/MixinTransferSimulator.json'; import * as MixinTransferSimulator from '../generated-artifacts/MixinTransferSimulator.json';
import * as MixinWrapperFunctions from '../generated-artifacts/MixinWrapperFunctions.json'; import * as MixinWrapperFunctions from '../generated-artifacts/MixinWrapperFunctions.json';
@@ -49,7 +48,6 @@ export const artifacts = {
MixinMatchOrders: MixinMatchOrders as ContractArtifact, MixinMatchOrders: MixinMatchOrders as ContractArtifact,
MixinProtocolFees: MixinProtocolFees as ContractArtifact, MixinProtocolFees: MixinProtocolFees as ContractArtifact,
MixinSignatureValidator: MixinSignatureValidator as ContractArtifact, MixinSignatureValidator: MixinSignatureValidator as ContractArtifact,
MixinStakingManager: MixinStakingManager as ContractArtifact,
MixinTransactions: MixinTransactions as ContractArtifact, MixinTransactions: MixinTransactions as ContractArtifact,
MixinTransferSimulator: MixinTransferSimulator as ContractArtifact, MixinTransferSimulator: MixinTransferSimulator as ContractArtifact,
MixinWrapperFunctions: MixinWrapperFunctions 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_match_orders';
export * from '../generated-wrappers/mixin_protocol_fees'; export * from '../generated-wrappers/mixin_protocol_fees';
export * from '../generated-wrappers/mixin_signature_validator'; 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_transactions';
export * from '../generated-wrappers/mixin_transfer_simulator'; export * from '../generated-wrappers/mixin_transfer_simulator';
export * from '../generated-wrappers/mixin_wrapper_functions'; export * from '../generated-wrappers/mixin_wrapper_functions';

View File

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

View File

@@ -164,26 +164,22 @@ blockchainTests('Exchange core internal functions', env => {
return _.assign({}, ORDER_DEFAULTS, details); return _.assign({}, ORDER_DEFAULTS, details);
} }
// FIXME - This test definitely needs to be updated
async function testUpdateFilledStateAsync( async function testUpdateFilledStateAsync(
order: OrderWithoutDomain, order: OrderWithoutDomain,
orderTakerAssetFilledAmount: BigNumber, orderTakerAssetFilledAmount: BigNumber,
takerAddress: string, takerAddress: string,
takerAssetFillAmount: BigNumber, takerAssetFillAmount: BigNumber,
protocolFeeMultiplier: BigNumber, // protocolFeeMultiplier: BigNumber,
gasPrice: BigNumber, // gasPrice: BigNumber,
isProtocolFeePaidInEth: boolean, // isProtocolFeePaidInEth: boolean,
): Promise<void> { ): Promise<void> {
const orderHash = randomHash(); const orderHash = randomHash();
const fillResults = LibReferenceFunctions.calculateFillResults( const fillResults = LibReferenceFunctions.calculateFillResults(order, takerAssetFillAmount);
order,
takerAssetFillAmount,
protocolFeeMultiplier,
gasPrice,
);
const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount); const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount);
const opts = isProtocolFeePaidInEth // const opts = isProtocolFeePaidInEth
? { value: fillResults.protocolFeePaid } // ? { value: fillResults.protocolFeePaid }
: { value: constants.ZERO_AMOUNT }; // : { value: constants.ZERO_AMOUNT };
// CAll `testUpdateFilledState()`, which will set the `filled` // CAll `testUpdateFilledState()`, which will set the `filled`
// state for this order to `orderTakerAssetFilledAmount` before // state for this order to `orderTakerAssetFilledAmount` before
// calling `_updateFilledState()`. // calling `_updateFilledState()`.
@@ -194,7 +190,7 @@ blockchainTests('Exchange core internal functions', env => {
orderHash, orderHash,
orderTakerAssetFilledAmount, orderTakerAssetFilledAmount,
fillResults, fillResults,
opts, // opts, // FIXME
), ),
); );
// Grab the new `filled` state for this order. // 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.takerAssetFilledAmount).to.bignumber.eq(fillResults.takerAssetFilledAmount);
expect(fillEvent.args.makerFeePaid).to.bignumber.eq(fillResults.makerFeePaid); expect(fillEvent.args.makerFeePaid).to.bignumber.eq(fillResults.makerFeePaid);
expect(fillEvent.args.takerFeePaid).to.bignumber.eq(fillResults.takerFeePaid); expect(fillEvent.args.takerFeePaid).to.bignumber.eq(fillResults.takerFeePaid);
expect(fillEvent.args.protocolFeePaid).to.bignumber.eq(fillResults.protocolFeePaid); // expect(fillEvent.args.protocolFeePaid).to.bignumber.eq(fillResults.protocolFeePaid);
expect(fillEvent.args.isProtocolFeePaidInEth).to.eq(isProtocolFeePaidInEth); // expect(fillEvent.args.isProtocolFeePaidInEth).to.eq(isProtocolFeePaidInEth);
expect(fillEvent.args.makerAssetData).to.eq(order.makerAssetData); expect(fillEvent.args.makerAssetData).to.eq(order.makerAssetData);
expect(fillEvent.args.takerAssetData).to.eq(order.takerAssetData); expect(fillEvent.args.takerAssetData).to.eq(order.takerAssetData);
expect(fillEvent.args.makerFeeAssetData).to.eq(order.makerFeeAssetData); expect(fillEvent.args.makerFeeAssetData).to.eq(order.makerFeeAssetData);
@@ -229,9 +225,6 @@ blockchainTests('Exchange core internal functions', env => {
order.takerAssetAmount.times(0.1), order.takerAssetAmount.times(0.1),
randomAddress(), randomAddress(),
order.takerAssetAmount.times(0.25), 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), order.takerAssetAmount.times(0.1),
randomAddress(), randomAddress(),
order.takerAssetAmount.times(0.25), 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 order = makeOrder();
const orderTakerAssetFilledAmount = constants.MAX_UINT256.dividedToIntegerBy(2); const orderTakerAssetFilledAmount = constants.MAX_UINT256.dividedToIntegerBy(2);
const takerAssetFillAmount = constants.MAX_UINT256.dividedToIntegerBy(2).plus(2); const takerAssetFillAmount = constants.MAX_UINT256.dividedToIntegerBy(2).plus(2);
// FIXME
const fillResults = { const fillResults = {
makerAssetFilledAmount: constants.ZERO_AMOUNT, makerAssetFilledAmount: constants.ZERO_AMOUNT,
takerAssetFilledAmount: takerAssetFillAmount, takerAssetFilledAmount: takerAssetFillAmount,
makerFeePaid: constants.ZERO_AMOUNT, makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT, takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: constants.ZERO_AMOUNT,
}; };
const expectedError = new SafeMathRevertErrors.Uint256BinOpError( const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow, SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow,
@@ -297,11 +289,13 @@ blockchainTests('Exchange core internal functions', env => {
const order = DEFAULT_ORDER; const order = DEFAULT_ORDER;
const orderHash = randomHash(); const orderHash = randomHash();
const takerAddress = randomAddress(); const takerAddress = randomAddress();
// FIXME
const fillResults = { const fillResults = {
makerAssetFilledAmount: ONE_ETHER.times(2), makerAssetFilledAmount: ONE_ETHER.times(2),
takerAssetFilledAmount: ONE_ETHER.times(10), takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01), makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: ONE_ETHER.times(0.025), takerFeePaid: ONE_ETHER.times(0.025),
protocolFeePaid: constants.ZERO_AMOUNT,
}; };
const receipt = await logDecoder.getTxWithDecodedLogsAsync( const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await testExchange.settleOrder.sendTransactionAsync(orderHash, order, takerAddress, fillResults), await testExchange.settleOrder.sendTransactionAsync(orderHash, order, takerAddress, fillResults),
@@ -371,12 +365,14 @@ blockchainTests('Exchange core internal functions', env => {
takerAssetFilledAmount: ONE_ETHER.times(10), takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01), makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: constants.MAX_UINT256, takerFeePaid: constants.MAX_UINT256,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}, },
right: { right: {
takerAssetFilledAmount: ONE_ETHER.times(20), takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4), makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02), makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: constants.MAX_UINT256_ROOT, takerFeePaid: constants.MAX_UINT256_ROOT,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}, },
profitInLeftMakerAsset: ONE_ETHER, profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2), profitInRightMakerAsset: ONE_ETHER.times(2),
@@ -418,12 +414,14 @@ blockchainTests('Exchange core internal functions', env => {
takerAssetFilledAmount: ONE_ETHER.times(10), takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01), makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: constants.MAX_UINT256, takerFeePaid: constants.MAX_UINT256,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}, },
right: { right: {
takerAssetFilledAmount: ONE_ETHER.times(20), takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4), makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02), makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: constants.MAX_UINT256_ROOT, takerFeePaid: constants.MAX_UINT256_ROOT,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}, },
profitInLeftMakerAsset: ONE_ETHER, profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2), profitInRightMakerAsset: ONE_ETHER.times(2),
@@ -454,12 +452,14 @@ blockchainTests('Exchange core internal functions', env => {
takerAssetFilledAmount: ONE_ETHER.times(10), takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01), makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: ONE_ETHER.times(0.025), takerFeePaid: ONE_ETHER.times(0.025),
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}, },
right: { right: {
takerAssetFilledAmount: ONE_ETHER.times(20), takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4), makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02), makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: ONE_ETHER.times(0.05), takerFeePaid: ONE_ETHER.times(0.05),
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}, },
profitInLeftMakerAsset: ONE_ETHER, profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2), profitInRightMakerAsset: ONE_ETHER.times(2),
@@ -550,12 +550,14 @@ blockchainTests('Exchange core internal functions', env => {
takerAssetFilledAmount: ONE_ETHER.times(10), takerAssetFilledAmount: ONE_ETHER.times(10),
makerFeePaid: ONE_ETHER.times(0.01), makerFeePaid: ONE_ETHER.times(0.01),
takerFeePaid: ONE_ETHER.times(0.025), takerFeePaid: ONE_ETHER.times(0.025),
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}, },
right: { right: {
takerAssetFilledAmount: ONE_ETHER.times(20), takerAssetFilledAmount: ONE_ETHER.times(20),
makerAssetFilledAmount: ONE_ETHER.times(4), makerAssetFilledAmount: ONE_ETHER.times(4),
makerFeePaid: ONE_ETHER.times(0.02), makerFeePaid: ONE_ETHER.times(0.02),
takerFeePaid: ONE_ETHER.times(0.05), takerFeePaid: ONE_ETHER.times(0.05),
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
}, },
profitInLeftMakerAsset: ONE_ETHER, profitInLeftMakerAsset: ONE_ETHER,
profitInRightMakerAsset: ONE_ETHER.times(2), 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 { BigNumber, SafeMathRevertErrors } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { calculateFillResults } from '../src/reference_functions';
describe('Reference functions', () => { describe('Reference functions', () => {
const ONE_ETHER = constants.ONE_ETHER; const ONE_ETHER = constants.ONE_ETHER;
const EMPTY_ORDER: Order = { const EMPTY_ORDER: Order = {
@@ -44,7 +42,9 @@ describe('Reference functions', () => {
takerAssetFilledAmount, takerAssetFilledAmount,
order.makerAssetAmount, 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', () => { it('reverts if computing `fillResults.makerFeePaid` overflows', () => {
@@ -65,7 +65,7 @@ describe('Reference functions', () => {
makerAssetFilledAmount, makerAssetFilledAmount,
order.makerFee, 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', () => { it('reverts if computing `fillResults.takerFeePaid` overflows', () => {
@@ -81,7 +81,7 @@ describe('Reference functions', () => {
takerAssetFilledAmount, takerAssetFilledAmount,
order.takerFee, 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', () => { it('reverts if `order.makerAssetAmount` is 0', () => {
@@ -91,7 +91,7 @@ describe('Reference functions', () => {
}); });
const takerAssetFilledAmount = ONE_ETHER; const takerAssetFilledAmount = ONE_ETHER;
const expectedError = new LibMathRevertErrors.DivisionByZeroError(); 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', () => { it('reverts if `order.takerAssetAmount` is 0', () => {
@@ -101,7 +101,7 @@ describe('Reference functions', () => {
}); });
const takerAssetFilledAmount = ONE_ETHER; const takerAssetFilledAmount = ONE_ETHER;
const expectedError = new LibMathRevertErrors.DivisionByZeroError(); 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`', () => { it('reverts if there is a rounding error computing `makerAsssetFilledAmount`', () => {
@@ -115,7 +115,7 @@ describe('Reference functions', () => {
order.takerAssetAmount, order.takerAssetAmount,
order.makerAssetAmount, 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`', () => { it('reverts if there is a rounding error computing `makerFeePaid`', () => {
@@ -135,7 +135,7 @@ describe('Reference functions', () => {
order.makerAssetAmount, order.makerAssetAmount,
order.makerFee, 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`', () => { it('reverts if there is a rounding error computing `takerFeePaid`', () => {
@@ -155,7 +155,7 @@ describe('Reference functions', () => {
order.makerAssetAmount, order.makerAssetAmount,
order.takerFee, 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({ const cancelTransaction = await makerTransactionFactory.newSignedTransactionAsync({
data: cancelData, data: cancelData,
}); });
await exchangeWrapperContract.cancelOrdersUpTo.awaitTransactionSuccessAsync( await exchangeWrapperContract.cancelOrdersUpTo.awaitTransactionSuccessAsync(
targetOrderEpoch, targetOrderEpoch,
cancelTransaction.salt, cancelTransaction.salt,
cancelTransaction.expirationTimeSeconds, cancelTransaction.expirationTimeSeconds,
cancelTransaction.signature, cancelTransaction.signature,
// { from: makerAddress },
{ from: makerAddress }, { from: makerAddress },
constants.AWAIT_TRANSACTION_MINED_MS,
); );
const takerAssetFillAmount = signedOrder.takerAssetAmount; const takerAssetFillAmount = signedOrder.takerAssetAmount;

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,6 @@ import {
ERC20BalancesByOwner, ERC20BalancesByOwner,
expect, expect,
getLatestBlockTimestampAsync, getLatestBlockTimestampAsync,
increaseTimeAndMineBlockAsync,
OrderFactory, OrderFactory,
} from '@0x/contracts-test-utils'; } from '@0x/contracts-test-utils';
import { assetDataUtils, ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils'; import { assetDataUtils, ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils';
@@ -48,11 +47,15 @@ blockchainTests.resets('Exchange wrappers', env => {
let defaultTakerAssetAddress: string; let defaultTakerAssetAddress: string;
let defaultFeeAssetAddress: string; let defaultFeeAssetAddress: string;
const DEFAULT_GAS_PRICE = new BigNumber(2);
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(150);
const nullFillResults: FillResults = { const nullFillResults: FillResults = {
makerAssetFilledAmount: constants.ZERO_AMOUNT, makerAssetFilledAmount: constants.ZERO_AMOUNT,
takerAssetFilledAmount: constants.ZERO_AMOUNT, takerAssetFilledAmount: constants.ZERO_AMOUNT,
makerFeePaid: constants.ZERO_AMOUNT, makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT, takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: constants.ZERO_AMOUNT,
}; };
before(async () => { before(async () => {
@@ -81,10 +84,16 @@ blockchainTests.resets('Exchange wrappers', env => {
exchange = await ExchangeContract.deployFrom0xArtifactAsync( exchange = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange, artifacts.Exchange,
env.provider, env.provider,
env.txDefaults, { ...env.txDefaults, from: owner },
{}, {},
new BigNumber(chainId), 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); exchangeWrapper = new ExchangeWrapper(exchange, env.provider);
await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
@@ -492,11 +501,14 @@ blockchainTests.resets('Exchange wrappers', env => {
.dividedToIntegerBy(signedOrder.makerAssetAmount); .dividedToIntegerBy(signedOrder.makerAssetAmount);
takerAssetFillAmounts.push(takerAssetFillAmount); 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({ expectedFillResults.push({
takerAssetFilledAmount: takerAssetFillAmount, takerAssetFilledAmount: takerAssetFillAmount,
makerAssetFilledAmount, makerAssetFilledAmount,
makerFeePaid: makerFee, makerFeePaid: makerFee,
takerFeePaid: takerFee, takerFeePaid: takerFee,
protocolFeePaid: constants.ZERO_AMOUNT,
}); });
erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
@@ -522,11 +534,13 @@ blockchainTests.resets('Exchange wrappers', env => {
].plus(makerFee.plus(takerFee)); ].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( const fillResults = await exchange.batchFillOrders.callAsync(
signedOrders, signedOrders,
takerAssetFillAmounts, takerAssetFillAmounts,
signedOrders.map(signedOrder => signedOrder.signature), signedOrders.map(signedOrder => signedOrder.signature),
{ from: takerAddress }, { from: takerAddress, gasPrice: DEFAULT_GAS_PRICE },
); );
await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, {
takerAssetFillAmounts, takerAssetFillAmounts,
@@ -559,11 +573,14 @@ blockchainTests.resets('Exchange wrappers', env => {
.dividedToIntegerBy(signedOrder.makerAssetAmount); .dividedToIntegerBy(signedOrder.makerAssetAmount);
takerAssetFillAmounts.push(takerAssetFillAmount); 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({ expectedFillResults.push({
takerAssetFilledAmount: takerAssetFillAmount, takerAssetFilledAmount: takerAssetFillAmount,
makerAssetFilledAmount, makerAssetFilledAmount,
makerFeePaid: makerFee, makerFeePaid: makerFee,
takerFeePaid: takerFee, takerFeePaid: takerFee,
protocolFeePaid: constants.ZERO_AMOUNT,
}); });
erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
@@ -642,11 +659,14 @@ blockchainTests.resets('Exchange wrappers', env => {
.dividedToIntegerBy(signedOrder.makerAssetAmount); .dividedToIntegerBy(signedOrder.makerAssetAmount);
takerAssetFillAmounts.push(takerAssetFillAmount); 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({ expectedFillResults.push({
takerAssetFilledAmount: takerAssetFillAmount, takerAssetFilledAmount: takerAssetFillAmount,
makerAssetFilledAmount, makerAssetFilledAmount,
makerFeePaid: makerFee, makerFeePaid: makerFee,
takerFeePaid: takerFee, takerFeePaid: takerFee,
protocolFeePaid: constants.ZERO_AMOUNT,
}); });
erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
@@ -712,11 +732,14 @@ blockchainTests.resets('Exchange wrappers', env => {
.dividedToIntegerBy(signedOrder.makerAssetAmount); .dividedToIntegerBy(signedOrder.makerAssetAmount);
takerAssetFillAmounts.push(takerAssetFillAmount); 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({ expectedFillResults.push({
takerAssetFilledAmount: takerAssetFillAmount, takerAssetFilledAmount: takerAssetFillAmount,
makerAssetFilledAmount, makerAssetFilledAmount,
makerFeePaid: makerFee, makerFeePaid: makerFee,
takerFeePaid: takerFee, takerFeePaid: takerFee,
protocolFeePaid: constants.ZERO_AMOUNT,
}); });
erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
@@ -853,6 +876,7 @@ blockchainTests.resets('Exchange wrappers', env => {
takerAssetFilledAmount: signedOrder.takerAssetAmount, takerAssetFilledAmount: signedOrder.takerAssetAmount,
makerFeePaid: signedOrder.makerFee, makerFeePaid: signedOrder.makerFee,
takerFeePaid: signedOrder.takerFee, takerFeePaid: signedOrder.takerFee,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME - This is what is being used now.
})) }))
.reduce( .reduce(
(totalFillResults, currentFillResults) => ({ (totalFillResults, currentFillResults) => ({
@@ -864,6 +888,7 @@ blockchainTests.resets('Exchange wrappers', env => {
), ),
makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid), makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid),
takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid), takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid),
protocolFeePaid: totalFillResults.protocolFeePaid.plus(currentFillResults.protocolFeePaid),
}), }),
nullFillResults, nullFillResults,
); );
@@ -923,6 +948,7 @@ blockchainTests.resets('Exchange wrappers', env => {
takerAssetFilledAmount: signedOrder.takerAssetAmount, takerAssetFilledAmount: signedOrder.takerAssetAmount,
makerFeePaid: signedOrder.makerFee, makerFeePaid: signedOrder.makerFee,
takerFeePaid: signedOrder.takerFee, takerFeePaid: signedOrder.takerFee,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
})) }))
.reduce( .reduce(
(totalFillResults, currentFillResults) => ({ (totalFillResults, currentFillResults) => ({
@@ -934,6 +960,7 @@ blockchainTests.resets('Exchange wrappers', env => {
), ),
makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid), makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid),
takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid), takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid),
protocolFeePaid: totalFillResults.protocolFeePaid.plus(currentFillResults.protocolFeePaid),
}), }),
nullFillResults, nullFillResults,
); );
@@ -1037,6 +1064,7 @@ blockchainTests.resets('Exchange wrappers', env => {
takerAssetFilledAmount: signedOrder.takerAssetAmount, takerAssetFilledAmount: signedOrder.takerAssetAmount,
makerFeePaid: signedOrder.makerFee, makerFeePaid: signedOrder.makerFee,
takerFeePaid: signedOrder.takerFee, takerFeePaid: signedOrder.takerFee,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
})) }))
.reduce( .reduce(
(totalFillResults, currentFillResults) => ({ (totalFillResults, currentFillResults) => ({
@@ -1048,6 +1076,7 @@ blockchainTests.resets('Exchange wrappers', env => {
), ),
makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid), makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid),
takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid), takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid),
protocolFeePaid: totalFillResults.protocolFeePaid.plus(currentFillResults.protocolFeePaid),
}), }),
nullFillResults, nullFillResults,
); );
@@ -1108,6 +1137,7 @@ blockchainTests.resets('Exchange wrappers', env => {
takerAssetFilledAmount: signedOrder.takerAssetAmount, takerAssetFilledAmount: signedOrder.takerAssetAmount,
makerFeePaid: signedOrder.makerFee, makerFeePaid: signedOrder.makerFee,
takerFeePaid: signedOrder.takerFee, takerFeePaid: signedOrder.takerFee,
protocolFeePaid: constants.ZERO_AMOUNT, // FIXME
})) }))
.reduce( .reduce(
(totalFillResults, currentFillResults) => ({ (totalFillResults, currentFillResults) => ({
@@ -1119,6 +1149,7 @@ blockchainTests.resets('Exchange wrappers', env => {
), ),
makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid), makerFeePaid: totalFillResults.makerFeePaid.plus(currentFillResults.makerFeePaid),
takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid), takerFeePaid: totalFillResults.takerFeePaid.plus(currentFillResults.takerFeePaid),
protocolFeePaid: totalFillResults.protocolFeePaid.plus(currentFillResults.protocolFeePaid),
}), }),
nullFillResults, 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 }); // 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 { ONE_ETHER, MAX_UINT256 } = constants;
const { addFillResults, getPartialAmountFloor } = LibReferenceFunctions; const { addFillResults, getPartialAmountFloor } = LibReferenceFunctions;
const { safeSub } = UtilReferenceFunctions; const { safeSub } = UtilReferenceFunctions;
const protocolFeeMultiplier = new BigNumber(150000);
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH); const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
const randomAssetData = () => hexRandom(34); const randomAssetData = () => hexRandom(34);
const randomAmount = (maxAmount: BigNumber = ONE_ETHER) => maxAmount.times(_.random(0, 100, true).toFixed(12)); 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, takerAssetFilledAmount: constants.ZERO_AMOUNT,
makerFeePaid: constants.ZERO_AMOUNT, makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT, takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: constants.ZERO_AMOUNT,
}; };
let testContract: TestWrapperFunctionsContract; let testContract: TestWrapperFunctionsContract;
let txHelper: TransactionHelper; let txHelper: TransactionHelper;
let owner: string;
let senderAddress: string; let senderAddress: string;
before(async () => { before(async () => {
[senderAddress] = await env.getAccountAddressesAsync(); [owner, senderAddress] = await env.getAccountAddressesAsync();
txHelper = new TransactionHelper(env.web3Wrapper, artifacts); txHelper = new TransactionHelper(env.web3Wrapper, artifacts);
testContract = await TestWrapperFunctionsContract.deployFrom0xArtifactAsync( testContract = await TestWrapperFunctionsContract.deployFrom0xArtifactAsync(
artifacts.TestWrapperFunctions, artifacts.TestWrapperFunctions,
env.provider, 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 { function randomOrder(fields?: Partial<Order>): Order {
@@ -96,6 +107,7 @@ blockchainTests('Exchange wrapper functions unit tests.', env => {
takerAssetFilledAmount: order.takerAssetAmount, takerAssetFilledAmount: order.takerAssetAmount,
makerFeePaid: order.makerFee, makerFeePaid: order.makerFee,
takerFeePaid: order.takerFee, takerFeePaid: order.takerFee,
protocolFeePaid: protocolFeeMultiplier,
}; };
} }
@@ -1309,50 +1321,5 @@ blockchainTests('Exchange wrapper functions unit tests.', env => {
return expect(tx).to.revertWith(expectedError); 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 // tslint:disable-next-line: max-file-line-count

View File

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

View File

@@ -21,37 +21,15 @@ pragma solidity ^0.5.9;
interface IStaking { 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. /// @dev Pays a protocol fee in ETH.
/// @param makerAddress The address of the order's maker. /// @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 external
payable; 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 { modifier refundFinalBalance {
_; _;
if (!shouldNotRefund) { if (!shouldNotRefund) {
msg.sender.transfer(address(this).balance); refundNonzeroBalance();
} }
} }
@@ -38,7 +38,16 @@ contract Refundable {
shouldNotRefund = true; shouldNotRefund = true;
_; _;
shouldNotRefund = false; 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 contract TestRefundable is
Refundable Refundable
{ {
function refundNonzeroBalanceExternal()
external
payable
{
refundNonzeroBalance();
}
function setShouldNotRefund(bool shouldNotRefundNew) function setShouldNotRefund(bool shouldNotRefundNew)
external external
{ {

View File

@@ -24,10 +24,33 @@ import "./TestRefundable.sol";
contract TestRefundableReceiver { contract TestRefundableReceiver {
/// @dev A payable fallback function is necessary to receive refunds from the `TestRefundable` contract. /// @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 () function ()
external external
payable 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 /// @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 /// 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 { BigNumber } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
@@ -8,6 +8,9 @@ blockchainTests('Refundable', env => {
let refundable: TestRefundableContract; let refundable: TestRefundableContract;
let receiver: TestRefundableReceiverContract; let receiver: TestRefundableReceiverContract;
const ONE_HUNDRED = new BigNumber(100);
const ONE_THOUSAND = new BigNumber(1000);
before(async () => { before(async () => {
// Create the refundable contract. // Create the refundable contract.
refundable = await TestRefundableContract.deployFrom0xArtifactAsync( 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 // 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". // 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 () => { 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. // Send 100 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testRefundFinalBalance.awaitTransactionSuccessAsync(refundable.address, false, { 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 () => { 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. // Send 100 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testRefundFinalBalance.awaitTransactionSuccessAsync(refundable.address, false, { 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. // 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 // 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". // 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 () => { 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. // Send 100 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, { 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 () => { 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. // Send 100 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, { 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. // Send 1000 wei to the refundable contract that should be refunded to the receiver contract.
await receiver.testDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, { 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 () => { it('should not refund the sender if `shouldNotRefund` is true', async () => {
/// Send 100 wei to the refundable contract that should not be refunded. /// Send 100 wei to the refundable contract that should not be refunded.
await receiver.testDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, { 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 () => { it('should disable the `disableRefundUntilEnd` modifier and refund when `shouldNotRefund` is false', async () => {
/// Send 100 wei to the refundable contract that should be refunded. /// Send 100 wei to the refundable contract that should be refunded.
await receiver.testNestedDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, false, { 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 () => { 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. /// Send 100 wei to the refundable contract that should not be refunded.
await receiver.testNestedDisableRefundUntilEnd.awaitTransactionSuccessAsync(refundable.address, true, { 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 () => { it('should disable the `refundFinalBalance` modifier and refund when `shouldNotRefund` is false', async () => {
/// Send 100 wei to the refundable contract that should be refunded. /// Send 100 wei to the refundable contract that should be refunded.
await receiver.testMixedRefunds.awaitTransactionSuccessAsync(refundable.address, false, { 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 () => { 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. /// Send 100 wei to the refundable contract that should not be refunded.
await receiver.testMixedRefunds.awaitTransactionSuccessAsync(refundable.address, true, { await receiver.testMixedRefunds.awaitTransactionSuccessAsync(refundable.address, true, {
value: new BigNumber(100), value: ONE_HUNDRED,
}); });
}); });
}); });

View File

@@ -1,6 +1,6 @@
export const constants = { export const constants = {
RPC_URL: 'http://localhost:8545', RPC_URL: 'http://localhost:8545',
RPC_PORT: 8545, RPC_PORT: 8545,
GAS_LIMIT: 7000000, GAS_LIMIT: 8000000,
TESTRPC_FIRST_ADDRESS: '0x5409ed021d9299bf6814279a6a1411a7e866a631', TESTRPC_FIRST_ADDRESS: '0x5409ed021d9299bf6814279a6a1411a7e866a631',
}; };