Address PR feedback and add support for affiliate fees

This commit is contained in:
Michael Zhu 2020-01-31 16:10:44 -08:00
parent 329719472a
commit d2313b30af
8 changed files with 278 additions and 125 deletions

View File

@ -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) {
bytes memory assetToTransfer = _cachedAssetData[_cacheIndex];
_cacheIndex++;
// Decode validator and params from `data` // Decode validator and params from `data`
(address validator, bytes memory propertyData) = abi.decode( (address tokenAddress, address validator, bytes memory propertyData) = abi.decode(
data, data,
(address, bytes) (address, address, bytes)
); );
// Execute staticcall while (remainingAmount != 0) {
(bool success, bytes memory returnData) = validator.staticcall(abi.encodeWithSelector( uint256 tokenId = _cachedTokenIds[cacheIndex];
IPropertyValidator(0).checkBrokerAsset.selector, cacheIndex++;
assetToTransfer,
propertyData
));
// Revert with returned data if staticcall is unsuccessful // Validate asset properties
if (!success) { IPropertyValidator(validator).checkBrokerAsset(
assembly { tokenId,
revert(add(returnData, 32), mload(returnData)) propertyData
} );
}
// 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
if (!order.makerAssetData.equals(WETH_ASSET_DATA)) {
order.makerAssetData.transferOut(fillResults.makerAssetFilledAmount); 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++) {
if (!orders[i].makerAssetData.equals(WETH_ASSET_DATA)) {
orders[i].makerAssetData.transferOut(fillResults[i].makerAssetFilledAmount); 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);
}
} }

View File

@ -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

View File

@ -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

View File

@ -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
);
}
} }

View File

@ -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");
} }
} }

View File

@ -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)) {

View File

@ -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,

View File

@ -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.