Address PR feedback and add support for affiliate fees
This commit is contained in:
parent
329719472a
commit
d2313b30af
@ -19,47 +19,66 @@
|
|||||||
pragma solidity ^0.5.9;
|
pragma solidity ^0.5.9;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
|
||||||
|
import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
|
||||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||||
import "@0x/contracts-exchange-libs/contracts/src/LibAssetDataTransfer.sol";
|
|
||||||
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
|
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
|
||||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||||
|
import "@0x/contracts-extensions/contracts/src/LibAssetDataTransfer.sol";
|
||||||
|
import "@0x/contracts-extensions/contracts/src/MixinWethUtils.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/Refundable.sol";
|
|
||||||
import "./interfaces/IBroker.sol";
|
import "./interfaces/IBroker.sol";
|
||||||
import "./interfaces/IPropertyValidator.sol";
|
import "./interfaces/IPropertyValidator.sol";
|
||||||
import "./libs/LibBrokerRichErrors.sol";
|
import "./libs/LibBrokerRichErrors.sol";
|
||||||
|
|
||||||
|
|
||||||
// solhint-disable space-after-comma
|
// solhint-disable space-after-comma, var-name-mixedcase
|
||||||
contract Broker is
|
contract Broker is
|
||||||
Refundable,
|
IBroker,
|
||||||
IBroker
|
MixinWethUtils
|
||||||
{
|
{
|
||||||
bytes[] internal _cachedAssetData;
|
// Contract addresses
|
||||||
|
|
||||||
|
// Address of the 0x Exchange contract
|
||||||
|
address internal EXCHANGE;
|
||||||
|
// Address of the 0x ERC1155 Asset Proxy contract
|
||||||
|
address internal ERC1155_PROXY;
|
||||||
|
|
||||||
|
// The following storage variables are used to cache data for the duration of the transcation.
|
||||||
|
// They should always cleared at the end of the transaction.
|
||||||
|
|
||||||
|
// Token IDs specified by the taker to be used to fill property-based orders.
|
||||||
|
uint256[] internal _cachedTokenIds;
|
||||||
|
// An index to the above array keeping track of which assets have been transferred.
|
||||||
uint256 internal _cacheIndex;
|
uint256 internal _cacheIndex;
|
||||||
|
// The address that called `brokerTrade` or `batchBrokerTrade`. Assets will be transferred to
|
||||||
|
// and from this address as the effectual taker of the orders.
|
||||||
address internal _sender;
|
address internal _sender;
|
||||||
address internal _EXCHANGE; // solhint-disable-line var-name-mixedcase
|
|
||||||
|
|
||||||
using LibSafeMath for uint256;
|
using LibSafeMath for uint256;
|
||||||
using LibBytes for bytes;
|
using LibBytes for bytes;
|
||||||
using LibAssetDataTransfer for bytes;
|
using LibAssetDataTransfer for bytes;
|
||||||
|
|
||||||
/// @param exchange Address of the 0x Exchange contract.
|
/// @param exchange Address of the 0x Exchange contract.
|
||||||
constructor (address exchange)
|
/// @param exchange Address of the Wrapped Ether contract.
|
||||||
|
/// @param exchange Address of the 0x ERC1155 Asset Proxy contract.
|
||||||
|
constructor (
|
||||||
|
address exchange,
|
||||||
|
address weth
|
||||||
|
)
|
||||||
public
|
public
|
||||||
|
MixinWethUtils(
|
||||||
|
exchange,
|
||||||
|
weth
|
||||||
|
)
|
||||||
{
|
{
|
||||||
_EXCHANGE = exchange;
|
EXCHANGE = exchange;
|
||||||
|
ERC1155_PROXY = IExchange(EXCHANGE).getAssetProxy(IAssetData(address(0)).ERC1155Assets.selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev A payable fallback function that makes this contract "payable". This is necessary to allow
|
|
||||||
/// this contract to gracefully handle refunds from the Exchange.
|
|
||||||
function ()
|
|
||||||
external
|
|
||||||
payable
|
|
||||||
{} // solhint-disable-line no-empty-blocks
|
|
||||||
|
|
||||||
/// @dev The Broker implements the ERC1155 transfer function to be compatible with the ERC1155 asset proxy
|
/// @dev The Broker implements the ERC1155 transfer function to be compatible with the ERC1155 asset proxy
|
||||||
/// @param from Since the Broker serves as the taker of the order, this should equal `address(this)`
|
/// @param from Since the Broker serves as the taker of the order, this should equal `address(this)`
|
||||||
/// @param to This should be the maker of the order.
|
/// @param to This should be the maker of the order.
|
||||||
@ -74,79 +93,95 @@ contract Broker is
|
|||||||
)
|
)
|
||||||
external
|
external
|
||||||
{
|
{
|
||||||
|
// Only the ERC1155 asset proxy contract should be calling this function.
|
||||||
|
if (msg.sender != ERC1155_PROXY) {
|
||||||
|
LibRichErrors.rrevert(LibBrokerRichErrors.OnlyERC1155ProxyError(
|
||||||
|
msg.sender
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// Only `takerAssetData` should be using Broker assets
|
||||||
if (from != address(this)) {
|
if (from != address(this)) {
|
||||||
LibRichErrors.rrevert(
|
LibRichErrors.rrevert(
|
||||||
LibBrokerRichErrors.InvalidFromAddressError(from)
|
LibBrokerRichErrors.InvalidFromAddressError(from)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Only one asset amount should be specified.
|
||||||
if (amounts.length != 1) {
|
if (amounts.length != 1) {
|
||||||
LibRichErrors.rrevert(
|
LibRichErrors.rrevert(
|
||||||
LibBrokerRichErrors.AmountsLengthMustEqualOneError(amounts.length)
|
LibBrokerRichErrors.AmountsLengthMustEqualOneError(amounts.length)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint256 cacheIndex = _cacheIndex;
|
||||||
uint256 remainingAmount = amounts[0];
|
uint256 remainingAmount = amounts[0];
|
||||||
if (_cachedAssetData.length.safeSub(_cacheIndex) < remainingAmount) {
|
|
||||||
|
// Verify that there are enough broker assets to transfer
|
||||||
|
if (_cachedTokenIds.length.safeSub(cacheIndex) < remainingAmount) {
|
||||||
LibRichErrors.rrevert(
|
LibRichErrors.rrevert(
|
||||||
LibBrokerRichErrors.TooFewBrokerAssetsProvidedError(_cachedAssetData.length)
|
LibBrokerRichErrors.TooFewBrokerAssetsProvidedError(_cachedTokenIds.length)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (remainingAmount != 0) {
|
// Decode validator and params from `data`
|
||||||
bytes memory assetToTransfer = _cachedAssetData[_cacheIndex];
|
(address tokenAddress, address validator, bytes memory propertyData) = abi.decode(
|
||||||
_cacheIndex++;
|
data,
|
||||||
|
(address, address, bytes)
|
||||||
|
);
|
||||||
|
|
||||||
// Decode validator and params from `data`
|
while (remainingAmount != 0) {
|
||||||
(address validator, bytes memory propertyData) = abi.decode(
|
uint256 tokenId = _cachedTokenIds[cacheIndex];
|
||||||
data,
|
cacheIndex++;
|
||||||
(address, bytes)
|
|
||||||
|
// Validate asset properties
|
||||||
|
IPropertyValidator(validator).checkBrokerAsset(
|
||||||
|
tokenId,
|
||||||
|
propertyData
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute staticcall
|
|
||||||
(bool success, bytes memory returnData) = validator.staticcall(abi.encodeWithSelector(
|
|
||||||
IPropertyValidator(0).checkBrokerAsset.selector,
|
|
||||||
assetToTransfer,
|
|
||||||
propertyData
|
|
||||||
));
|
|
||||||
|
|
||||||
// Revert with returned data if staticcall is unsuccessful
|
|
||||||
if (!success) {
|
|
||||||
assembly {
|
|
||||||
revert(add(returnData, 32), mload(returnData))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the transfer
|
// Perform the transfer
|
||||||
assetToTransfer.transferERC721Token(
|
IERC721Token(tokenAddress).transferFrom(
|
||||||
_sender,
|
_sender,
|
||||||
to,
|
to,
|
||||||
1
|
tokenId
|
||||||
);
|
);
|
||||||
|
|
||||||
remainingAmount--;
|
remainingAmount--;
|
||||||
}
|
}
|
||||||
|
// Update cache index in storage
|
||||||
|
_cacheIndex = cacheIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Fills a single property-based order by the given amount using the given assets.
|
/// @dev Fills a single property-based order by the given amount using the given assets.
|
||||||
/// @param brokeredAssets Assets specified by the taker to be used to fill the order.
|
/// Pays protocol fees using either the ETH supplied by the taker to the transaction or
|
||||||
/// @param order The property-based order to fill.
|
/// WETH acquired from the maker during settlement. The final WETH balance is sent to the taker.
|
||||||
|
/// @param brokeredTokenIds Token IDs specified by the taker to be used to fill the orders.
|
||||||
|
/// @param order The property-based order to fill. The format of a property-based order is the
|
||||||
|
/// same as that of a normal order, except the takerAssetData. Instaed of specifying a
|
||||||
|
/// specific ERC721 asset, the takerAssetData should be ERC1155 assetData where the
|
||||||
|
/// underlying tokenAddress is this contract's address and the desired properties are
|
||||||
|
/// encoded in the extra data field. Also note that takerFees must be denominated in
|
||||||
|
/// WETH (or zero).
|
||||||
/// @param takerAssetFillAmount The amount to fill the order by.
|
/// @param takerAssetFillAmount The amount to fill the order by.
|
||||||
/// @param signature The maker's signature of the given order.
|
/// @param signature The maker's signature of the given order.
|
||||||
/// @param fillFunctionSelector The selector for either `fillOrder` or `fillOrKillOrder`.
|
/// @param fillFunctionSelector The selector for either `fillOrder` or `fillOrKillOrder`.
|
||||||
|
/// @param ethFeeAmounts Amounts of ETH, denominated in Wei, that are paid to corresponding feeRecipients.
|
||||||
|
/// @param feeRecipients Addresses that will receive ETH when orders are filled.
|
||||||
/// @return fillResults Amounts filled and fees paid by the maker and taker.
|
/// @return fillResults Amounts filled and fees paid by the maker and taker.
|
||||||
function brokerTrade(
|
function brokerTrade(
|
||||||
bytes[] memory brokeredAssets,
|
uint256[] memory brokeredTokenIds,
|
||||||
LibOrder.Order memory order,
|
LibOrder.Order memory order,
|
||||||
uint256 takerAssetFillAmount,
|
uint256 takerAssetFillAmount,
|
||||||
bytes memory signature,
|
bytes memory signature,
|
||||||
bytes4 fillFunctionSelector
|
bytes4 fillFunctionSelector,
|
||||||
|
uint256[] memory ethFeeAmounts,
|
||||||
|
address payable[] memory feeRecipients
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
payable
|
payable
|
||||||
refundFinalBalance
|
|
||||||
returns (LibFillResults.FillResults memory fillResults)
|
returns (LibFillResults.FillResults memory fillResults)
|
||||||
{
|
{
|
||||||
// Cache the taker-supplied asset data
|
// Cache the taker-supplied asset data
|
||||||
_cachedAssetData = brokeredAssets;
|
_cachedTokenIds = brokeredTokenIds;
|
||||||
// Cache the sender's address
|
// Cache the sender's address
|
||||||
_sender = msg.sender;
|
_sender = msg.sender;
|
||||||
|
|
||||||
@ -158,6 +193,8 @@ contract Broker is
|
|||||||
LibBrokerRichErrors.InvalidFunctionSelectorError(fillFunctionSelector);
|
LibBrokerRichErrors.InvalidFunctionSelectorError(fillFunctionSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pay ETH affiliate fees to all feeRecipient addresses
|
||||||
|
_transferEthFeesAndWrapRemaining(ethFeeAmounts, feeRecipients);
|
||||||
|
|
||||||
// Perform the fill
|
// Perform the fill
|
||||||
bytes memory fillCalldata = abi.encodeWithSelector(
|
bytes memory fillCalldata = abi.encodeWithSelector(
|
||||||
@ -167,47 +204,59 @@ contract Broker is
|
|||||||
signature
|
signature
|
||||||
);
|
);
|
||||||
// solhint-disable-next-line avoid-call-value
|
// solhint-disable-next-line avoid-call-value
|
||||||
(bool didSucceed, bytes memory returnData) = _EXCHANGE.call.value(msg.value)(fillCalldata);
|
(bool didSucceed, bytes memory returnData) = EXCHANGE.call(fillCalldata);
|
||||||
if (didSucceed) {
|
if (didSucceed) {
|
||||||
fillResults = abi.decode(returnData, (LibFillResults.FillResults));
|
fillResults = abi.decode(returnData, (LibFillResults.FillResults));
|
||||||
} else {
|
} else {
|
||||||
assembly {
|
// Re-throw error
|
||||||
revert(add(returnData, 32), mload(returnData))
|
LibRichErrors.rrevert(returnData);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer maker asset to taker
|
// Transfer maker asset to taker
|
||||||
order.makerAssetData.transferOut(fillResults.makerAssetFilledAmount);
|
if (!order.makerAssetData.equals(WETH_ASSET_DATA)) {
|
||||||
|
order.makerAssetData.transferOut(fillResults.makerAssetFilledAmount);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear storage
|
// Refund remaining ETH to msg.sender.
|
||||||
delete _cachedAssetData;
|
_transferEthRefund(WETH.balanceOf(address(this)));
|
||||||
_cacheIndex = 0;
|
|
||||||
_sender = address(0);
|
_clearStorage();
|
||||||
|
|
||||||
return fillResults;
|
return fillResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Fills multiple property-based orders by the given amounts using the given assets.
|
/// @dev Fills multiple property-based orders by the given amounts using the given assets.
|
||||||
/// @param brokeredAssets Assets specified by the taker to be used to fill the orders.
|
/// Pays protocol fees using either the ETH supplied by the taker to the transaction or
|
||||||
/// @param orders The property-based orders to fill.
|
/// WETH acquired from the maker during settlement. The final WETH balance is sent to the taker.
|
||||||
|
/// @param brokeredTokenIds Token IDs specified by the taker to be used to fill the orders.
|
||||||
|
/// @param orders The property-based orders to fill. The format of a property-based order is the
|
||||||
|
/// same as that of a normal order, except the takerAssetData. Instaed of specifying a
|
||||||
|
/// specific ERC721 asset, the takerAssetData should be ERC1155 assetData where the
|
||||||
|
/// underlying tokenAddress is this contract's address and the desired properties are
|
||||||
|
/// encoded in the extra data field. Also note that takerFees must be denominated in
|
||||||
|
/// WETH (or zero).
|
||||||
/// @param takerAssetFillAmounts The amounts to fill the orders by.
|
/// @param takerAssetFillAmounts The amounts to fill the orders by.
|
||||||
/// @param signatures The makers' signatures for the given orders.
|
/// @param signatures The makers' signatures for the given orders.
|
||||||
/// @param batchFillFunctionSelector The selector for either `batchFillOrders`, `batchFillOrKillOrders`, or `batchFillOrdersNoThrow`.
|
/// @param batchFillFunctionSelector The selector for either `batchFillOrders`,
|
||||||
|
/// `batchFillOrKillOrders`, or `batchFillOrdersNoThrow`.
|
||||||
|
/// @param ethFeeAmounts Amounts of ETH, denominated in Wei, that are paid to corresponding feeRecipients.
|
||||||
|
/// @param feeRecipients Addresses that will receive ETH when orders are filled.
|
||||||
/// @return fillResults Amounts filled and fees paid by the makers and taker.
|
/// @return fillResults Amounts filled and fees paid by the makers and taker.
|
||||||
function batchBrokerTrade(
|
function batchBrokerTrade(
|
||||||
bytes[] memory brokeredAssets,
|
uint256[] memory brokeredTokenIds,
|
||||||
LibOrder.Order[] memory orders,
|
LibOrder.Order[] memory orders,
|
||||||
uint256[] memory takerAssetFillAmounts,
|
uint256[] memory takerAssetFillAmounts,
|
||||||
bytes[] memory signatures,
|
bytes[] memory signatures,
|
||||||
bytes4 batchFillFunctionSelector
|
bytes4 batchFillFunctionSelector,
|
||||||
|
uint256[] memory ethFeeAmounts,
|
||||||
|
address payable[] memory feeRecipients
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
payable
|
payable
|
||||||
refundFinalBalance
|
|
||||||
returns (LibFillResults.FillResults[] memory fillResults)
|
returns (LibFillResults.FillResults[] memory fillResults)
|
||||||
{
|
{
|
||||||
// Cache the taker-supplied asset data
|
// Cache the taker-supplied asset data
|
||||||
_cachedAssetData = brokeredAssets;
|
_cachedTokenIds = brokeredTokenIds;
|
||||||
// Cache the sender's address
|
// Cache the sender's address
|
||||||
_sender = msg.sender;
|
_sender = msg.sender;
|
||||||
|
|
||||||
@ -220,6 +269,9 @@ contract Broker is
|
|||||||
LibBrokerRichErrors.InvalidFunctionSelectorError(batchFillFunctionSelector);
|
LibBrokerRichErrors.InvalidFunctionSelectorError(batchFillFunctionSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pay ETH affiliate fees to all feeRecipient addresses
|
||||||
|
_transferEthFeesAndWrapRemaining(ethFeeAmounts, feeRecipients);
|
||||||
|
|
||||||
// Perform the batch fill
|
// Perform the batch fill
|
||||||
bytes memory batchFillCalldata = abi.encodeWithSelector(
|
bytes memory batchFillCalldata = abi.encodeWithSelector(
|
||||||
batchFillFunctionSelector,
|
batchFillFunctionSelector,
|
||||||
@ -228,26 +280,35 @@ contract Broker is
|
|||||||
signatures
|
signatures
|
||||||
);
|
);
|
||||||
// solhint-disable-next-line avoid-call-value
|
// solhint-disable-next-line avoid-call-value
|
||||||
(bool didSucceed, bytes memory returnData) = _EXCHANGE.call.value(msg.value)(batchFillCalldata);
|
(bool didSucceed, bytes memory returnData) = EXCHANGE.call(batchFillCalldata);
|
||||||
if (didSucceed) {
|
if (didSucceed) {
|
||||||
// solhint-disable-next-line indent
|
// solhint-disable-next-line indent
|
||||||
fillResults = abi.decode(returnData, (LibFillResults.FillResults[]));
|
fillResults = abi.decode(returnData, (LibFillResults.FillResults[]));
|
||||||
} else {
|
} else {
|
||||||
assembly {
|
// Re-throw error
|
||||||
revert(add(returnData, 32), mload(returnData))
|
LibRichErrors.rrevert(returnData);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer maker assets to taker
|
// Transfer maker assets to taker
|
||||||
for (uint256 i = 0; i < orders.length; i++) {
|
for (uint256 i = 0; i < orders.length; i++) {
|
||||||
orders[i].makerAssetData.transferOut(fillResults[i].makerAssetFilledAmount);
|
if (!orders[i].makerAssetData.equals(WETH_ASSET_DATA)) {
|
||||||
|
orders[i].makerAssetData.transferOut(fillResults[i].makerAssetFilledAmount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear storage
|
// Refund remaining ETH to msg.sender.
|
||||||
delete _cachedAssetData;
|
_transferEthRefund(WETH.balanceOf(address(this)));
|
||||||
_cacheIndex = 0;
|
|
||||||
_sender = address(0);
|
_clearStorage();
|
||||||
|
|
||||||
return fillResults;
|
return fillResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _clearStorage()
|
||||||
|
private
|
||||||
|
{
|
||||||
|
delete _cachedTokenIds;
|
||||||
|
_cacheIndex = 0;
|
||||||
|
_sender = address(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,36 +27,59 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
|||||||
interface IBroker {
|
interface IBroker {
|
||||||
|
|
||||||
/// @dev Fills a single property-based order by the given amount using the given assets.
|
/// @dev Fills a single property-based order by the given amount using the given assets.
|
||||||
/// @param brokeredAssets Assets specified by the taker to be used to fill the order.
|
/// Pays protocol fees using either the ETH supplied by the taker to the transaction or
|
||||||
/// @param order The property-based order to fill.
|
/// WETH acquired from the maker during settlement. The final WETH balance is sent to the taker.
|
||||||
|
/// @param brokeredTokenIds Token IDs specified by the taker to be used to fill the orders.
|
||||||
|
/// @param order The property-based order to fill. The format of a property-based order is the
|
||||||
|
/// same as that of a normal order, except the takerAssetData. Instaed of specifying a
|
||||||
|
/// specific ERC721 asset, the takerAssetData should be ERC1155 assetData where the
|
||||||
|
/// underlying tokenAddress is this contract's address and the desired properties are
|
||||||
|
/// encoded in the extra data field. Also note that takerFees must be denominated in
|
||||||
|
/// WETH (or zero).
|
||||||
/// @param takerAssetFillAmount The amount to fill the order by.
|
/// @param takerAssetFillAmount The amount to fill the order by.
|
||||||
/// @param signature The maker's signature of the given order.
|
/// @param signature The maker's signature of the given order.
|
||||||
/// @param fillFunctionSelector The selector for either `fillOrder` or `fillOrKillOrder`.
|
/// @param fillFunctionSelector The selector for either `fillOrder` or `fillOrKillOrder`.
|
||||||
|
/// @param ethFeeAmounts Amounts of ETH, denominated in Wei, that are paid to corresponding feeRecipients.
|
||||||
|
/// @param feeRecipients Addresses that will receive ETH when orders are filled.
|
||||||
/// @return fillResults Amounts filled and fees paid by the maker and taker.
|
/// @return fillResults Amounts filled and fees paid by the maker and taker.
|
||||||
function brokerTrade(
|
function brokerTrade(
|
||||||
bytes[] calldata brokeredAssets,
|
uint256[] calldata brokeredTokenIds,
|
||||||
LibOrder.Order calldata order,
|
LibOrder.Order calldata order,
|
||||||
uint256 takerAssetFillAmount,
|
uint256 takerAssetFillAmount,
|
||||||
bytes calldata signature,
|
bytes calldata signature,
|
||||||
bytes4 fillFunctionSelector
|
bytes4 fillFunctionSelector,
|
||||||
|
uint256[] calldata ethFeeAmounts,
|
||||||
|
address payable[] calldata feeRecipients
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
payable
|
payable
|
||||||
returns (LibFillResults.FillResults memory fillResults);
|
returns (LibFillResults.FillResults memory fillResults);
|
||||||
|
|
||||||
/// @dev Fills multiple property-based orders by the given amounts using the given assets.
|
/// @dev Fills multiple property-based orders by the given amounts using the given assets.
|
||||||
/// @param brokeredAssets Assets specified by the taker to be used to fill the orders.
|
/// Pays protocol fees using either the ETH supplied by the taker to the transaction or
|
||||||
/// @param orders The property-based orders to fill.
|
/// WETH acquired from the maker during settlement. The final WETH balance is sent to the taker.
|
||||||
|
/// @param brokeredTokenIds Token IDs specified by the taker to be used to fill the orders.
|
||||||
|
/// @param orders The property-based orders to fill. The format of a property-based order is the
|
||||||
|
/// same as that of a normal order, except the takerAssetData. Instaed of specifying a
|
||||||
|
/// specific ERC721 asset, the takerAssetData should be ERC1155 assetData where the
|
||||||
|
/// underlying tokenAddress is this contract's address and the desired properties are
|
||||||
|
/// encoded in the extra data field. Also note that takerFees must be denominated in
|
||||||
|
/// WETH (or zero).
|
||||||
/// @param takerAssetFillAmounts The amounts to fill the orders by.
|
/// @param takerAssetFillAmounts The amounts to fill the orders by.
|
||||||
/// @param signatures The makers' signatures for the given orders.
|
/// @param signatures The makers' signatures for the given orders.
|
||||||
/// @param batchFillFunctionSelector The selector for either `batchFillOrders`, `batchFillOrKillOrders`, or `batchFillOrdersNoThrow`.
|
/// @param batchFillFunctionSelector The selector for either `batchFillOrders`,
|
||||||
|
/// `batchFillOrKillOrders`, or `batchFillOrdersNoThrow`.
|
||||||
|
/// @param ethFeeAmounts Amounts of ETH, denominated in Wei, that are paid to corresponding feeRecipients.
|
||||||
|
/// @param feeRecipients Addresses that will receive ETH when orders are filled.
|
||||||
/// @return fillResults Amounts filled and fees paid by the makers and taker.
|
/// @return fillResults Amounts filled and fees paid by the makers and taker.
|
||||||
function batchBrokerTrade(
|
function batchBrokerTrade(
|
||||||
bytes[] calldata brokeredAssets,
|
uint256[] calldata brokeredTokenIds,
|
||||||
LibOrder.Order[] calldata orders,
|
LibOrder.Order[] calldata orders,
|
||||||
uint256[] calldata takerAssetFillAmounts,
|
uint256[] calldata takerAssetFillAmounts,
|
||||||
bytes[] calldata signatures,
|
bytes[] calldata signatures,
|
||||||
bytes4 batchFillFunctionSelector
|
bytes4 batchFillFunctionSelector,
|
||||||
|
uint256[] calldata ethFeeAmounts,
|
||||||
|
address payable[] calldata feeRecipients
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
payable
|
payable
|
||||||
|
@ -23,10 +23,11 @@ pragma experimental ABIEncoderV2;
|
|||||||
interface IPropertyValidator {
|
interface IPropertyValidator {
|
||||||
|
|
||||||
/// @dev Checks that the given asset data satisfies the properties encoded in `propertyData`.
|
/// @dev Checks that the given asset data satisfies the properties encoded in `propertyData`.
|
||||||
/// @param assetData The encoded asset to check.
|
/// Should revert if the asset does not satisfy the specified properties.
|
||||||
|
/// @param tokenId The ERC721 tokenId of the asset to check.
|
||||||
/// @param propertyData Encoded properties or auxiliary data needed to perform the check.
|
/// @param propertyData Encoded properties or auxiliary data needed to perform the check.
|
||||||
function checkBrokerAsset(
|
function checkBrokerAsset(
|
||||||
bytes calldata assetData,
|
uint256 tokenId,
|
||||||
bytes calldata propertyData
|
bytes calldata propertyData
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
|
@ -21,7 +21,6 @@ pragma solidity ^0.5.9;
|
|||||||
|
|
||||||
library LibBrokerRichErrors {
|
library LibBrokerRichErrors {
|
||||||
|
|
||||||
|
|
||||||
// bytes4(keccak256("InvalidFromAddressError(address)"))
|
// bytes4(keccak256("InvalidFromAddressError(address)"))
|
||||||
bytes4 internal constant INVALID_FROM_ADDRESS_ERROR_SELECTOR =
|
bytes4 internal constant INVALID_FROM_ADDRESS_ERROR_SELECTOR =
|
||||||
0x906bfb3c;
|
0x906bfb3c;
|
||||||
@ -38,6 +37,10 @@ library LibBrokerRichErrors {
|
|||||||
bytes4 internal constant INVALID_FUNCTION_SELECTOR_ERROR_SELECTOR =
|
bytes4 internal constant INVALID_FUNCTION_SELECTOR_ERROR_SELECTOR =
|
||||||
0x540943f1;
|
0x540943f1;
|
||||||
|
|
||||||
|
// bytes4(keccak256("OnlyERC1155ProxyError(address)"))
|
||||||
|
bytes4 internal constant ONLY_ERC_1155_PROXY_ERROR_SELECTOR =
|
||||||
|
0xccc529af;
|
||||||
|
|
||||||
// solhint-disable func-name-mixedcase
|
// solhint-disable func-name-mixedcase
|
||||||
function InvalidFromAddressError(
|
function InvalidFromAddressError(
|
||||||
address from
|
address from
|
||||||
@ -90,4 +93,17 @@ library LibBrokerRichErrors {
|
|||||||
selector
|
selector
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function OnlyERC1155ProxyError(
|
||||||
|
address sender
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (bytes memory)
|
||||||
|
{
|
||||||
|
return abi.encodeWithSelector(
|
||||||
|
ONLY_ERC_1155_PROXY_ERROR_SELECTOR,
|
||||||
|
sender
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,10 +38,11 @@ contract GodsUnchainedValidator is
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Checks that the given card (encoded as assetData) has the proto and quality encoded in `propertyData`.
|
/// @dev Checks that the given card (encoded as assetData) has the proto and quality encoded in `propertyData`.
|
||||||
/// @param assetData The card (encoded as ERC721 assetData) to check.
|
/// Reverts if the card doesn't match the specified proto and quality.
|
||||||
|
/// @param tokenId The ERC721 tokenId of the card to check.
|
||||||
/// @param propertyData Encoded proto and quality that the card is expected to have.
|
/// @param propertyData Encoded proto and quality that the card is expected to have.
|
||||||
function checkBrokerAsset(
|
function checkBrokerAsset(
|
||||||
bytes calldata assetData,
|
uint256 tokenId,
|
||||||
bytes calldata propertyData
|
bytes calldata propertyData
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
@ -52,13 +53,9 @@ contract GodsUnchainedValidator is
|
|||||||
(uint16, uint8)
|
(uint16, uint8)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Decode and validate asset data.
|
// Validate card properties.
|
||||||
address token = assetData.readAddress(16);
|
|
||||||
require(token == address(GODS_UNCHAINED), "TOKEN_ADDRESS_MISMATCH");
|
|
||||||
uint256 tokenId = assetData.readUint256(36);
|
|
||||||
|
|
||||||
(uint16 proto, uint8 quality) = GODS_UNCHAINED.getDetails(tokenId);
|
(uint16 proto, uint8 quality) = GODS_UNCHAINED.getDetails(tokenId);
|
||||||
require(proto == expectedProto, "PROTO_MISMATCH");
|
require(proto == expectedProto, "GodsUnchainedValidator/PROTO_MISMATCH");
|
||||||
require(quality == expectedQuality, "QUALITY_MISMATCH");
|
require(quality == expectedQuality, "GodsUnchainedValidator/QUALITY_MISMATCH");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ contract MixinWethUtils {
|
|||||||
|
|
||||||
// solhint-disable var-name-mixedcase
|
// solhint-disable var-name-mixedcase
|
||||||
IEtherToken internal WETH;
|
IEtherToken internal WETH;
|
||||||
|
bytes internal WETH_ASSET_DATA;
|
||||||
// solhint-enable var-name-mixedcase
|
// solhint-enable var-name-mixedcase
|
||||||
|
|
||||||
using LibSafeMath for uint256;
|
using LibSafeMath for uint256;
|
||||||
@ -43,6 +44,10 @@ contract MixinWethUtils {
|
|||||||
public
|
public
|
||||||
{
|
{
|
||||||
WETH = IEtherToken(weth);
|
WETH = IEtherToken(weth);
|
||||||
|
WETH_ASSET_DATA = abi.encodeWithSelector(
|
||||||
|
IAssetData(address(0)).ERC20Token.selector,
|
||||||
|
weth
|
||||||
|
);
|
||||||
|
|
||||||
address proxyAddress = IExchange(exchange).getAssetProxy(IAssetData(address(0)).ERC20Token.selector);
|
address proxyAddress = IExchange(exchange).getAssetProxy(IAssetData(address(0)).ERC20Token.selector);
|
||||||
if (proxyAddress == address(0)) {
|
if (proxyAddress == address(0)) {
|
||||||
|
@ -21,7 +21,7 @@ import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance
|
|||||||
import { LocalBalanceStore } from '../framework/balances/local_balance_store';
|
import { LocalBalanceStore } from '../framework/balances/local_balance_store';
|
||||||
import { DeploymentManager } from '../framework/deployment_manager';
|
import { DeploymentManager } from '../framework/deployment_manager';
|
||||||
|
|
||||||
blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
blockchainTests.resets.only('Broker <> Gods Unchained integration tests', env => {
|
||||||
let deployment: DeploymentManager;
|
let deployment: DeploymentManager;
|
||||||
let balanceStore: BlockchainBalanceStore;
|
let balanceStore: BlockchainBalanceStore;
|
||||||
let initialBalances: LocalBalanceStore;
|
let initialBalances: LocalBalanceStore;
|
||||||
@ -34,7 +34,6 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
let validator: GodsUnchainedValidatorContract;
|
let validator: GodsUnchainedValidatorContract;
|
||||||
|
|
||||||
let godsUnchainedTokenIds: BigNumber[];
|
let godsUnchainedTokenIds: BigNumber[];
|
||||||
let erc721AssetData: string[];
|
|
||||||
const makerSpecifiedProto = new BigNumber(1337);
|
const makerSpecifiedProto = new BigNumber(1337);
|
||||||
const makerSpecifiedQuality = new BigNumber(25);
|
const makerSpecifiedQuality = new BigNumber(25);
|
||||||
|
|
||||||
@ -69,10 +68,12 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
env.txDefaults,
|
env.txDefaults,
|
||||||
BrokerArtifacts,
|
BrokerArtifacts,
|
||||||
deployment.exchange.address,
|
deployment.exchange.address,
|
||||||
|
deployment.tokens.weth.address,
|
||||||
);
|
);
|
||||||
|
|
||||||
const takerAssetData = godsUnchainedUtils.encodeBrokerAssetData(
|
const takerAssetData = godsUnchainedUtils.encodeBrokerAssetData(
|
||||||
broker.address,
|
broker.address,
|
||||||
|
godsUnchained.address,
|
||||||
validator.address,
|
validator.address,
|
||||||
makerSpecifiedProto,
|
makerSpecifiedProto,
|
||||||
makerSpecifiedQuality,
|
makerSpecifiedQuality,
|
||||||
@ -102,9 +103,6 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
broker.address,
|
broker.address,
|
||||||
5,
|
5,
|
||||||
);
|
);
|
||||||
erc721AssetData = godsUnchainedTokenIds.map(tokenId =>
|
|
||||||
assetDataUtils.encodeERC721AssetData(godsUnchained.address, tokenId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const tokenOwners = {
|
const tokenOwners = {
|
||||||
Maker: maker.address,
|
Maker: maker.address,
|
||||||
@ -129,7 +127,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function simulateBrokerFills(
|
function simulateBrokerFills(
|
||||||
brokeredAssets: string[],
|
brokeredAssets: BigNumber[],
|
||||||
orders: SignedOrder[],
|
orders: SignedOrder[],
|
||||||
takerAssetFillAmounts: BigNumber[],
|
takerAssetFillAmounts: BigNumber[],
|
||||||
receipt: TransactionReceiptWithDecodedLogs,
|
receipt: TransactionReceiptWithDecodedLogs,
|
||||||
@ -139,7 +137,8 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
expectedBalances.burnGas(receipt.from, DeploymentManager.gasPrice.times(receipt.gasUsed));
|
expectedBalances.burnGas(receipt.from, DeploymentManager.gasPrice.times(receipt.gasUsed));
|
||||||
// Taker -> Maker
|
// Taker -> Maker
|
||||||
for (const brokeredAsset of brokeredAssets) {
|
for (const brokeredAsset of brokeredAssets) {
|
||||||
expectedBalances.transferAsset(taker.address, maker.address, new BigNumber(1), brokeredAsset);
|
const erc721AssetData = assetDataUtils.encodeERC721AssetData(godsUnchained.address, brokeredAsset);
|
||||||
|
expectedBalances.transferAsset(taker.address, maker.address, new BigNumber(1), erc721AssetData);
|
||||||
}
|
}
|
||||||
// Maker -> Taker
|
// Maker -> Taker
|
||||||
for (const [i, order] of orders.entries()) {
|
for (const [i, order] of orders.entries()) {
|
||||||
@ -156,6 +155,11 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
deployment.staking.stakingProxy.address,
|
deployment.staking.stakingProxy.address,
|
||||||
DeploymentManager.protocolFee.times(orders.length),
|
DeploymentManager.protocolFee.times(orders.length),
|
||||||
);
|
);
|
||||||
|
expectedBalances.wrapEth(
|
||||||
|
deployment.staking.stakingProxy.address,
|
||||||
|
deployment.tokens.weth.address,
|
||||||
|
DeploymentManager.protocolFee.times(orders.length),
|
||||||
|
);
|
||||||
return expectedBalances;
|
return expectedBalances;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,11 +182,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with one valid asset`, async () => {
|
it(`${fnName} with one valid asset`, async () => {
|
||||||
const receipt = await broker
|
const receipt = await broker
|
||||||
.brokerTrade(
|
.brokerTrade(
|
||||||
[erc721AssetData[0]],
|
[godsUnchainedTokenIds[0]],
|
||||||
order,
|
order,
|
||||||
new BigNumber(1),
|
new BigNumber(1),
|
||||||
order.signature,
|
order.signature,
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -190,7 +196,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
gasPrice: DeploymentManager.gasPrice,
|
gasPrice: DeploymentManager.gasPrice,
|
||||||
});
|
});
|
||||||
const expectedBalances = simulateBrokerFills(
|
const expectedBalances = simulateBrokerFills(
|
||||||
[erc721AssetData[0]],
|
[godsUnchainedTokenIds[0]],
|
||||||
[order],
|
[order],
|
||||||
[new BigNumber(1)],
|
[new BigNumber(1)],
|
||||||
receipt,
|
receipt,
|
||||||
@ -201,11 +207,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with two valid assets`, async () => {
|
it(`${fnName} with two valid assets`, async () => {
|
||||||
const receipt = await broker
|
const receipt = await broker
|
||||||
.brokerTrade(
|
.brokerTrade(
|
||||||
[erc721AssetData[0], erc721AssetData[1]],
|
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[1]],
|
||||||
order,
|
order,
|
||||||
new BigNumber(2),
|
new BigNumber(2),
|
||||||
order.signature,
|
order.signature,
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -214,7 +222,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expectedBalances = simulateBrokerFills(
|
const expectedBalances = simulateBrokerFills(
|
||||||
[erc721AssetData[0], erc721AssetData[1]],
|
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[1]],
|
||||||
[order],
|
[order],
|
||||||
[new BigNumber(2)],
|
[new BigNumber(2)],
|
||||||
receipt,
|
receipt,
|
||||||
@ -225,11 +233,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with one invalid asset`, async () => {
|
it(`${fnName} with one invalid asset`, async () => {
|
||||||
const tx = broker
|
const tx = broker
|
||||||
.brokerTrade(
|
.brokerTrade(
|
||||||
[erc721AssetData[2]],
|
[godsUnchainedTokenIds[2]],
|
||||||
order,
|
order,
|
||||||
new BigNumber(1),
|
new BigNumber(1),
|
||||||
order.signature,
|
order.signature,
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -241,11 +251,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with one valid asset, one invalid asset`, async () => {
|
it(`${fnName} with one valid asset, one invalid asset`, async () => {
|
||||||
const tx = broker
|
const tx = broker
|
||||||
.brokerTrade(
|
.brokerTrade(
|
||||||
[erc721AssetData[0], erc721AssetData[2]], // valid, invalid
|
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[2]], // valid, invalid
|
||||||
order,
|
order,
|
||||||
new BigNumber(2),
|
new BigNumber(2),
|
||||||
order.signature,
|
order.signature,
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -257,11 +269,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with too few assets`, async () => {
|
it(`${fnName} with too few assets`, async () => {
|
||||||
const tx = broker
|
const tx = broker
|
||||||
.brokerTrade(
|
.brokerTrade(
|
||||||
[erc721AssetData[0]], // One asset provided
|
[godsUnchainedTokenIds[0]], // One asset provided
|
||||||
order,
|
order,
|
||||||
new BigNumber(2), // But two are required for the fill
|
new BigNumber(2), // But two are required for the fill
|
||||||
order.signature,
|
order.signature,
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -273,11 +287,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with same asset twice`, async () => {
|
it(`${fnName} with same asset twice`, async () => {
|
||||||
const tx = broker
|
const tx = broker
|
||||||
.brokerTrade(
|
.brokerTrade(
|
||||||
[erc721AssetData[0], erc721AssetData[0]],
|
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[0]],
|
||||||
order,
|
order,
|
||||||
new BigNumber(2),
|
new BigNumber(2),
|
||||||
order.signature,
|
order.signature,
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -289,11 +305,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with excess assets`, async () => {
|
it(`${fnName} with excess assets`, async () => {
|
||||||
const receipt = await broker
|
const receipt = await broker
|
||||||
.brokerTrade(
|
.brokerTrade(
|
||||||
erc721AssetData,
|
godsUnchainedTokenIds,
|
||||||
order,
|
order,
|
||||||
new BigNumber(2),
|
new BigNumber(2),
|
||||||
order.signature,
|
order.signature,
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -302,7 +320,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expectedBalances = simulateBrokerFills(
|
const expectedBalances = simulateBrokerFills(
|
||||||
[erc721AssetData[0], erc721AssetData[1]], // 3rd card isn't transferred
|
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[1]], // 3rd card isn't transferred
|
||||||
[order],
|
[order],
|
||||||
[new BigNumber(2)],
|
[new BigNumber(2)],
|
||||||
receipt,
|
receipt,
|
||||||
@ -314,11 +332,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`Reverts if insufficient ETH is provided`, async () => {
|
it(`Reverts if insufficient ETH is provided`, async () => {
|
||||||
const tx = broker
|
const tx = broker
|
||||||
.brokerTrade(
|
.brokerTrade(
|
||||||
[erc721AssetData[0]],
|
[godsUnchainedTokenIds[0]],
|
||||||
order,
|
order,
|
||||||
new BigNumber(1),
|
new BigNumber(1),
|
||||||
order.signature,
|
order.signature,
|
||||||
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
|
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -330,18 +350,25 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`Refunds sender if excess ETH is provided`, async () => {
|
it(`Refunds sender if excess ETH is provided`, async () => {
|
||||||
const receipt = await broker
|
const receipt = await broker
|
||||||
.brokerTrade(
|
.brokerTrade(
|
||||||
[erc721AssetData[0]],
|
[godsUnchainedTokenIds[0]],
|
||||||
order,
|
order,
|
||||||
new BigNumber(1),
|
new BigNumber(1),
|
||||||
order.signature,
|
order.signature,
|
||||||
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
|
deployment.exchange.getSelector(ExchangeFunctionName.FillOrder),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
value: DeploymentManager.protocolFee.plus(1), // 1 wei gets refunded
|
value: DeploymentManager.protocolFee.plus(1), // 1 wei gets refunded
|
||||||
gasPrice: DeploymentManager.gasPrice,
|
gasPrice: DeploymentManager.gasPrice,
|
||||||
});
|
});
|
||||||
const expectedBalances = simulateBrokerFills([erc721AssetData[0]], [order], [new BigNumber(1)], receipt);
|
const expectedBalances = simulateBrokerFills(
|
||||||
|
[godsUnchainedTokenIds[0]],
|
||||||
|
[order],
|
||||||
|
[new BigNumber(1)],
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
await balanceStore.updateBalancesAsync();
|
await balanceStore.updateBalancesAsync();
|
||||||
balanceStore.assertEquals(expectedBalances);
|
balanceStore.assertEquals(expectedBalances);
|
||||||
});
|
});
|
||||||
@ -376,6 +403,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
await maker.signOrderAsync({
|
await maker.signOrderAsync({
|
||||||
takerAssetData: godsUnchainedUtils.encodeBrokerAssetData(
|
takerAssetData: godsUnchainedUtils.encodeBrokerAssetData(
|
||||||
broker.address,
|
broker.address,
|
||||||
|
godsUnchained.address,
|
||||||
validator.address,
|
validator.address,
|
||||||
firstOrderProto,
|
firstOrderProto,
|
||||||
firstOrderQuality,
|
firstOrderQuality,
|
||||||
@ -384,6 +412,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
await maker.signOrderAsync({
|
await maker.signOrderAsync({
|
||||||
takerAssetData: godsUnchainedUtils.encodeBrokerAssetData(
|
takerAssetData: godsUnchainedUtils.encodeBrokerAssetData(
|
||||||
broker.address,
|
broker.address,
|
||||||
|
godsUnchained.address,
|
||||||
validator.address,
|
validator.address,
|
||||||
secondOrderProto,
|
secondOrderProto,
|
||||||
secondOrderQuality,
|
secondOrderQuality,
|
||||||
@ -396,11 +425,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with one order, one valid asset`, async () => {
|
it(`${fnName} with one order, one valid asset`, async () => {
|
||||||
const receipt = await broker
|
const receipt = await broker
|
||||||
.batchBrokerTrade(
|
.batchBrokerTrade(
|
||||||
[erc721AssetData[0]],
|
[godsUnchainedTokenIds[0]],
|
||||||
[orders[0]],
|
[orders[0]],
|
||||||
[new BigNumber(1)],
|
[new BigNumber(1)],
|
||||||
[orders[0].signature],
|
[orders[0].signature],
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -409,7 +440,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expectedBalances = simulateBrokerFills(
|
const expectedBalances = simulateBrokerFills(
|
||||||
[erc721AssetData[0]],
|
[godsUnchainedTokenIds[0]],
|
||||||
[orders[0]],
|
[orders[0]],
|
||||||
[new BigNumber(1)],
|
[new BigNumber(1)],
|
||||||
receipt,
|
receipt,
|
||||||
@ -420,11 +451,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with two orders, one valid asset each`, async () => {
|
it(`${fnName} with two orders, one valid asset each`, async () => {
|
||||||
const receipt = await broker
|
const receipt = await broker
|
||||||
.batchBrokerTrade(
|
.batchBrokerTrade(
|
||||||
[erc721AssetData[0], erc721AssetData[2]], // valid for 1st order, valid for 2nd
|
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[2]], // valid for 1st order, valid for 2nd
|
||||||
orders,
|
orders,
|
||||||
[new BigNumber(1), new BigNumber(1)],
|
[new BigNumber(1), new BigNumber(1)],
|
||||||
[orders[0].signature, orders[1].signature],
|
[orders[0].signature, orders[1].signature],
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -433,7 +466,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expectedBalances = simulateBrokerFills(
|
const expectedBalances = simulateBrokerFills(
|
||||||
[erc721AssetData[0], erc721AssetData[2]],
|
[godsUnchainedTokenIds[0], godsUnchainedTokenIds[2]],
|
||||||
orders,
|
orders,
|
||||||
[new BigNumber(1), new BigNumber(1)],
|
[new BigNumber(1), new BigNumber(1)],
|
||||||
receipt,
|
receipt,
|
||||||
@ -444,11 +477,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with two orders, two valid assets each`, async () => {
|
it(`${fnName} with two orders, two valid assets each`, async () => {
|
||||||
const receipt = await broker
|
const receipt = await broker
|
||||||
.batchBrokerTrade(
|
.batchBrokerTrade(
|
||||||
erc721AssetData.slice(0, 4),
|
godsUnchainedTokenIds.slice(0, 4),
|
||||||
orders,
|
orders,
|
||||||
[new BigNumber(2), new BigNumber(2)],
|
[new BigNumber(2), new BigNumber(2)],
|
||||||
[orders[0].signature, orders[1].signature],
|
[orders[0].signature, orders[1].signature],
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -457,7 +492,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expectedBalances = simulateBrokerFills(
|
const expectedBalances = simulateBrokerFills(
|
||||||
erc721AssetData.slice(0, 4),
|
godsUnchainedTokenIds.slice(0, 4),
|
||||||
orders,
|
orders,
|
||||||
[new BigNumber(2), new BigNumber(2)],
|
[new BigNumber(2), new BigNumber(2)],
|
||||||
receipt,
|
receipt,
|
||||||
@ -468,11 +503,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`${fnName} with two orders, two valid assets each + excess asset`, async () => {
|
it(`${fnName} with two orders, two valid assets each + excess asset`, async () => {
|
||||||
const receipt = await broker
|
const receipt = await broker
|
||||||
.batchBrokerTrade(
|
.batchBrokerTrade(
|
||||||
erc721AssetData,
|
godsUnchainedTokenIds,
|
||||||
orders,
|
orders,
|
||||||
[new BigNumber(2), new BigNumber(2)],
|
[new BigNumber(2), new BigNumber(2)],
|
||||||
[orders[0].signature, orders[1].signature],
|
[orders[0].signature, orders[1].signature],
|
||||||
deployment.exchange.getSelector(fnName),
|
deployment.exchange.getSelector(fnName),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -481,7 +518,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expectedBalances = simulateBrokerFills(
|
const expectedBalances = simulateBrokerFills(
|
||||||
erc721AssetData.slice(0, 4), // 5th card isn't transferred
|
godsUnchainedTokenIds.slice(0, 4), // 5th card isn't transferred
|
||||||
orders,
|
orders,
|
||||||
[new BigNumber(2), new BigNumber(2)],
|
[new BigNumber(2), new BigNumber(2)],
|
||||||
receipt,
|
receipt,
|
||||||
@ -493,11 +530,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`batchFillOrders reverts on invalid asset`, async () => {
|
it(`batchFillOrders reverts on invalid asset`, async () => {
|
||||||
const tx = broker
|
const tx = broker
|
||||||
.batchBrokerTrade(
|
.batchBrokerTrade(
|
||||||
[...erc721AssetData.slice(0, 3), erc721AssetData[4]], // Last card isn't valid for 2nd order
|
[...godsUnchainedTokenIds.slice(0, 3), godsUnchainedTokenIds[4]], // Last card isn't valid for 2nd order
|
||||||
orders,
|
orders,
|
||||||
[new BigNumber(2), new BigNumber(2)],
|
[new BigNumber(2), new BigNumber(2)],
|
||||||
[orders[0].signature, orders[1].signature],
|
[orders[0].signature, orders[1].signature],
|
||||||
deployment.exchange.getSelector(ExchangeFunctionName.BatchFillOrders),
|
deployment.exchange.getSelector(ExchangeFunctionName.BatchFillOrders),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -509,11 +548,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`batchFillOrKillOrders reverts on invalid asset`, async () => {
|
it(`batchFillOrKillOrders reverts on invalid asset`, async () => {
|
||||||
const tx = broker
|
const tx = broker
|
||||||
.batchBrokerTrade(
|
.batchBrokerTrade(
|
||||||
[...erc721AssetData.slice(0, 3), erc721AssetData[4]], // Last card isn't valid for 2nd order
|
[...godsUnchainedTokenIds.slice(0, 3), godsUnchainedTokenIds[4]], // Last card isn't valid for 2nd order
|
||||||
orders,
|
orders,
|
||||||
[new BigNumber(2), new BigNumber(2)],
|
[new BigNumber(2), new BigNumber(2)],
|
||||||
[orders[0].signature, orders[1].signature],
|
[orders[0].signature, orders[1].signature],
|
||||||
deployment.exchange.getSelector(ExchangeFunctionName.BatchFillOrKillOrders),
|
deployment.exchange.getSelector(ExchangeFunctionName.BatchFillOrKillOrders),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -525,11 +566,13 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
it(`batchFillOrdersNoThrow catches revert on invalid asset`, async () => {
|
it(`batchFillOrdersNoThrow catches revert on invalid asset`, async () => {
|
||||||
const receipt = await broker
|
const receipt = await broker
|
||||||
.batchBrokerTrade(
|
.batchBrokerTrade(
|
||||||
[...erc721AssetData.slice(0, 3), erc721AssetData[4]], // Last card isn't valid for 2nd order
|
[...godsUnchainedTokenIds.slice(0, 3), godsUnchainedTokenIds[4]], // Last card isn't valid for 2nd order
|
||||||
orders,
|
orders,
|
||||||
[new BigNumber(2), new BigNumber(2)],
|
[new BigNumber(2), new BigNumber(2)],
|
||||||
[orders[0].signature, orders[1].signature],
|
[orders[0].signature, orders[1].signature],
|
||||||
deployment.exchange.getSelector(ExchangeFunctionName.BatchFillOrdersNoThrow),
|
deployment.exchange.getSelector(ExchangeFunctionName.BatchFillOrdersNoThrow),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({
|
.awaitTransactionSuccessAsync({
|
||||||
from: taker.address,
|
from: taker.address,
|
||||||
@ -537,7 +580,7 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
|||||||
gasPrice: DeploymentManager.gasPrice,
|
gasPrice: DeploymentManager.gasPrice,
|
||||||
});
|
});
|
||||||
const expectedBalances = simulateBrokerFills(
|
const expectedBalances = simulateBrokerFills(
|
||||||
erc721AssetData.slice(0, 2), // First order gets filled
|
godsUnchainedTokenIds.slice(0, 2), // First order gets filled
|
||||||
[orders[0]],
|
[orders[0]],
|
||||||
[new BigNumber(2)],
|
[new BigNumber(2)],
|
||||||
receipt,
|
receipt,
|
||||||
|
@ -31,11 +31,18 @@ export class InvalidFunctionSelectorError extends RevertError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class OnlyERC1155ProxyError extends RevertError {
|
||||||
|
constructor(sender?: string) {
|
||||||
|
super('OnlyERC1155ProxyError', 'OnlyERC1155ProxyError(address sender)', { sender });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const types = [
|
const types = [
|
||||||
InvalidFromAddressError,
|
InvalidFromAddressError,
|
||||||
AmountsLengthMustEqualOneError,
|
AmountsLengthMustEqualOneError,
|
||||||
TooFewBrokerAssetsProvidedError,
|
TooFewBrokerAssetsProvidedError,
|
||||||
InvalidFunctionSelectorError,
|
InvalidFunctionSelectorError,
|
||||||
|
OnlyERC1155ProxyError,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Register the types we've defined.
|
// Register the types we've defined.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user