@0x:contracts-exchange Added protocol fees to fill order

This commit is contained in:
Alex Towle
2019-08-22 22:50:18 -07:00
parent c688b11c86
commit 2c970a0466
22 changed files with 366 additions and 82 deletions

View File

@@ -22,9 +22,12 @@ pragma experimental ABIEncoderV2;
import "../src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
import "@0x/contracts-utils/contracts/src/Refundable.sol";
contract ExchangeWrapper {
contract ExchangeWrapper is
Refundable
{
// Exchange contract.
// solhint-disable-next-line var-name-mixedcase
@@ -51,7 +54,6 @@ contract ExchangeWrapper {
external
{
address makerAddress = msg.sender;
// Encode arguments into byte array.
bytes memory data = abi.encodeWithSelector(
EXCHANGE.cancelOrdersUpTo.selector,
@@ -86,6 +88,8 @@ contract ExchangeWrapper {
bytes memory takerSignature
)
public
payable
refund
{
address takerAddress = msg.sender;
@@ -106,6 +110,6 @@ contract ExchangeWrapper {
});
// Call `fillOrder` via `executeTransaction`.
EXCHANGE.executeTransaction(transaction, takerSignature);
EXCHANGE.executeTransaction.value(msg.value)(transaction, takerSignature);
}
}

View File

@@ -22,12 +22,14 @@ pragma experimental ABIEncoderV2;
import "../src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "@0x/contracts-utils/contracts/src/LibEIP1271.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "@0x/contracts-utils/contracts/src/Refundable.sol";
contract Whitelist is
Ownable,
Refundable,
LibEIP1271
{
// Mapping of address => whitelist status.
@@ -101,6 +103,8 @@ contract Whitelist is
bytes memory orderSignature
)
public
payable
refund
{
address takerAddress = msg.sender;
@@ -140,6 +144,6 @@ contract Whitelist is
});
// Call `fillOrder` via `executeTransaction`.
EXCHANGE.executeTransaction(transaction, TX_ORIGIN_SIGNATURE);
EXCHANGE.executeTransaction.value(msg.value)(transaction, TX_ORIGIN_SIGNATURE);
}
}

View File

@@ -18,13 +18,16 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "@0x/contracts-utils/contracts/src/Refundable.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol";
import "@0x/contracts-staking/contracts/src/interfaces/IStaking.sol";
import "./interfaces/IExchangeCore.sol";
import "./MixinAssetProxyDispatcher.sol";
import "./MixinSignatureValidator.sol";
@@ -32,6 +35,7 @@ import "./MixinStakingManager.sol";
contract MixinExchangeCore is
Refundable,
LibEIP712ExchangeDomain,
IExchangeCore,
MixinAssetProxyDispatcher,
@@ -40,6 +44,7 @@ contract MixinExchangeCore is
{
using LibOrder for LibOrder.Order;
using LibSafeMath for uint256;
using LibBytes for bytes;
// Mapping of orderHash => amount of takerAsset already bought by maker
mapping (bytes32 => uint256) public filled;
@@ -96,7 +101,9 @@ contract MixinExchangeCore is
bytes memory signature
)
public
payable
nonReentrant
refund
returns (LibFillResults.FillResults memory fillResults)
{
fillResults = _fillOrder(
@@ -210,7 +217,7 @@ contract MixinExchangeCore is
uint256 takerAssetFilledAmount = LibSafeMath.min256(takerAssetFillAmount, remainingTakerAssetAmount);
// Compute proportional fill amounts
fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount);
fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount, protocolFeeMultiplier);
bytes32 orderHash = orderInfo.orderHash;
@@ -273,21 +280,126 @@ contract MixinExchangeCore is
// Emit a Fill() event THE HARD WAY to avoid a stack overflow.
// All this logic is equivalent to:
emit Fill(
order.makerAddress,
order.feeRecipientAddress,
order.makerAssetData,
order.takerAssetData,
order.makerFeeAssetData,
order.takerFeeAssetData,
fillResults.makerAssetFilledAmount,
fillResults.takerAssetFilledAmount,
fillResults.makerFeePaid,
fillResults.takerFeePaid,
takerAddress,
msg.sender,
orderHash
// emit Fill(
// order.makerAddress,
// order.feeRecipientAddress,
// orderHash,
// takerAddress,
// msg.sender,
// fillResults.makerAssetFilledAmount,
// fillResults.takerAssetFilledAmount,
// fillResults.makerFeePaid,
// fillResults.takerFeePaid,
// fillResults.protocolFeePaid,
// msg.value >= fillResults.protocolFeePaid,
// order.makerAssetData,
// order.takerAssetData,
// order.makerFeeAssetData,
// order.takerFeeAssetData
// );
// There are 15 total fields in the `Fill()` event. Out of all of these fields,
// 3 are indexed fields. Due to the evm semantics of event logging, the 12 non-indexed
// fields will be abi-encoded into a bytes structure and then put into the `data` slot
// of the event logs.
//
// Since there are 12 fields, we will need 12 * 32 = 384 bytes to store the fields. Due
// to the fact that 4 of the fields are of type `bytes`, there is added complexity in that
// these fields in the log data are actually byte offsets to the length-prefaced `bytes`
// of the field. This means that we also need to account for 4 length slots -- 4 * 32 = 128
// more bytes in the log data -- and for the length of the bytes themselves -- total_length =
// order.makerAssetData.length + order.takerAssetData.length + order.makerFeeAssetData.length
// + order.takerFeeAssetData.length.
//
// Altogether, the log data will be 384 + 128 + total_length = 512 + total_length bytes long,
// which explains the memory allocation of the bytes below:
bytes memory logData = new bytes(
512
+ order.makerAssetData.length
+ order.takerAssetData.length
+ order.makerFeeAssetData.length
+ order.takerFeeAssetData.length
);
uint256 argOffset = 0;
// Push takerAddress to logData
logData.writeAddress(argOffset, takerAddress);
argOffset += 32;
// Push senderAddress to logData
logData.writeAddress(argOffset, msg.sender);
argOffset += 32;
// Push makerAssetFilledAmount to logData
logData.writeUint256(argOffset, fillResults.makerAssetFilledAmount);
argOffset += 32;
// Push takerAssetFilledAmount to logData
logData.writeUint256(argOffset, fillResults.takerAssetFilledAmount);
argOffset += 32;
// Push makerFeePaid to logData
logData.writeUint256(argOffset, fillResults.makerFeePaid);
argOffset += 32;
// Push takerFeePaid to logData
logData.writeUint256(argOffset, fillResults.takerFeePaid);
argOffset += 32;
// Push protocolFeePaid to logData
logData.writeUint256(argOffset, fillResults.protocolFeePaid);
argOffset += 32;
// Push isProtocolFeePaidInEth to logData
logData.writeUint256(argOffset, msg.value >= fillResults.protocolFeePaid ? 1 : 0);
argOffset += 32;
// The next four fields are `bytes` fields. Constructing the logData for these fields
// entails storing a byte offset in their respective memory slots and then storing their
// length and contents in the location pointed to by the byte offset.
//
// With this in mind, the `dataOffset` will initially be set to the thirteenth memory slot
// in logData and will be incremented by the length of every assetData `bytes` that is placed
// and 32 bytes that reflects the slot that stores the length of the array.
uint256 dataOffset = argOffset + 128;
// Push makerAssetData to logData
logData.writeUint256(argOffset, dataOffset);
logData.writeBytesWithLength(dataOffset, order.makerAssetData);
argOffset += 32;
dataOffset += order.makerAssetData.length;
// Push takerAssetData to logData
logData.writeUint256(argOffset, dataOffset);
logData.writeBytesWithLength(dataOffset, order.takerAssetData);
argOffset += 32;
dataOffset += order.takerAssetData.length;
// Push makerFeeAssetData to logData
logData.writeUint256(argOffset, dataOffset);
logData.writeBytesWithLength(dataOffset, order.makerFeeAssetData);
argOffset += 32;
dataOffset += order.makerFeeAssetData.length;
// Push takerFeeAssetData to logData
logData.writeUint256(argOffset, dataOffset);
logData.writeBytesWithLength(dataOffset, order.takerFeeAssetData);
argOffset += 32;
dataOffset += order.takerFeeAssetData.length;
// Add the event topics and log the event.
bytes32 fillTopic = FILL_EVENT_TOPIC;
assembly {
log4(
add(logData, 0x20), // A pointer to the log data
mload(logData), // The size of the log data
fillTopic, // The `Fill()` event's name topic
mload(order), // address indexed makerAddress
mload(add(order, 0x40)), // address indexed feeRecipientAddress
orderHash // bytes32 indexed orderHash
)
}
}
/// @dev Updates state with results of cancelling an order.
@@ -453,5 +565,27 @@ contract MixinExchangeCore is
order.feeRecipientAddress,
fillResults.makerFeePaid
);
// Transfer protocol fee -> staking if the fee should be paid
if (staking != address(0)) {
// If sufficient ether was sent to the contract, the protocol fee should be paid in ETH.
// Otherwise the fee should be paid in WETH.
uint256 protocolFee = fillResults.protocolFeePaid;
if (address(this).balance >= protocolFee) {
IStaking(staking).payProtocolFee.value(protocolFee)(order.makerAddress);
} else {
// Transfer the Weth
_dispatchTransferFrom(
orderHash,
WETH_ASSET_DATA,
takerAddress,
staking,
protocolFee
);
// Attribute the protocol fee to the maker
IStaking(staking).recordProtocolFee(order.makerAddress, protocolFee);
}
}
}
}

View File

@@ -45,7 +45,9 @@ contract MixinMatchOrders is
bytes[] memory rightSignatures
)
public
payable
nonReentrant
refund
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults)
{
return _batchMatchOrders(
@@ -73,7 +75,9 @@ contract MixinMatchOrders is
bytes[] memory rightSignatures
)
public
payable
nonReentrant
refund
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults)
{
return _batchMatchOrders(
@@ -101,7 +105,9 @@ contract MixinMatchOrders is
bytes memory rightSignature
)
public
payable
nonReentrant
refund
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
return _matchOrders(
@@ -129,7 +135,9 @@ contract MixinMatchOrders is
bytes memory rightSignature
)
public
payable
nonReentrant
refund
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
return _matchOrders(
@@ -277,7 +285,7 @@ contract MixinMatchOrders is
// Update the batched fill results once the leftIdx is updated.
batchMatchedFillResults.left[leftIdx++] = leftFillResults;
// Clear the intermediate fill results value.
leftFillResults = LibFillResults.FillResults(0, 0, 0, 0);
leftFillResults = LibFillResults.FillResults(0, 0, 0, 0, 0);
// If all of the left orders have been filled, break out of the loop.
// Otherwise, update the current right order.
@@ -297,7 +305,7 @@ contract MixinMatchOrders is
// Update the batched fill results once the rightIdx is updated.
batchMatchedFillResults.right[rightIdx++] = rightFillResults;
// Clear the intermediate fill results value.
rightFillResults = LibFillResults.FillResults(0, 0, 0, 0);
rightFillResults = LibFillResults.FillResults(0, 0, 0, 0, 0);
// If all of the right orders have been filled, break out of the loop.
// Otherwise, update the current right order.
@@ -377,6 +385,7 @@ contract MixinMatchOrders is
rightOrder,
leftOrderInfo.orderTakerAssetFilledAmount,
rightOrderInfo.orderTakerAssetFilledAmount,
protocolFeeMultiplier,
shouldMaximallyFillOrders
);
@@ -473,7 +482,6 @@ contract MixinMatchOrders is
takerAddress,
matchedFillResults.profitInLeftMakerAsset
);
_dispatchTransferFrom(
rightOrderHash,
rightOrder.makerAssetData,
@@ -482,6 +490,41 @@ contract MixinMatchOrders is
matchedFillResults.profitInRightMakerAsset
);
// Pay protocol fees
if (staking != address(0)) {
// matchedFillResults.left.protocolFeePaid == matchedFillResults.right.protocolFeePaid
// so we only use matchedFillResults.left.protocolFeePaid as a gas optimization.
uint256 protocolFee = matchedFillResults.left.protocolFeePaid;
// Construct an array of makers and fee amounts so that the staking contract will only need to be called once.
address[] memory makers = new address[](2);
makers[0] = leftOrder.makerAddress;
makers[1] = rightOrder.makerAddress;
uint256[] memory fees = new uint256[](2);
fees[0] = protocolFee;
fees[1] = protocolFee;
// If sufficient ether was sent to the contract, the protocol fee should be paid in ETH.
// Otherwise the fee should be paid in WETH.
if (address(this).balance >= 2 * protocolFee) {
// Forward the protocol fees
IStaking(staking).batchPayProtocolFees.value(2 * protocolFee)(makers, fees);
} else {
// Transfer the weth from the takerAddress.
// Note: `_dispatchTransferFrom` is only called once as a gas optimization.
_dispatchTransferFrom(
leftOrderHash,
WETH_ASSET_DATA,
takerAddress,
staking,
2 * protocolFee
);
// Attribute the protocol fees to the maker addresses
IStaking(staking).batchRecordProtocolFees(makers, fees);
}
}
// Settle taker fees.
if (
leftFeeRecipientAddress == rightFeeRecipientAddress &&

View File

@@ -23,11 +23,13 @@ import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/Refundable.sol";
import "./interfaces/ITransactions.sol";
import "./interfaces/ISignatureValidator.sol";
contract MixinTransactions is
Refundable,
LibEIP712ExchangeDomain,
ISignatureValidator,
ITransactions
@@ -50,6 +52,8 @@ contract MixinTransactions is
bytes memory signature
)
public
payable
refund
returns (bytes memory)
{
return _executeTransaction(transaction, signature);
@@ -64,6 +68,8 @@ contract MixinTransactions is
bytes[] memory signatures
)
public
payable
refund
returns (bytes[] memory)
{
uint256 length = transactions.length;

View File

@@ -46,7 +46,9 @@ contract MixinWrapperFunctions is
bytes memory signature
)
public
payable
nonReentrant
refund
returns (LibFillResults.FillResults memory fillResults)
{
fillResults = _fillOrKillOrder(
@@ -69,6 +71,8 @@ contract MixinWrapperFunctions is
bytes memory signature
)
public
payable
refund
returns (LibFillResults.FillResults memory fillResults)
{
// ABI encode calldata for `fillOrder`
@@ -99,7 +103,9 @@ contract MixinWrapperFunctions is
bytes[] memory signatures
)
public
payable
nonReentrant
refund
returns (LibFillResults.FillResults[] memory fillResults)
{
uint256 ordersLength = orders.length;
@@ -125,7 +131,9 @@ contract MixinWrapperFunctions is
bytes[] memory signatures
)
public
payable
nonReentrant
refund
returns (LibFillResults.FillResults[] memory fillResults)
{
uint256 ordersLength = orders.length;
@@ -151,6 +159,8 @@ contract MixinWrapperFunctions is
bytes[] memory signatures
)
public
payable
refund
returns (LibFillResults.FillResults[] memory fillResults)
{
uint256 ordersLength = orders.length;
@@ -176,6 +186,8 @@ contract MixinWrapperFunctions is
bytes[] memory signatures
)
public
payable
refund
returns (LibFillResults.FillResults memory fillResults)
{
bytes memory takerAssetData = orders[0].takerAssetData;
@@ -220,6 +232,8 @@ contract MixinWrapperFunctions is
bytes[] memory signatures
)
public
payable
refund
returns (LibFillResults.FillResults memory fillResults)
{
bytes memory makerAssetData = orders[0].makerAssetData;

View File

@@ -25,21 +25,26 @@ import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
contract IExchangeCore {
// keccak256("Fill(address,address,bytes32,address,address,uint256,uint256,uint256,uint256,uint256,bool,bytes,bytes,bytes,bytes)")
bytes32 internal constant FILL_EVENT_TOPIC = 0x266de417a663e51231ccdf89b2794cea06fde5e2c433d76473160b32d31fd867;
// Fill event is emitted whenever an order is filled.
event Fill(
address indexed makerAddress, // Address that created the order.
address indexed feeRecipientAddress, // Address that received fees.
bytes makerAssetData, // Encoded data specific to makerAsset.
bytes takerAssetData, // Encoded data specific to takerAsset.
bytes makerFeeAssetData, // Encoded data specific to makerFeeAsset.
bytes takerFeeAssetData, // Encoded data specific to takerFeeAsset.
bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getTypedDataHash).
address takerAddress, // Address that filled the order.
address senderAddress, // Address that called the Exchange contract (msg.sender).
uint256 makerAssetFilledAmount, // Amount of makerAsset sold by maker and bought by taker.
uint256 takerAssetFilledAmount, // Amount of takerAsset sold by taker and bought by maker.
uint256 makerFeePaid, // Amount of makerFeeAssetData paid to feeRecipient by maker.
uint256 takerFeePaid, // Amount of takerFeeAssetData paid to feeRecipient by taker.
address takerAddress, // Address that filled the order.
address senderAddress, // Address that called the Exchange contract (msg.sender).
bytes32 indexed orderHash // EIP712 hash of order (see LibOrder.getTypedDataHash).
uint256 protocolFeePaid, // Amount of eth or weth paid to the staking contract.
bool isProtocolFeePaidInEth, // Indicates whether the protocol fee is paid in ETH or WETH.
bytes makerAssetData, // Encoded data specific to makerAsset.
bytes takerAssetData, // Encoded data specific to takerAsset.
bytes makerFeeAssetData, // Encoded data specific to makerFeeAsset.
bytes takerFeeAssetData // Encoded data specific to takerFeeAsset.
);
// Cancel event is emitted whenever an individual order is cancelled.
@@ -76,6 +81,7 @@ contract IExchangeCore {
bytes memory signature
)
public
payable
returns (LibFillResults.FillResults memory fillResults);
/// @dev After calling, the order can not be filled anymore.

View File

@@ -40,6 +40,7 @@ contract IMatchOrders {
bytes[] memory rightSignatures
)
public
payable
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults);
/// @dev Match complementary orders that have a profitable spread.
@@ -58,6 +59,7 @@ contract IMatchOrders {
bytes[] memory rightSignatures
)
public
payable
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults);
/// @dev Match two complementary orders that have a profitable spread.
@@ -76,6 +78,7 @@ contract IMatchOrders {
bytes memory rightSignature
)
public
payable
returns (LibFillResults.MatchedFillResults memory matchedFillResults);
/// @dev Match two complementary orders that have a profitable spread.
@@ -94,5 +97,6 @@ contract IMatchOrders {
bytes memory rightSignature
)
public
payable
returns (LibFillResults.MatchedFillResults memory matchedFillResults);
}

View File

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

View File

@@ -36,6 +36,7 @@ contract ITransactions {
bytes memory signature
)
public
payable
returns (bytes memory);
/// @dev Executes a batch of Exchange method calls in the context of signer(s).
@@ -47,6 +48,7 @@ contract ITransactions {
bytes[] memory signatures
)
public
payable
returns (bytes[] memory);
/// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`).

View File

@@ -35,6 +35,7 @@ contract IWrapperFunctions {
bytes memory signature
)
public
payable
returns (LibFillResults.FillResults memory fillResults);
/// @dev Fills an order with specified parameters and ECDSA signature.
@@ -49,6 +50,7 @@ contract IWrapperFunctions {
bytes memory signature
)
public
payable
returns (LibFillResults.FillResults memory fillResults);
/// @dev Synchronously executes multiple calls of fillOrder.
@@ -62,6 +64,7 @@ contract IWrapperFunctions {
bytes[] memory signatures
)
public
payable
returns (LibFillResults.FillResults[] memory fillResults);
/// @dev Synchronously executes multiple calls of fillOrKill.
@@ -75,6 +78,7 @@ contract IWrapperFunctions {
bytes[] memory signatures
)
public
payable
returns (LibFillResults.FillResults[] memory fillResults);
/// @dev Fills an order with specified parameters and ECDSA signature.
@@ -89,6 +93,7 @@ contract IWrapperFunctions {
bytes[] memory signatures
)
public
payable
returns (LibFillResults.FillResults[] memory fillResults);
/// @dev Executes multiple calls of fillOrderNoThrow until total amount of takerAsset is sold by taker.
@@ -102,6 +107,7 @@ contract IWrapperFunctions {
bytes[] memory signatures
)
public
payable
returns (LibFillResults.FillResults memory fillResults);
/// @dev Executes multiple calls of fillOrderNoThrow until total amount of makerAsset is bought by taker.
@@ -141,6 +147,7 @@ contract IWrapperFunctions {
bytes[] memory signatures
)
public
payable
returns (LibFillResults.FillResults memory fillResults);
/// @dev Synchronously cancels multiple orders in a single transaction.

View File

@@ -66,6 +66,7 @@ contract TestExchangeInternals is
LibFillResults.FillResults memory fillResults
)
public
payable
{
filled[LibOrder.getTypedDataHash(order, EIP712_EXCHANGE_DOMAIN_HASH)] = orderTakerAssetFilledAmount;
_updateFilledState(

View File

@@ -1,6 +1,3 @@
export * from './artifacts';
export * from './wrappers';
export * from '../test/utils';
import * as ReferenceFunctionsToExport from './reference_functions';
export import ReferenceFunctions = ReferenceFunctionsToExport;

View File

@@ -1,24 +0,0 @@
import { ReferenceFunctions as ExchangeLibsReferenceFunctions } from '@0x/contracts-exchange-libs';
import { FillResults, OrderWithoutDomain } from '@0x/types';
import { BigNumber } from '@0x/utils';
const { safeGetPartialAmountFloor } = ExchangeLibsReferenceFunctions;
/**
* Calculates amounts filled and fees paid by maker and taker.
*/
export function calculateFillResults(order: OrderWithoutDomain, takerAssetFilledAmount: BigNumber): FillResults {
const makerAssetFilledAmount = safeGetPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
const makerFeePaid = safeGetPartialAmountFloor(makerAssetFilledAmount, order.makerAssetAmount, order.makerFee);
const takerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee);
return {
makerAssetFilledAmount,
takerAssetFilledAmount,
makerFeePaid,
takerFeePaid,
};
}

View File

@@ -6,6 +6,7 @@ import {
} from '@0x/contracts-asset-proxy';
import { artifacts as erc20Artifacts } from '@0x/contracts-erc20';
import { artifacts as erc721Artifacts } from '@0x/contracts-erc721';
import { ReferenceFunctions as LibReferenceFunctions } from '@0x/contracts-exchange-libs';
import {
expect,
FillEventArgs,
@@ -16,14 +17,13 @@ import {
Web3ProviderEngine,
} from '@0x/contracts-test-utils';
import { orderHashUtils } from '@0x/order-utils';
import { FillResults, SignedOrder } from '@0x/types';
import { AssetProxyId, FillResults, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { TransactionReceiptWithDecodedLogs, ZeroExProvider } from 'ethereum-types';
import * as _ from 'lodash';
import { artifacts, ExchangeContract } from '../../src';
import { calculateFillResults } from '../../src/reference_functions';
import { BalanceStore } from '../balance_stores/balance_store';
import { BlockchainBalanceStore } from '../balance_stores/blockchain_balance_store';
import { LocalBalanceStore } from '../balance_stores/local_balance_store';
@@ -47,11 +47,23 @@ export class FillOrderWrapper {
takerAddress: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
initBalanceStore: BalanceStore,
stakingOpts: {
gasPrice: BigNumber;
messageValue: BigNumber;
protocolFeeMultiplier: BigNumber;
stakingAddress: string;
wethAddress: string;
},
): [FillResults, FillEventArgs, BalanceStore] {
const balanceStore = LocalBalanceStore.create(initBalanceStore);
const takerAssetFillAmount =
opts.takerAssetFillAmount !== undefined ? opts.takerAssetFillAmount : signedOrder.takerAssetAmount;
const fillResults = calculateFillResults(signedOrder, takerAssetFillAmount);
const fillResults = LibReferenceFunctions.calculateFillResults(
signedOrder,
takerAssetFillAmount,
stakingOpts.protocolFeeMultiplier,
stakingOpts.gasPrice,
);
const fillEvent = FillOrderWrapper.simulateFillEvent(signedOrder, takerAddress, fillResults);
// Taker -> Maker
balanceStore.transferAsset(
@@ -81,6 +93,18 @@ export class FillOrderWrapper {
fillResults.makerFeePaid,
signedOrder.makerFeeAssetData,
);
if (stakingOpts.messageValue.isGreaterThanOrEqualTo(fillResults.protocolFeePaid)) {
// Pay the protocol fee in ETH.
balanceStore.transferAsset(takerAddress, stakingOpts.stakingAddress, fillResults.protocolFeePaid, '');
} else {
// Pay the protocol fee in WETH.
balanceStore.transferAsset(
takerAddress,
stakingOpts.stakingAddress,
fillResults.protocolFeePaid,
AssetProxyId.ERC20,
);
}
return [fillResults, fillEvent, balanceStore];
}

View File

@@ -72,7 +72,7 @@ export class BalanceStore {
* Constructor.
*/
public constructor() {
this._balances = { erc20: {}, erc721: {}, erc1155: {} };
this._balances = { erc20: {}, erc721: {}, erc1155: {}, eth: {} };
}
/**

View File

@@ -169,10 +169,21 @@ blockchainTests('Exchange core internal functions', env => {
orderTakerAssetFilledAmount: BigNumber,
takerAddress: string,
takerAssetFillAmount: BigNumber,
protocolFeeMultiplier: BigNumber,
gasPrice: BigNumber,
isProtocolFeePaidInEth: boolean,
): Promise<void> {
const orderHash = randomHash();
const fillResults = LibReferenceFunctions.calculateFillResults(order, takerAssetFillAmount);
const fillResults = LibReferenceFunctions.calculateFillResults(
order,
takerAssetFillAmount,
protocolFeeMultiplier,
gasPrice,
);
const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount);
const opts = isProtocolFeePaidInEth
? { value: fillResults.protocolFeePaid }
: { value: constants.ZERO_AMOUNT };
// CAll `testUpdateFilledState()`, which will set the `filled`
// state for this order to `orderTakerAssetFilledAmount` before
// calling `_updateFilledState()`.
@@ -183,6 +194,7 @@ blockchainTests('Exchange core internal functions', env => {
orderHash,
orderTakerAssetFilledAmount,
fillResults,
opts,
),
);
// Grab the new `filled` state for this order.
@@ -195,26 +207,44 @@ blockchainTests('Exchange core internal functions', env => {
expect(fillEvent.event).to.eq('Fill');
expect(fillEvent.args.makerAddress).to.eq(order.makerAddress);
expect(fillEvent.args.feeRecipientAddress).to.eq(order.feeRecipientAddress);
expect(fillEvent.args.makerAssetData).to.eq(order.makerAssetData);
expect(fillEvent.args.takerAssetData).to.eq(order.takerAssetData);
expect(fillEvent.args.makerFeeAssetData).to.eq(order.makerFeeAssetData);
expect(fillEvent.args.takerFeeAssetData).to.eq(order.takerFeeAssetData);
expect(fillEvent.args.orderHash).to.eq(orderHash);
expect(fillEvent.args.takerAddress).to.eq(takerAddress);
expect(fillEvent.args.senderAddress).to.eq(senderAddress);
expect(fillEvent.args.makerAssetFilledAmount).to.bignumber.eq(fillResults.makerAssetFilledAmount);
expect(fillEvent.args.takerAssetFilledAmount).to.bignumber.eq(fillResults.takerAssetFilledAmount);
expect(fillEvent.args.makerFeePaid).to.bignumber.eq(fillResults.makerFeePaid);
expect(fillEvent.args.takerFeePaid).to.bignumber.eq(fillResults.takerFeePaid);
expect(fillEvent.args.takerAddress).to.eq(takerAddress);
expect(fillEvent.args.senderAddress).to.eq(senderAddress);
expect(fillEvent.args.orderHash).to.eq(orderHash);
expect(fillEvent.args.protocolFeePaid).to.bignumber.eq(fillResults.protocolFeePaid);
expect(fillEvent.args.isProtocolFeePaidInEth).to.eq(isProtocolFeePaidInEth);
expect(fillEvent.args.makerAssetData).to.eq(order.makerAssetData);
expect(fillEvent.args.takerAssetData).to.eq(order.takerAssetData);
expect(fillEvent.args.makerFeeAssetData).to.eq(order.makerFeeAssetData);
expect(fillEvent.args.takerFeeAssetData).to.eq(order.takerFeeAssetData);
}
it('emits a `Fill` event and updates `filled` state correctly', async () => {
it('emits a `Fill` event and updates `filled` state correctly if protocol fee is paid in ETH', async () => {
const order = makeOrder();
return testUpdateFilledStateAsync(
order,
order.takerAssetAmount.times(0.1),
randomAddress(),
order.takerAssetAmount.times(0.25),
new BigNumber(150000),
new BigNumber(100000),
true,
);
});
it('emits a `Fill` event and updates `filled` state correctly if protocol fee is paid in WETH', async () => {
const order = makeOrder();
return testUpdateFilledStateAsync(
order,
order.takerAssetAmount.times(0.1),
randomAddress(),
order.takerAssetAmount.times(0.25),
new BigNumber(100000),
new BigNumber(150000),
true,
);
});

View File

@@ -5,8 +5,6 @@ import { FillResults, OrderInfo, OrderStatus, SignatureType } from '@0x/types';
import { BigNumber, SafeMathRevertErrors } from '@0x/utils';
import * as _ from 'lodash';
import { calculateFillResults } from '../src/reference_functions';
import {
AssetBalances,
createBadAssetData,
@@ -110,7 +108,10 @@ blockchainTests('Isolated fillOrder() tests', env => {
takerAssetFillAmount: BigNumber,
): FillResults {
const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount);
return calculateFillResults(order, BigNumber.min(takerAssetFillAmount, remainingTakerAssetAmount));
return LibReferenceFunctions.calculateFillResults(
order,
BigNumber.min(takerAssetFillAmount, remainingTakerAssetAmount),
);
}
function calculateExpectedOrderInfo(order: Order, orderInfo: OrderInfo, fillResults: FillResults): OrderInfo {

View File

@@ -49,10 +49,10 @@ blockchainTests.resets('Exchange wrappers', env => {
let defaultFeeAssetAddress: string;
const nullFillResults: FillResults = {
makerAssetFilledAmount: new BigNumber(0),
takerAssetFilledAmount: new BigNumber(0),
makerFeePaid: new BigNumber(0),
takerFeePaid: new BigNumber(0),
makerAssetFilledAmount: constants.ZERO_AMOUNT,
takerAssetFilledAmount: constants.ZERO_AMOUNT,
makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT,
};
before(async () => {