Move LibAssetDataTransfer and MixinWeth(Utils) to contracts-extensions

This commit is contained in:
Michael Zhu 2020-01-31 14:57:32 -08:00
parent a2fcab47d4
commit 329719472a
65 changed files with 565 additions and 4826 deletions

View File

@ -52,6 +52,8 @@
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md", "homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
"devDependencies": { "devDependencies": {
"@0x/abi-gen": "^5.1.0", "@0x/abi-gen": "^5.1.0",
"@0x/contracts-asset-proxy": "^3.1.1",
"@0x/contracts-erc20": "^3.0.4",
"@0x/contracts-erc721": "^3.0.4", "@0x/contracts-erc721": "^3.0.4",
"@0x/contracts-exchange": "^3.1.0", "@0x/contracts-exchange": "^3.1.0",
"@0x/contracts-exchange-libs": "^4.1.0", "@0x/contracts-exchange-libs": "^4.1.0",

View File

@ -13,20 +13,22 @@ export const godsUnchainedUtils = {
}, },
/** /**
* Encodes the given proto and quality into ERC1155 asset data to be used as the takerAssetData * Encodes the given proto and quality into ERC1155 asset data to be used as the takerAssetData
* of a property-based GodsUnchained order. Must also provide the address of the Broker and * of a property-based GodsUnchained order. Must also provide the addresses of the Broker,
* the GodsUnchainedValidator contract. The optional bundleSize parameter specifies how many * GodsUnchained, and GodsUnchainedValidator contracts. The optional bundleSize parameter specifies
* cards are expected for each "unit" of the takerAssetAmount. For example, If the * how many cards are expected for each "unit" of the takerAssetAmount. For example, If the
* takerAssetAmount is 3 and the bundleSize is 2, the taker must provide 2, 4, or 6 cards * takerAssetAmount is 3 and the bundleSize is 2, the taker must provide 2, 4, or 6 cards
* with the given proto and quality to fill the order. If an odd number is provided, the fill fails. * with the given proto and quality to fill the order. If an odd number is provided, the fill fails.
*/ */
encodeBrokerAssetData( encodeBrokerAssetData(
brokerAddress: string, brokerAddress: string,
godsUnchainedAddress: string,
validatorAddress: string, validatorAddress: string,
proto: BigNumber, proto: BigNumber,
quality: BigNumber, quality: BigNumber,
bundleSize: number = 1, bundleSize: number = 1,
): string { ): string {
const dataEncoder = AbiEncoder.create([ const dataEncoder = AbiEncoder.create([
{ name: 'godsUnchainedAddress', type: 'address' },
{ name: 'validatorAddress', type: 'address' }, { name: 'validatorAddress', type: 'address' },
{ name: 'propertyData', type: 'bytes' }, { name: 'propertyData', type: 'bytes' },
]); ]);
@ -34,7 +36,7 @@ export const godsUnchainedUtils = {
{ name: 'proto', type: 'uint16' }, { name: 'proto', type: 'uint16' },
{ name: 'quality', type: 'uint8' }, { name: 'quality', type: 'uint8' },
]).encode({ proto, quality }); ]).encode({ proto, quality });
const data = dataEncoder.encode({ validatorAddress, propertyData }); const data = dataEncoder.encode({ godsUnchainedAddress, validatorAddress, propertyData });
return assetDataUtils.encodeERC1155AssetData(brokerAddress, [], [new BigNumber(bundleSize)], data); return assetDataUtils.encodeERC1155AssetData(brokerAddress, [], [new BigNumber(bundleSize)], data);
}, },
}; };

View File

@ -1,5 +1,4 @@
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils'; import { blockchainTests, constants, expect, getRandomInteger } from '@0x/contracts-test-utils';
import { assetDataUtils } from '@0x/order-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -39,28 +38,18 @@ blockchainTests.resets('GodsUnchainedValidator unit tests', env => {
it('succeeds if assetData proto and quality match propertyData', async () => { it('succeeds if assetData proto and quality match propertyData', async () => {
const tokenId = getRandomInteger(0, constants.MAX_UINT256); const tokenId = getRandomInteger(0, constants.MAX_UINT256);
await godsUnchained.setTokenProperties(tokenId, proto, quality).awaitTransactionSuccessAsync(); await godsUnchained.setTokenProperties(tokenId, proto, quality).awaitTransactionSuccessAsync();
const assetData = assetDataUtils.encodeERC721AssetData(godsUnchained.address, tokenId); await validator.checkBrokerAsset(tokenId, propertyData).callAsync();
await validator.checkBrokerAsset(assetData, propertyData).callAsync();
});
it('reverts if assetData tokenAddress is not the GU contract address', async () => {
const tokenId = getRandomInteger(0, constants.MAX_UINT256);
await godsUnchained.setTokenProperties(tokenId, proto, quality).awaitTransactionSuccessAsync();
const assetData = assetDataUtils.encodeERC721AssetData(randomAddress(), tokenId);
const tx = validator.checkBrokerAsset(assetData, propertyData).callAsync();
expect(tx).to.revertWith('TOKEN_ADDRESS_MISMATCH');
}); });
it("reverts if assetData proto doesn't match propertyData", async () => { it("reverts if assetData proto doesn't match propertyData", async () => {
const tokenId = getRandomInteger(0, constants.MAX_UINT256); const tokenId = getRandomInteger(0, constants.MAX_UINT256);
await godsUnchained.setTokenProperties(tokenId, proto.plus(1), quality).awaitTransactionSuccessAsync(); await godsUnchained.setTokenProperties(tokenId, proto.plus(1), quality).awaitTransactionSuccessAsync();
const assetData = assetDataUtils.encodeERC721AssetData(godsUnchained.address, tokenId); const tx = validator.checkBrokerAsset(tokenId, propertyData).callAsync();
const tx = validator.checkBrokerAsset(assetData, propertyData).callAsync();
expect(tx).to.revertWith('PROTO_MISMATCH'); expect(tx).to.revertWith('PROTO_MISMATCH');
}); });
it("reverts if assetData quality doesn't match proeprtyData", async () => { it("reverts if assetData quality doesn't match proeprtyData", async () => {
const tokenId = getRandomInteger(0, constants.MAX_UINT256); const tokenId = getRandomInteger(0, constants.MAX_UINT256);
await godsUnchained.setTokenProperties(tokenId, proto, quality.plus(1)).awaitTransactionSuccessAsync(); await godsUnchained.setTokenProperties(tokenId, proto, quality.plus(1)).awaitTransactionSuccessAsync();
const assetData = assetDataUtils.encodeERC721AssetData(godsUnchained.address, tokenId); const tx = validator.checkBrokerAsset(tokenId, propertyData).callAsync();
const tx = validator.checkBrokerAsset(assetData, propertyData).callAsync();
expect(tx).to.revertWith('QUALITY_MISMATCH'); expect(tx).to.revertWith('QUALITY_MISMATCH');
}); });
}); });

View File

@ -41,13 +41,13 @@ yarn install
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
```bash ```bash
PKG=@0x/contracts-extensions yarn build PKG=@0x/contracts-dev-utils yarn build
``` ```
Or continuously rebuild on change: Or continuously rebuild on change:
```bash ```bash
PKG=@0x/contracts-extensions yarn watch PKG=@0x/contracts-dev-utils yarn watch
``` ```
### Clean ### Clean

View File

@ -3,7 +3,7 @@
"version": "4.1.0", "version": "4.1.0",
"changes": [ "changes": [
{ {
"note": "Moved LibAssetDataTransfer to exchange-libs", "note": "Refactor, moved LibAssetDataTransfer and MixinWeth(Utils) to extensions",
"pr": 2455 "pr": 2455
} }
] ]

View File

@ -19,19 +19,32 @@
pragma solidity ^0.5.9; pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "./MixinForwarderCore.sol"; import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
import "./libs/LibConstants.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibMath.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/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "./libs/LibForwarderRichErrors.sol";
import "./MixinExchangeWrapper.sol";
import "./MixinReceiver.sol"; import "./MixinReceiver.sol";
import "./interfaces/IForwarder.sol";
// solhint-disable no-empty-blocks
// MixinAssets, MixinExchangeWrapper, and MixinWeth are all inherited via
// MixinForwarderCore.
contract Forwarder is contract Forwarder is
LibConstants, IForwarder,
MixinForwarderCore, Ownable,
MixinWethUtils,
MixinExchangeWrapper,
MixinReceiver MixinReceiver
{ {
using LibBytes for bytes;
using LibAssetDataTransfer for bytes;
using LibSafeMath for uint256;
constructor ( constructor (
address _exchange, address _exchange,
address _exchangeV2, address _exchangeV2,
@ -39,11 +52,158 @@ contract Forwarder is
) )
public public
Ownable() Ownable()
LibConstants( MixinWethUtils(
_exchange, _exchange,
_exchangeV2,
_weth _weth
) )
MixinForwarderCore() MixinExchangeWrapper(
{} _exchange,
_exchangeV2
)
{} // solhint-disable-line no-empty-blocks
/// @dev Withdraws assets from this contract. It may be used by the owner to withdraw assets
/// that were accidentally sent to this contract.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of the asset to withdraw.
function withdrawAsset(
bytes calldata assetData,
uint256 amount
)
external
onlyOwner
{
assetData.transferOut(amount);
}
/// @dev Approves the respective proxy for a given asset to transfer tokens on the Forwarder contract's behalf.
/// This is necessary because an order fee denominated in the maker asset (i.e. a percentage fee) is sent by the
/// Forwarder contract to the fee recipient.
/// This method needs to be called before forwarding orders of a maker asset that hasn't
/// previously been approved.
/// @param assetData Byte array encoded for the respective asset proxy.
function approveMakerAssetProxy(bytes calldata assetData)
external
{
bytes4 proxyId = assetData.readBytes4(0);
bytes4 erc20ProxyId = IAssetData(address(0)).ERC20Token.selector;
// For now we only care about ERC20, since percentage fees on ERC721 tokens are invalid.
if (proxyId == erc20ProxyId) {
address proxyAddress = EXCHANGE.getAssetProxy(erc20ProxyId);
if (proxyAddress == address(0)) {
LibRichErrors.rrevert(LibForwarderRichErrors.UnregisteredAssetProxyError());
}
address token = assetData.readAddress(16);
LibERC20Token.approve(token, proxyAddress, MAX_UINT256);
}
}
/// @dev Purchases as much of orders' makerAssets as possible by selling as much of the ETH value sent
/// as possible, accounting for order and forwarder fees.
/// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset.
/// @param signatures Proofs that orders have been created by makers.
/// @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 wethSpentAmount Amount of WETH spent on the given set of orders.
/// @return makerAssetAcquiredAmount Amount of maker asset acquired from the given set of orders.
function marketSellOrdersWithEth(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
uint256[] memory ethFeeAmounts,
address payable[] memory feeRecipients
)
public
payable
returns (
uint256 wethSpentAmount,
uint256 makerAssetAcquiredAmount
)
{
// Pay ETH affiliate fees to all feeRecipient addresses
uint256 wethRemaining = _transferEthFeesAndWrapRemaining(
ethFeeAmounts,
feeRecipients
);
// Spends up to wethRemaining to fill orders, transfers purchased assets to msg.sender,
// and pays WETH order fees.
(
wethSpentAmount,
makerAssetAcquiredAmount
) = _marketSellNoThrow(
orders,
wethRemaining,
signatures
);
// Ensure that no extra WETH owned by this contract has been spent.
if (wethSpentAmount > wethRemaining) {
LibRichErrors.rrevert(LibForwarderRichErrors.OverspentWethError(
wethSpentAmount,
msg.value
));
}
// Calculate amount of WETH that hasn't been spent.
wethRemaining = wethRemaining.safeSub(wethSpentAmount);
// Refund remaining ETH to msg.sender.
_transferEthRefund(wethRemaining);
}
/// @dev Attempt to buy makerAssetBuyAmount of makerAsset by selling ETH provided with transaction.
/// The Forwarder may *fill* more than makerAssetBuyAmount of the makerAsset so that it can
/// pay takerFees where takerFeeAssetData == makerAssetData (i.e. percentage fees).
/// Any ETH not spent will be refunded to sender.
/// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset.
/// @param makerAssetBuyAmount Desired amount of makerAsset to purchase.
/// @param signatures Proofs that orders have been created by makers.
/// @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 wethSpentAmount Amount of WETH spent on the given set of orders.
/// @return makerAssetAcquiredAmount Amount of maker asset acquired from the given set of orders.
function marketBuyOrdersWithEth(
LibOrder.Order[] memory orders,
uint256 makerAssetBuyAmount,
bytes[] memory signatures,
uint256[] memory ethFeeAmounts,
address payable[] memory feeRecipients
)
public
payable
returns (
uint256 wethSpentAmount,
uint256 makerAssetAcquiredAmount
)
{
// Pay ETH affiliate fees to all feeRecipient addresses
uint256 wethRemaining = _transferEthFeesAndWrapRemaining(
ethFeeAmounts,
feeRecipients
);
// Attempts to fill the desired amount of makerAsset and trasnfer purchased assets to msg.sender.
(
wethSpentAmount,
makerAssetAcquiredAmount
) = _marketBuyFillOrKill(
orders,
makerAssetBuyAmount,
signatures
);
// Ensure that no extra WETH owned by this contract has been spent.
if (wethSpentAmount > wethRemaining) {
LibRichErrors.rrevert(LibForwarderRichErrors.OverspentWethError(
wethSpentAmount,
msg.value
));
}
// Calculate amount of WETH that hasn't been spent.
wethRemaining = wethRemaining.safeSub(wethSpentAmount);
// Refund remaining ETH to msg.sender.
_transferEthRefund(wethRemaining);
}
} }

View File

@ -1,76 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "@0x/contracts-exchange-libs/contracts/src/LibAssetDataTransfer.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
import "./libs/LibConstants.sol";
import "./libs/LibForwarderRichErrors.sol";
import "./interfaces/IAssets.sol";
contract MixinAssets is
Ownable,
LibConstants,
IAssets
{
using LibBytes for bytes;
using LibAssetDataTransfer for bytes;
/// @dev Withdraws assets from this contract. It may be used by the owner to withdraw assets
/// that were accidentally sent to this contract.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of the asset to withdraw.
function withdrawAsset(
bytes calldata assetData,
uint256 amount
)
external
onlyOwner
{
assetData.transferOut(amount);
}
/// @dev Approves the respective proxy for a given asset to transfer tokens on the Forwarder contract's behalf.
/// This is necessary because an order fee denominated in the maker asset (i.e. a percentage fee) is sent by the
/// Forwarder contract to the fee recipient.
/// This method needs to be called before forwarding orders of a maker asset that hasn't
/// previously been approved.
/// @param assetData Byte array encoded for the respective asset proxy.
function approveMakerAssetProxy(bytes calldata assetData)
external
{
bytes4 proxyId = assetData.readBytes4(0);
bytes4 erc20ProxyId = IAssetData(address(0)).ERC20Token.selector;
// For now we only care about ERC20, since percentage fees on ERC721 tokens are invalid.
if (proxyId == erc20ProxyId) {
address proxyAddress = EXCHANGE.getAssetProxy(erc20ProxyId);
if (proxyAddress == address(0)) {
LibRichErrors.rrevert(LibForwarderRichErrors.UnregisteredAssetProxyError());
}
address token = assetData.readAddress(16);
LibERC20Token.approve(token, proxyAddress, MAX_UINT);
}
}
}

View File

@ -19,28 +19,60 @@
pragma solidity ^0.5.9; pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.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/LibMath.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "@0x/contracts-extensions/contracts/src/LibAssetDataTransfer.sol";
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "./libs/LibConstants.sol"; import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "./libs/LibForwarderRichErrors.sol"; import "./libs/LibForwarderRichErrors.sol";
import "./interfaces/IExchangeV2.sol"; import "./interfaces/IExchangeV2.sol";
import "./MixinAssets.sol";
contract MixinExchangeWrapper is contract MixinExchangeWrapper {
LibConstants,
MixinAssets // The v2 order id is the first 4 bytes of the ExchangeV2 order schema hash.
{ // bytes4(keccak256(abi.encodePacked(
// "Order(",
// "address makerAddress,",
// "address takerAddress,",
// "address feeRecipientAddress,",
// "address senderAddress,",
// "uint256 makerAssetAmount,",
// "uint256 takerAssetAmount,",
// "uint256 makerFee,",
// "uint256 takerFee,",
// "uint256 expirationTimeSeconds,",
// "uint256 salt,",
// "bytes makerAssetData,",
// "bytes takerAssetData",
// ")"
// )));
bytes4 constant public EXCHANGE_V2_ORDER_ID = 0x770501f8;
// solhint-disable var-name-mixedcase
IExchange internal EXCHANGE;
IExchangeV2 internal EXCHANGE_V2;
// solhint-enable var-name-mixedcase
using LibBytes for bytes; using LibBytes for bytes;
using LibAssetDataTransfer for bytes;
using LibSafeMath for uint256; using LibSafeMath for uint256;
constructor (
address _exchange,
address _exchangeV2
)
public
{
EXCHANGE = IExchange(_exchange);
EXCHANGE_V2 = IExchangeV2(_exchangeV2);
}
/// @dev Fills the input order. /// @dev Fills the input order.
/// Returns false if the transaction would otherwise revert. /// Returns false if the transaction would otherwise revert.
/// @param order Order struct containing order specifications. /// @param order Order struct containing order specifications.

View File

@ -1,147 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
import "./libs/LibConstants.sol";
import "./libs/LibForwarderRichErrors.sol";
import "./interfaces/IAssets.sol";
import "./interfaces/IForwarderCore.sol";
import "./MixinExchangeWrapper.sol";
import "./MixinWeth.sol";
contract MixinForwarderCore is
LibConstants,
IAssets,
IForwarderCore,
MixinWeth,
MixinExchangeWrapper
{
using LibBytes for bytes;
using LibSafeMath for uint256;
/// @dev Constructor approves ERC20 proxy to transfer WETH on this contract's behalf.
constructor ()
public
{
address proxyAddress = EXCHANGE.getAssetProxy(IAssetData(address(0)).ERC20Token.selector);
if (proxyAddress == address(0)) {
LibRichErrors.rrevert(LibForwarderRichErrors.UnregisteredAssetProxyError());
}
ETHER_TOKEN.approve(proxyAddress, MAX_UINT);
address protocolFeeCollector = EXCHANGE.protocolFeeCollector();
if (protocolFeeCollector != address(0)) {
ETHER_TOKEN.approve(protocolFeeCollector, MAX_UINT);
}
}
/// @dev Purchases as much of orders' makerAssets as possible by selling as much of the ETH value sent
/// as possible, accounting for order and forwarder fees.
/// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset.
/// @param signatures Proofs that orders have been created by makers.
/// @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 wethSpentAmount Amount of WETH spent on the given set of orders.
/// @return makerAssetAcquiredAmount Amount of maker asset acquired from the given set of orders.
function marketSellOrdersWithEth(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
uint256[] memory ethFeeAmounts,
address payable[] memory feeRecipients
)
public
payable
returns (
uint256 wethSpentAmount,
uint256 makerAssetAcquiredAmount
)
{
// Pay ETH affiliate fees to all feeRecipient addresses
uint256 wethRemaining = _transferEthFeesAndWrapRemaining(
ethFeeAmounts,
feeRecipients
);
// Spends up to wethRemaining to fill orders, transfers purchased assets to msg.sender,
// and pays WETH order fees.
(
wethSpentAmount,
makerAssetAcquiredAmount
) = _marketSellNoThrow(
orders,
wethRemaining,
signatures
);
// Refund remaining ETH to msg.sender.
_transferEthRefund(wethRemaining, wethSpentAmount);
}
/// @dev Attempt to buy makerAssetBuyAmount of makerAsset by selling ETH provided with transaction.
/// The Forwarder may *fill* more than makerAssetBuyAmount of the makerAsset so that it can
/// pay takerFees where takerFeeAssetData == makerAssetData (i.e. percentage fees).
/// Any ETH not spent will be refunded to sender.
/// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset.
/// @param makerAssetBuyAmount Desired amount of makerAsset to purchase.
/// @param signatures Proofs that orders have been created by makers.
/// @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 wethSpentAmount Amount of WETH spent on the given set of orders.
/// @return makerAssetAcquiredAmount Amount of maker asset acquired from the given set of orders.
function marketBuyOrdersWithEth(
LibOrder.Order[] memory orders,
uint256 makerAssetBuyAmount,
bytes[] memory signatures,
uint256[] memory ethFeeAmounts,
address payable[] memory feeRecipients
)
public
payable
returns (
uint256 wethSpentAmount,
uint256 makerAssetAcquiredAmount
)
{
// Pay ETH affiliate fees to all feeRecipient addresses
uint256 wethRemaining = _transferEthFeesAndWrapRemaining(
ethFeeAmounts,
feeRecipients
);
// Attempts to fill the desired amount of makerAsset and trasnfer purchased assets to msg.sender.
(
wethSpentAmount,
makerAssetAcquiredAmount
) = _marketBuyFillOrKill(
orders,
makerAssetBuyAmount,
signatures
);
// Refund remaining ETH to msg.sender.
_transferEthRefund(wethRemaining, wethSpentAmount);
}
}

View File

@ -1,45 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
contract IAssets {
/// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to
/// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
/// used to withdraw assets that were accidentally sent to this contract.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of ERC20 token to withdraw.
function withdrawAsset(
bytes calldata assetData,
uint256 amount
)
external;
/// @dev Approves the respective proxy for a given asset to transfer tokens on the Forwarder contract's behalf.
/// This is necessary because an order fee denominated in the maker asset (i.e. a percentage fee) is sent by the
/// Forwarder contract to the fee recipient.
/// This method needs to be called before forwarding orders of a maker asset that hasn't
/// previously been approved.
/// @param assetData Byte array encoded for the respective asset proxy.
function approveMakerAssetProxy(
bytes calldata assetData
)
external;
}

View File

@ -19,12 +19,77 @@
pragma solidity ^0.5.9; pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "./IForwarderCore.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "./IAssets.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
// solhint-disable no-empty-blocks contract IForwarder {
contract IForwarder is
IForwarderCore, /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to
IAssets /// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
{} /// used to withdraw assets that were accidentally sent to this contract.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of ERC20 token to withdraw.
function withdrawAsset(
bytes calldata assetData,
uint256 amount
)
external;
/// @dev Approves the respective proxy for a given asset to transfer tokens on the Forwarder contract's behalf.
/// This is necessary because an order fee denominated in the maker asset (i.e. a percentage fee) is sent by the
/// Forwarder contract to the fee recipient.
/// This method needs to be called before forwarding orders of a maker asset that hasn't
/// previously been approved.
/// @param assetData Byte array encoded for the respective asset proxy.
function approveMakerAssetProxy(
bytes calldata assetData
)
external;
/// @dev Purchases as much of orders' makerAssets as possible by selling as much of the ETH value sent
/// as possible, accounting for order and forwarder fees.
/// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset.
/// @param signatures Proofs that orders have been created by makers.
/// @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 wethSpentAmount Amount of WETH spent on the given set of orders.
/// @return makerAssetAcquiredAmount Amount of maker asset acquired from the given set of orders.
function marketSellOrdersWithEth(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
uint256[] memory ethFeeAmounts,
address payable[] memory feeRecipients
)
public
payable
returns (
uint256 wethSpentAmount,
uint256 makerAssetAcquiredAmount
);
/// @dev Attempt to buy makerAssetBuyAmount of makerAsset by selling ETH provided with transaction.
/// The Forwarder may *fill* more than makerAssetBuyAmount of the makerAsset so that it can
/// pay takerFees where takerFeeAssetData == makerAssetData (i.e. percentage fees).
/// Any ETH not spent will be refunded to sender.
/// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset.
/// @param makerAssetBuyAmount Desired amount of makerAsset to purchase.
/// @param signatures Proofs that orders have been created by makers.
/// @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 wethSpentAmount Amount of WETH spent on the given set of orders.
/// @return makerAssetAcquiredAmount Amount of maker asset acquired from the given set of orders.
function marketBuyOrdersWithEth(
LibOrder.Order[] memory orders,
uint256 makerAssetBuyAmount,
bytes[] memory signatures,
uint256[] memory ethFeeAmounts,
address payable[] memory feeRecipients
)
public
payable
returns (
uint256 wethSpentAmount,
uint256 makerAssetAcquiredAmount
);
}

View File

@ -1,73 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
contract IForwarderCore {
/// @dev Purchases as much of orders' makerAssets as possible by selling as much of the ETH value sent
/// as possible, accounting for order and forwarder fees.
/// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset.
/// @param signatures Proofs that orders have been created by makers.
/// @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 wethSpentAmount Amount of WETH spent on the given set of orders.
/// @return makerAssetAcquiredAmount Amount of maker asset acquired from the given set of orders.
function marketSellOrdersWithEth(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
uint256[] memory ethFeeAmounts,
address payable[] memory feeRecipients
)
public
payable
returns (
uint256 wethSpentAmount,
uint256 makerAssetAcquiredAmount
);
/// @dev Attempt to buy makerAssetBuyAmount of makerAsset by selling ETH provided with transaction.
/// The Forwarder may *fill* more than makerAssetBuyAmount of the makerAsset so that it can
/// pay takerFees where takerFeeAssetData == makerAssetData (i.e. percentage fees).
/// Any ETH not spent will be refunded to sender.
/// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset.
/// @param makerAssetBuyAmount Desired amount of makerAsset to purchase.
/// @param signatures Proofs that orders have been created by makers.
/// @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 wethSpentAmount Amount of WETH spent on the given set of orders.
/// @return makerAssetAcquiredAmount Amount of maker asset acquired from the given set of orders.
function marketBuyOrdersWithEth(
LibOrder.Order[] memory orders,
uint256 makerAssetBuyAmount,
bytes[] memory signatures,
uint256[] memory ethFeeAmounts,
address payable[] memory feeRecipients
)
public
payable
returns (
uint256 wethSpentAmount,
uint256 makerAssetAcquiredAmount
);
}

View File

@ -1,66 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "../interfaces/IExchangeV2.sol";
contract LibConstants {
uint256 constant internal MAX_UINT = uint256(-1);
// The v2 order id is the first 4 bytes of the ExchangeV2 order schema hash.
// bytes4(keccak256(abi.encodePacked(
// "Order(",
// "address makerAddress,",
// "address takerAddress,",
// "address feeRecipientAddress,",
// "address senderAddress,",
// "uint256 makerAssetAmount,",
// "uint256 takerAssetAmount,",
// "uint256 makerFee,",
// "uint256 takerFee,",
// "uint256 expirationTimeSeconds,",
// "uint256 salt,",
// "bytes makerAssetData,",
// "bytes takerAssetData",
// ")"
// )));
bytes4 constant public EXCHANGE_V2_ORDER_ID = 0x770501f8;
// solhint-disable var-name-mixedcase
IExchange internal EXCHANGE;
IExchangeV2 internal EXCHANGE_V2;
IEtherToken internal ETHER_TOKEN;
// solhint-enable var-name-mixedcase
constructor (
address _exchange,
address _exchangeV2,
address _weth
)
public
{
EXCHANGE = IExchange(_exchange);
EXCHANGE_V2 = IExchangeV2(_exchangeV2);
ETHER_TOKEN = IEtherToken(_weth);
}
}

View File

@ -33,22 +33,10 @@ library LibForwarderRichErrors {
bytes4 internal constant UNSUPPORTED_FEE_ERROR_SELECTOR = bytes4 internal constant UNSUPPORTED_FEE_ERROR_SELECTOR =
0x31360af1; 0x31360af1;
// bytes4(keccak256("InsufficientEthForFeeError(uint256,uint256)"))
bytes4 internal constant INSUFFICIENT_ETH_FOR_FEE_ERROR_SELECTOR =
0xecf40fd9;
// bytes4(keccak256("OverspentWethError(uint256,uint256)")) // bytes4(keccak256("OverspentWethError(uint256,uint256)"))
bytes4 internal constant OVERSPENT_WETH_ERROR_SELECTOR = bytes4 internal constant OVERSPENT_WETH_ERROR_SELECTOR =
0xcdcbed5d; 0xcdcbed5d;
// bytes4(keccak256("DefaultFunctionWethContractOnlyError(address)"))
bytes4 internal constant DEFAULT_FUNCTION_WETH_CONTRACT_ONLY_ERROR_SELECTOR =
0x08b18698;
// bytes4(keccak256("EthFeeLengthMismatchError(uint256,uint256)"))
bytes4 internal constant ETH_FEE_LENGTH_MISMATCH_ERROR_SELECTOR =
0x3ecb6ceb;
// solhint-disable func-name-mixedcase // solhint-disable func-name-mixedcase
function UnregisteredAssetProxyError() function UnregisteredAssetProxyError()
internal internal
@ -86,21 +74,6 @@ library LibForwarderRichErrors {
); );
} }
function InsufficientEthForFeeError(
uint256 ethFeeRequired,
uint256 ethAvailable
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
INSUFFICIENT_ETH_FOR_FEE_ERROR_SELECTOR,
ethFeeRequired,
ethAvailable
);
}
function OverspentWethError( function OverspentWethError(
uint256 wethSpent, uint256 wethSpent,
uint256 msgValue uint256 msgValue
@ -115,32 +88,4 @@ library LibForwarderRichErrors {
msgValue msgValue
); );
} }
function DefaultFunctionWethContractOnlyError(
address senderAddress
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
DEFAULT_FUNCTION_WETH_CONTRACT_ONLY_ERROR_SELECTOR,
senderAddress
);
}
function EthFeeLengthMismatchError(
uint256 ethFeesLength,
uint256 feeRecipientsLength
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
ETH_FEE_LENGTH_MISMATCH_ERROR_SELECTOR,
ethFeesLength,
feeRecipientsLength
);
}
} }

View File

@ -19,14 +19,12 @@
pragma solidity ^0.5.9; pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibAssetDataTransfer.sol"; import "@0x/contracts-extensions/contracts/src/LibAssetDataTransfer.sol";
import "../src/MixinExchangeWrapper.sol"; import "../src/MixinExchangeWrapper.sol";
import "../src/libs/LibConstants.sol";
import "../src/MixinReceiver.sol"; import "../src/MixinReceiver.sol";
contract TestForwarder is contract TestForwarder is
LibConstants,
MixinExchangeWrapper, MixinExchangeWrapper,
MixinReceiver MixinReceiver
{ {
@ -35,8 +33,7 @@ contract TestForwarder is
// solhint-disable no-empty-blocks // solhint-disable no-empty-blocks
constructor () constructor ()
public public
LibConstants( MixinExchangeWrapper(
address(0),
address(0), address(0),
address(0) address(0)
) )

View File

@ -39,7 +39,7 @@
}, },
"config": { "config": {
"publicInterfaceContracts": "Forwarder,IExchangeV2", "publicInterfaceContracts": "Forwarder,IExchangeV2",
"abis": "./test/generated-artifacts/@(Forwarder|IAssets|IExchangeV2|IForwarder|IForwarderCore|LibConstants|LibForwarderRichErrors|MixinAssets|MixinExchangeWrapper|MixinForwarderCore|MixinReceiver|MixinWeth|TestForwarder).json", "abis": "./test/generated-artifacts/@(Forwarder|IExchangeV2|IForwarder|LibForwarderRichErrors|MixinExchangeWrapper|MixinReceiver|TestForwarder).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
}, },
"repository": { "repository": {

View File

@ -6,30 +6,18 @@
import { ContractArtifact } from 'ethereum-types'; import { ContractArtifact } from 'ethereum-types';
import * as Forwarder from '../test/generated-artifacts/Forwarder.json'; import * as Forwarder from '../test/generated-artifacts/Forwarder.json';
import * as IAssets from '../test/generated-artifacts/IAssets.json';
import * as IExchangeV2 from '../test/generated-artifacts/IExchangeV2.json'; import * as IExchangeV2 from '../test/generated-artifacts/IExchangeV2.json';
import * as IForwarder from '../test/generated-artifacts/IForwarder.json'; import * as IForwarder from '../test/generated-artifacts/IForwarder.json';
import * as IForwarderCore from '../test/generated-artifacts/IForwarderCore.json';
import * as LibConstants from '../test/generated-artifacts/LibConstants.json';
import * as LibForwarderRichErrors from '../test/generated-artifacts/LibForwarderRichErrors.json'; import * as LibForwarderRichErrors from '../test/generated-artifacts/LibForwarderRichErrors.json';
import * as MixinAssets from '../test/generated-artifacts/MixinAssets.json';
import * as MixinExchangeWrapper from '../test/generated-artifacts/MixinExchangeWrapper.json'; import * as MixinExchangeWrapper from '../test/generated-artifacts/MixinExchangeWrapper.json';
import * as MixinForwarderCore from '../test/generated-artifacts/MixinForwarderCore.json';
import * as MixinReceiver from '../test/generated-artifacts/MixinReceiver.json'; import * as MixinReceiver from '../test/generated-artifacts/MixinReceiver.json';
import * as MixinWeth from '../test/generated-artifacts/MixinWeth.json';
import * as TestForwarder from '../test/generated-artifacts/TestForwarder.json'; import * as TestForwarder from '../test/generated-artifacts/TestForwarder.json';
export const artifacts = { export const artifacts = {
Forwarder: Forwarder as ContractArtifact, Forwarder: Forwarder as ContractArtifact,
MixinAssets: MixinAssets as ContractArtifact,
MixinExchangeWrapper: MixinExchangeWrapper as ContractArtifact, MixinExchangeWrapper: MixinExchangeWrapper as ContractArtifact,
MixinForwarderCore: MixinForwarderCore as ContractArtifact,
MixinReceiver: MixinReceiver as ContractArtifact, MixinReceiver: MixinReceiver as ContractArtifact,
MixinWeth: MixinWeth as ContractArtifact,
IAssets: IAssets as ContractArtifact,
IExchangeV2: IExchangeV2 as ContractArtifact, IExchangeV2: IExchangeV2 as ContractArtifact,
IForwarder: IForwarder as ContractArtifact, IForwarder: IForwarder as ContractArtifact,
IForwarderCore: IForwarderCore as ContractArtifact,
LibConstants: LibConstants as ContractArtifact,
LibForwarderRichErrors: LibForwarderRichErrors as ContractArtifact, LibForwarderRichErrors: LibForwarderRichErrors as ContractArtifact,
TestForwarder: TestForwarder as ContractArtifact, TestForwarder: TestForwarder as ContractArtifact,
}; };

View File

@ -4,15 +4,9 @@
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
export * from '../test/generated-wrappers/forwarder'; export * from '../test/generated-wrappers/forwarder';
export * from '../test/generated-wrappers/i_assets';
export * from '../test/generated-wrappers/i_exchange_v2'; export * from '../test/generated-wrappers/i_exchange_v2';
export * from '../test/generated-wrappers/i_forwarder'; export * from '../test/generated-wrappers/i_forwarder';
export * from '../test/generated-wrappers/i_forwarder_core';
export * from '../test/generated-wrappers/lib_constants';
export * from '../test/generated-wrappers/lib_forwarder_rich_errors'; export * from '../test/generated-wrappers/lib_forwarder_rich_errors';
export * from '../test/generated-wrappers/mixin_assets';
export * from '../test/generated-wrappers/mixin_exchange_wrapper'; export * from '../test/generated-wrappers/mixin_exchange_wrapper';
export * from '../test/generated-wrappers/mixin_forwarder_core';
export * from '../test/generated-wrappers/mixin_receiver'; export * from '../test/generated-wrappers/mixin_receiver';
export * from '../test/generated-wrappers/mixin_weth';
export * from '../test/generated-wrappers/test_forwarder'; export * from '../test/generated-wrappers/test_forwarder';

View File

@ -6,17 +6,11 @@
"generated-artifacts/Forwarder.json", "generated-artifacts/Forwarder.json",
"generated-artifacts/IExchangeV2.json", "generated-artifacts/IExchangeV2.json",
"test/generated-artifacts/Forwarder.json", "test/generated-artifacts/Forwarder.json",
"test/generated-artifacts/IAssets.json",
"test/generated-artifacts/IExchangeV2.json", "test/generated-artifacts/IExchangeV2.json",
"test/generated-artifacts/IForwarder.json", "test/generated-artifacts/IForwarder.json",
"test/generated-artifacts/IForwarderCore.json",
"test/generated-artifacts/LibConstants.json",
"test/generated-artifacts/LibForwarderRichErrors.json", "test/generated-artifacts/LibForwarderRichErrors.json",
"test/generated-artifacts/MixinAssets.json",
"test/generated-artifacts/MixinExchangeWrapper.json", "test/generated-artifacts/MixinExchangeWrapper.json",
"test/generated-artifacts/MixinForwarderCore.json",
"test/generated-artifacts/MixinReceiver.json", "test/generated-artifacts/MixinReceiver.json",
"test/generated-artifacts/MixinWeth.json",
"test/generated-artifacts/TestForwarder.json" "test/generated-artifacts/TestForwarder.json"
], ],
"exclude": ["./deploy/solc/solc_bin"] "exclude": ["./deploy/solc/solc_bin"]

View File

@ -39,7 +39,7 @@
}, },
"config": { "config": {
"publicInterfaceContracts": "IWallet,LibEIP712ExchangeDomain,LibExchangeRichErrors,LibMath,LibMathRichErrors,LibOrder,LibZeroExTransaction", "publicInterfaceContracts": "IWallet,LibEIP712ExchangeDomain,LibExchangeRichErrors,LibMath,LibMathRichErrors,LibOrder,LibZeroExTransaction",
"abis": "./test/generated-artifacts/@(IWallet|LibAssetDataTransfer|LibAssetDataTransferRichErrors|LibEIP712ExchangeDomain|LibExchangeRichErrors|LibFillResults|LibMath|LibMathRichErrors|LibOrder|LibZeroExTransaction|TestLibEIP712ExchangeDomain|TestLibFillResults|TestLibMath|TestLibOrder|TestLibZeroExTransaction).json", "abis": "./test/generated-artifacts/@(IWallet|LibEIP712ExchangeDomain|LibExchangeRichErrors|LibFillResults|LibMath|LibMathRichErrors|LibOrder|LibZeroExTransaction|TestLibEIP712ExchangeDomain|TestLibFillResults|TestLibMath|TestLibOrder|TestLibZeroExTransaction).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
}, },
"repository": { "repository": {

View File

@ -8,7 +8,7 @@ export {
LibOrderContract, LibOrderContract,
LibZeroExTransactionContract, LibZeroExTransactionContract,
} from './wrappers'; } from './wrappers';
export { LibAssetDataTransferRevertErrors, LibMathRevertErrors } from '@0x/utils'; export { LibMathRevertErrors } from '@0x/utils';
import * as ReferenceFunctionsToExport from './reference_functions'; import * as ReferenceFunctionsToExport from './reference_functions';
export import ReferenceFunctions = ReferenceFunctionsToExport; export import ReferenceFunctions = ReferenceFunctionsToExport;

View File

@ -6,8 +6,6 @@
import { ContractArtifact } from 'ethereum-types'; import { ContractArtifact } from 'ethereum-types';
import * as IWallet from '../test/generated-artifacts/IWallet.json'; import * as IWallet from '../test/generated-artifacts/IWallet.json';
import * as LibAssetDataTransfer from '../test/generated-artifacts/LibAssetDataTransfer.json';
import * as LibAssetDataTransferRichErrors from '../test/generated-artifacts/LibAssetDataTransferRichErrors.json';
import * as LibEIP712ExchangeDomain from '../test/generated-artifacts/LibEIP712ExchangeDomain.json'; import * as LibEIP712ExchangeDomain from '../test/generated-artifacts/LibEIP712ExchangeDomain.json';
import * as LibExchangeRichErrors from '../test/generated-artifacts/LibExchangeRichErrors.json'; import * as LibExchangeRichErrors from '../test/generated-artifacts/LibExchangeRichErrors.json';
import * as LibFillResults from '../test/generated-artifacts/LibFillResults.json'; import * as LibFillResults from '../test/generated-artifacts/LibFillResults.json';
@ -22,8 +20,6 @@ import * as TestLibOrder from '../test/generated-artifacts/TestLibOrder.json';
import * as TestLibZeroExTransaction from '../test/generated-artifacts/TestLibZeroExTransaction.json'; import * as TestLibZeroExTransaction from '../test/generated-artifacts/TestLibZeroExTransaction.json';
export const artifacts = { export const artifacts = {
IWallet: IWallet as ContractArtifact, IWallet: IWallet as ContractArtifact,
LibAssetDataTransfer: LibAssetDataTransfer as ContractArtifact,
LibAssetDataTransferRichErrors: LibAssetDataTransferRichErrors as ContractArtifact,
LibEIP712ExchangeDomain: LibEIP712ExchangeDomain as ContractArtifact, LibEIP712ExchangeDomain: LibEIP712ExchangeDomain as ContractArtifact,
LibExchangeRichErrors: LibExchangeRichErrors as ContractArtifact, LibExchangeRichErrors: LibExchangeRichErrors as ContractArtifact,
LibFillResults: LibFillResults as ContractArtifact, LibFillResults: LibFillResults as ContractArtifact,

View File

@ -4,8 +4,6 @@
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
export * from '../test/generated-wrappers/i_wallet'; export * from '../test/generated-wrappers/i_wallet';
export * from '../test/generated-wrappers/lib_asset_data_transfer';
export * from '../test/generated-wrappers/lib_asset_data_transfer_rich_errors';
export * from '../test/generated-wrappers/lib_e_i_p712_exchange_domain'; export * from '../test/generated-wrappers/lib_e_i_p712_exchange_domain';
export * from '../test/generated-wrappers/lib_exchange_rich_errors'; export * from '../test/generated-wrappers/lib_exchange_rich_errors';
export * from '../test/generated-wrappers/lib_fill_results'; export * from '../test/generated-wrappers/lib_fill_results';

View File

@ -11,8 +11,6 @@
"generated-artifacts/LibOrder.json", "generated-artifacts/LibOrder.json",
"generated-artifacts/LibZeroExTransaction.json", "generated-artifacts/LibZeroExTransaction.json",
"test/generated-artifacts/IWallet.json", "test/generated-artifacts/IWallet.json",
"test/generated-artifacts/LibAssetDataTransfer.json",
"test/generated-artifacts/LibAssetDataTransferRichErrors.json",
"test/generated-artifacts/LibEIP712ExchangeDomain.json", "test/generated-artifacts/LibEIP712ExchangeDomain.json",
"test/generated-artifacts/LibExchangeRichErrors.json", "test/generated-artifacts/LibExchangeRichErrors.json",
"test/generated-artifacts/LibFillResults.json", "test/generated-artifacts/LibFillResults.json",

View File

@ -1,4 +1,13 @@
[ [
{
"version": "6.0.0",
"changes": [
{
"note": "New year, new me: remove everything, add MixinWethUtils and LibAssetDataTransfer",
"pr": 2455
}
]
},
{ {
"timestamp": 1580811564, "timestamp": 1580811564,
"version": "5.1.4", "version": "5.1.4",

View File

@ -1,6 +1,6 @@
## Extensions ## Extensions
This package implements various extensions to the 0x protocol. Extension contracts can add various rules around how orders are settled while still getting the interoperability and security benefits of using the underlying 0x protocol contracts. Addresses of the deployed contracts can be found in this 0x [guide](https://0x.org/docs/guides/0x-cheat-sheet) or the [DEPLOYS](./DEPLOYS.json) file within this package. This package includes utility smart contracts that may be useful for developing on top of the core 0x Exchange. For example, `MixinWethUtils` includes functions to pay affiliate fees in ETH, wrap ETH to be used for 0x trades and protocol fees, and unwrap WETH to refund the sender.
## Installation ## Installation
@ -12,7 +12,7 @@ npm install @0x/contracts-extensions --save
## Bug bounty ## Bug bounty
A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0x.org/docs/guides/bug-bounty-program). A bug bounty for the 3.0 contracts is ongoing! Instructions can be found [here](https://0x.org/docs/guides/bug-bounty-program).
## Contributing ## Contributing

View File

@ -1,46 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "./interfaces/IThresholdAsset.sol";
import "./MixinBalanceThresholdFilterCore.sol";
contract BalanceThresholdFilter is
MixinBalanceThresholdFilterCore
{
/// @dev Constructs BalanceThresholdFilter.
/// @param exchange Address of 0x exchange.
/// @param thresholdAsset The asset that must be held by makers/takers.
/// @param balanceThreshold The minimum balance of `thresholdAsset` that must be held by makers/takers.
constructor (
address exchange,
address thresholdAsset,
uint256 balanceThreshold
)
public
{
EXCHANGE = IExchange(exchange);
THRESHOLD_ASSET = IThresholdAsset(thresholdAsset);
BALANCE_THRESHOLD = balanceThreshold;
}
}

View File

@ -1,136 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "./MixinExchangeCalldata.sol";
import "./interfaces/IBalanceThresholdFilterCore.sol";
contract MixinBalanceThresholdFilterCore is
IBalanceThresholdFilterCore,
MixinExchangeCalldata
{
/// @dev Executes an Exchange transaction iff the maker and taker meet
/// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR
/// the exchange function is a cancellation.
/// Supported Exchange functions:
/// batchFillOrders
/// batchFillOrdersNoThrow
/// batchFillOrKillOrders
/// fillOrder
/// fillOrderNoThrow
/// fillOrKillOrder
/// marketBuyOrders
/// marketBuyOrdersNoThrow
/// marketSellOrders
/// marketSellOrdersNoThrow
/// matchOrders
/// cancelOrder
/// batchCancelOrders
/// cancelOrdersUpTo
/// Trying to call any other exchange function will throw.
/// @param salt Arbitrary number to ensure uniqueness of transaction hash.
/// @param signerAddress Address of transaction signer.
/// @param signedExchangeTransaction AbiV2 encoded calldata.
/// @param signature Proof of signer transaction by signer.
function executeTransaction(
uint256 salt,
address signerAddress,
bytes calldata signedExchangeTransaction,
bytes calldata signature
)
external
{
// Get accounts whose balances must be validated
address[] memory addressesToValidate = _getAddressesToValidate(signerAddress);
// Validate account balances
uint256 balanceThreshold = BALANCE_THRESHOLD;
IThresholdAsset thresholdAsset = THRESHOLD_ASSET;
for (uint256 i = 0; i < addressesToValidate.length; ++i) {
uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]);
require(
addressBalance >= balanceThreshold,
"AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"
);
}
emit ValidatedAddresses(addressesToValidate);
LibZeroExTransaction.ZeroExTransaction memory transaction = LibZeroExTransaction.ZeroExTransaction({
salt: salt,
data: signedExchangeTransaction,
signerAddress: signerAddress
});
// All addresses are valid. Execute exchange function.
EXCHANGE.executeTransaction(transaction, signature);
}
/// @dev Constructs an array of addresses to be validated.
/// Addresses depend on which Exchange function is to be called
/// (defined by `signedExchangeTransaction` above).
/// @param signerAddress Address of transaction signer.
/// @return addressesToValidate Array of addresses to validate.
function _getAddressesToValidate(address signerAddress)
internal
pure
returns (address[] memory addressesToValidate)
{
bytes4 exchangeFunctionSelector = bytes4(_exchangeCalldataload(0));
// solhint-disable expression-indent
if (
exchangeFunctionSelector == IExchange(address(0)).batchFillOrders.selector ||
exchangeFunctionSelector == IExchange(address(0)).batchFillOrdersNoThrow.selector ||
exchangeFunctionSelector == IExchange(address(0)).batchFillOrKillOrders.selector ||
exchangeFunctionSelector == IExchange(address(0)).marketBuyOrders.selector ||
exchangeFunctionSelector == IExchange(address(0)).marketBuyOrdersNoThrow.selector ||
exchangeFunctionSelector == IExchange(address(0)).marketSellOrders.selector ||
exchangeFunctionSelector == IExchange(address(0)).marketSellOrdersNoThrow.selector
) {
addressesToValidate = _loadMakerAddressesFromOrderArray(0);
addressesToValidate = addressesToValidate.append(signerAddress);
} else if (
exchangeFunctionSelector == IExchange(address(0)).fillOrder.selector ||
exchangeFunctionSelector == IExchange(address(0)).fillOrderNoThrow.selector ||
exchangeFunctionSelector == IExchange(address(0)).fillOrKillOrder.selector
) {
address makerAddress = _loadMakerAddressFromOrder(0);
addressesToValidate = addressesToValidate.append(makerAddress);
addressesToValidate = addressesToValidate.append(signerAddress);
} else if (exchangeFunctionSelector == IExchange(address(0)).matchOrders.selector) {
address leftMakerAddress = _loadMakerAddressFromOrder(0);
addressesToValidate = addressesToValidate.append(leftMakerAddress);
address rightMakerAddress = _loadMakerAddressFromOrder(1);
addressesToValidate = addressesToValidate.append(rightMakerAddress);
addressesToValidate = addressesToValidate.append(signerAddress);
} else if (
exchangeFunctionSelector != IExchange(address(0)).cancelOrder.selector &&
exchangeFunctionSelector != IExchange(address(0)).batchCancelOrders.selector &&
exchangeFunctionSelector != IExchange(address(0)).cancelOrdersUpTo.selector
) {
revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR");
}
// solhint-enable expression-indent
return addressesToValidate;
}
}

View File

@ -1,104 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/LibAddressArray.sol";
contract MixinExchangeCalldata {
using LibAddressArray for address[];
/// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata,
/// which is accessed through `signedExchangeTransaction`.
/// @param offset Offset into the Exchange calldata.
/// @return value Corresponding 32 byte value stored at `offset`.
function _exchangeCalldataload(uint256 offset)
internal
pure
returns (bytes32 value)
{
assembly {
// Pointer to exchange transaction
// 0x04 for calldata selector
// 0x40 to access `signedExchangeTransaction`, which is the third parameter
let exchangeTxPtr := calldataload(0x44)
// Offset into Exchange calldata
// We compute this by adding 0x24 to the `exchangeTxPtr` computed above.
// 0x04 for calldata selector
// 0x20 for length field of `signedExchangeTransaction`
let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset))
value := calldataload(exchangeCalldataOffset)
}
return value;
}
/// @dev Convenience function that skips the 4 byte selector when loading
/// from the embedded Exchange calldata.
/// @param offset Offset into the Exchange calldata (minus the 4 byte selector)
/// @return value Corresponding 32 byte value stored at `offset` + 4.
function _loadExchangeData(uint256 offset)
internal
pure
returns (bytes32 value)
{
value = _exchangeCalldataload(offset + 4);
return value;
}
/// @dev Extracts the maker address from an order stored in the Exchange calldata
/// (which is embedded in `signedExchangeTransaction`).
/// @param orderParamIndex Index of the order in the Exchange function's signature.
/// @return makerAddress The extracted maker address.
function _loadMakerAddressFromOrder(uint256 orderParamIndex)
internal
pure
returns (address makerAddress)
{
uint256 orderOffsetInBytes = orderParamIndex * 32;
uint256 orderPtr = uint256(_loadExchangeData(orderOffsetInBytes));
makerAddress = address(uint256(_loadExchangeData(orderPtr)));
return makerAddress;
}
/// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
/// (which is embedded in `signedExchangeTransaction`).
/// @param orderArrayParamIndex Index of the order array in the Exchange function's signature
/// @return makerAddresses The extracted maker addresses.
function _loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex)
internal
pure
returns (address[] memory makerAddresses)
{
uint256 orderArrayOffsetInBytes = orderArrayParamIndex * 32;
uint256 orderArrayPtr = uint256(_loadExchangeData(orderArrayOffsetInBytes));
uint256 orderArrayLength = uint256(_loadExchangeData(orderArrayPtr));
uint256 orderArrayLengthInBytes = orderArrayLength * 32;
uint256 orderArrayElementPtr = orderArrayPtr + 32;
uint256 orderArrayElementEndPtr = orderArrayElementPtr + orderArrayLengthInBytes;
for (uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 32) {
uint256 orderPtr = uint256(_loadExchangeData(orderPtrOffset));
address makerAddress = address(uint256(_loadExchangeData(orderPtr + orderArrayElementPtr)));
makerAddresses = makerAddresses.append(makerAddress);
}
return makerAddresses;
}
}

View File

@ -1,55 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
contract IBalanceThresholdFilterCore {
/// @dev Executes an Exchange transaction iff the maker and taker meet
/// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR
/// the exchange function is a cancellation.
/// Supported Exchange functions:
/// - batchFillOrders
/// - batchFillOrdersNoThrow
/// - batchFillOrKillOrders
/// - fillOrder
/// - fillOrderNoThrow
/// - fillOrKillOrder
/// - marketBuyOrders
/// - marketBuyOrdersNoThrow
/// - marketSellOrders
/// - marketSellOrdersNoThrow
/// - matchOrders
/// - cancelOrder
/// - batchCancelOrders
/// - cancelOrdersUpTo
/// Trying to call any other exchange function will throw.
/// @param salt Arbitrary number to ensure uniqueness of transaction hash.
/// @param signerAddress Address of transaction signer.
/// @param signedExchangeTransaction AbiV2 encoded calldata.
/// @param signature Proof of signer transaction by signer.
function executeTransaction(
uint256 salt,
address signerAddress,
bytes calldata signedExchangeTransaction,
bytes calldata signature
)
external;
}

View File

@ -1,31 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
contract IThresholdAsset {
/// @param _owner The address from which the balance will be retrieved
/// @return Balance of owner
function balanceOf(address _owner)
external
view
returns (uint256);
}

View File

@ -1,199 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
contract DutchAuction {
using LibBytes for bytes;
using LibSafeMath for uint256;
// solhint-disable var-name-mixedcase
IExchange internal EXCHANGE;
struct AuctionDetails {
uint256 beginTimeSeconds; // Auction begin unix timestamp: sellOrder.makerAssetData
uint256 endTimeSeconds; // Auction end unix timestamp: sellOrder.expiryTimeSeconds
uint256 beginAmount; // Auction begin amount: sellOrder.makerAssetData
uint256 endAmount; // Auction end amount: sellOrder.takerAssetAmount
uint256 currentAmount; // Calculated amount given block.timestamp
uint256 currentTimeSeconds; // block.timestamp
}
constructor (address _exchange)
public
{
EXCHANGE = IExchange(_exchange);
}
/// @dev Matches the buy and sell orders at an amount given the following: the current block time, the auction
/// start time and the auction begin amount. The sell order is a an order at the lowest amount
/// at the end of the auction. Excess from the match is transferred to the seller.
/// Over time the price moves from beginAmount to endAmount given the current block.timestamp.
/// sellOrder.expiryTimeSeconds is the end time of the auction.
/// sellOrder.takerAssetAmount is the end amount of the auction (lowest possible amount).
/// sellOrder.makerAssetData is the ABI encoded Asset Proxy data with the following data appended
/// buyOrder.makerAssetData is the buyers bid on the auction, must meet the amount for the current block timestamp
/// (uint256 beginTimeSeconds, uint256 beginAmount).
/// This function reverts in the following scenarios:
/// * Auction has not started (auctionDetails.currentTimeSeconds < auctionDetails.beginTimeSeconds)
/// * Auction has expired (auctionDetails.endTimeSeconds < auctionDetails.currentTimeSeconds)
/// * Amount is invalid: Buy order amount is too low (buyOrder.makerAssetAmount < auctionDetails.currentAmount)
/// * Amount is invalid: Invalid begin amount (auctionDetails.beginAmount > auctionDetails.endAmount)
/// * Any failure in the 0x Match Orders
/// @param buyOrder The Buyer's order. This order is for the current expected price of the auction.
/// @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction).
/// @param buySignature Proof that order was created by the buyer.
/// @param sellSignature Proof that order was created by the seller.
/// @return matchedFillResults amounts filled and fees paid by maker and taker of matched orders.
function matchOrders(
LibOrder.Order memory buyOrder,
LibOrder.Order memory sellOrder,
bytes memory buySignature,
bytes memory sellSignature
)
public
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
AuctionDetails memory auctionDetails = getAuctionDetails(sellOrder);
// Ensure the auction has not yet started
require(
auctionDetails.currentTimeSeconds >= auctionDetails.beginTimeSeconds,
"AUCTION_NOT_STARTED"
);
// Ensure the auction has not expired. This will fail later in 0x but we can save gas by failing early
require(
sellOrder.expirationTimeSeconds > auctionDetails.currentTimeSeconds,
"AUCTION_EXPIRED"
);
// Validate the buyer amount is greater than the current auction amount
require(
buyOrder.makerAssetAmount >= auctionDetails.currentAmount,
"INVALID_AMOUNT"
);
// Match orders, maximally filling `buyOrder`
matchedFillResults = EXCHANGE.matchOrders(
buyOrder,
sellOrder,
buySignature,
sellSignature
);
// The difference in sellOrder.takerAssetAmount and current amount is given as spread to the matcher
// This may include additional spread from the buyOrder.makerAssetAmount and the currentAmount.
// e.g currentAmount is 30, sellOrder.takerAssetAmount is 10 and buyOrder.makerAssetamount is 40.
// 10 (40-30) is returned to the buyer, 20 (30-10) sent to the seller and 10 has previously
// been transferred to the seller during matchOrders
uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
if (leftMakerAssetSpreadAmount > 0) {
// ERC20 Asset data itself is encoded as follows:
//
// | Area | Offset | Length | Contents |
// |----------|--------|---------|-------------------------------------|
// | Header | 0 | 4 | function selector |
// | Params | | 1 * 32 | function parameters: |
// | | 4 | 12 | 1. token address padding |
// | | 16 | 20 | 2. token address |
bytes memory assetData = sellOrder.takerAssetData;
address token = assetData.readAddress(16);
// Calculate the excess from the buy order. This can occur if the buyer sends in a higher
// amount than the calculated current amount
uint256 buyerExcessAmount = buyOrder.makerAssetAmount.safeSub(auctionDetails.currentAmount);
uint256 sellerExcessAmount = leftMakerAssetSpreadAmount.safeSub(buyerExcessAmount);
// Return the difference between auctionDetails.currentAmount and sellOrder.takerAssetAmount
// to the seller
if (sellerExcessAmount > 0) {
IERC20Token(token).transfer(sellOrder.makerAddress, sellerExcessAmount);
}
// Return the difference between buyOrder.makerAssetAmount and auctionDetails.currentAmount
// to the buyer
if (buyerExcessAmount > 0) {
IERC20Token(token).transfer(buyOrder.makerAddress, buyerExcessAmount);
}
}
return matchedFillResults;
}
/// @dev Calculates the Auction Details for the given order
/// @param order The sell order
/// @return AuctionDetails
function getAuctionDetails(LibOrder.Order memory order)
public
returns (AuctionDetails memory auctionDetails)
{
uint256 makerAssetDataLength = order.makerAssetData.length;
// It is unknown the encoded data of makerAssetData, we assume the last 64 bytes
// are the Auction Details encoding.
// Auction Details is encoded as follows:
//
// | Area | Offset | Length | Contents |
// |----------|--------|---------|-------------------------------------|
// | Params | | 2 * 32 | parameters: |
// | | -64 | 32 | 1. auction begin unix timestamp |
// | | -32 | 32 | 2. auction begin begin amount |
// ERC20 asset data length is 4+32, 64 for auction details results in min length 100
require(
makerAssetDataLength >= 100,
"INVALID_ASSET_DATA"
);
uint256 auctionBeginTimeSeconds = order.makerAssetData.readUint256(makerAssetDataLength - 64);
uint256 auctionBeginAmount = order.makerAssetData.readUint256(makerAssetDataLength - 32);
// Ensure the auction has a valid begin time
require(
order.expirationTimeSeconds > auctionBeginTimeSeconds,
"INVALID_BEGIN_TIME"
);
uint256 auctionDurationSeconds = order.expirationTimeSeconds-auctionBeginTimeSeconds;
// Ensure the auction goes from high to low
uint256 minAmount = order.takerAssetAmount;
require(
auctionBeginAmount > minAmount,
"INVALID_AMOUNT"
);
uint256 amountDelta = auctionBeginAmount-minAmount;
// solhint-disable-next-line not-rely-on-time
uint256 timestamp = block.timestamp;
auctionDetails.beginTimeSeconds = auctionBeginTimeSeconds;
auctionDetails.endTimeSeconds = order.expirationTimeSeconds;
auctionDetails.beginAmount = auctionBeginAmount;
auctionDetails.endAmount = minAmount;
auctionDetails.currentTimeSeconds = timestamp;
uint256 remainingDurationSeconds = order.expirationTimeSeconds-timestamp;
if (timestamp < auctionBeginTimeSeconds) {
// If the auction has not yet begun the current amount is the auctionBeginAmount
auctionDetails.currentAmount = auctionBeginAmount;
} else if (timestamp >= order.expirationTimeSeconds) {
// If the auction has ended the current amount is the minAmount.
// Auction end time is guaranteed by 0x Exchange due to the order expiration
auctionDetails.currentAmount = minAmount;
} else {
auctionDetails.currentAmount = minAmount.safeAdd(
remainingDurationSeconds.safeMul(amountDelta).safeDiv(auctionDurationSeconds)
);
}
return auctionDetails;
}
}

View File

@ -26,7 +26,7 @@ import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol"; import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol"; import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol";
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol"; import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
import "./LibAssetDataTransferRichErrors.sol"; import "./rich-errors/LibAssetDataTransferRichErrors.sol";
library LibAssetDataTransfer { library LibAssetDataTransfer {

View File

@ -12,30 +12,57 @@
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations \under the License.
*/ */
pragma solidity ^0.5.9; pragma solidity ^0.5.9;
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.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 "./libs/LibConstants.sol"; import "./rich-errors/LibWethUtilsRichErrors.sol";
import "./libs/LibForwarderRichErrors.sol";
contract MixinWeth is contract MixinWethUtils {
LibConstants
{ uint256 constant internal MAX_UINT256 = uint256(-1);
// solhint-disable var-name-mixedcase
IEtherToken internal WETH;
// solhint-enable var-name-mixedcase
using LibSafeMath for uint256; using LibSafeMath for uint256;
constructor (
address exchange,
address weth
)
public
{
WETH = IEtherToken(weth);
address proxyAddress = IExchange(exchange).getAssetProxy(IAssetData(address(0)).ERC20Token.selector);
if (proxyAddress == address(0)) {
LibRichErrors.rrevert(LibWethUtilsRichErrors.UnregisteredAssetProxyError());
}
WETH.approve(proxyAddress, MAX_UINT256);
address protocolFeeCollector = IExchange(exchange).protocolFeeCollector();
if (protocolFeeCollector != address(0)) {
WETH.approve(protocolFeeCollector, MAX_UINT256);
}
}
/// @dev Default payable function, this allows us to withdraw WETH /// @dev Default payable function, this allows us to withdraw WETH
function () function ()
external external
payable payable
{ {
if (msg.sender != address(ETHER_TOKEN)) { if (msg.sender != address(WETH)) {
LibRichErrors.rrevert(LibForwarderRichErrors.DefaultFunctionWethContractOnlyError( LibRichErrors.rrevert(LibWethUtilsRichErrors.DefaultFunctionWethContractOnlyError(
msg.sender msg.sender
)); ));
} }
@ -55,7 +82,7 @@ contract MixinWeth is
uint256 feesLen = ethFeeAmounts.length; uint256 feesLen = ethFeeAmounts.length;
// ethFeeAmounts len must equal feeRecipients len // ethFeeAmounts len must equal feeRecipients len
if (feesLen != feeRecipients.length) { if (feesLen != feeRecipients.length) {
LibRichErrors.rrevert(LibForwarderRichErrors.EthFeeLengthMismatchError( LibRichErrors.rrevert(LibWethUtilsRichErrors.EthFeeLengthMismatchError(
feesLen, feesLen,
feeRecipients.length feeRecipients.length
)); ));
@ -69,7 +96,7 @@ contract MixinWeth is
uint256 ethFeeAmount = ethFeeAmounts[i]; uint256 ethFeeAmount = ethFeeAmounts[i];
// Ensure there is enough ETH to pay the fee // Ensure there is enough ETH to pay the fee
if (ethRemaining < ethFeeAmount) { if (ethRemaining < ethFeeAmount) {
LibRichErrors.rrevert(LibForwarderRichErrors.InsufficientEthForFeeError( LibRichErrors.rrevert(LibWethUtilsRichErrors.InsufficientEthForFeeError(
ethFeeAmount, ethFeeAmount,
ethRemaining ethRemaining
)); ));
@ -80,37 +107,24 @@ contract MixinWeth is
} }
// Convert remaining ETH to WETH. // Convert remaining ETH to WETH.
ETHER_TOKEN.deposit.value(ethRemaining)(); WETH.deposit.value(ethRemaining)();
return ethRemaining; return ethRemaining;
} }
/// @dev Refunds any excess ETH to msg.sender. /// @dev Unwraps and refunds WETH to msg.sender.
/// @param initialWethAmount Amount of WETH available after transferring affiliate fees. /// @param refundAmount Amount of WETH balance to refund.
/// @param wethSpent Amount of WETH spent when filling orders.
function _transferEthRefund( function _transferEthRefund(
uint256 initialWethAmount, uint256 refundAmount
uint256 wethSpent
) )
internal internal
{ {
// Ensure that no extra WETH owned by this contract has been spent. // Do nothing if no WETH to refund
if (wethSpent > initialWethAmount) { if (refundAmount > 0) {
LibRichErrors.rrevert(LibForwarderRichErrors.OverspentWethError( // Convert WETH to ETH
wethSpent, WETH.withdraw(refundAmount);
msg.value // Transfer ETH to sender
)); msg.sender.transfer(refundAmount);
}
// Calculate amount of WETH that hasn't been spent.
uint256 wethRemaining = initialWethAmount.safeSub(wethSpent);
// Do nothing if no WETH remaining
if (wethRemaining > 0) {
// Convert remaining WETH to ETH
ETHER_TOKEN.withdraw(wethRemaining);
// Transfer remaining ETH to sender
msg.sender.transfer(wethRemaining);
} }
} }
} }

View File

@ -1,195 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
import "./libs/LibConstants.sol";
import "./interfaces/IAssets.sol";
contract MixinAssets is
IAssets,
Ownable,
LibConstants
{
using LibBytes for bytes;
/// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to
/// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
/// used to withdraw assets that were accidentally sent to this contract.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to withdraw.
function withdrawAsset(
bytes calldata assetData,
uint256 amount
)
external
onlyOwner
{
_transferAssetToSender(assetData, amount);
}
/// @dev Approves or disapproves an AssetProxy to spend asset.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to approve for respective proxy.
function approveAssetProxy(
bytes calldata assetData,
uint256 amount
)
external
onlyOwner
{
bytes4 proxyId = assetData.readBytes4(0);
if (proxyId == ERC20_DATA_ID) {
_approveERC20Token(assetData, amount);
} else if (proxyId == ERC721_DATA_ID) {
_approveERC721Token(assetData, amount);
} else {
revert("UNSUPPORTED_ASSET_PROXY");
}
}
/// @dev Transfers given amount of asset to sender.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to transfer to sender.
function _transferAssetToSender(
bytes memory assetData,
uint256 amount
)
internal
{
bytes4 proxyId = assetData.readBytes4(0);
if (proxyId == ERC20_DATA_ID) {
_transferERC20Token(assetData, amount);
} else if (proxyId == ERC721_DATA_ID) {
_transferERC721Token(assetData, amount);
} else {
revert("UNSUPPORTED_ASSET_PROXY");
}
}
/// @dev Decodes ERC20 assetData and transfers given amount to sender.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to transfer to sender.
function _transferERC20Token(
bytes memory assetData,
uint256 amount
)
internal
{
// 4 byte id + 12 0 bytes before ABI encoded token address.
address token = assetData.readAddress(16);
// Transfer tokens.
// We do a raw call so we can check the success separate
// from the return data.
(bool success,) = token.call(abi.encodeWithSelector(
ERC20_TRANSFER_SELECTOR,
msg.sender,
amount
));
require(
success,
"TRANSFER_FAILED"
);
// Check return data.
// If there is no return data, we assume the token incorrectly
// does not return a bool. In this case we expect it to revert
// on failure, which was handled above.
// If the token does return data, we require that it is a single
// value that evaluates to true.
assembly {
if returndatasize {
success := 0
if eq(returndatasize, 32) {
// First 64 bytes of memory are reserved scratch space
returndatacopy(0, 0, 32)
success := mload(0)
}
}
}
require(
success,
"TRANSFER_FAILED"
);
}
/// @dev Decodes ERC721 assetData and transfers given amount to sender.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to transfer to sender.
function _transferERC721Token(
bytes memory assetData,
uint256 amount
)
internal
{
require(
amount == 1,
"INVALID_AMOUNT"
);
// Decode asset data.
// 4 byte id + 12 0 bytes before ABI encoded token address.
address token = assetData.readAddress(16);
// 4 byte id + 32 byte ABI encoded token address before token id.
uint256 tokenId = assetData.readUint256(36);
// Perform transfer.
IERC721Token(token).transferFrom(
address(this),
msg.sender,
tokenId
);
}
/// @dev Sets approval for ERC20 AssetProxy.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to approve for respective proxy.
function _approveERC20Token(
bytes memory assetData,
uint256 amount
)
internal
{
address token = assetData.readAddress(16);
require(
IERC20Token(token).approve(ERC20_PROXY_ADDRESS, amount),
"APPROVAL_FAILED"
);
}
/// @dev Sets approval for ERC721 AssetProxy.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to approve for respective proxy.
function _approveERC721Token(
bytes memory assetData,
uint256 amount
)
internal
{
address token = assetData.readAddress(16);
bool approval = amount >= 1;
IERC721Token(token).setApprovalForAll(ERC721_PROXY_ADDRESS, approval);
}
}

View File

@ -1,86 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "./libs/LibConstants.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
contract MixinMatchOrders is
Ownable,
LibConstants
{
/// @dev Match two complementary orders that have a profitable spread.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
/// The profit made by the left order is then used to fill the right order as much as possible.
/// This results in a spread being taken in terms of both assets. The spread is held within this contract.
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftSignature Proof that order was created by the left maker.
/// @param rightSignature Proof that order was created by the right maker.
function matchOrders(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
bytes memory leftSignature,
bytes memory rightSignature
)
public
onlyOwner
{
// Match orders, maximally filling `leftOrder`
LibFillResults.MatchedFillResults memory matchedFillResults = EXCHANGE.matchOrders(
leftOrder,
rightOrder,
leftSignature,
rightSignature
);
uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
uint256 rightOrderTakerAssetAmount = rightOrder.takerAssetAmount;
// Do not attempt to call `fillOrder` if no spread was taken or `rightOrder` has been completely filled
if (leftMakerAssetSpreadAmount == 0 || matchedFillResults.right.takerAssetFilledAmount == rightOrderTakerAssetAmount) {
return;
}
// The `assetData` fields of the `rightOrder` could have been null for the `matchOrders` call. We reassign them before calling `fillOrder`.
rightOrder.makerAssetData = leftOrder.takerAssetData;
rightOrder.takerAssetData = leftOrder.makerAssetData;
// Query `rightOrder` info to check if it has been completely filled
// We need to make this check in case the `rightOrder` was partially filled before the `matchOrders` call
LibOrder.OrderInfo memory orderInfo = EXCHANGE.getOrderInfo(rightOrder);
// Do not attempt to call `fillOrder` if order has been completely filled
if (orderInfo.orderTakerAssetFilledAmount == rightOrderTakerAssetAmount) {
return;
}
// We do not need to pass in a signature since it was already validated in the `matchOrders` call
EXCHANGE.fillOrder(
rightOrder,
leftMakerAssetSpreadAmount,
""
);
}
}

View File

@ -1,38 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "./libs/LibConstants.sol";
import "./MixinMatchOrders.sol";
import "./MixinAssets.sol";
// solhint-disable no-empty-blocks
contract OrderMatcher is
MixinMatchOrders,
MixinAssets
{
constructor (address _exchange)
public
LibConstants(_exchange)
Ownable()
{}
}

View File

@ -1,43 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
contract IAssets {
/// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to
/// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be
/// used to withdraw assets that were accidentally sent to this contract.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to withdraw.
function withdrawAsset(
bytes calldata assetData,
uint256 amount
)
external;
/// @dev Approves or disapproves an AssetProxy to spend asset.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param amount Amount of asset to approve for respective proxy.
function approveAssetProxy(
bytes calldata assetData,
uint256 amount
)
external;
}

View File

@ -1,43 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract IMatchOrders {
/// @dev Match two complementary orders that have a profitable spread.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
/// The profit made by the left order is then used to fill the right order as much as possible.
/// This results in a spread being taken in terms of both assets. The spread is held within this contract.
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftSignature Proof that order was created by the left maker.
/// @param rightSignature Proof that order was created by the right maker.
function matchOrders(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
bytes memory leftSignature,
bytes memory rightSignature
)
public;
}

View File

@ -1,31 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol";
import "./IMatchOrders.sol";
import "./IAssets.sol";
// solhint-disable no-empty-blocks
contract IOrderMatcher is
IOwnable,
IMatchOrders,
IAssets
{}

View File

@ -1,56 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
contract LibConstants {
// bytes4(keccak256("transfer(address,uint256)"))
bytes4 constant internal ERC20_TRANSFER_SELECTOR = 0xa9059cbb;
// bytes4(keccak256("ERC20Token(address)"))
bytes4 constant internal ERC20_DATA_ID = 0xf47261b0;
// bytes4(keccak256("ERC721Token(address,uint256)"))
bytes4 constant internal ERC721_DATA_ID = 0x02571792;
// solhint-disable var-name-mixedcase
IExchange internal EXCHANGE;
address internal ERC20_PROXY_ADDRESS;
address internal ERC721_PROXY_ADDRESS;
// solhint-enable var-name-mixedcase
constructor (address _exchange)
public
{
EXCHANGE = IExchange(_exchange);
ERC20_PROXY_ADDRESS = EXCHANGE.getAssetProxy(ERC20_DATA_ID);
require(
ERC20_PROXY_ADDRESS != address(0),
"UNREGISTERED_ASSET_PROXY"
);
ERC721_PROXY_ADDRESS = EXCHANGE.getAssetProxy(ERC721_DATA_ID);
require(
ERC721_PROXY_ADDRESS != address(0),
"UNREGISTERED_ASSET_PROXY"
);
}
}

View File

@ -0,0 +1,91 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
library LibWethUtilsRichErrors {
// bytes4(keccak256("UnregisteredAssetProxyError()"))
bytes4 internal constant UNREGISTERED_ASSET_PROXY_ERROR_SELECTOR =
0xf3b96b8d;
// bytes4(keccak256("InsufficientEthForFeeError(uint256,uint256)"))
bytes4 internal constant INSUFFICIENT_ETH_FOR_FEE_ERROR_SELECTOR =
0xecf40fd9;
// bytes4(keccak256("DefaultFunctionWethContractOnlyError(address)"))
bytes4 internal constant DEFAULT_FUNCTION_WETH_CONTRACT_ONLY_ERROR_SELECTOR =
0x08b18698;
// bytes4(keccak256("EthFeeLengthMismatchError(uint256,uint256)"))
bytes4 internal constant ETH_FEE_LENGTH_MISMATCH_ERROR_SELECTOR =
0x3ecb6ceb;
// solhint-disable func-name-mixedcase
function UnregisteredAssetProxyError()
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(UNREGISTERED_ASSET_PROXY_ERROR_SELECTOR);
}
function InsufficientEthForFeeError(
uint256 ethFeeRequired,
uint256 ethAvailable
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
INSUFFICIENT_ETH_FOR_FEE_ERROR_SELECTOR,
ethFeeRequired,
ethAvailable
);
}
function DefaultFunctionWethContractOnlyError(
address senderAddress
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
DEFAULT_FUNCTION_WETH_CONTRACT_ONLY_ERROR_SELECTOR,
senderAddress
);
}
function EthFeeLengthMismatchError(
uint256 ethFeesLength,
uint256 feeRecipientsLength
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
ETH_FEE_LENGTH_MISMATCH_ERROR_SELECTOR,
ethFeesLength,
feeRecipientsLength
);
}
}

View File

@ -38,8 +38,7 @@
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
}, },
"config": { "config": {
"publicInterfaceContracts": "DutchAuction,OrderMatcher,BalanceThresholdFilter", "abis": "./test/generated-artifacts/@(LibAssetDataTransfer|LibAssetDataTransferRichErrors|LibWethUtilsRichErrors|MixinWethUtils).json",
"abis": "./generated-artifacts/@(BalanceThresholdFilter|DutchAuction|Exchange|ExchangeWrapper|OrderMatcher|WETH9).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
}, },
"repository": { "repository": {

View File

@ -5,17 +5,13 @@
*/ */
import { ContractArtifact } from 'ethereum-types'; import { ContractArtifact } from 'ethereum-types';
import * as BalanceThresholdFilter from '../generated-artifacts/BalanceThresholdFilter.json'; import * as LibAssetDataTransfer from '../generated-artifacts/LibAssetDataTransfer.json';
import * as DutchAuction from '../generated-artifacts/DutchAuction.json'; import * as LibAssetDataTransferRichErrors from '../generated-artifacts/LibAssetDataTransferRichErrors.json';
import * as Exchange from '../generated-artifacts/Exchange.json'; import * as LibWethUtilsRichErrors from '../generated-artifacts/LibWethUtilsRichErrors.json';
import * as ExchangeWrapper from '../generated-artifacts/ExchangeWrapper.json'; import * as MixinWethUtils from '../generated-artifacts/MixinWethUtils.json';
import * as OrderMatcher from '../generated-artifacts/OrderMatcher.json';
import * as WETH9 from '../generated-artifacts/WETH9.json';
export const artifacts = { export const artifacts = {
WETH9: WETH9 as ContractArtifact, LibAssetDataTransfer: LibAssetDataTransfer as ContractArtifact,
ExchangeWrapper: ExchangeWrapper as ContractArtifact, MixinWethUtils: MixinWethUtils as ContractArtifact,
Exchange: Exchange as ContractArtifact, LibAssetDataTransferRichErrors: LibAssetDataTransferRichErrors as ContractArtifact,
BalanceThresholdFilter: BalanceThresholdFilter as ContractArtifact, LibWethUtilsRichErrors: LibWethUtilsRichErrors as ContractArtifact,
DutchAuction: DutchAuction as ContractArtifact,
OrderMatcher: OrderMatcher as ContractArtifact,
}; };

View File

@ -1,5 +1,5 @@
export { artifacts } from './artifacts'; export { artifacts } from './artifacts';
export { BalanceThresholdFilterContract, DutchAuctionContract, OrderMatcherContract } from './wrappers'; export { LibAssetDataTransferRevertErrors, MixinWethUtilsRevertErrors } from '@0x/utils';
export { export {
ContractArtifact, ContractArtifact,
ContractChains, ContractChains,

View File

@ -3,6 +3,7 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually. * Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
export * from '../generated-wrappers/balance_threshold_filter'; export * from '../generated-wrappers/lib_asset_data_transfer';
export * from '../generated-wrappers/dutch_auction'; export * from '../generated-wrappers/lib_asset_data_transfer_rich_errors';
export * from '../generated-wrappers/order_matcher'; export * from '../generated-wrappers/lib_weth_utils_rich_errors';
export * from '../generated-wrappers/mixin_weth_utils';

View File

@ -0,0 +1,17 @@
/*
* -----------------------------------------------------------------------------
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
import { ContractArtifact } from 'ethereum-types';
import * as LibAssetDataTransfer from '../test/generated-artifacts/LibAssetDataTransfer.json';
import * as LibAssetDataTransferRichErrors from '../test/generated-artifacts/LibAssetDataTransferRichErrors.json';
import * as LibWethUtilsRichErrors from '../test/generated-artifacts/LibWethUtilsRichErrors.json';
import * as MixinWethUtils from '../test/generated-artifacts/MixinWethUtils.json';
export const artifacts = {
LibAssetDataTransfer: LibAssetDataTransfer as ContractArtifact,
MixinWethUtils: MixinWethUtils as ContractArtifact,
LibAssetDataTransferRichErrors: LibAssetDataTransferRichErrors as ContractArtifact,
LibWethUtilsRichErrors: LibWethUtilsRichErrors as ContractArtifact,
};

File diff suppressed because it is too large Load Diff

View File

@ -1,368 +0,0 @@
import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { ExchangeContract } from '@0x/contracts-exchange';
import {
chaiSetup,
constants,
ContractName,
ERC20BalancesByOwner,
getLatestBlockTimestampAsync,
OrderFactory,
provider,
txDefaults,
web3Wrapper,
} from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { generatePseudoRandomSalt } from '@0x/order-utils';
import { RevertReason, SignedOrder } from '@0x/types';
import { BigNumber, providerUtils } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as chai from 'chai';
import * as _ from 'lodash';
import { DutchAuctionContract, DutchAuctionTestWrapper, WETH9Contract } from './wrappers';
import { artifacts } from './artifacts';
import { encodeDutchAuctionAssetData } from './utils';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const DECIMALS_DEFAULT = 18;
describe(ContractName.DutchAuction, () => {
let chainId: number;
let makerAddress: string;
let owner: string;
let takerAddress: string;
let feeRecipientAddress: string;
let defaultMakerAssetAddress: string;
let zrxToken: DummyERC20TokenContract;
let erc20TokenA: DummyERC20TokenContract;
let erc721Token: DummyERC721TokenContract;
let dutchAuctionContract: DutchAuctionContract;
let wethContract: WETH9Contract;
let sellerOrderFactory: OrderFactory;
let buyerOrderFactory: OrderFactory;
let erc20Wrapper: ERC20Wrapper;
let erc20Balances: ERC20BalancesByOwner;
let currentBlockTimestamp: number;
let auctionBeginTimeSeconds: BigNumber;
let auctionEndTimeSeconds: BigNumber;
let auctionBeginAmount: BigNumber;
let auctionEndAmount: BigNumber;
let sellOrder: SignedOrder;
let buyOrder: SignedOrder;
let erc721MakerAssetIds: BigNumber[];
const tenMinutesInSeconds = 10 * 60;
let dutchAuctionTestWrapper: DutchAuctionTestWrapper;
let defaultERC20MakerAssetData: string;
const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, provider);
before(async () => {
await blockchainLifecycle.startAsync();
chainId = await providerUtils.getChainIdAsync(provider);
const accounts = await web3Wrapper.getAvailableAddressesAsync();
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
const numDummyErc20ToDeploy = 2;
[erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
numDummyErc20ToDeploy,
constants.DUMMY_TOKEN_DECIMALS,
);
const erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
[erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
const erc721Proxy = await erc721Wrapper.deployProxyAsync();
await erc721Wrapper.setBalancesAndAllowancesAsync();
const erc721Balances = await erc721Wrapper.getBalancesAsync();
erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address];
wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults, artifacts);
erc20Wrapper.addDummyTokenContract(wethContract as any);
const zrxAssetData = await devUtils.encodeERC20AssetData(zrxToken.address).callAsync();
const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
provider,
txDefaults,
artifacts,
zrxAssetData,
new BigNumber(chainId),
);
await exchangeInstance.registerAssetProxy.awaitTransactionSuccessAsync(erc20Proxy.address, {
from: owner,
});
await exchangeInstance.registerAssetProxy.awaitTransactionSuccessAsync(erc721Proxy.address, {
from: owner,
});
await erc20Proxy.addAuthorizedAddress(exchangeInstance.address).sendTransactionAsync({
from: owner,
});
await erc721Proxy.addAuthorizedAddress(exchangeInstance.address).sendTransactionAsync({
from: owner,
});
const dutchAuctionInstance = await DutchAuctionContract.deployFrom0xArtifactAsync(
artifacts.DutchAuction,
provider,
txDefaults,
artifacts,
exchangeInstance.address,
);
dutchAuctionContract = new DutchAuctionContract(dutchAuctionInstance.address, provider);
dutchAuctionTestWrapper = new DutchAuctionTestWrapper(dutchAuctionInstance, provider);
defaultMakerAssetAddress = erc20TokenA.address;
const defaultTakerAssetAddress = wethContract.address;
// Set up taker WETH balance and allowance
await web3Wrapper.awaitTransactionSuccessAsync(
await wethContract.deposit.sendTransactionAsync({
from: takerAddress,
value: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT),
}),
);
await web3Wrapper.awaitTransactionSuccessAsync(
await wethContract
.approve(erc20Proxy.address, constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
.sendTransactionAsync({ from: takerAddress }),
);
web3Wrapper.abiDecoder.addABI(exchangeInstance.abi);
web3Wrapper.abiDecoder.addABI(zrxToken.abi);
erc20Wrapper.addTokenOwnerAddress(dutchAuctionContract.address);
currentBlockTimestamp = await getLatestBlockTimestampAsync();
// Default auction begins 10 minutes ago
auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp).minus(tenMinutesInSeconds);
// Default auction ends 10 from now
auctionEndTimeSeconds = new BigNumber(currentBlockTimestamp).plus(tenMinutesInSeconds);
auctionBeginAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT);
auctionEndAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT);
// Default sell order and buy order are exact mirrors
const sellerDefaultOrderParams = {
salt: generatePseudoRandomSalt(),
makerAddress,
feeRecipientAddress,
// taker address or sender address should be set to the ducth auction contract
takerAddress: dutchAuctionContract.address,
makerAssetData: encodeDutchAuctionAssetData(
await devUtils.encodeERC20AssetData(defaultMakerAssetAddress).callAsync(),
auctionBeginTimeSeconds,
auctionBeginAmount,
),
takerAssetData: await devUtils.encodeERC20AssetData(defaultTakerAssetAddress).callAsync(),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT),
takerAssetAmount: auctionEndAmount,
expirationTimeSeconds: auctionEndTimeSeconds,
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
exchangeAddress: exchangeInstance.address,
chainId,
};
// Default buy order is for the auction begin price
const buyerDefaultOrderParams = {
...sellerDefaultOrderParams,
makerAddress: takerAddress,
makerAssetData: sellerDefaultOrderParams.takerAssetData,
takerAssetData: sellerDefaultOrderParams.makerAssetData,
makerAssetAmount: auctionBeginAmount,
takerAssetAmount: sellerDefaultOrderParams.makerAssetAmount,
};
const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)];
sellerOrderFactory = new OrderFactory(makerPrivateKey, sellerDefaultOrderParams);
buyerOrderFactory = new OrderFactory(takerPrivateKey, buyerDefaultOrderParams);
defaultERC20MakerAssetData = await devUtils.encodeERC20AssetData(defaultMakerAssetAddress).callAsync();
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
erc20Balances = await erc20Wrapper.getBalancesAsync();
sellOrder = await sellerOrderFactory.newSignedOrderAsync();
buyOrder = await buyerOrderFactory.newSignedOrderAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('matchOrders', () => {
it('should be worth the begin price at the begining of the auction', async () => {
auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp + 2);
const makerAssetData = encodeDutchAuctionAssetData(
defaultERC20MakerAssetData,
auctionBeginTimeSeconds,
auctionBeginAmount,
);
sellOrder = await sellerOrderFactory.newSignedOrderAsync({ makerAssetData });
const auctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
expect(auctionDetails.currentTimeSeconds).to.be.bignumber.lte(auctionBeginTimeSeconds);
expect(auctionDetails.currentAmount).to.be.bignumber.equal(auctionBeginAmount);
expect(auctionDetails.beginAmount).to.be.bignumber.equal(auctionBeginAmount);
});
it('should be be worth the end price at the end of the auction', async () => {
auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds * 2);
auctionEndTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds);
const makerAssetData = encodeDutchAuctionAssetData(
defaultERC20MakerAssetData,
auctionBeginTimeSeconds,
auctionBeginAmount,
);
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
makerAssetData,
expirationTimeSeconds: auctionEndTimeSeconds,
});
const auctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
expect(auctionDetails.currentTimeSeconds).to.be.bignumber.gte(auctionEndTimeSeconds);
expect(auctionDetails.currentAmount).to.be.bignumber.equal(auctionEndAmount);
expect(auctionDetails.beginAmount).to.be.bignumber.equal(auctionBeginAmount);
});
it('should match orders at current amount and send excess to buyer', async () => {
const beforeAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
makerAssetAmount: beforeAuctionDetails.currentAmount.times(2),
});
await dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
const afterAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(newBalances[dutchAuctionContract.address][wethContract.address]).to.be.bignumber.equal(
constants.ZERO_AMOUNT,
);
// HACK gte used here due to a bug in ganache where the timestamp can change
// between multiple calls to the same block. Which can move the amount in our case
// ref: https://github.com/trufflesuite/ganache-core/issues/111
expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte(
erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount),
);
expect(newBalances[takerAddress][wethContract.address]).to.be.bignumber.gte(
erc20Balances[takerAddress][wethContract.address].minus(beforeAuctionDetails.currentAmount),
);
});
it('maker fees on sellOrder are paid to the fee receipient', async () => {
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
makerFee: new BigNumber(1),
});
await dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
const afterAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte(
erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount),
);
expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
erc20Balances[feeRecipientAddress][zrxToken.address].plus(sellOrder.makerFee),
);
});
it('maker fees on buyOrder are paid to the fee receipient', async () => {
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
makerFee: new BigNumber(1),
});
await dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
const newBalances = await erc20Wrapper.getBalancesAsync();
const afterAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte(
erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount),
);
expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
erc20Balances[feeRecipientAddress][zrxToken.address].plus(buyOrder.makerFee),
);
});
it('should revert when auction expires', async () => {
auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds * 2);
auctionEndTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds);
const makerAssetData = encodeDutchAuctionAssetData(
defaultERC20MakerAssetData,
auctionBeginTimeSeconds,
auctionBeginAmount,
);
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
expirationTimeSeconds: auctionEndTimeSeconds,
makerAssetData,
});
const tx = dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
return expect(tx).to.revertWith(RevertReason.AuctionExpired);
});
it('cannot be filled for less than the current price', async () => {
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
makerAssetAmount: sellOrder.takerAssetAmount,
});
const tx = dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
return expect(tx).to.revertWith(RevertReason.AuctionInvalidAmount);
});
it('auction begin amount must be higher than final amount ', async () => {
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
takerAssetAmount: auctionBeginAmount.plus(1),
});
const tx = dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
return expect(tx).to.revertWith(RevertReason.AuctionInvalidAmount);
});
it('begin time is less than end time', async () => {
auctionBeginTimeSeconds = new BigNumber(auctionEndTimeSeconds).plus(tenMinutesInSeconds);
const makerAssetData = encodeDutchAuctionAssetData(
defaultERC20MakerAssetData,
auctionBeginTimeSeconds,
auctionBeginAmount,
);
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
expirationTimeSeconds: auctionEndTimeSeconds,
makerAssetData,
});
const tx = dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
return expect(tx).to.revertWith(RevertReason.AuctionInvalidBeginTime);
});
it('asset data contains auction parameters', async () => {
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
makerAssetData: await devUtils.encodeERC20AssetData(defaultMakerAssetAddress).callAsync(),
});
const tx = dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
return expect(tx).to.revertWith(RevertReason.InvalidAssetData);
});
describe('ERC721', () => {
it('should match orders when ERC721', async () => {
const makerAssetId = erc721MakerAssetIds[0];
const erc721MakerAssetData = await devUtils
.encodeERC721AssetData(erc721Token.address, makerAssetId)
.callAsync();
const makerAssetData = encodeDutchAuctionAssetData(
erc721MakerAssetData,
auctionBeginTimeSeconds,
auctionBeginAmount,
);
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
makerAssetAmount: new BigNumber(1),
makerAssetData,
});
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
takerAssetAmount: new BigNumber(1),
takerAssetData: sellOrder.makerAssetData,
});
await dutchAuctionTestWrapper.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
const afterAuctionDetails = await dutchAuctionTestWrapper.getAuctionDetailsAsync(sellOrder);
const newBalances = await erc20Wrapper.getBalancesAsync();
// HACK gte used here due to a bug in ganache where the timestamp can change
// between multiple calls to the same block. Which can move the amount in our case
// ref: https://github.com/trufflesuite/ganache-core/issues/111
expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte(
erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount),
);
const newOwner = await erc721Token.ownerOf(makerAssetId).callAsync();
expect(newOwner).to.be.bignumber.equal(takerAddress);
});
});
});
});

View File

@ -1,19 +0,0 @@
import { env, EnvVars } from '@0x/dev-utils';
import { coverage, profiler, provider } from '@0x/contracts-test-utils';
import { providerUtils } from '@0x/utils';
before('start web3 provider', () => {
providerUtils.startProviderEngine(provider);
});
after('generate coverage report', async () => {
if (env.parseBoolean(EnvVars.SolidityCoverage)) {
const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
await coverageSubprovider.writeCoverageAsync();
}
if (env.parseBoolean(EnvVars.SolidityProfiler)) {
const profilerSubprovider = profiler.getProfilerSubproviderSingleton();
await profilerSubprovider.writeProfilerOutputAsync();
}
provider.stop();
});

View File

@ -1,796 +0,0 @@
import {
artifacts as proxyArtifacts,
ERC20ProxyContract,
ERC20Wrapper,
ERC721ProxyContract,
} from '@0x/contracts-asset-proxy';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { artifacts as erc721Artifacts, DummyERC721TokenContract } from '@0x/contracts-erc721';
import { ExchangeContract, ExchangeRevertErrors } from '@0x/contracts-exchange';
import {
chaiSetup,
constants,
ERC20BalancesByOwner,
expectContractCreationFailedAsync,
LogDecoder,
OrderFactory,
orderUtils,
provider,
sendTransactionResult,
txDefaults,
web3Wrapper,
} from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { RevertReason } from '@0x/types';
import { BigNumber, providerUtils } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import * as _ from 'lodash';
import { ExchangeFillEventArgs, OrderMatcherContract } from './wrappers';
import { artifacts } from './artifacts';
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
chaiSetup.configure();
const expect = chai.expect;
// tslint:disable:no-unnecessary-type-assertion
describe('OrderMatcher', () => {
let chainId: number;
let makerAddressLeft: string;
let makerAddressRight: string;
let owner: string;
let takerAddress: string;
let feeRecipientAddressLeft: string;
let feeRecipientAddressRight: string;
let erc20TokenA: DummyERC20TokenContract;
let erc20TokenB: DummyERC20TokenContract;
let zrxToken: DummyERC20TokenContract;
let exchange: ExchangeContract;
let erc20Proxy: ERC20ProxyContract;
let erc721Proxy: ERC721ProxyContract;
let orderMatcher: OrderMatcherContract;
let erc20BalancesByOwner: ERC20BalancesByOwner;
let erc20Wrapper: ERC20Wrapper;
let orderFactoryLeft: OrderFactory;
let orderFactoryRight: OrderFactory;
let leftMakerAssetData: string;
let leftTakerAssetData: string;
let defaultERC20MakerAssetAddress: string;
let defaultERC20TakerAssetAddress: string;
const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, provider, txDefaults);
before(async () => {
await blockchainLifecycle.startAsync();
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
before(async () => {
chainId = await providerUtils.getChainIdAsync(provider);
// Create accounts
const accounts = await web3Wrapper.getAvailableAddressesAsync();
// Hack(albrow): Both Prettier and TSLint insert a trailing comma below
// but that is invalid syntax as of TypeScript version >= 2.8. We don't
// have the right fine-grained configuration options in TSLint,
// Prettier, or TypeScript, to reconcile this, so we will just have to
// wait for them to sort it out. We disable TSLint and Prettier for
// this part of the code for now. This occurs several times in this
// file. See https://github.com/prettier/prettier/issues/4624.
// prettier-ignore
const usedAddresses = ([
owner,
makerAddressLeft,
makerAddressRight,
takerAddress,
feeRecipientAddressLeft,
// tslint:disable-next-line:trailing-comma
feeRecipientAddressRight
] = _.slice(accounts, 0, 6));
// Create wrappers
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
// Deploy ERC20 token & ERC20 proxy
const numDummyErc20ToDeploy = 3;
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
numDummyErc20ToDeploy,
constants.DUMMY_TOKEN_DECIMALS,
);
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
// Deploy ERC721 proxy
erc721Proxy = await ERC721ProxyContract.deployFrom0xArtifactAsync(
proxyArtifacts.ERC721Proxy,
provider,
txDefaults,
artifacts,
);
// Depoy exchange
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
provider,
txDefaults,
artifacts,
await devUtils.encodeERC20AssetData(zrxToken.address).callAsync(),
new BigNumber(chainId),
);
await exchange.registerAssetProxy(erc20Proxy.address).awaitTransactionSuccessAsync({
from: owner,
});
await exchange.registerAssetProxy(erc721Proxy.address).awaitTransactionSuccessAsync({
from: owner,
}); // Authorize ERC20 trades by exchange
await web3Wrapper.awaitTransactionSuccessAsync(
await erc20Proxy.addAuthorizedAddress(exchange.address).sendTransactionAsync({
from: owner,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Deploy OrderMatcher
orderMatcher = await OrderMatcherContract.deployFrom0xArtifactAsync(
artifacts.OrderMatcher,
provider,
txDefaults,
artifacts,
exchange.address,
);
// Set default addresses
defaultERC20MakerAssetAddress = erc20TokenA.address;
defaultERC20TakerAssetAddress = erc20TokenB.address;
leftMakerAssetData = await devUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress).callAsync();
leftTakerAssetData = await devUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress).callAsync();
// Set OrderMatcher balances and allowances
await erc20TokenA
.setBalance(orderMatcher.address, constants.INITIAL_ERC20_BALANCE)
.awaitTransactionSuccessAsync({ from: owner }, { pollingIntervalMs: constants.AWAIT_TRANSACTION_MINED_MS });
await erc20TokenB
.setBalance(orderMatcher.address, constants.INITIAL_ERC20_BALANCE)
.awaitTransactionSuccessAsync({ from: owner }, { pollingIntervalMs: constants.AWAIT_TRANSACTION_MINED_MS });
await orderMatcher
.approveAssetProxy(leftMakerAssetData, constants.INITIAL_ERC20_ALLOWANCE)
.awaitTransactionSuccessAsync({}, { pollingIntervalMs: constants.AWAIT_TRANSACTION_MINED_MS });
await orderMatcher
.approveAssetProxy(leftTakerAssetData, constants.INITIAL_ERC20_ALLOWANCE)
.awaitTransactionSuccessAsync({}, { pollingIntervalMs: constants.AWAIT_TRANSACTION_MINED_MS });
// Create default order parameters
const defaultOrderParamsLeft = {
...constants.STATIC_ORDER_PARAMS,
makerAddress: makerAddressLeft,
makerAssetData: leftMakerAssetData,
takerAssetData: leftTakerAssetData,
feeRecipientAddress: feeRecipientAddressLeft,
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
exchangeAddress: exchange.address,
chainId,
};
const defaultOrderParamsRight = {
...constants.STATIC_ORDER_PARAMS,
makerAddress: makerAddressRight,
makerAssetData: leftTakerAssetData,
takerAssetData: leftMakerAssetData,
feeRecipientAddress: feeRecipientAddressRight,
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
exchangeAddress: exchange.address,
chainId,
};
const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)];
orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft);
const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('constructor', () => {
it('should revert if assetProxy is unregistered', async () => {
const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
provider,
txDefaults,
artifacts,
constants.NULL_BYTES,
new BigNumber(chainId),
);
return expectContractCreationFailedAsync(
(OrderMatcherContract.deployFrom0xArtifactAsync(
artifacts.OrderMatcher,
provider,
txDefaults,
artifacts,
exchangeInstance.address,
) as any) as sendTransactionResult,
RevertReason.UnregisteredAssetProxy,
);
});
});
describe('matchOrders', () => {
beforeEach(async () => {
erc20BalancesByOwner = await erc20Wrapper.getBalancesAsync();
});
it('should revert if not called by owner', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
});
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
const tx = web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: takerAddress,
gas: constants.MAX_MATCH_ORDERS_GAS,
});
return expect(tx).to.revertWith(RevertReason.OnlyContractOwner);
});
it('should transfer the correct amounts when orders completely fill each other', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
});
// Match signedOrderLeft with signedOrderRight
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
// Right Maker
amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
// Taker
leftMakerAssetSpreadAmount: signedOrderLeft.makerAssetAmount.minus(signedOrderRight.takerAssetAmount),
};
const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: owner,
gas: constants.MAX_MATCH_ORDERS_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const newErc20Balances = await erc20Wrapper.getBalancesAsync();
expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByRightMaker,
),
);
expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByRightMaker,
),
);
expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
);
});
it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
});
// Match signedOrderLeft with signedOrderRight
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
// Right Maker
amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
};
const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: owner,
gas: constants.MAX_MATCH_ORDERS_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const newErc20Balances = await erc20Wrapper.getBalancesAsync();
expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByRightMaker,
),
);
expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByRightMaker,
),
);
expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(initialLeftMakerAssetTakerBalance);
});
it('should transfer the correct amounts when left order is completely filled and right order would be partially filled', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
});
// Match signedOrderLeft with signedOrderRight
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
// Right Maker
amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
// Taker
leftMakerAssetSpreadAmount: signedOrderLeft.makerAssetAmount.minus(signedOrderRight.takerAssetAmount),
leftTakerAssetSpreadAmount: signedOrderRight.makerAssetAmount.minus(signedOrderLeft.takerAssetAmount),
};
const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const initialLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf(orderMatcher.address).callAsync();
// Match signedOrderLeft with signedOrderRight
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: owner,
gas: constants.MAX_MATCH_ORDERS_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const newLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf(orderMatcher.address).callAsync();
const newErc20Balances = await erc20Wrapper.getBalancesAsync();
expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByRightMaker,
),
);
expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByRightMaker,
),
);
expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
);
expect(newLeftTakerAssetTakerBalance).to.be.bignumber.equal(
initialLeftTakerAssetTakerBalance.plus(expectedTransferAmounts.leftTakerAssetSpreadAmount),
);
});
it('should not call fillOrder when rightOrder is completely filled after matchOrders call and orders were never partially filled', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
});
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
const logDecoder = new LogDecoder(web3Wrapper, artifacts);
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: owner,
gas: constants.MAX_MATCH_ORDERS_GAS,
}),
);
const fillLogs = _.filter(
txReceipt.logs,
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
);
// Only 2 Fill logs should exist for `matchOrders` call. `fillOrder` should not have been called and should not have emitted a Fill event.
expect(fillLogs.length).to.be.equal(2);
});
it('should not call fillOrder when rightOrder is completely filled after matchOrders call and orders were initially partially filled', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
});
let params = orderUtils.createFill(signedOrderLeft, signedOrderLeft.takerAssetAmount.dividedToIntegerBy(5));
await exchange
.fillOrder(params.order, params.takerAssetFillAmount, params.signature)
.awaitTransactionSuccessAsync({ from: takerAddress });
params = orderUtils.createFill(signedOrderRight, signedOrderRight.takerAssetAmount.dividedToIntegerBy(5));
await exchange
.fillOrder(params.order, params.takerAssetFillAmount, params.signature)
.awaitTransactionSuccessAsync({ from: takerAddress });
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
const logDecoder = new LogDecoder(web3Wrapper, artifacts);
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: owner,
gas: constants.MAX_MATCH_ORDERS_GAS,
}),
);
const fillLogs = _.filter(
txReceipt.logs,
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
);
// Only 2 Fill logs should exist for `matchOrders` call. `fillOrder` should not have been called and should not have emitted a Fill event.
expect(fillLogs.length).to.be.equal(2);
});
it('should only take a spread in rightMakerAsset if entire leftMakerAssetSpread amount can be used to fill rightOrder after matchOrders call', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.9), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(990), 18),
});
const initialLeftMakerAssetSpreadAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1.09), 18);
const leftTakerAssetSpreadAmount = initialLeftMakerAssetSpreadAmount
.times(signedOrderRight.makerAssetAmount)
.dividedToIntegerBy(signedOrderRight.takerAssetAmount);
// Match signedOrderLeft with signedOrderRight
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
// Right Maker
amountSoldByRightMaker: signedOrderLeft.takerAssetAmount.plus(leftTakerAssetSpreadAmount),
amountBoughtByRightMaker: signedOrderLeft.makerAssetAmount,
// Taker
leftMakerAssetSpreadAmount: constants.ZERO_AMOUNT,
leftTakerAssetSpreadAmount,
};
const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const initialLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf(orderMatcher.address).callAsync();
// Match signedOrderLeft with signedOrderRight
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: owner,
gas: constants.MAX_MATCH_ORDERS_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const newLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf(orderMatcher.address).callAsync();
const newErc20Balances = await erc20Wrapper.getBalancesAsync();
expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByRightMaker,
),
);
expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByRightMaker,
),
);
expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
);
expect(newLeftTakerAssetTakerBalance).to.be.bignumber.equal(
initialLeftTakerAssetTakerBalance.plus(expectedTransferAmounts.leftTakerAssetSpreadAmount),
);
});
it("should succeed if rightOrder's makerAssetData and takerAssetData are not provided", async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
});
// Match signedOrderLeft with signedOrderRight
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
// Right Maker
amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
// Taker
leftMakerAssetSpreadAmount: signedOrderLeft.makerAssetAmount.minus(signedOrderRight.takerAssetAmount),
leftTakerAssetSpreadAmount: signedOrderRight.makerAssetAmount.minus(signedOrderLeft.takerAssetAmount),
};
const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const initialLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf(orderMatcher.address).callAsync();
// Match signedOrderLeft with signedOrderRight
signedOrderRight.makerAssetData = constants.NULL_BYTES;
signedOrderRight.takerAssetData = constants.NULL_BYTES;
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: owner,
gas: constants.MAX_MATCH_ORDERS_GAS,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
const newLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf(orderMatcher.address).callAsync();
const newErc20Balances = await erc20Wrapper.getBalancesAsync();
expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
expectedTransferAmounts.amountSoldByRightMaker,
),
);
expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByLeftMaker,
),
);
expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
expectedTransferAmounts.amountBoughtByRightMaker,
),
);
expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
);
expect(newLeftTakerAssetTakerBalance).to.be.bignumber.equal(
initialLeftTakerAssetTakerBalance.plus(expectedTransferAmounts.leftTakerAssetSpreadAmount),
);
});
it('should revert with the correct reason if matchOrders call reverts', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
});
signedOrderRight.signature = `0xff${signedOrderRight.signature.slice(4)}`;
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.SignatureError();
const tx = web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: owner,
gas: constants.MAX_MATCH_ORDERS_GAS,
});
return expect(tx).to.revertWith(expectedError);
});
it('should revert with the correct reason if fillOrder call reverts', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
});
// Matcher will not have enough allowance to fill rightOrder
await web3Wrapper.awaitTransactionSuccessAsync(
await orderMatcher.approveAssetProxy(leftMakerAssetData, constants.ZERO_AMOUNT).sendTransactionAsync({
from: owner,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const data = exchange
.matchOrders(signedOrderLeft, signedOrderRight, signedOrderLeft.signature, signedOrderRight.signature)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError();
const tx = web3Wrapper.sendTransactionAsync({
data,
to: orderMatcher.address,
from: owner,
gas: constants.MAX_MATCH_ORDERS_GAS,
});
return expect(tx).to.revertWith(expectedError);
});
});
describe('withdrawAsset', () => {
it('should allow owner to withdraw ERC20 tokens', async () => {
const erc20AWithdrawAmount = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
expect(erc20AWithdrawAmount).to.be.bignumber.gt(constants.ZERO_AMOUNT);
await web3Wrapper.awaitTransactionSuccessAsync(
await orderMatcher.withdrawAsset(leftMakerAssetData, erc20AWithdrawAmount).sendTransactionAsync({
from: owner,
}),
);
const newBalance = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
expect(newBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should allow owner to withdraw ERC721 tokens', async () => {
const erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
erc721Artifacts.DummyERC721Token,
provider,
txDefaults,
artifacts,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
);
const tokenId = new BigNumber(1);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Token.mint(orderMatcher.address, tokenId).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const assetData = await devUtils.encodeERC721AssetData(erc721Token.address, tokenId).callAsync();
const withdrawAmount = new BigNumber(1);
await web3Wrapper.awaitTransactionSuccessAsync(
await orderMatcher.withdrawAsset(assetData, withdrawAmount).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const erc721Owner = await erc721Token.ownerOf(tokenId).callAsync();
expect(erc721Owner).to.be.equal(owner);
});
it('should revert if not called by owner', async () => {
const erc20AWithdrawAmount = await erc20TokenA.balanceOf(orderMatcher.address).callAsync();
expect(erc20AWithdrawAmount).to.be.bignumber.gt(constants.ZERO_AMOUNT);
const tx = orderMatcher.withdrawAsset(leftMakerAssetData, erc20AWithdrawAmount).sendTransactionAsync({
from: takerAddress,
});
return expect(tx).to.revertWith(RevertReason.OnlyContractOwner);
});
});
describe('approveAssetProxy', () => {
it('should be able to set an allowance for ERC20 tokens', async () => {
const allowance = new BigNumber(55465465426546);
await web3Wrapper.awaitTransactionSuccessAsync(
await orderMatcher.approveAssetProxy(leftMakerAssetData, allowance).sendTransactionAsync({
from: owner,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const newAllowance = await erc20TokenA.allowance(orderMatcher.address, erc20Proxy.address).callAsync();
expect(newAllowance).to.be.bignumber.equal(allowance);
});
it('should be able to approve an ERC721 token by passing in allowance = 1', async () => {
const erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
erc721Artifacts.DummyERC721Token,
provider,
txDefaults,
artifacts,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
);
const assetData = await devUtils
.encodeERC721AssetData(erc721Token.address, constants.ZERO_AMOUNT)
.callAsync();
const allowance = new BigNumber(1);
await web3Wrapper.awaitTransactionSuccessAsync(
await orderMatcher.approveAssetProxy(assetData, allowance).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const isApproved = await erc721Token
.isApprovedForAll(orderMatcher.address, erc721Proxy.address)
.callAsync();
expect(isApproved).to.be.equal(true);
});
it('should be able to approve an ERC721 token by passing in allowance > 1', async () => {
const erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
erc721Artifacts.DummyERC721Token,
provider,
txDefaults,
artifacts,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
);
const assetData = await devUtils
.encodeERC721AssetData(erc721Token.address, constants.ZERO_AMOUNT)
.callAsync();
const allowance = new BigNumber(2);
await web3Wrapper.awaitTransactionSuccessAsync(
await orderMatcher.approveAssetProxy(assetData, allowance).sendTransactionAsync({ from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const isApproved = await erc721Token
.isApprovedForAll(orderMatcher.address, erc721Proxy.address)
.callAsync();
expect(isApproved).to.be.equal(true);
});
it('should revert if not called by owner', async () => {
const approval = new BigNumber(1);
const tx = orderMatcher.approveAssetProxy(leftMakerAssetData, approval).sendTransactionAsync({
from: takerAddress,
});
return expect(tx).to.revertWith(RevertReason.OnlyContractOwner);
});
});
});
// tslint:disable:max-file-line-count
// tslint:enable:no-unnecessary-type-assertion

View File

@ -1,253 +0,0 @@
import { ExchangeContract } from '@0x/contracts-exchange';
import {
FillResults,
formatters,
LogDecoder,
OrderInfo,
orderUtils,
TransactionFactory,
Web3ProviderEngine,
} from '@0x/contracts-test-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import { BalanceThresholdFilterContract } from './wrappers';
import { artifacts } from './artifacts';
export class BalanceThresholdWrapper {
private readonly _balanceThresholdFilter: BalanceThresholdFilterContract;
private readonly _signerTransactionFactory: TransactionFactory;
private readonly _exchange: ExchangeContract;
private readonly _web3Wrapper: Web3Wrapper;
private readonly _logDecoder: LogDecoder;
constructor(
balanceThresholdFilter: BalanceThresholdFilterContract,
exchangeContract: ExchangeContract,
signerTransactionFactory: TransactionFactory,
provider: Web3ProviderEngine,
) {
this._balanceThresholdFilter = balanceThresholdFilter;
this._exchange = exchangeContract;
this._signerTransactionFactory = signerTransactionFactory;
this._web3Wrapper = new Web3Wrapper(provider);
this._logDecoder = new LogDecoder(this._web3Wrapper, artifacts);
}
public async fillOrderAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const data = this._exchange
.fillOrder(params.order, params.takerAssetFillAmount, params.signature)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async fillOrKillOrderAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const data = this._exchange
.fillOrKillOrder(params.order, params.takerAssetFillAmount, params.signature)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async fillOrderNoThrowAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const data = this._exchange
.fillOrderNoThrow(params.order, params.takerAssetFillAmount, params.signature)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
return txReceipt;
}
public async batchFillOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[] } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
const data = this._exchange
.batchFillOrders(params.orders, params.takerAssetFillAmounts, params.signatures)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async batchFillOrKillOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[] } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
const data = this._exchange
.batchFillOrKillOrders(params.orders, params.takerAssetFillAmounts, params.signatures)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async batchFillOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
const data = this._exchange
.batchFillOrKillOrders(params.orders, params.takerAssetFillAmounts, params.signatures)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
return txReceipt;
}
public async marketSellOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmount: BigNumber },
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
const data = this._exchange
.marketSellOrders(params.orders, params.takerAssetFillAmount, params.signatures)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async marketSellOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmount: BigNumber; gas?: number },
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
const data = this._exchange
.marketSellOrdersNoThrow(params.orders, params.takerAssetFillAmount, params.signatures)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
return txReceipt;
}
public async marketBuyOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { makerAssetFillAmount: BigNumber },
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
const data = this._exchange
.marketBuyOrders(params.orders, params.makerAssetFillAmount, params.signatures)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async marketBuyOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { makerAssetFillAmount: BigNumber; gas?: number },
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
const data = this._exchange
.marketBuyOrdersNoThrow(params.orders, params.makerAssetFillAmount, params.signatures)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
return txReceipt;
}
public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createCancel(signedOrder);
const data = this._exchange.cancelOrder(params.order).getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async batchCancelOrdersAsync(
orders: SignedOrder[],
from: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createBatchCancel(orders);
const data = this._exchange.batchCancelOrders(params.orders).getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise<TransactionReceiptWithDecodedLogs> {
const data = this._exchange.cancelOrdersUpTo(salt).getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise<BigNumber> {
const filledAmount = await this._exchange.filled(orderHashHex).callAsync();
return filledAmount;
}
public async isCancelledAsync(orderHashHex: string): Promise<boolean> {
const isCancelled = await this._exchange.cancelled(orderHashHex).callAsync();
return isCancelled;
}
public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise<BigNumber> {
const orderEpoch = await this._exchange.orderEpoch(makerAddress, senderAddress).callAsync();
return orderEpoch;
}
public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
const orderInfo = await this._exchange.getOrderInfo(signedOrder).callAsync();
return orderInfo;
}
public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise<OrderInfo[]> {
const ordersInfo = (await this._exchange.getOrdersInfo(signedOrders)).callAsync() as OrderInfo[];
return ordersInfo;
}
public async matchOrdersAsync(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
from: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
const data = this._exchange
.matchOrders(params.left, params.right, params.leftSignature, params.rightSignature)
.getABIEncodedTransactionData();
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async getFillOrderResultsAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
): Promise<FillResults> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const fillResults = await this._exchange
.fillOrder(params.order, params.takerAssetFillAmount, params.signature)
.callAsync({ from });
return fillResults;
}
public abiEncodeFillOrder(signedOrder: SignedOrder, opts: { takerAssetFillAmount?: BigNumber } = {}): string {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const data = this._exchange
.fillOrder(params.order, params.takerAssetFillAmount, params.signature)
.getABIEncodedTransactionData();
return data;
}
public getBalanceThresholdAddress(): string {
return this._balanceThresholdFilter.address;
}
public getExchangeAddress(): string {
return this._exchange.address;
}
private async _executeTransactionAsync(
abiEncodedExchangeTxData: string,
from: string,
gas?: number,
): Promise<TransactionReceiptWithDecodedLogs> {
const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData);
const txOpts = gas === undefined ? { from } : { from, gas };
const txHash = await this._balanceThresholdFilter
.executeTransaction(
signedExchangeTx.salt,
signedExchangeTx.signerAddress,
signedExchangeTx.data,
signedExchangeTx.signature,
txOpts,
)
.sendTransactionAsync();
const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
return txReceipt;
}
}

View File

@ -1,58 +0,0 @@
import { artifacts as erc721Artifacts } from '@0x/contracts-erc721';
import { artifacts as exchangeArtifacts } from '@0x/contracts-exchange';
import { LogDecoder, Web3ProviderEngine } from '@0x/contracts-test-utils';
import { DutchAuctionDetails, SignedOrder } from '@0x/types';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
import { DutchAuctionContract } from '../../generated-wrappers/dutch_auction';
import { artifacts } from '../../src/artifacts';
export class DutchAuctionTestWrapper {
private readonly _dutchAuctionContract: DutchAuctionContract;
private readonly _web3Wrapper: Web3Wrapper;
private readonly _logDecoder: LogDecoder;
constructor(contractInstance: DutchAuctionContract, provider: Web3ProviderEngine) {
this._dutchAuctionContract = contractInstance;
this._web3Wrapper = new Web3Wrapper(provider);
this._logDecoder = new LogDecoder(this._web3Wrapper, {
...artifacts,
...exchangeArtifacts,
...erc721Artifacts,
});
}
/**
* Matches the buy and sell orders at an amount given the following: the current block time, the auction
* start time and the auction begin amount. The sell order is a an order at the lowest amount
* at the end of the auction. Excess from the match is transferred to the seller.
* Over time the price moves from beginAmount to endAmount given the current block.timestamp.
* @param buyOrder The Buyer's order. This order is for the current expected price of the auction.
* @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction).
* @param from Address the transaction is being sent from.
* @return Transaction receipt with decoded logs.
*/
public async matchOrdersAsync(
buyOrder: SignedOrder,
sellOrder: SignedOrder,
from: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const txHash = await this._dutchAuctionContract
.matchOrders(buyOrder, sellOrder, buyOrder.signature, sellOrder.signature)
.sendTransactionAsync({
from,
});
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
return tx;
}
/**
* Calculates the Auction Details for the given order
* @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction).
* @return The dutch auction details.
*/
public async getAuctionDetailsAsync(sellOrder: SignedOrder): Promise<DutchAuctionDetails> {
const auctionDetails = await this._dutchAuctionContract.getAuctionDetails(sellOrder).callAsync();
return auctionDetails;
}
}

View File

@ -1,23 +0,0 @@
import { BigNumber } from '@0x/utils';
import * as ethAbi from 'ethereumjs-abi';
import * as ethUtil from 'ethereumjs-util';
export * from './balance_threshold_wrapper';
export * from './dutch_auction_test_wrapper';
// tslint:disable-next-line:completed-docs
export function encodeDutchAuctionAssetData(
assetData: string,
beginTimeSeconds: BigNumber,
beginAmount: BigNumber,
): string {
const assetDataBuffer = ethUtil.toBuffer(assetData);
const abiEncodedAuctionData = (ethAbi as any).rawEncode(
['uint256', 'uint256'],
[beginTimeSeconds.toString(), beginAmount.toString()],
);
const abiEncodedAuctionDataBuffer = ethUtil.toBuffer(abiEncodedAuctionData);
const dutchAuctionDataBuffer = Buffer.concat([assetDataBuffer, abiEncodedAuctionDataBuffer]);
const dutchAuctionData = ethUtil.bufferToHex(dutchAuctionDataBuffer);
return dutchAuctionData;
}

View File

@ -0,0 +1,9 @@
/*
* -----------------------------------------------------------------------------
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
export * from '../test/generated-wrappers/lib_asset_data_transfer';
export * from '../test/generated-wrappers/lib_asset_data_transfer_rich_errors';
export * from '../test/generated-wrappers/lib_weth_utils_rich_errors';
export * from '../test/generated-wrappers/mixin_weth_utils';

View File

@ -3,12 +3,14 @@
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [ "files": [
"generated-artifacts/BalanceThresholdFilter.json", "generated-artifacts/LibAssetDataTransfer.json",
"generated-artifacts/DutchAuction.json", "generated-artifacts/LibAssetDataTransferRichErrors.json",
"generated-artifacts/Exchange.json", "generated-artifacts/LibWethUtilsRichErrors.json",
"generated-artifacts/ExchangeWrapper.json", "generated-artifacts/MixinWethUtils.json",
"generated-artifacts/OrderMatcher.json", "test/generated-artifacts/LibAssetDataTransfer.json",
"generated-artifacts/WETH9.json" "test/generated-artifacts/LibAssetDataTransferRichErrors.json",
"test/generated-artifacts/LibWethUtilsRichErrors.json",
"test/generated-artifacts/MixinWethUtils.json"
], ],
"exclude": ["./deploy/solc/solc_bin"] "exclude": ["./deploy/solc/solc_bin"]
} }

View File

@ -59,6 +59,7 @@
"@0x/contracts-dev-utils": "^1.0.5", "@0x/contracts-dev-utils": "^1.0.5",
"@0x/contracts-exchange-forwarder": "^4.0.5", "@0x/contracts-exchange-forwarder": "^4.0.5",
"@0x/contracts-exchange-libs": "^4.1.1", "@0x/contracts-exchange-libs": "^4.1.1",
"@0x/contracts-extensions": "^5.1.4",
"@0x/contracts-gen": "^2.0.5", "@0x/contracts-gen": "^2.0.5",
"@0x/contracts-utils": "^4.2.0", "@0x/contracts-utils": "^4.2.0",
"@0x/coordinator-server": "^1.0.5", "@0x/coordinator-server": "^1.0.5",

View File

@ -3,6 +3,7 @@ import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { DummyERC721TokenContract } from '@0x/contracts-erc721'; import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange'; import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange';
import { artifacts, ExchangeForwarderRevertErrors, ForwarderContract } from '@0x/contracts-exchange-forwarder'; import { artifacts, ExchangeForwarderRevertErrors, ForwarderContract } from '@0x/contracts-exchange-forwarder';
import { MixinWethUtilsRevertErrors } from '@0x/contracts-extensions';
import { import {
blockchainTests, blockchainTests,
constants, constants,
@ -426,7 +427,7 @@ blockchainTests('Forwarder integration tests', env => {
}); });
const forwarderFeeAmounts = [toBaseUnitAmount(0.2)]; const forwarderFeeAmounts = [toBaseUnitAmount(0.2)];
const forwarderFeeRecipientAddresses: string[] = []; const forwarderFeeRecipientAddresses: string[] = [];
const revertError = new ExchangeForwarderRevertErrors.EthFeeLengthMismatchError( const revertError = new MixinWethUtilsRevertErrors.EthFeeLengthMismatchError(
new BigNumber(forwarderFeeAmounts.length), new BigNumber(forwarderFeeAmounts.length),
new BigNumber(forwarderFeeRecipientAddresses.length), new BigNumber(forwarderFeeRecipientAddresses.length),
); );
@ -443,7 +444,7 @@ blockchainTests('Forwarder integration tests', env => {
}); });
const forwarderFeeAmounts: BigNumber[] = []; const forwarderFeeAmounts: BigNumber[] = [];
const forwarderFeeRecipientAddresses = [randomAddress()]; const forwarderFeeRecipientAddresses = [randomAddress()];
const revertError = new ExchangeForwarderRevertErrors.EthFeeLengthMismatchError( const revertError = new MixinWethUtilsRevertErrors.EthFeeLengthMismatchError(
new BigNumber(forwarderFeeAmounts.length), new BigNumber(forwarderFeeAmounts.length),
new BigNumber(forwarderFeeRecipientAddresses.length), new BigNumber(forwarderFeeRecipientAddresses.length),
); );
@ -768,7 +769,7 @@ blockchainTests('Forwarder integration tests', env => {
const order = await maker.signOrderAsync(); const order = await maker.signOrderAsync();
const forwarderFeeAmounts = [toBaseUnitAmount(1)]; const forwarderFeeAmounts = [toBaseUnitAmount(1)];
const value = forwarderFeeAmounts[0].minus(1); const value = forwarderFeeAmounts[0].minus(1);
const revertError = new ExchangeForwarderRevertErrors.InsufficientEthForFeeError( const revertError = new MixinWethUtilsRevertErrors.InsufficientEthForFeeError(
forwarderFeeAmounts[0], forwarderFeeAmounts[0],
value, value,
); );

View File

@ -25,9 +25,9 @@
"install:all": "yarn install", "install:all": "yarn install",
"wsrun": "wsrun", "wsrun": "wsrun",
"lerna": "lerna", "lerna": "lerna",
"build": "lerna link && wsrun build $PKG -r --stages --fast-exit --exclude-missing --exclude @0x/contracts-extensions", "build": "lerna link && wsrun build $PKG -r --stages --fast-exit --exclude-missing",
"build:ci": "lerna link && wsrun build:ci $PKG --fast-exit -r --stages --exclude-missing --exclude @0x/contracts-extensions", "build:ci": "lerna link && wsrun build:ci $PKG --fast-exit -r --stages --exclude-missing",
"build:contracts": "lerna link && wsrun build -p ${npm_package_config_contractsPackages} -c --fast-exit -r --stages --exclude-missing --exclude @0x/contracts-extensions", "build:contracts": "lerna link && wsrun build -p ${npm_package_config_contractsPackages} -c --fast-exit -r --stages --exclude-missing",
"build:monorepo_scripts": "PKG=@0x/monorepo-scripts yarn build", "build:monorepo_scripts": "PKG=@0x/monorepo-scripts yarn build",
"build:ts": "tsc -b", "build:ts": "tsc -b",
"watch:ts": "tsc -b -w", "watch:ts": "tsc -b -w",
@ -39,7 +39,7 @@
"contracts:watch": "wsrun watch $PKG --parallel --exclude-missing", "contracts:watch": "wsrun watch $PKG --parallel --exclude-missing",
"remove_node_modules": "lerna clean --yes; rm -rf node_modules", "remove_node_modules": "lerna clean --yes; rm -rf node_modules",
"rebuild": "run-s clean build", "rebuild": "run-s clean build",
"test": "wsrun test $PKG --fast-exit --serial --exclude-missing --exclude @0x/asset-swapper --exclude @0x/orderbook --exclude @0x/contracts-extensions", "test": "wsrun test $PKG --fast-exit --serial --exclude-missing --exclude @0x/asset-swapper --exclude @0x/orderbook",
"test:contracts": "wsrun test -p ${npm_package_config_contractsPackages} -c --fast-exit --exclude-missing", "test:contracts": "wsrun test -p ${npm_package_config_contractsPackages} -c --fast-exit --exclude-missing",
"generate_doc": "node ./packages/monorepo-scripts/lib/doc_generate.js", "generate_doc": "node ./packages/monorepo-scripts/lib/doc_generate.js",
"upload_md_docs": "aws s3 rm --recursive s3://docs-markdown; wsrun s3:sync_md_docs --exclude-missing", "upload_md_docs": "aws s3 rm --recursive s3://docs-markdown; wsrun s3:sync_md_docs --exclude-missing",

View File

@ -3,7 +3,7 @@
"version": "5.3.0", "version": "5.3.0",
"changes": [ "changes": [
{ {
"note": "Added Broker revert errors", "note": "Added Broker, MixinWethUtils revert errors",
"pr": 2455 "pr": 2455
}, },
{ {

View File

@ -30,9 +30,10 @@ export {
export import BrokerRevertErrors = require('./revert_errors/broker/revert_errors'); export import BrokerRevertErrors = require('./revert_errors/broker/revert_errors');
export import CoordinatorRevertErrors = require('./revert_errors/coordinator/revert_errors'); export import CoordinatorRevertErrors = require('./revert_errors/coordinator/revert_errors');
export import ExchangeForwarderRevertErrors = require('./revert_errors/exchange-forwarder/revert_errors'); export import ExchangeForwarderRevertErrors = require('./revert_errors/exchange-forwarder/revert_errors');
export import LibAssetDataTransferRevertErrors = require('./revert_errors/exchange-libs/lib_asset_data_transfer_revert_errors');
export import LibMathRevertErrors = require('./revert_errors/exchange-libs/lib_math_revert_errors'); export import LibMathRevertErrors = require('./revert_errors/exchange-libs/lib_math_revert_errors');
export import ExchangeRevertErrors = require('./revert_errors/exchange/revert_errors'); export import ExchangeRevertErrors = require('./revert_errors/exchange/revert_errors');
export import LibAssetDataTransferRevertErrors = require('./revert_errors/extensions/lib_asset_data_transfer_revert_errors');
export import MixinWethUtilsRevertErrors = require('./revert_errors/extensions/mixin_weth_utils_revert_errors');
export import FixedMathRevertErrors = require('./revert_errors/staking/fixed_math_revert_errors'); export import FixedMathRevertErrors = require('./revert_errors/staking/fixed_math_revert_errors');
export import StakingRevertErrors = require('./revert_errors/staking/staking_revert_errors'); export import StakingRevertErrors = require('./revert_errors/staking/staking_revert_errors');
export import AuthorizableRevertErrors = require('./revert_errors/utils/authorizable_revert_errors'); export import AuthorizableRevertErrors = require('./revert_errors/utils/authorizable_revert_errors');

View File

@ -28,16 +28,6 @@ export class UnsupportedFeeError extends RevertError {
} }
} }
export class InsufficientEthForFeeError extends RevertError {
constructor(ethFeeRequired?: BigNumber | number | string, ethAvailable?: BigNumber | number | string) {
super(
'InsufficientEthForFeeError',
'InsufficientEthForFeeError(uint256 ethFeeRequired, uint256 ethAvailable)',
{ ethFeeRequired, ethAvailable },
);
}
}
export class OverspentWethError extends RevertError { export class OverspentWethError extends RevertError {
constructor(wethSpent?: BigNumber | number | string, msgValue?: BigNumber | number | string) { constructor(wethSpent?: BigNumber | number | string, msgValue?: BigNumber | number | string) {
super('OverspentWethError', 'OverspentWethError(uint256 wethSpent, uint256 msgValue)', { super('OverspentWethError', 'OverspentWethError(uint256 wethSpent, uint256 msgValue)', {
@ -47,42 +37,18 @@ export class OverspentWethError extends RevertError {
} }
} }
export class DefaultFunctionWethContractOnlyError extends RevertError {
constructor(senderAddress?: string) {
super('DefaultFunctionWethContractOnlyError', 'DefaultFunctionWethContractOnlyError(address senderAddress)', {
senderAddress,
});
}
}
export class MsgValueCannotEqualZeroError extends RevertError { export class MsgValueCannotEqualZeroError extends RevertError {
constructor() { constructor() {
super('MsgValueCannotEqualZeroError', 'MsgValueCannotEqualZeroError()', {}); super('MsgValueCannotEqualZeroError', 'MsgValueCannotEqualZeroError()', {});
} }
} }
export class EthFeeLengthMismatchError extends RevertError {
constructor(ethFeesLength?: BigNumber | number | string, feeRecipientsLength?: BigNumber | number | string) {
super(
'EthFeeLengthMismatchError',
'EthFeeLengthMismatchError(uint256 ethFeesLength, uint256 feeRecipientsLength)',
{
ethFeesLength,
feeRecipientsLength,
},
);
}
}
const types = [ const types = [
UnregisteredAssetProxyError, UnregisteredAssetProxyError,
CompleteBuyFailedError, CompleteBuyFailedError,
UnsupportedFeeError, UnsupportedFeeError,
InsufficientEthForFeeError,
OverspentWethError, OverspentWethError,
DefaultFunctionWethContractOnlyError,
MsgValueCannotEqualZeroError, MsgValueCannotEqualZeroError,
EthFeeLengthMismatchError,
]; ];
// Register the types we've defined. // Register the types we've defined.

View File

@ -0,0 +1,48 @@
import { BigNumber } from '../../configured_bignumber';
import { RevertError } from '../../revert_error';
// tslint:disable:max-classes-per-file
export class UnregisteredAssetProxyError extends RevertError {
constructor() {
super('UnregisteredAssetProxyError', 'UnregisteredAssetProxyError()', {});
}
}
export class InsufficientEthForFeeError extends RevertError {
constructor(ethFeeRequired?: BigNumber | number | string, ethAvailable?: BigNumber | number | string) {
super(
'InsufficientEthForFeeError',
'InsufficientEthForFeeError(uint256 ethFeeRequired, uint256 ethAvailable)',
{ ethFeeRequired, ethAvailable },
);
}
}
export class DefaultFunctionWethContractOnlyError extends RevertError {
constructor(senderAddress?: string) {
super('DefaultFunctionWethContractOnlyError', 'DefaultFunctionWethContractOnlyError(address senderAddress)', {
senderAddress,
});
}
}
export class EthFeeLengthMismatchError extends RevertError {
constructor(ethFeesLength?: BigNumber | number | string, feeRecipientsLength?: BigNumber | number | string) {
super(
'EthFeeLengthMismatchError',
'EthFeeLengthMismatchError(uint256 ethFeesLength, uint256 feeRecipientsLength)',
{
ethFeesLength,
feeRecipientsLength,
},
);
}
}
const types = [InsufficientEthForFeeError, DefaultFunctionWethContractOnlyError, EthFeeLengthMismatchError];
// Register the types we've defined.
for (const type of types) {
RevertError.registerType(type);
}