@0x:contracts-exchange
Added protocol fees to fill order
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 &&
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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`).
|
||||
|
@@ -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.
|
||||
|
@@ -66,6 +66,7 @@ contract TestExchangeInternals is
|
||||
LibFillResults.FillResults memory fillResults
|
||||
)
|
||||
public
|
||||
payable
|
||||
{
|
||||
filled[LibOrder.getTypedDataHash(order, EIP712_EXCHANGE_DOMAIN_HASH)] = orderTakerAssetFilledAmount;
|
||||
_updateFilledState(
|
||||
|
@@ -1,6 +1,3 @@
|
||||
export * from './artifacts';
|
||||
export * from './wrappers';
|
||||
export * from '../test/utils';
|
||||
|
||||
import * as ReferenceFunctionsToExport from './reference_functions';
|
||||
export import ReferenceFunctions = ReferenceFunctionsToExport;
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
@@ -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];
|
||||
}
|
||||
|
||||
|
@@ -72,7 +72,7 @@ export class BalanceStore {
|
||||
* Constructor.
|
||||
*/
|
||||
public constructor() {
|
||||
this._balances = { erc20: {}, erc721: {}, erc1155: {} };
|
||||
this._balances = { erc20: {}, erc721: {}, erc1155: {}, eth: {} };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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 () => {
|
||||
|
Reference in New Issue
Block a user