Cherry pick DevUtils
refactor code from #2456
This commit is contained in:
51
contracts/dev-utils/contracts/src/Addresses.sol
Normal file
51
contracts/dev-utils/contracts/src/Addresses.sol
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
|
||||
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.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract Addresses is
|
||||
DeploymentConstants
|
||||
{
|
||||
address public exchangeAddress;
|
||||
address public erc20ProxyAddress;
|
||||
address public erc721ProxyAddress;
|
||||
address public erc1155ProxyAddress;
|
||||
address public staticCallProxyAddress;
|
||||
address public chaiBridgeAddress;
|
||||
|
||||
constructor (
|
||||
address exchange_,
|
||||
address chaiBridge_
|
||||
)
|
||||
public
|
||||
{
|
||||
exchangeAddress = exchange_;
|
||||
chaiBridgeAddress = chaiBridge_;
|
||||
erc20ProxyAddress = IExchange(exchange_).getAssetProxy(IAssetData(address(0)).ERC20Token.selector);
|
||||
erc721ProxyAddress = IExchange(exchange_).getAssetProxy(IAssetData(address(0)).ERC721Token.selector);
|
||||
erc1155ProxyAddress = IExchange(exchange_).getAssetProxy(IAssetData(address(0)).ERC1155Assets.selector);
|
||||
staticCallProxyAddress = IExchange(exchange_).getAssetProxy(IAssetData(address(0)).StaticCall.selector);
|
||||
}
|
||||
}
|
374
contracts/dev-utils/contracts/src/AssetBalance.sol
Normal file
374
contracts/dev-utils/contracts/src/AssetBalance.sol
Normal file
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
|
||||
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.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
|
||||
import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IChai.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "./Addresses.sol";
|
||||
import "./LibAssetData.sol";
|
||||
|
||||
|
||||
contract AssetBalance is
|
||||
Addresses
|
||||
{
|
||||
// 2^256 - 1
|
||||
uint256 constant internal _MAX_UINT256 = uint256(-1);
|
||||
|
||||
using LibBytes for bytes;
|
||||
|
||||
/// @dev Returns the owner's balance of the assets(s) specified in
|
||||
/// assetData. When the asset data contains multiple assets (eg in
|
||||
/// ERC1155 or Multi-Asset), the return value indicates how many
|
||||
/// complete "baskets" of those assets are owned by owner.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Details of asset, encoded per the AssetProxy contract specification.
|
||||
/// @return Number of assets (or asset baskets) held by owner.
|
||||
function getBalance(address ownerAddress, bytes memory assetData)
|
||||
public
|
||||
returns (uint256 balance)
|
||||
{
|
||||
// Get id of AssetProxy contract
|
||||
bytes4 assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
if (assetProxyId == IAssetData(address(0)).ERC20Token.selector) {
|
||||
// Get ERC20 token address
|
||||
address tokenAddress = assetData.readAddress(16);
|
||||
balance = LibERC20Token.balanceOf(tokenAddress, ownerAddress);
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC721Token.selector) {
|
||||
// Get ERC721 token address and id
|
||||
(, address tokenAddress, uint256 tokenId) = LibAssetData.decodeERC721AssetData(assetData);
|
||||
|
||||
// Check if id is owned by ownerAddress
|
||||
bytes memory ownerOfCalldata = abi.encodeWithSelector(
|
||||
IERC721Token(address(0)).ownerOf.selector,
|
||||
tokenId
|
||||
);
|
||||
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(ownerOfCalldata);
|
||||
address currentOwnerAddress = (success && returnData.length == 32) ? returnData.readAddress(12) : address(0);
|
||||
balance = currentOwnerAddress == ownerAddress ? 1 : 0;
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC1155Assets.selector) {
|
||||
// Get ERC1155 token address, array of ids, and array of values
|
||||
(, address tokenAddress, uint256[] memory tokenIds, uint256[] memory tokenValues,) = LibAssetData.decodeERC1155AssetData(assetData);
|
||||
|
||||
uint256 length = tokenIds.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
// Skip over the token if the corresponding value is 0.
|
||||
if (tokenValues[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Encode data for `balanceOf(ownerAddress, tokenIds[i])
|
||||
bytes memory balanceOfData = abi.encodeWithSelector(
|
||||
IERC1155(address(0)).balanceOf.selector,
|
||||
ownerAddress,
|
||||
tokenIds[i]
|
||||
);
|
||||
|
||||
// Query balance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData);
|
||||
uint256 totalBalance = success && returnData.length == 32 ? returnData.readUint256(0) : 0;
|
||||
|
||||
// Scale total balance down by corresponding value in assetData
|
||||
uint256 scaledBalance = totalBalance / tokenValues[i];
|
||||
if (scaledBalance == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (scaledBalance < balance || balance == 0) {
|
||||
balance = scaledBalance;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).StaticCall.selector) {
|
||||
// Encode data for `staticCallProxy.transferFrom(assetData,...)`
|
||||
bytes memory transferFromData = abi.encodeWithSelector(
|
||||
IAssetProxy(address(0)).transferFrom.selector,
|
||||
assetData,
|
||||
address(0), // `from` address is not used
|
||||
address(0), // `to` address is not used
|
||||
0 // `amount` is not used
|
||||
);
|
||||
|
||||
// Check if staticcall would be successful
|
||||
(bool success,) = staticCallProxyAddress.staticcall(transferFromData);
|
||||
|
||||
// Success means that the staticcall can be made an unlimited amount of times
|
||||
balance = success ? _MAX_UINT256 : 0;
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC20Bridge.selector) {
|
||||
// Get address of ERC20 token and bridge contract
|
||||
(, address tokenAddress, address bridgeAddress, ) = LibAssetData.decodeERC20BridgeAssetData(assetData);
|
||||
if (tokenAddress == _getDaiAddress() && bridgeAddress == chaiBridgeAddress) {
|
||||
uint256 chaiBalance = LibERC20Token.balanceOf(_getChaiAddress(), ownerAddress);
|
||||
// Calculate Dai balance
|
||||
balance = _convertChaiToDaiAmount(chaiBalance);
|
||||
}
|
||||
// Balance will be 0 if bridge is not supported
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).MultiAsset.selector) {
|
||||
// Get array of values and array of assetDatas
|
||||
(, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = LibAssetData.decodeMultiAssetData(assetData);
|
||||
|
||||
uint256 length = nestedAssetData.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
// Skip over the asset if the corresponding amount is 0.
|
||||
if (assetAmounts[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query balance of individual assetData
|
||||
uint256 totalBalance = getBalance(ownerAddress, nestedAssetData[i]);
|
||||
|
||||
// Scale total balance down by corresponding value in assetData
|
||||
uint256 scaledBalance = totalBalance / assetAmounts[i];
|
||||
if (scaledBalance == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (scaledBalance < balance || balance == 0) {
|
||||
balance = scaledBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Balance will be 0 if assetProxyId is unknown
|
||||
return balance;
|
||||
}
|
||||
|
||||
/// @dev Calls getBalance() for each element of assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Array of asset details, each encoded per the AssetProxy contract specification.
|
||||
/// @return Array of asset balances from getBalance(), with each element
|
||||
/// corresponding to the same-indexed element in the assetData input.
|
||||
function getBatchBalances(address ownerAddress, bytes[] memory assetData)
|
||||
public
|
||||
returns (uint256[] memory balances)
|
||||
{
|
||||
uint256 length = assetData.length;
|
||||
balances = new uint256[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
balances[i] = getBalance(ownerAddress, assetData[i]);
|
||||
}
|
||||
return balances;
|
||||
}
|
||||
|
||||
/// @dev Returns the number of asset(s) (described by assetData) that
|
||||
/// the corresponding AssetProxy contract is authorized to spend. When the asset data contains
|
||||
/// multiple assets (eg for Multi-Asset), the return value indicates
|
||||
/// how many complete "baskets" of those assets may be spent by all of the corresponding
|
||||
/// AssetProxy contracts.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Details of asset, encoded per the AssetProxy contract specification.
|
||||
/// @return Number of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend.
|
||||
function getAssetProxyAllowance(address ownerAddress, bytes memory assetData)
|
||||
public
|
||||
returns (uint256 allowance)
|
||||
{
|
||||
// Get id of AssetProxy contract
|
||||
bytes4 assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
if (assetProxyId == IAssetData(address(0)).MultiAsset.selector) {
|
||||
// Get array of values and array of assetDatas
|
||||
(, uint256[] memory amounts, bytes[] memory nestedAssetData) = LibAssetData.decodeMultiAssetData(assetData);
|
||||
|
||||
uint256 length = nestedAssetData.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
// Skip over the asset if the corresponding amount is 0.
|
||||
if (amounts[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query allowance of individual assetData
|
||||
uint256 totalAllowance = getAssetProxyAllowance(ownerAddress, nestedAssetData[i]);
|
||||
|
||||
// Scale total allowance down by corresponding value in assetData
|
||||
uint256 scaledAllowance = totalAllowance / amounts[i];
|
||||
if (scaledAllowance == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (scaledAllowance < allowance || allowance == 0) {
|
||||
allowance = scaledAllowance;
|
||||
}
|
||||
}
|
||||
return allowance;
|
||||
}
|
||||
|
||||
if (assetProxyId == IAssetData(address(0)).ERC20Token.selector) {
|
||||
// Get ERC20 token address
|
||||
address tokenAddress = assetData.readAddress(16);
|
||||
allowance = LibERC20Token.allowance(tokenAddress, ownerAddress, erc20ProxyAddress);
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC721Token.selector) {
|
||||
// Get ERC721 token address and id
|
||||
(, address tokenAddress, uint256 tokenId) = LibAssetData.decodeERC721AssetData(assetData);
|
||||
|
||||
// Encode data for `isApprovedForAll(ownerAddress, erc721ProxyAddress)`
|
||||
bytes memory isApprovedForAllData = abi.encodeWithSelector(
|
||||
IERC721Token(address(0)).isApprovedForAll.selector,
|
||||
ownerAddress,
|
||||
erc721ProxyAddress
|
||||
);
|
||||
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData);
|
||||
|
||||
// If not approved for all, call `getApproved(tokenId)`
|
||||
if (!success || returnData.length != 32 || returnData.readUint256(0) != 1) {
|
||||
// Encode data for `getApproved(tokenId)`
|
||||
bytes memory getApprovedData = abi.encodeWithSelector(IERC721Token(address(0)).getApproved.selector, tokenId);
|
||||
(success, returnData) = tokenAddress.staticcall(getApprovedData);
|
||||
|
||||
// Allowance is 1 if successful and the approved address is the ERC721Proxy
|
||||
allowance = success && returnData.length == 32 && returnData.readAddress(12) == erc721ProxyAddress ? 1 : 0;
|
||||
} else {
|
||||
// Allowance is 2^256 - 1 if `isApprovedForAll` returned true
|
||||
allowance = _MAX_UINT256;
|
||||
}
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC1155Assets.selector) {
|
||||
// Get ERC1155 token address
|
||||
(, address tokenAddress, , , ) = LibAssetData.decodeERC1155AssetData(assetData);
|
||||
|
||||
// Encode data for `isApprovedForAll(ownerAddress, erc1155ProxyAddress)`
|
||||
bytes memory isApprovedForAllData = abi.encodeWithSelector(
|
||||
IERC1155(address(0)).isApprovedForAll.selector,
|
||||
ownerAddress,
|
||||
erc1155ProxyAddress
|
||||
);
|
||||
|
||||
// Query allowance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData);
|
||||
allowance = success && returnData.length == 32 && returnData.readUint256(0) == 1 ? _MAX_UINT256 : 0;
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).StaticCall.selector) {
|
||||
// The StaticCallProxy does not require any approvals
|
||||
allowance = _MAX_UINT256;
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC20Bridge.selector) {
|
||||
// Get address of ERC20 token and bridge contract
|
||||
(, address tokenAddress, address bridgeAddress,) = LibAssetData.decodeERC20BridgeAssetData(assetData);
|
||||
if (tokenAddress == _getDaiAddress() && bridgeAddress == chaiBridgeAddress) {
|
||||
uint256 chaiAllowance = LibERC20Token.allowance(_getChaiAddress(), ownerAddress, chaiBridgeAddress);
|
||||
// Dai allowance is unlimited if Chai allowance is unlimited
|
||||
allowance = chaiAllowance == _MAX_UINT256 ? _MAX_UINT256 : _convertChaiToDaiAmount(chaiAllowance);
|
||||
}
|
||||
// Allowance will be 0 if bridge is not supported
|
||||
}
|
||||
|
||||
// Allowance will be 0 if the assetProxyId is unknown
|
||||
return allowance;
|
||||
}
|
||||
|
||||
/// @dev Calls getAssetProxyAllowance() for each element of assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Array of asset details, each encoded per the AssetProxy contract specification.
|
||||
/// @return An array of asset allowances from getAllowance(), with each
|
||||
/// element corresponding to the same-indexed element in the assetData input.
|
||||
function getBatchAssetProxyAllowances(address ownerAddress, bytes[] memory assetData)
|
||||
public
|
||||
returns (uint256[] memory allowances)
|
||||
{
|
||||
uint256 length = assetData.length;
|
||||
allowances = new uint256[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
allowances[i] = getAssetProxyAllowance(ownerAddress, assetData[i]);
|
||||
}
|
||||
return allowances;
|
||||
}
|
||||
|
||||
/// @dev Calls getBalance() and getAllowance() for assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Details of asset, encoded per the AssetProxy contract specification.
|
||||
/// @return Number of assets (or asset baskets) held by owner, and number
|
||||
/// of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend.
|
||||
function getBalanceAndAssetProxyAllowance(
|
||||
address ownerAddress,
|
||||
bytes memory assetData
|
||||
)
|
||||
public
|
||||
returns (uint256 balance, uint256 allowance)
|
||||
{
|
||||
balance = getBalance(ownerAddress, assetData);
|
||||
allowance = getAssetProxyAllowance(ownerAddress, assetData);
|
||||
return (balance, allowance);
|
||||
}
|
||||
|
||||
/// @dev Calls getBatchBalances() and getBatchAllowances() for each element of assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Array of asset details, each encoded per the AssetProxy contract specification.
|
||||
/// @return An array of asset balances from getBalance(), and an array of
|
||||
/// asset allowances from getAllowance(), with each element
|
||||
/// corresponding to the same-indexed element in the assetData input.
|
||||
function getBatchBalancesAndAssetProxyAllowances(
|
||||
address ownerAddress,
|
||||
bytes[] memory assetData
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory balances, uint256[] memory allowances)
|
||||
{
|
||||
balances = getBatchBalances(ownerAddress, assetData);
|
||||
allowances = getBatchAssetProxyAllowances(ownerAddress, assetData);
|
||||
return (balances, allowances);
|
||||
}
|
||||
|
||||
/// @dev Converts an amount of Chai into its equivalent Dai amount.
|
||||
/// Also accumulates Dai from DSR if called after the last time it was collected.
|
||||
/// @param chaiAmount Amount of Chai to converts.
|
||||
function _convertChaiToDaiAmount(uint256 chaiAmount)
|
||||
internal
|
||||
returns (uint256 daiAmount)
|
||||
{
|
||||
PotLike pot = IChai(_getChaiAddress()).pot();
|
||||
// Accumulate savings if called after last time savings were collected
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
uint256 chiMultiplier = (now > pot.rho())
|
||||
? pot.drip()
|
||||
: pot.chi();
|
||||
daiAmount = LibMath.getPartialAmountFloor(chiMultiplier, 10**27, chaiAmount);
|
||||
return daiAmount;
|
||||
}
|
||||
|
||||
/// @dev Returns an order MAKER's balance of the assets(s) specified in
|
||||
/// makerAssetData. Unlike `getBalanceAndAssetProxyAllowance()`, this
|
||||
/// can handle maker asset types that depend on taker tokens being
|
||||
/// transferred to the maker first.
|
||||
/// @param order The order.
|
||||
/// @return balance Quantity of assets transferrable from maker to taker.
|
||||
function _getConvertibleMakerBalanceAndAssetProxyAllowance(
|
||||
LibOrder.Order memory order
|
||||
)
|
||||
internal
|
||||
returns (uint256 balance, uint256 allowance)
|
||||
{
|
||||
if (order.makerAssetData.length < 4) {
|
||||
return (0, 0);
|
||||
}
|
||||
return (
|
||||
getBalance(order.makerAddress, order.makerAssetData),
|
||||
getAssetProxyAllowance(order.makerAddress, order.makerAssetData)
|
||||
);
|
||||
}
|
||||
}
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma solidity ^0.5.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol";
|
||||
@@ -24,27 +24,29 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibEIP712.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "./Addresses.sol";
|
||||
import "./OrderValidationUtils.sol";
|
||||
import "./OrderTransferSimulationUtils.sol";
|
||||
import "./EthBalanceChecker.sol";
|
||||
import "./ExternalFunctions.sol";
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract DevUtils is
|
||||
Addresses,
|
||||
OrderValidationUtils,
|
||||
LibEIP712ExchangeDomain,
|
||||
EthBalanceChecker
|
||||
EthBalanceChecker,
|
||||
ExternalFunctions
|
||||
{
|
||||
constructor (
|
||||
address _exchange,
|
||||
address _chaiBridge
|
||||
address exchange_,
|
||||
address chaiBridge_
|
||||
)
|
||||
public
|
||||
OrderValidationUtils(
|
||||
_exchange,
|
||||
_chaiBridge
|
||||
Addresses(
|
||||
exchange_,
|
||||
chaiBridge_
|
||||
)
|
||||
OrderTransferSimulationUtils(_exchange)
|
||||
LibEIP712ExchangeDomain(uint256(0), address(0)) // null args because because we only use constants
|
||||
{}
|
||||
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma solidity ^0.5.16;
|
||||
|
||||
|
||||
contract EthBalanceChecker {
|
||||
|
322
contracts/dev-utils/contracts/src/ExternalFunctions.sol
Normal file
322
contracts/dev-utils/contracts/src/ExternalFunctions.sol
Normal file
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
|
||||
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.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./Addresses.sol";
|
||||
import "./LibAssetData.sol";
|
||||
import "./LibTransactionDecoder.sol";
|
||||
import "./LibOrderTransferSimulation.sol";
|
||||
|
||||
|
||||
contract ExternalFunctions is
|
||||
Addresses
|
||||
{
|
||||
|
||||
/// @dev Decodes the call data for an Exchange contract method call.
|
||||
/// @param transactionData ABI-encoded calldata for an Exchange
|
||||
/// contract method call.
|
||||
/// @return The name of the function called, and the parameters it was
|
||||
/// given. For single-order fills and cancels, the arrays will have
|
||||
/// just one element.
|
||||
function decodeZeroExTransactionData(bytes memory transactionData)
|
||||
public
|
||||
pure
|
||||
returns(
|
||||
string memory functionName,
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
bytes[] memory signatures
|
||||
)
|
||||
{
|
||||
return LibTransactionDecoder.decodeZeroExTransactionData(transactionData);
|
||||
}
|
||||
|
||||
/// @dev Decode AssetProxy identifier
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-20, ERC-721, ERC1155, or MultiAsset asset.
|
||||
/// @return The AssetProxy identifier
|
||||
function decodeAssetProxyId(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId
|
||||
)
|
||||
{
|
||||
return LibAssetData.decodeAssetProxyId(assetData);
|
||||
}
|
||||
|
||||
/// @dev Encode ERC-20 asset data into the format described in the AssetProxy contract specification.
|
||||
/// @param tokenAddress The address of the ERC-20 contract hosting the asset to be traded.
|
||||
/// @return AssetProxy-compliant data describing the asset.
|
||||
function encodeERC20AssetData(address tokenAddress)
|
||||
public
|
||||
pure
|
||||
returns (bytes memory assetData)
|
||||
{
|
||||
return LibAssetData.encodeERC20AssetData(tokenAddress);
|
||||
}
|
||||
|
||||
/// @dev Decode ERC-20 asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-20 asset.
|
||||
/// @return The AssetProxy identifier, and the address of the ERC-20
|
||||
/// contract hosting this asset.
|
||||
function decodeERC20AssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
address tokenAddress
|
||||
)
|
||||
{
|
||||
return LibAssetData.decodeERC20AssetData(assetData);
|
||||
}
|
||||
|
||||
/// @dev Encode ERC-721 asset data into the format described in the AssetProxy specification.
|
||||
/// @param tokenAddress The address of the ERC-721 contract hosting the asset to be traded.
|
||||
/// @param tokenId The identifier of the specific asset to be traded.
|
||||
/// @return AssetProxy-compliant asset data describing the asset.
|
||||
function encodeERC721AssetData(address tokenAddress, uint256 tokenId)
|
||||
public
|
||||
pure
|
||||
returns (bytes memory assetData)
|
||||
{
|
||||
return LibAssetData.encodeERC721AssetData(tokenAddress, tokenId);
|
||||
}
|
||||
|
||||
/// @dev Decode ERC-721 asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-721 asset.
|
||||
/// @return The ERC-721 AssetProxy identifier, the address of the ERC-721
|
||||
/// contract hosting this asset, and the identifier of the specific
|
||||
/// asset to be traded.
|
||||
function decodeERC721AssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
address tokenAddress,
|
||||
uint256 tokenId
|
||||
)
|
||||
{
|
||||
return LibAssetData.decodeERC721AssetData(assetData);
|
||||
}
|
||||
|
||||
/// @dev Encode ERC-1155 asset data into the format described in the AssetProxy contract specification.
|
||||
/// @param tokenAddress The address of the ERC-1155 contract hosting the asset(s) to be traded.
|
||||
/// @param tokenIds The identifiers of the specific assets to be traded.
|
||||
/// @param tokenValues The amounts of each asset to be traded.
|
||||
/// @param callbackData Data to be passed to receiving contracts when a transfer is performed.
|
||||
/// @return AssetProxy-compliant asset data describing the set of assets.
|
||||
function encodeERC1155AssetData(
|
||||
address tokenAddress,
|
||||
uint256[] memory tokenIds,
|
||||
uint256[] memory tokenValues,
|
||||
bytes memory callbackData
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (bytes memory assetData)
|
||||
{
|
||||
return LibAssetData.encodeERC1155AssetData(
|
||||
tokenAddress,
|
||||
tokenIds,
|
||||
tokenValues,
|
||||
callbackData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Decode ERC-1155 asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-1155 set of assets.
|
||||
/// @return The ERC-1155 AssetProxy identifier, the address of the ERC-1155
|
||||
/// contract hosting the assets, an array of the identifiers of the
|
||||
/// assets to be traded, an array of asset amounts to be traded, and
|
||||
/// callback data. Each element of the arrays corresponds to the
|
||||
/// same-indexed element of the other array. Return values specified as
|
||||
/// `memory` are returned as pointers to locations within the memory of
|
||||
/// the input parameter `assetData`.
|
||||
function decodeERC1155AssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
address tokenAddress,
|
||||
uint256[] memory tokenIds,
|
||||
uint256[] memory tokenValues,
|
||||
bytes memory callbackData
|
||||
)
|
||||
{
|
||||
return LibAssetData.decodeERC1155AssetData(assetData);
|
||||
}
|
||||
|
||||
/// @dev Encode data for multiple assets, per the AssetProxy contract specification.
|
||||
/// @param amounts The amounts of each asset to be traded.
|
||||
/// @param nestedAssetData AssetProxy-compliant data describing each asset to be traded.
|
||||
/// @return AssetProxy-compliant data describing the set of assets.
|
||||
function encodeMultiAssetData(uint256[] memory amounts, bytes[] memory nestedAssetData)
|
||||
public
|
||||
pure
|
||||
returns (bytes memory assetData)
|
||||
{
|
||||
return LibAssetData.encodeMultiAssetData(amounts, nestedAssetData);
|
||||
}
|
||||
|
||||
/// @dev Decode multi-asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant data describing a multi-asset basket.
|
||||
/// @return The Multi-Asset AssetProxy identifier, an array of the amounts
|
||||
/// of the assets to be traded, and an array of the
|
||||
/// AssetProxy-compliant data describing each asset to be traded. Each
|
||||
/// element of the arrays corresponds to the same-indexed element of the other array.
|
||||
function decodeMultiAssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
uint256[] memory amounts,
|
||||
bytes[] memory nestedAssetData
|
||||
)
|
||||
{
|
||||
return LibAssetData.decodeMultiAssetData(assetData);
|
||||
}
|
||||
|
||||
/// @dev Encode StaticCall asset data into the format described in the AssetProxy contract specification.
|
||||
/// @param staticCallTargetAddress Target address of StaticCall.
|
||||
/// @param staticCallData Data that will be passed to staticCallTargetAddress in the StaticCall.
|
||||
/// @param expectedReturnDataHash Expected Keccak-256 hash of the StaticCall return data.
|
||||
/// @return AssetProxy-compliant asset data describing the set of assets.
|
||||
function encodeStaticCallAssetData(
|
||||
address staticCallTargetAddress,
|
||||
bytes memory staticCallData,
|
||||
bytes32 expectedReturnDataHash
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (bytes memory assetData)
|
||||
{
|
||||
return LibAssetData.encodeStaticCallAssetData(
|
||||
staticCallTargetAddress,
|
||||
staticCallData,
|
||||
expectedReturnDataHash
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Decode StaticCall asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant asset data describing a StaticCall asset
|
||||
/// @return The StaticCall AssetProxy identifier, the target address of the StaticCAll, the data to be
|
||||
/// passed to the target address, and the expected Keccak-256 hash of the static call return data.
|
||||
function decodeStaticCallAssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
address staticCallTargetAddress,
|
||||
bytes memory staticCallData,
|
||||
bytes32 expectedReturnDataHash
|
||||
)
|
||||
{
|
||||
return LibAssetData.decodeStaticCallAssetData(assetData);
|
||||
}
|
||||
|
||||
/// @dev Decode ERC20Bridge asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC20Bridge asset
|
||||
/// @return The ERC20BridgeProxy identifier, the address of the ERC20 token to transfer, the address
|
||||
/// of the bridge contract, and extra data to be passed to the bridge contract.
|
||||
function decodeERC20BridgeAssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
address tokenAddress,
|
||||
address bridgeAddress,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
{
|
||||
return LibAssetData.decodeERC20BridgeAssetData(assetData);
|
||||
}
|
||||
|
||||
/// @dev Reverts if assetData is not of a valid format for its given proxy id.
|
||||
/// @param assetData AssetProxy compliant asset data.
|
||||
function revertIfInvalidAssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
{
|
||||
return LibAssetData.revertIfInvalidAssetData(assetData);
|
||||
}
|
||||
|
||||
/// @dev Simulates the maker transfers within an order and returns the index of the first failed transfer.
|
||||
/// @param order The order to simulate transfers for.
|
||||
/// @param takerAddress The address of the taker that will fill the order.
|
||||
/// @param takerAssetFillAmount The amount of takerAsset that the taker wished to fill.
|
||||
/// @return The index of the first failed transfer (or 4 if all transfers are successful).
|
||||
function getSimulatedOrderMakerTransferResults(
|
||||
LibOrder.Order memory order,
|
||||
address takerAddress,
|
||||
uint256 takerAssetFillAmount
|
||||
)
|
||||
public
|
||||
returns (LibOrderTransferSimulation.OrderTransferResults orderTransferResults)
|
||||
{
|
||||
return LibOrderTransferSimulation.getSimulatedOrderMakerTransferResults(
|
||||
exchangeAddress,
|
||||
order,
|
||||
takerAddress,
|
||||
takerAssetFillAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Simulates all of the transfers within an order and returns the index of the first failed transfer.
|
||||
/// @param order The order to simulate transfers for.
|
||||
/// @param takerAddress The address of the taker that will fill the order.
|
||||
/// @param takerAssetFillAmount The amount of takerAsset that the taker wished to fill.
|
||||
/// @return The index of the first failed transfer (or 4 if all transfers are successful).
|
||||
function getSimulatedOrderTransferResults(
|
||||
LibOrder.Order memory order,
|
||||
address takerAddress,
|
||||
uint256 takerAssetFillAmount
|
||||
)
|
||||
public
|
||||
returns (LibOrderTransferSimulation.OrderTransferResults orderTransferResults)
|
||||
{
|
||||
return LibOrderTransferSimulation.getSimulatedOrderTransferResults(
|
||||
exchangeAddress,
|
||||
order,
|
||||
takerAddress,
|
||||
takerAssetFillAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Simulates all of the transfers for each given order and returns the indices of each first failed transfer.
|
||||
/// @param orders Array of orders to individually simulate transfers for.
|
||||
/// @param takerAddresses Array of addresses of takers that will fill each order.
|
||||
/// @param takerAssetFillAmounts Array of amounts of takerAsset that will be filled for each order.
|
||||
/// @return The indices of the first failed transfer (or 4 if all transfers are successful) for each order.
|
||||
function getSimulatedOrdersTransferResults(
|
||||
LibOrder.Order[] memory orders,
|
||||
address[] memory takerAddresses,
|
||||
uint256[] memory takerAssetFillAmounts
|
||||
)
|
||||
public
|
||||
returns (LibOrderTransferSimulation.OrderTransferResults[] memory orderTransferResults)
|
||||
{
|
||||
return LibOrderTransferSimulation.getSimulatedOrdersTransferResults(
|
||||
exchangeAddress,
|
||||
orders,
|
||||
takerAddresses,
|
||||
takerAssetFillAmounts
|
||||
);
|
||||
}
|
||||
}
|
@@ -16,357 +16,17 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma solidity ^0.5.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
||||
import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
|
||||
import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IChai.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
|
||||
|
||||
contract LibAssetData is
|
||||
DeploymentConstants
|
||||
{
|
||||
// 2^256 - 1
|
||||
uint256 constant internal _MAX_UINT256 = uint256(-1);
|
||||
library LibAssetData {
|
||||
|
||||
using LibBytes for bytes;
|
||||
|
||||
// solhint-disable var-name-mixedcase
|
||||
IExchange internal _EXCHANGE;
|
||||
address internal _ERC20_PROXY_ADDRESS;
|
||||
address internal _ERC721_PROXY_ADDRESS;
|
||||
address internal _ERC1155_PROXY_ADDRESS;
|
||||
address internal _STATIC_CALL_PROXY_ADDRESS;
|
||||
address internal _CHAI_BRIDGE_ADDRESS;
|
||||
// solhint-enable var-name-mixedcase
|
||||
|
||||
constructor (
|
||||
address _exchange,
|
||||
address _chaiBridge
|
||||
)
|
||||
public
|
||||
{
|
||||
_EXCHANGE = IExchange(_exchange);
|
||||
_ERC20_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).ERC20Token.selector);
|
||||
_ERC721_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).ERC721Token.selector);
|
||||
_ERC1155_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).ERC1155Assets.selector);
|
||||
_STATIC_CALL_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).StaticCall.selector);
|
||||
_CHAI_BRIDGE_ADDRESS = _chaiBridge;
|
||||
}
|
||||
|
||||
/// @dev Returns the owner's balance of the assets(s) specified in
|
||||
/// assetData. When the asset data contains multiple assets (eg in
|
||||
/// ERC1155 or Multi-Asset), the return value indicates how many
|
||||
/// complete "baskets" of those assets are owned by owner.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Details of asset, encoded per the AssetProxy contract specification.
|
||||
/// @return Number of assets (or asset baskets) held by owner.
|
||||
function getBalance(address ownerAddress, bytes memory assetData)
|
||||
public
|
||||
returns (uint256 balance)
|
||||
{
|
||||
// Get id of AssetProxy contract
|
||||
bytes4 assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
if (assetProxyId == IAssetData(address(0)).ERC20Token.selector) {
|
||||
// Get ERC20 token address
|
||||
address tokenAddress = assetData.readAddress(16);
|
||||
balance = _erc20BalanceOf(tokenAddress, ownerAddress);
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC721Token.selector) {
|
||||
// Get ERC721 token address and id
|
||||
(, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData);
|
||||
|
||||
// Check if id is owned by ownerAddress
|
||||
bytes memory ownerOfCalldata = abi.encodeWithSelector(
|
||||
IERC721Token(address(0)).ownerOf.selector,
|
||||
tokenId
|
||||
);
|
||||
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(ownerOfCalldata);
|
||||
address currentOwnerAddress = (success && returnData.length == 32) ? returnData.readAddress(12) : address(0);
|
||||
balance = currentOwnerAddress == ownerAddress ? 1 : 0;
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC1155Assets.selector) {
|
||||
// Get ERC1155 token address, array of ids, and array of values
|
||||
(, address tokenAddress, uint256[] memory tokenIds, uint256[] memory tokenValues,) = decodeERC1155AssetData(assetData);
|
||||
|
||||
uint256 length = tokenIds.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
// Skip over the token if the corresponding value is 0.
|
||||
if (tokenValues[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Encode data for `balanceOf(ownerAddress, tokenIds[i])
|
||||
bytes memory balanceOfData = abi.encodeWithSelector(
|
||||
IERC1155(address(0)).balanceOf.selector,
|
||||
ownerAddress,
|
||||
tokenIds[i]
|
||||
);
|
||||
|
||||
// Query balance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData);
|
||||
uint256 totalBalance = success && returnData.length == 32 ? returnData.readUint256(0) : 0;
|
||||
|
||||
// Scale total balance down by corresponding value in assetData
|
||||
uint256 scaledBalance = totalBalance / tokenValues[i];
|
||||
if (scaledBalance == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (scaledBalance < balance || balance == 0) {
|
||||
balance = scaledBalance;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).StaticCall.selector) {
|
||||
// Encode data for `staticCallProxy.transferFrom(assetData,...)`
|
||||
bytes memory transferFromData = abi.encodeWithSelector(
|
||||
IAssetProxy(address(0)).transferFrom.selector,
|
||||
assetData,
|
||||
address(0), // `from` address is not used
|
||||
address(0), // `to` address is not used
|
||||
0 // `amount` is not used
|
||||
);
|
||||
|
||||
// Check if staticcall would be successful
|
||||
(bool success,) = _STATIC_CALL_PROXY_ADDRESS.staticcall(transferFromData);
|
||||
|
||||
// Success means that the staticcall can be made an unlimited amount of times
|
||||
balance = success ? _MAX_UINT256 : 0;
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC20Bridge.selector) {
|
||||
// Get address of ERC20 token and bridge contract
|
||||
(, address tokenAddress, address bridgeAddress,) = decodeERC20BridgeAssetData(assetData);
|
||||
if (tokenAddress == _getDaiAddress() && bridgeAddress == _CHAI_BRIDGE_ADDRESS) {
|
||||
uint256 chaiBalance = _erc20BalanceOf(_getChaiAddress(), ownerAddress);
|
||||
// Calculate Dai balance
|
||||
balance = _convertChaiToDaiAmount(chaiBalance);
|
||||
}
|
||||
// Balance will be 0 if bridge is not supported
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).MultiAsset.selector) {
|
||||
// Get array of values and array of assetDatas
|
||||
(, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData);
|
||||
|
||||
uint256 length = nestedAssetData.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
// Skip over the asset if the corresponding amount is 0.
|
||||
if (assetAmounts[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query balance of individual assetData
|
||||
uint256 totalBalance = getBalance(ownerAddress, nestedAssetData[i]);
|
||||
|
||||
// Scale total balance down by corresponding value in assetData
|
||||
uint256 scaledBalance = totalBalance / assetAmounts[i];
|
||||
if (scaledBalance == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (scaledBalance < balance || balance == 0) {
|
||||
balance = scaledBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Balance will be 0 if assetProxyId is unknown
|
||||
return balance;
|
||||
}
|
||||
|
||||
/// @dev Calls getBalance() for each element of assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Array of asset details, each encoded per the AssetProxy contract specification.
|
||||
/// @return Array of asset balances from getBalance(), with each element
|
||||
/// corresponding to the same-indexed element in the assetData input.
|
||||
function getBatchBalances(address ownerAddress, bytes[] memory assetData)
|
||||
public
|
||||
returns (uint256[] memory balances)
|
||||
{
|
||||
uint256 length = assetData.length;
|
||||
balances = new uint256[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
balances[i] = getBalance(ownerAddress, assetData[i]);
|
||||
}
|
||||
return balances;
|
||||
}
|
||||
|
||||
/// @dev Returns the number of asset(s) (described by assetData) that
|
||||
/// the corresponding AssetProxy contract is authorized to spend. When the asset data contains
|
||||
/// multiple assets (eg for Multi-Asset), the return value indicates
|
||||
/// how many complete "baskets" of those assets may be spent by all of the corresponding
|
||||
/// AssetProxy contracts.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Details of asset, encoded per the AssetProxy contract specification.
|
||||
/// @return Number of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend.
|
||||
function getAssetProxyAllowance(address ownerAddress, bytes memory assetData)
|
||||
public
|
||||
returns (uint256 allowance)
|
||||
{
|
||||
// Get id of AssetProxy contract
|
||||
bytes4 assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
if (assetProxyId == IAssetData(address(0)).MultiAsset.selector) {
|
||||
// Get array of values and array of assetDatas
|
||||
(, uint256[] memory amounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData);
|
||||
|
||||
uint256 length = nestedAssetData.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
// Skip over the asset if the corresponding amount is 0.
|
||||
if (amounts[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query allowance of individual assetData
|
||||
uint256 totalAllowance = getAssetProxyAllowance(ownerAddress, nestedAssetData[i]);
|
||||
|
||||
// Scale total allowance down by corresponding value in assetData
|
||||
uint256 scaledAllowance = totalAllowance / amounts[i];
|
||||
if (scaledAllowance == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (scaledAllowance < allowance || allowance == 0) {
|
||||
allowance = scaledAllowance;
|
||||
}
|
||||
}
|
||||
return allowance;
|
||||
}
|
||||
|
||||
if (assetProxyId == IAssetData(address(0)).ERC20Token.selector) {
|
||||
// Get ERC20 token address
|
||||
address tokenAddress = assetData.readAddress(16);
|
||||
|
||||
// Encode data for `allowance(ownerAddress, _ERC20_PROXY_ADDRESS)`
|
||||
bytes memory allowanceData = abi.encodeWithSelector(
|
||||
IERC20Token(address(0)).allowance.selector,
|
||||
ownerAddress,
|
||||
_ERC20_PROXY_ADDRESS
|
||||
);
|
||||
|
||||
// Query allowance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(allowanceData);
|
||||
allowance = success && returnData.length == 32 ? returnData.readUint256(0) : 0;
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC721Token.selector) {
|
||||
// Get ERC721 token address and id
|
||||
(, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData);
|
||||
|
||||
// Encode data for `isApprovedForAll(ownerAddress, _ERC721_PROXY_ADDRESS)`
|
||||
bytes memory isApprovedForAllData = abi.encodeWithSelector(
|
||||
IERC721Token(address(0)).isApprovedForAll.selector,
|
||||
ownerAddress,
|
||||
_ERC721_PROXY_ADDRESS
|
||||
);
|
||||
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData);
|
||||
|
||||
// If not approved for all, call `getApproved(tokenId)`
|
||||
if (!success || returnData.length != 32 || returnData.readUint256(0) != 1) {
|
||||
// Encode data for `getApproved(tokenId)`
|
||||
bytes memory getApprovedData = abi.encodeWithSelector(IERC721Token(address(0)).getApproved.selector, tokenId);
|
||||
(success, returnData) = tokenAddress.staticcall(getApprovedData);
|
||||
|
||||
// Allowance is 1 if successful and the approved address is the ERC721Proxy
|
||||
allowance = success && returnData.length == 32 && returnData.readAddress(12) == _ERC721_PROXY_ADDRESS ? 1 : 0;
|
||||
} else {
|
||||
// Allowance is 2^256 - 1 if `isApprovedForAll` returned true
|
||||
allowance = _MAX_UINT256;
|
||||
}
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC1155Assets.selector) {
|
||||
// Get ERC1155 token address
|
||||
(, address tokenAddress, , , ) = decodeERC1155AssetData(assetData);
|
||||
|
||||
// Encode data for `isApprovedForAll(ownerAddress, _ERC1155_PROXY_ADDRESS)`
|
||||
bytes memory isApprovedForAllData = abi.encodeWithSelector(
|
||||
IERC1155(address(0)).isApprovedForAll.selector,
|
||||
ownerAddress,
|
||||
_ERC1155_PROXY_ADDRESS
|
||||
);
|
||||
|
||||
// Query allowance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData);
|
||||
allowance = success && returnData.length == 32 && returnData.readUint256(0) == 1 ? _MAX_UINT256 : 0;
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).StaticCall.selector) {
|
||||
// The StaticCallProxy does not require any approvals
|
||||
allowance = _MAX_UINT256;
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC20Bridge.selector) {
|
||||
// Get address of ERC20 token and bridge contract
|
||||
(, address tokenAddress, address bridgeAddress,) = decodeERC20BridgeAssetData(assetData);
|
||||
if (tokenAddress == _getDaiAddress() && bridgeAddress == _CHAI_BRIDGE_ADDRESS) {
|
||||
bytes memory allowanceData = abi.encodeWithSelector(
|
||||
IERC20Token(address(0)).allowance.selector,
|
||||
ownerAddress,
|
||||
_CHAI_BRIDGE_ADDRESS
|
||||
);
|
||||
(bool success, bytes memory returnData) = _getChaiAddress().staticcall(allowanceData);
|
||||
uint256 chaiAllowance = success && returnData.length == 32 ? returnData.readUint256(0) : 0;
|
||||
// Dai allowance is unlimited if Chai allowance is unlimited
|
||||
allowance = chaiAllowance == _MAX_UINT256 ? _MAX_UINT256 : _convertChaiToDaiAmount(chaiAllowance);
|
||||
}
|
||||
// Allowance will be 0 if bridge is not supported
|
||||
}
|
||||
|
||||
// Allowance will be 0 if the assetProxyId is unknown
|
||||
return allowance;
|
||||
}
|
||||
|
||||
/// @dev Calls getAssetProxyAllowance() for each element of assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Array of asset details, each encoded per the AssetProxy contract specification.
|
||||
/// @return An array of asset allowances from getAllowance(), with each
|
||||
/// element corresponding to the same-indexed element in the assetData input.
|
||||
function getBatchAssetProxyAllowances(address ownerAddress, bytes[] memory assetData)
|
||||
public
|
||||
returns (uint256[] memory allowances)
|
||||
{
|
||||
uint256 length = assetData.length;
|
||||
allowances = new uint256[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
allowances[i] = getAssetProxyAllowance(ownerAddress, assetData[i]);
|
||||
}
|
||||
return allowances;
|
||||
}
|
||||
|
||||
/// @dev Calls getBalance() and getAllowance() for assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Details of asset, encoded per the AssetProxy contract specification.
|
||||
/// @return Number of assets (or asset baskets) held by owner, and number
|
||||
/// of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend.
|
||||
function getBalanceAndAssetProxyAllowance(address ownerAddress, bytes memory assetData)
|
||||
public
|
||||
returns (uint256 balance, uint256 allowance)
|
||||
{
|
||||
balance = getBalance(ownerAddress, assetData);
|
||||
allowance = getAssetProxyAllowance(ownerAddress, assetData);
|
||||
return (balance, allowance);
|
||||
}
|
||||
|
||||
/// @dev Calls getBatchBalances() and getBatchAllowances() for each element of assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Array of asset details, each encoded per the AssetProxy contract specification.
|
||||
/// @return An array of asset balances from getBalance(), and an array of
|
||||
/// asset allowances from getAllowance(), with each element
|
||||
/// corresponding to the same-indexed element in the assetData input.
|
||||
function getBatchBalancesAndAssetProxyAllowances(address ownerAddress, bytes[] memory assetData)
|
||||
public
|
||||
returns (uint256[] memory balances, uint256[] memory allowances)
|
||||
{
|
||||
balances = getBatchBalances(ownerAddress, assetData);
|
||||
allowances = getBatchAssetProxyAllowances(ownerAddress, assetData);
|
||||
return (balances, allowances);
|
||||
}
|
||||
|
||||
/// @dev Decode AssetProxy identifier
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-20, ERC-721, ERC1155, or MultiAsset asset.
|
||||
/// @return The AssetProxy identifier
|
||||
@@ -691,44 +351,4 @@ contract LibAssetData is
|
||||
revert("WRONG_PROXY_ID");
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Queries balance of an ERC20 token. Returns 0 if call was unsuccessful.
|
||||
/// @param tokenAddress Address of ERC20 token.
|
||||
/// @param ownerAddress Address of owner of ERC20 token.
|
||||
/// @return balance ERC20 token balance of owner.
|
||||
function _erc20BalanceOf(
|
||||
address tokenAddress,
|
||||
address ownerAddress
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256 balance)
|
||||
{
|
||||
// Encode data for `balanceOf(ownerAddress)`
|
||||
bytes memory balanceOfData = abi.encodeWithSelector(
|
||||
IERC20Token(address(0)).balanceOf.selector,
|
||||
ownerAddress
|
||||
);
|
||||
|
||||
// Query balance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData);
|
||||
balance = success && returnData.length == 32 ? returnData.readUint256(0) : 0;
|
||||
return balance;
|
||||
}
|
||||
|
||||
/// @dev Converts an amount of Chai into its equivalent Dai amount.
|
||||
/// Also accumulates Dai from DSR if called after the last time it was collected.
|
||||
/// @param chaiAmount Amount of Chai to converts.
|
||||
function _convertChaiToDaiAmount(uint256 chaiAmount)
|
||||
internal
|
||||
returns (uint256 daiAmount)
|
||||
{
|
||||
PotLike pot = IChai(_getChaiAddress()).pot();
|
||||
// Accumulate savings if called after last time savings were collected
|
||||
uint256 chiMultiplier = (now > pot.rho())
|
||||
? pot.drip()
|
||||
: pot.chi();
|
||||
daiAmount = LibMath.getPartialAmountFloor(chiMultiplier, 10**27, chaiAmount);
|
||||
return daiAmount;
|
||||
}
|
||||
}
|
||||
|
227
contracts/dev-utils/contracts/src/LibOrderTransferSimulation.sol
Normal file
227
contracts/dev-utils/contracts/src/LibOrderTransferSimulation.sol
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
|
||||
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.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/libs/LibExchangeRichErrorDecoder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.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/LibBytes.sol";
|
||||
|
||||
|
||||
library LibOrderTransferSimulation {
|
||||
using LibBytes for bytes;
|
||||
|
||||
enum OrderTransferResults {
|
||||
TakerAssetDataFailed, // Transfer of takerAsset failed
|
||||
MakerAssetDataFailed, // Transfer of makerAsset failed
|
||||
TakerFeeAssetDataFailed, // Transfer of takerFeeAsset failed
|
||||
MakerFeeAssetDataFailed, // Transfer of makerFeeAsset failed
|
||||
TransfersSuccessful // All transfers in the order were successful
|
||||
}
|
||||
|
||||
// NOTE(jalextowle): This is a random address that we use to avoid issues that addresses like `address(1)`
|
||||
// may cause later.
|
||||
address constant internal UNUSED_ADDRESS = address(0x377f698C4c287018D09b516F415317aEC5919332);
|
||||
|
||||
// keccak256(abi.encodeWithSignature("Error(string)", "TRANSFERS_SUCCESSFUL"));
|
||||
bytes32 constant internal _TRANSFERS_SUCCESSFUL_RESULT_HASH = 0xf43f26ea5a94b478394a975e856464913dc1a8a1ca70939d974aa7c238aa0ce0;
|
||||
|
||||
/// @dev Simulates the maker transfers within an order and returns the index of the first failed transfer.
|
||||
/// @param order The order to simulate transfers for.
|
||||
/// @param takerAddress The address of the taker that will fill the order.
|
||||
/// @param takerAssetFillAmount The amount of takerAsset that the taker wished to fill.
|
||||
/// @return The index of the first failed transfer (or 4 if all transfers are successful).
|
||||
function getSimulatedOrderMakerTransferResults(
|
||||
address exchange,
|
||||
LibOrder.Order memory order,
|
||||
address takerAddress,
|
||||
uint256 takerAssetFillAmount
|
||||
)
|
||||
public
|
||||
returns (OrderTransferResults orderTransferResults)
|
||||
{
|
||||
LibFillResults.FillResults memory fillResults = LibFillResults.calculateFillResults(
|
||||
order,
|
||||
takerAssetFillAmount,
|
||||
IExchange(exchange).protocolFeeMultiplier(),
|
||||
tx.gasprice
|
||||
);
|
||||
|
||||
bytes[] memory assetData = new bytes[](2);
|
||||
address[] memory fromAddresses = new address[](2);
|
||||
address[] memory toAddresses = new address[](2);
|
||||
uint256[] memory amounts = new uint256[](2);
|
||||
|
||||
// Transfer `makerAsset` from maker to taker
|
||||
assetData[0] = order.makerAssetData;
|
||||
fromAddresses[0] = order.makerAddress;
|
||||
toAddresses[0] = takerAddress == address(0) ? UNUSED_ADDRESS : takerAddress;
|
||||
amounts[0] = fillResults.makerAssetFilledAmount;
|
||||
|
||||
// Transfer `makerFeeAsset` from maker to feeRecipient
|
||||
assetData[1] = order.makerFeeAssetData;
|
||||
fromAddresses[1] = order.makerAddress;
|
||||
toAddresses[1] = order.feeRecipientAddress == address(0) ? UNUSED_ADDRESS : order.feeRecipientAddress;
|
||||
amounts[1] = fillResults.makerFeePaid;
|
||||
|
||||
return _simulateTransferFromCalls(
|
||||
exchange,
|
||||
assetData,
|
||||
fromAddresses,
|
||||
toAddresses,
|
||||
amounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Simulates all of the transfers within an order and returns the index of the first failed transfer.
|
||||
/// @param order The order to simulate transfers for.
|
||||
/// @param takerAddress The address of the taker that will fill the order.
|
||||
/// @param takerAssetFillAmount The amount of takerAsset that the taker wished to fill.
|
||||
/// @return The index of the first failed transfer (or 4 if all transfers are successful).
|
||||
function getSimulatedOrderTransferResults(
|
||||
address exchange,
|
||||
LibOrder.Order memory order,
|
||||
address takerAddress,
|
||||
uint256 takerAssetFillAmount
|
||||
)
|
||||
public
|
||||
returns (OrderTransferResults orderTransferResults)
|
||||
{
|
||||
LibFillResults.FillResults memory fillResults = LibFillResults.calculateFillResults(
|
||||
order,
|
||||
takerAssetFillAmount,
|
||||
IExchange(exchange).protocolFeeMultiplier(),
|
||||
tx.gasprice
|
||||
);
|
||||
|
||||
// Create input arrays
|
||||
bytes[] memory assetData = new bytes[](4);
|
||||
address[] memory fromAddresses = new address[](4);
|
||||
address[] memory toAddresses = new address[](4);
|
||||
uint256[] memory amounts = new uint256[](4);
|
||||
|
||||
// Transfer `takerAsset` from taker to maker
|
||||
assetData[0] = order.takerAssetData;
|
||||
fromAddresses[0] = takerAddress;
|
||||
toAddresses[0] = order.makerAddress;
|
||||
amounts[0] = takerAssetFillAmount;
|
||||
|
||||
// Transfer `makerAsset` from maker to taker
|
||||
assetData[1] = order.makerAssetData;
|
||||
fromAddresses[1] = order.makerAddress;
|
||||
toAddresses[1] = takerAddress == address(0) ? UNUSED_ADDRESS : takerAddress;
|
||||
amounts[1] = fillResults.makerAssetFilledAmount;
|
||||
|
||||
// Transfer `takerFeeAsset` from taker to feeRecipient
|
||||
assetData[2] = order.takerFeeAssetData;
|
||||
fromAddresses[2] = takerAddress;
|
||||
toAddresses[2] = order.feeRecipientAddress == address(0) ? UNUSED_ADDRESS : order.feeRecipientAddress;
|
||||
amounts[2] = fillResults.takerFeePaid;
|
||||
|
||||
// Transfer `makerFeeAsset` from maker to feeRecipient
|
||||
assetData[3] = order.makerFeeAssetData;
|
||||
fromAddresses[3] = order.makerAddress;
|
||||
toAddresses[3] = order.feeRecipientAddress == address(0) ? UNUSED_ADDRESS : order.feeRecipientAddress;
|
||||
amounts[3] = fillResults.makerFeePaid;
|
||||
|
||||
return _simulateTransferFromCalls(
|
||||
exchange,
|
||||
assetData,
|
||||
fromAddresses,
|
||||
toAddresses,
|
||||
amounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Simulates all of the transfers for each given order and returns the indices of each first failed transfer.
|
||||
/// @param orders Array of orders to individually simulate transfers for.
|
||||
/// @param takerAddresses Array of addresses of takers that will fill each order.
|
||||
/// @param takerAssetFillAmounts Array of amounts of takerAsset that will be filled for each order.
|
||||
/// @return The indices of the first failed transfer (or 4 if all transfers are successful) for each order.
|
||||
function getSimulatedOrdersTransferResults(
|
||||
address exchange,
|
||||
LibOrder.Order[] memory orders,
|
||||
address[] memory takerAddresses,
|
||||
uint256[] memory takerAssetFillAmounts
|
||||
)
|
||||
public
|
||||
returns (OrderTransferResults[] memory orderTransferResults)
|
||||
{
|
||||
uint256 length = orders.length;
|
||||
orderTransferResults = new OrderTransferResults[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
orderTransferResults[i] = getSimulatedOrderTransferResults(
|
||||
exchange,
|
||||
orders[i],
|
||||
takerAddresses[i],
|
||||
takerAssetFillAmounts[i]
|
||||
);
|
||||
}
|
||||
return orderTransferResults;
|
||||
}
|
||||
|
||||
/// @dev Makes the simulation call with information about the transfers and processes
|
||||
/// the returndata.
|
||||
/// @param assetData The assetdata to use to make transfers.
|
||||
/// @param fromAddresses The addresses to transfer funds.
|
||||
/// @param toAddresses The addresses that will receive funds
|
||||
/// @param amounts The amounts involved in the transfer.
|
||||
function _simulateTransferFromCalls(
|
||||
address exchange,
|
||||
bytes[] memory assetData,
|
||||
address[] memory fromAddresses,
|
||||
address[] memory toAddresses,
|
||||
uint256[] memory amounts
|
||||
)
|
||||
private
|
||||
returns (OrderTransferResults orderTransferResults)
|
||||
{
|
||||
// Encode data for `simulateDispatchTransferFromCalls(assetData, fromAddresses, toAddresses, amounts)`
|
||||
bytes memory simulateDispatchTransferFromCallsData = abi.encodeWithSelector(
|
||||
IExchange(address(0)).simulateDispatchTransferFromCalls.selector,
|
||||
assetData,
|
||||
fromAddresses,
|
||||
toAddresses,
|
||||
amounts
|
||||
);
|
||||
|
||||
// Perform call and catch revert
|
||||
(, bytes memory returnData) = address(exchange).call(simulateDispatchTransferFromCallsData);
|
||||
|
||||
bytes4 selector = returnData.readBytes4(0);
|
||||
if (selector == LibExchangeRichErrors.AssetProxyDispatchErrorSelector()) {
|
||||
// Decode AssetProxyDispatchError and return index of failed transfer
|
||||
(, bytes32 failedTransferIndex,) = LibExchangeRichErrorDecoder.decodeAssetProxyDispatchError(returnData);
|
||||
return OrderTransferResults(uint8(uint256(failedTransferIndex)));
|
||||
} else if (selector == LibExchangeRichErrors.AssetProxyTransferErrorSelector()) {
|
||||
// Decode AssetProxyTransferError and return index of failed transfer
|
||||
(bytes32 failedTransferIndex, ,) = LibExchangeRichErrorDecoder.decodeAssetProxyTransferError(returnData);
|
||||
return OrderTransferResults(uint8(uint256(failedTransferIndex)));
|
||||
} else if (keccak256(returnData) == _TRANSFERS_SUCCESSFUL_RESULT_HASH) {
|
||||
// All transfers were successful
|
||||
return OrderTransferResults.TransfersSuccessful;
|
||||
} else {
|
||||
revert("UNKNOWN_RETURN_DATA");
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma solidity ^0.5.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
@@ -24,7 +24,7 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
|
||||
|
||||
contract LibTransactionDecoder {
|
||||
library LibTransactionDecoder {
|
||||
|
||||
using LibBytes for bytes;
|
||||
|
||||
|
@@ -16,36 +16,26 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma solidity ^0.5.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
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/LibMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "./Addresses.sol";
|
||||
import "./AssetBalance.sol";
|
||||
import "./LibAssetData.sol";
|
||||
import "./OrderTransferSimulationUtils.sol";
|
||||
import "./LibOrderTransferSimulation.sol";
|
||||
|
||||
|
||||
contract OrderValidationUtils is
|
||||
LibAssetData,
|
||||
OrderTransferSimulationUtils
|
||||
Addresses,
|
||||
AssetBalance
|
||||
{
|
||||
using LibBytes for bytes;
|
||||
using LibSafeMath for uint256;
|
||||
|
||||
constructor (
|
||||
address _exchange,
|
||||
address _chaiBridge
|
||||
)
|
||||
public
|
||||
LibAssetData(
|
||||
_exchange,
|
||||
_chaiBridge
|
||||
)
|
||||
{}
|
||||
|
||||
/// @dev Fetches all order-relevant information needed to validate if the supplied order is fillable.
|
||||
/// @param order The order structure.
|
||||
/// @param signature Signature provided by maker that proves the order's authenticity.
|
||||
@@ -65,23 +55,22 @@ contract OrderValidationUtils is
|
||||
)
|
||||
{
|
||||
// Get info specific to order
|
||||
orderInfo = _EXCHANGE.getOrderInfo(order);
|
||||
orderInfo = IExchange(exchangeAddress).getOrderInfo(order);
|
||||
|
||||
// Validate the maker's signature
|
||||
address makerAddress = order.makerAddress;
|
||||
isValidSignature = _EXCHANGE.isValidOrderSignature(
|
||||
isValidSignature = IExchange(exchangeAddress).isValidOrderSignature(
|
||||
order,
|
||||
signature
|
||||
);
|
||||
|
||||
// Get the transferable amount of the `makerAsset`
|
||||
uint256 transferableMakerAssetAmount = getTransferableAssetAmount(makerAddress, order.makerAssetData);
|
||||
uint256 transferableMakerAssetAmount = _getTransferableConvertedMakerAssetAmount(
|
||||
order
|
||||
);
|
||||
|
||||
// Assign to stack variables to reduce redundant mloads/sloads
|
||||
uint256 takerAssetAmount = order.takerAssetAmount;
|
||||
uint256 makerFee = order.makerFee;
|
||||
|
||||
// Get the amount of `takerAsset` that is transferable to maker given the transferability of `makerAsset`, `makerFeeAsset`,
|
||||
// Get the amount of `takerAsset` that is transferable to maker given the
|
||||
// transferability of `makerAsset`, `makerFeeAsset`,
|
||||
// and the total amounts specified in the order
|
||||
uint256 transferableTakerAssetAmount;
|
||||
if (order.makerAssetData.equals(order.makerFeeAssetData)) {
|
||||
@@ -89,32 +78,35 @@ contract OrderValidationUtils is
|
||||
// transferableMakerAssetAmount / (makerAssetAmount + makerFee)
|
||||
transferableTakerAssetAmount = LibMath.getPartialAmountFloor(
|
||||
transferableMakerAssetAmount,
|
||||
order.makerAssetAmount.safeAdd(makerFee),
|
||||
takerAssetAmount
|
||||
order.makerAssetAmount.safeAdd(order.makerFee),
|
||||
order.takerAssetAmount
|
||||
);
|
||||
} else {
|
||||
// If `makerFee` is 0, the % that can be filled is (transferableMakerAssetAmount / makerAssetAmount)
|
||||
if (makerFee == 0) {
|
||||
if (order.makerFee == 0) {
|
||||
transferableTakerAssetAmount = LibMath.getPartialAmountFloor(
|
||||
transferableMakerAssetAmount,
|
||||
order.makerAssetAmount,
|
||||
takerAssetAmount
|
||||
order.takerAssetAmount
|
||||
);
|
||||
|
||||
// If `makerAsset` does not equal `makerFeeAsset`, the % that can be filled is the lower of
|
||||
// (transferableMakerAssetAmount / makerAssetAmount) and (transferableMakerAssetFeeAmount / makerFee)
|
||||
} else {
|
||||
// Get the transferable amount of the `makerFeeAsset`
|
||||
uint256 transferableMakerFeeAssetAmount = getTransferableAssetAmount(makerAddress, order.makerFeeAssetData);
|
||||
uint256 transferableMakerFeeAssetAmount = getTransferableAssetAmount(
|
||||
makerAddress,
|
||||
order.makerFeeAssetData
|
||||
);
|
||||
uint256 transferableMakerToTakerAmount = LibMath.getPartialAmountFloor(
|
||||
transferableMakerAssetAmount,
|
||||
order.makerAssetAmount,
|
||||
takerAssetAmount
|
||||
order.takerAssetAmount
|
||||
);
|
||||
uint256 transferableMakerFeeToTakerAmount = LibMath.getPartialAmountFloor(
|
||||
transferableMakerFeeAssetAmount,
|
||||
makerFee,
|
||||
takerAssetAmount
|
||||
order.makerFee,
|
||||
order.takerAssetAmount
|
||||
);
|
||||
transferableTakerAssetAmount = LibSafeMath.min256(transferableMakerToTakerAmount, transferableMakerFeeToTakerAmount);
|
||||
}
|
||||
@@ -122,16 +114,17 @@ contract OrderValidationUtils is
|
||||
|
||||
// `fillableTakerAssetAmount` is the lower of the order's remaining `takerAssetAmount` and the `transferableTakerAssetAmount`
|
||||
fillableTakerAssetAmount = LibSafeMath.min256(
|
||||
takerAssetAmount.safeSub(orderInfo.orderTakerAssetFilledAmount),
|
||||
order.takerAssetAmount.safeSub(orderInfo.orderTakerAssetFilledAmount),
|
||||
transferableTakerAssetAmount
|
||||
);
|
||||
|
||||
// Execute the maker transfers.
|
||||
fillableTakerAssetAmount = getSimulatedOrderMakerTransferResults(
|
||||
fillableTakerAssetAmount = LibOrderTransferSimulation.getSimulatedOrderMakerTransferResults(
|
||||
exchangeAddress,
|
||||
order,
|
||||
order.takerAddress,
|
||||
fillableTakerAssetAmount
|
||||
) == OrderTransferResults.TransfersSuccessful ? fillableTakerAssetAmount : 0;
|
||||
) == LibOrderTransferSimulation.OrderTransferResults.TransfersSuccessful ? fillableTakerAssetAmount : 0;
|
||||
|
||||
if (!_isAssetDataValid(order.takerAssetData)) {
|
||||
fillableTakerAssetAmount = 0;
|
||||
@@ -181,7 +174,7 @@ contract OrderValidationUtils is
|
||||
return (ordersInfo, fillableTakerAssetAmounts, isValidSignature);
|
||||
}
|
||||
|
||||
/// @dev Gets the amount of an asset transferable by the owner.
|
||||
/// @dev Gets the amount of an asset transferable by the maker of an order.
|
||||
/// @param ownerAddress Address of the owner of the asset.
|
||||
/// @param assetData Description of tokens, per the AssetProxy contract specification.
|
||||
/// @return The amount of the asset tranferable by the owner.
|
||||
@@ -193,7 +186,26 @@ contract OrderValidationUtils is
|
||||
public
|
||||
returns (uint256 transferableAssetAmount)
|
||||
{
|
||||
(uint256 balance, uint256 allowance) = getBalanceAndAssetProxyAllowance(ownerAddress, assetData);
|
||||
(uint256 balance, uint256 allowance) = getBalanceAndAssetProxyAllowance(
|
||||
ownerAddress,
|
||||
assetData
|
||||
);
|
||||
transferableAssetAmount = LibSafeMath.min256(balance, allowance);
|
||||
return transferableAssetAmount;
|
||||
}
|
||||
|
||||
/// @dev Gets the amount of an asset transferable by the maker of an order.
|
||||
/// Similar to `getTransferableAssetAmount()`, but can handle maker asset
|
||||
/// types that depend on taker assets being transferred first (e.g., Dydx bridge).
|
||||
/// @param order The order.
|
||||
/// @return transferableAssetAmount Amount of maker asset that can be transferred.
|
||||
function _getTransferableConvertedMakerAssetAmount(
|
||||
LibOrder.Order memory order
|
||||
)
|
||||
internal
|
||||
returns (uint256 transferableAssetAmount)
|
||||
{
|
||||
(uint256 balance, uint256 allowance) = _getConvertibleMakerBalanceAndAssetProxyAllowance(order);
|
||||
transferableAssetAmount = LibSafeMath.min256(balance, allowance);
|
||||
return transferableAssetAmount;
|
||||
}
|
||||
@@ -221,7 +233,8 @@ contract OrderValidationUtils is
|
||||
}
|
||||
|
||||
// Get array of values and array of assetDatas
|
||||
(, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData);
|
||||
(, , bytes[] memory nestedAssetData) =
|
||||
LibAssetData.decodeMultiAssetData(assetData);
|
||||
|
||||
uint256 length = nestedAssetData.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
|
Reference in New Issue
Block a user