Add LibAssetData to contracts/asset-proxy (#1779)

* Stop restarting node unnecesssarily during test

* Add new, empty LibAssetData

* Support encoding & decoding of ERC20 asset data

* Support encoding & decoding of ERC721 asset data

* Support encoding & decoding of ERC1155 asset data

* Support encoding & decoding of multi-asset data

* Support querying ERC20 balance from asset data

* Support querying ERC721 balance from asset data

* Support querying ERC1155 balance from asset data

* Support querying balance from multi-asset data

* Support querying ERC20 allowance from asset data

* Support querying ERC721 allowance from asset data

* Support querying ERC1155 allowance from asset data

* In tests, wait for allowance set before checking

* Introduce temporary variable `assetDataBody`

* Handle edge case in multi-asset balance query

* Support multi-asset allowance query by asset data

* Move variable declaration up for readability.

* Make all solhint-disable's cite specific rules

And move the directives to the ends of lines whenever possible

* Rename query tests to include " by asset data"

* Extract test helper method

* Extract another test helper method

* Support batch queries of allowances & balances

* In LibAssetData.sol, use IERC1155, not ...Mintable

* Rename balance*() return vars: amount -> balance

* Fix bug in ERC721 balance query

Was using method balanceOf(), but needed to be using ownerOf().

getERC721TokenOwner() method lifted from
@0x/extensions/contracts/src/OrderValidator/OrderValidator.sol

* Reuse new en/decoders; avoid abi.decode().

* Start lowest allowance/balance from 0, not MAX_INT

* Properly implement ERC1155 balance querying

* Split lines for readability

* Also check isApprovedForAll in 721 allowance query

* Add neglected division of allowances by amounts

* Rename methods: balanceOf -> getBalance

* Rename methods: allowance -> getAllowance

* Add methods: getBalanceAndAllowance() & batch...()

* Rename return vars: amount -> allowance

* Add devdoc comments

* Rename batchGet* methods to getBatch*

* Remove refactoring relic

* Add revert messages to all require() calls

* Reduce gas usage for ERC1155 asset data decoding

* Don't use dockerized solc for ERC20 contracts

Because they demand solc version 0.4.26, and it seems as though the tag
for that version has been deleted from dockerhub.

Without this, @0x/contracts-erc20 was failing to build.

* Rename batch functions to use plurals

* Skip dockerized solc for contracts needing 0.4.26

I seems as though the tag for that version has been deleted from
dockerhub.

Without this, these contracts were failing to build.

* Make revert reasons follow snake case convention
This commit is contained in:
F. Eugene Aumson 2019-04-29 15:46:26 -04:00 committed by GitHub
parent bbc06be091
commit 0de2b6983b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 860 additions and 17 deletions

View File

@ -30,6 +30,7 @@
"src/MultiAssetProxy.sol",
"src/interfaces/IAssetData.sol",
"src/interfaces/IAssetProxy.sol",
"src/interfaces/IAuthorizable.sol"
"src/interfaces/IAuthorizable.sol",
"src/libs/LibAssetData.sol"
]
}

View File

@ -0,0 +1,420 @@
/*
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.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
library LibAssetData {
bytes4 constant public ERC20_PROXY_ID = bytes4(keccak256("ERC20Token(address)"));
bytes4 constant public ERC721_PROXY_ID = bytes4(keccak256("ERC721Token(address,uint256)"));
bytes4 constant public ERC1155_PROXY_ID = bytes4(keccak256("ERC1155Assets(address,uint256[],uint256[],bytes)"));
bytes4 constant public MULTI_ASSET_PROXY_ID = bytes4(keccak256("MultiAsset(uint256[],bytes[])"));
/// @dev Returns the owner's balance of the token(s) specified in
/// assetData. When the asset data contains multiple tokens (eg in
/// ERC1155 or Multi-Asset), the return value indicates how many
/// complete "baskets" of those tokens are owned by owner.
/// @param owner Owner of the tokens specified by assetData.
/// @param assetData Description of tokens, per the AssetProxy contract
/// specification.
/// @return Number of tokens (or token baskets) held by owner.
function getBalance(address owner, bytes memory assetData)
public
view
returns (uint256 balance)
{
bytes4 proxyId = LibBytes.readBytes4(assetData, 0);
if (proxyId == ERC20_PROXY_ID) {
address tokenAddress = LibBytes.readAddress(assetData, 16);
return IERC20Token(tokenAddress).balanceOf(owner);
} else if (proxyId == ERC721_PROXY_ID) {
(, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData);
return getERC721TokenOwner(tokenAddress, tokenId) == owner ? 1 : 0;
} else if (proxyId == ERC1155_PROXY_ID) {
uint256 lowestTokenBalance = 0;
(
,
address tokenAddress,
uint256[] memory tokenIds,
uint256[] memory tokenValues,
) = decodeERC1155AssetData(assetData);
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 tokenBalance = IERC1155(tokenAddress).balanceOf(owner, tokenIds[i]) / tokenValues[i];
if (tokenBalance < lowestTokenBalance || lowestTokenBalance == 0) {
lowestTokenBalance = tokenBalance;
}
}
return lowestTokenBalance;
} else if (proxyId == MULTI_ASSET_PROXY_ID) {
uint256 lowestAssetBalance = 0;
(, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData);
for (uint256 i = 0; i < nestedAssetData.length; i++) {
uint256 assetBalance = getBalance(owner, nestedAssetData[i]) / assetAmounts[i];
if (assetBalance < lowestAssetBalance || lowestAssetBalance == 0) {
lowestAssetBalance = assetBalance;
}
}
return lowestAssetBalance;
} else {
revert("UNSUPPORTED_PROXY_IDENTIFIER");
}
}
/// @dev Calls getBalance() for each element of assetData.
/// @param owner Owner of the tokens specified by assetData.
/// @param assetData Array of token descriptors, each encoded per the
/// AssetProxy contract specification.
/// @return Array of token balances from getBalance(), with each element
/// corresponding to the same-indexed element in the assetData input.
function getBatchBalances(address owner, bytes[] memory assetData)
public
view
returns (uint256[] memory balances)
{
balances = new uint256[](assetData.length);
for (uint256 i = 0; i < assetData.length; i++) {
balances[i] = getBalance(owner, assetData[i]);
}
}
/// @dev Returns the number of token(s) (described by assetData) that
/// spender is authorized to spend. When the asset data contains
/// multiple tokens (eg for Multi-Asset), the return value indicates
/// how many complete "baskets" of those tokens may be spent by spender.
/// @param owner Owner of the tokens specified by assetData.
/// @param spender Address whose authority to spend is in question.
/// @param assetData Description of tokens, per the AssetProxy contract
/// specification.
/// @return Number of tokens (or token baskets) that the spender is
/// authorized to spend.
function getAllowance(address owner, address spender, bytes memory assetData)
public
view
returns (uint256 allowance)
{
bytes4 proxyId = LibBytes.readBytes4(assetData, 0);
if (proxyId == ERC20_PROXY_ID) {
address tokenAddress = LibBytes.readAddress(assetData, 16);
return IERC20Token(tokenAddress).allowance(owner, spender);
} else if (proxyId == ERC721_PROXY_ID) {
(, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData);
IERC721Token token = IERC721Token(tokenAddress);
if (spender == token.getApproved(tokenId) || token.isApprovedForAll(owner, spender)) {
return 1;
} else {
return 0;
}
} else if (proxyId == ERC1155_PROXY_ID) {
(, address tokenAddress, , , ) = decodeERC1155AssetData(assetData);
if (IERC1155(tokenAddress).isApprovedForAll(owner, spender)) {
return 1;
} else {
return 0;
}
} else if (proxyId == MULTI_ASSET_PROXY_ID) {
uint256 lowestAssetAllowance = 0;
// solhint-disable-next-line indent
(, uint256[] memory amounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData);
for (uint256 i = 0; i < nestedAssetData.length; i++) {
uint256 assetAllowance = getAllowance(owner, spender, nestedAssetData[i]) / amounts[i];
if (assetAllowance < lowestAssetAllowance || lowestAssetAllowance == 0) {
lowestAssetAllowance = assetAllowance;
}
}
return lowestAssetAllowance;
} else {
revert("UNSUPPORTED_PROXY_IDENTIFIER");
}
}
/// @dev Calls getAllowance() for each element of assetData.
/// @param owner Owner of the tokens specified by assetData.
/// @param spender Address whose authority to spend is in question.
/// @param assetData Description of tokens, per the AssetProxy contract
/// specification.
/// @return An array of token allowances from getAllowance(), with each
/// element corresponding to the same-indexed element in the assetData
/// input.
function getBatchAllowances(address owner, address spender, bytes[] memory assetData)
public
view
returns (uint256[] memory allowances)
{
allowances = new uint256[](assetData.length);
for (uint256 i = 0; i < assetData.length; i++) {
allowances[i] = getAllowance(owner, spender, assetData[i]);
}
}
/// @dev Calls getBalance() and getAllowance() for assetData.
/// @param owner Owner of the tokens specified by assetData.
/// @param spender Address whose authority to spend is in question.
/// @param assetData Description of tokens, per the AssetProxy contract
/// specification.
/// @return Number of tokens (or token baskets) held by owner, and number
/// of tokens (or token baskets) that the spender is authorized to
/// spend.
function getBalanceAndAllowance(address owner, address spender, bytes memory assetData)
public
view
returns (uint256 balance, uint256 allowance)
{
balance = getBalance(owner, assetData);
allowance = getAllowance(owner, spender, assetData);
}
/// @dev Calls getBatchBalances() and getBatchAllowances() for each element
/// of assetData.
/// @param owner Owner of the tokens specified by assetData.
/// @param spender Address whose authority to spend is in question.
/// @param assetData Description of tokens, per the AssetProxy contract
/// specification.
/// @return An array of token balances from getBalance(), and an array of
/// token allowances from getAllowance(), with each element
/// corresponding to the same-indexed element in the assetData input.
function getBatchBalancesAndAllowances(address owner, address spender, bytes[] memory assetData)
public
view
returns (uint256[] memory balances, uint256[] memory allowances)
{
balances = getBatchBalances(owner, assetData);
allowances = getBatchAllowances(owner, spender, 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
/// token to be traded.
/// @return AssetProxy-compliant data describing the asset.
function encodeERC20AssetData(address tokenAddress)
public
pure
returns (bytes memory assetData)
{
return abi.encodeWithSelector(ERC20_PROXY_ID, 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 ERC-20 AssetProxy identifier, and the address of the ERC-20
/// contract hosting this asset.
function decodeERC20AssetData(bytes memory assetData)
public
pure
returns (
bytes4 proxyId,
address tokenAddress
)
{
proxyId = LibBytes.readBytes4(assetData, 0);
require(proxyId == ERC20_PROXY_ID, "WRONG_PROXY_ID");
tokenAddress = LibBytes.readAddress(assetData, 16);
}
/// @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
/// token to be traded.
/// @param tokenId The identifier of the specific token to be traded.
/// @return AssetProxy-compliant asset data describing the asset.
function encodeERC721AssetData(
address tokenAddress,
uint256 tokenId
)
public
pure
returns (bytes memory assetData)
{
return abi.encodeWithSelector(ERC721_PROXY_ID, 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
/// token to be traded.
function decodeERC721AssetData(bytes memory assetData)
public
pure
returns (
bytes4 proxyId,
address tokenAddress,
uint256 tokenId
)
{
proxyId = LibBytes.readBytes4(assetData, 0);
require(proxyId == ERC721_PROXY_ID, "WRONG_PROXY_ID");
tokenAddress = LibBytes.readAddress(assetData, 16);
tokenId = LibBytes.readUint256(assetData, 36);
}
/// @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
/// token(s) to be traded.
/// @param tokenIds The identifiers of the specific tokens to be traded.
/// @param tokenValues The amounts of each token to be traded.
/// @param callbackData ...
/// @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 abi.encodeWithSelector(ERC1155_PROXY_ID, 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
/// tokens to be traded, an array of token 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 proxyId,
address tokenAddress,
uint256[] memory tokenIds,
uint256[] memory tokenValues,
bytes memory callbackData
)
{
proxyId = LibBytes.readBytes4(assetData, 0);
require(proxyId == ERC1155_PROXY_ID, "WRONG_PROXY_ID");
assembly {
// Skip selector and length to get to the first parameter:
assetData := add(assetData, 36)
// Read the value of the first parameter:
tokenAddress := mload(assetData)
// Point to the next parameter's data:
tokenIds := add(assetData, mload(add(assetData, 32)))
// Point to the next parameter's data:
tokenValues := add(assetData, mload(add(assetData, 64)))
// Point to the next parameter's data:
callbackData := add(assetData, mload(add(assetData, 96)))
}
}
/// @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)
{
assetData = abi.encodeWithSelector(MULTI_ASSET_PROXY_ID, 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 proxyId,
uint256[] memory amounts,
bytes[] memory nestedAssetData
)
{
proxyId = LibBytes.readBytes4(assetData, 0);
require(proxyId == MULTI_ASSET_PROXY_ID, "WRONG_PROXY_ID");
// solhint-disable-next-line indent
(amounts, nestedAssetData) = abi.decode(LibBytes.slice(assetData, 4, assetData.length), (uint256[], bytes[]));
}
/// @dev Calls `token.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned token.
/// @param token Address of ERC721 token.
/// @param tokenId The identifier for the specific NFT.
/// @return Owner of tokenId or null address if unowned.
function getERC721TokenOwner(address token, uint256 tokenId)
public
view
returns (address owner)
{
assembly {
// load free memory pointer
let cdStart := mload(64)
// bytes4(keccak256(ownerOf(uint256))) = 0x6352211e
mstore(cdStart, 0x6352211e00000000000000000000000000000000000000000000000000000000)
mstore(add(cdStart, 4), tokenId)
// staticcall `ownerOf(tokenId)`
// `ownerOf` will revert if tokenId is not owned
let success := staticcall(
gas, // forward all gas
token, // call token contract
cdStart, // start of calldata
36, // length of input is 36 bytes
cdStart, // write output over input
32 // size of output is 32 bytes
)
// Success implies that tokenId is owned
// Copy owner from return data if successful
if success {
owner := mload(cdStart)
}
}
// Owner initialized to address(0), no need to modify if call is unsuccessful
return owner;
}
}

View File

@ -34,7 +34,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"config": {
"abis": "./generated-artifacts/@(ERC1155Proxy|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAuthorizable|MixinAuthorizable|MultiAssetProxy).json",
"abis": "./generated-artifacts/@(ERC1155Proxy|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAuthorizable|LibAssetData|MixinAuthorizable|MultiAssetProxy).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {

View File

@ -11,12 +11,14 @@ import * as ERC721Proxy from '../generated-artifacts/ERC721Proxy.json';
import * as IAssetData from '../generated-artifacts/IAssetData.json';
import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json';
import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json';
import * as LibAssetData from '../generated-artifacts/LibAssetData.json';
import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json';
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
export const artifacts = {
LibAssetData: LibAssetData as ContractArtifact,
ERC1155Proxy: ERC1155Proxy as ContractArtifact,
ERC20Proxy: ERC20Proxy as ContractArtifact,
ERC721Proxy: ERC721Proxy as ContractArtifact,
ERC1155Proxy: ERC1155Proxy as ContractArtifact,
MixinAuthorizable: MixinAuthorizable as ContractArtifact,
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
IAssetData: IAssetData as ContractArtifact,

View File

@ -9,5 +9,6 @@ export * from '../generated-wrappers/erc721_proxy';
export * from '../generated-wrappers/i_asset_data';
export * from '../generated-wrappers/i_asset_proxy';
export * from '../generated-wrappers/i_authorizable';
export * from '../generated-wrappers/lib_asset_data';
export * from '../generated-wrappers/mixin_authorizable';
export * from '../generated-wrappers/multi_asset_proxy';

View File

@ -0,0 +1,426 @@
// TODO: change test titles to say "... from asset data"
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import {
artifacts as erc1155Artifacts,
ERC1155MintableContract,
ERC1155TransferSingleEventArgs,
IERC1155MintableContract,
} from '@0x/contracts-erc1155';
import { artifacts as erc20Artifacts, DummyERC20TokenContract, IERC20TokenContract } from '@0x/contracts-erc20';
import { artifacts as erc721Artifacts, DummyERC721TokenContract, IERC721TokenContract } from '@0x/contracts-erc721';
import { chaiSetup, constants, LogDecoder, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { artifacts, LibAssetDataContract } from '../src';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const KNOWN_ERC20_ENCODING = {
address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48',
};
const KNOWN_ERC721_ENCODING = {
address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
tokenId: new BigNumber(1),
assetData:
'0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001',
};
const KNOWN_ERC1155_ENCODING = {
tokenAddress: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
tokenIds: [new BigNumber(100), new BigNumber(1001), new BigNumber(10001)],
tokenValues: [new BigNumber(200), new BigNumber(2001), new BigNumber(20001)],
callbackData:
'0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001',
assetData:
'0xa7cb5fb70000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000',
};
const KNOWN_MULTI_ASSET_ENCODING = {
amounts: [new BigNumber(70), new BigNumber(1), new BigNumber(18)],
nestedAssetData: [
KNOWN_ERC20_ENCODING.assetData,
KNOWN_ERC721_ENCODING.assetData,
KNOWN_ERC1155_ENCODING.assetData,
],
assetData:
'0x94cfcdd7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000204a7cb5fb70000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c4800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
};
describe('LibAssetData', () => {
let libAssetData: LibAssetDataContract;
let tokenOwnerAddress: string;
let approvedSpenderAddress: string;
let anotherApprovedSpenderAddress: string;
let erc20TokenAddress: string;
const erc20TokenTotalSupply = new BigNumber(1);
let erc721TokenAddress: string;
const firstERC721TokenId = new BigNumber(1);
const numberOfERC721Tokens = 10;
let erc1155MintableAddress: string;
let erc1155TokenId: BigNumber;
before(async () => {
await blockchainLifecycle.startAsync();
libAssetData = await LibAssetDataContract.deployFrom0xArtifactAsync(
artifacts.LibAssetData,
provider,
txDefaults,
);
[
tokenOwnerAddress,
approvedSpenderAddress,
anotherApprovedSpenderAddress,
] = await web3Wrapper.getAvailableAddressesAsync();
erc20TokenAddress = (await DummyERC20TokenContract.deployFrom0xArtifactAsync(
erc20Artifacts.DummyERC20Token,
provider,
txDefaults,
'Dummy',
'DUM',
new BigNumber(1),
erc20TokenTotalSupply,
)).address;
const erc721TokenContract = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
erc721Artifacts.DummyERC721Token,
provider,
txDefaults,
'Dummy',
'DUM',
);
erc721TokenAddress = erc721TokenContract.address;
// mint `numberOfERC721Tokens` tokens
const transactionMinedPromises = [];
for (let i = 0; i < numberOfERC721Tokens; i++) {
transactionMinedPromises.push(
web3Wrapper.awaitTransactionSuccessAsync(
await erc721TokenContract.mint.sendTransactionAsync(
tokenOwnerAddress,
firstERC721TokenId.plus(i - 1),
),
constants.AWAIT_TRANSACTION_MINED_MS,
),
);
}
await Promise.all(transactionMinedPromises);
const erc1155MintableContract = await ERC1155MintableContract.deployFrom0xArtifactAsync(
erc1155Artifacts.ERC1155Mintable,
provider,
txDefaults,
);
erc1155MintableAddress = erc1155MintableContract.address;
// Somewhat re-inventing the wheel here, but the prior art currently
// exists only as an unexported test util in the erc1155 package
// (Erc1155Wrapper.mintFungibleTokensAsync() in erc1155/test/utils/).
// This is concise enough to justify duplication, but it sure is ugly.
// tslint:disable-next-line no-unnecessary-type-assertion
erc1155TokenId = ((await new LogDecoder(web3Wrapper, erc1155Artifacts).getTxWithDecodedLogsAsync(
await erc1155MintableContract.create.sendTransactionAsync('uri:Dummy', /*isNonFungible:*/ false),
)).logs[0] as LogWithDecodedArgs<ERC1155TransferSingleEventArgs>).args.id;
await web3Wrapper.awaitTransactionSuccessAsync(
await erc1155MintableContract.mintFungible.sendTransactionAsync(
erc1155TokenId,
[tokenOwnerAddress],
[new BigNumber(1)],
),
constants.AWAIT_TRANSACTION_MINED_MS,
);
});
async function setERC20AllowanceAsync(): Promise<any> {
return web3Wrapper.awaitTransactionSuccessAsync(
await new IERC20TokenContract(
erc20Artifacts.IERC20Token.compilerOutput.abi,
erc20TokenAddress,
provider,
).approve.sendTransactionAsync(approvedSpenderAddress, new BigNumber(1), { from: tokenOwnerAddress }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
}
async function setERC721AllowanceAsync(): Promise<any> {
return web3Wrapper.awaitTransactionSuccessAsync(
await new IERC721TokenContract(
erc721Artifacts.IERC721Token.compilerOutput.abi,
erc721TokenAddress,
provider,
).approve.sendTransactionAsync(approvedSpenderAddress, new BigNumber(1), { from: tokenOwnerAddress }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
}
after(async () => {
await blockchainLifecycle.revertAsync();
});
it('should have a deployed-to address', () => {
expect(libAssetData.address.slice(0, 2)).to.equal('0x');
});
it('should encode ERC20 asset data', async () => {
expect(await libAssetData.encodeERC20AssetData.callAsync(KNOWN_ERC20_ENCODING.address)).to.equal(
KNOWN_ERC20_ENCODING.assetData,
);
});
it('should decode ERC20 asset data', async () => {
expect(await libAssetData.decodeERC20AssetData.callAsync(KNOWN_ERC20_ENCODING.assetData)).to.deep.equal([
AssetProxyId.ERC20,
KNOWN_ERC20_ENCODING.address,
]);
});
it('should encode ERC721 asset data', async () => {
expect(
await libAssetData.encodeERC721AssetData.callAsync(
KNOWN_ERC721_ENCODING.address,
KNOWN_ERC721_ENCODING.tokenId,
),
).to.equal(KNOWN_ERC721_ENCODING.assetData);
});
it('should decode ERC721 asset data', async () => {
expect(await libAssetData.decodeERC721AssetData.callAsync(KNOWN_ERC721_ENCODING.assetData)).to.deep.equal([
AssetProxyId.ERC721,
KNOWN_ERC721_ENCODING.address,
KNOWN_ERC721_ENCODING.tokenId,
]);
});
it('should encode ERC1155 asset data', async () => {
expect(
await libAssetData.encodeERC1155AssetData.callAsync(
KNOWN_ERC1155_ENCODING.tokenAddress,
KNOWN_ERC1155_ENCODING.tokenIds,
KNOWN_ERC1155_ENCODING.tokenValues,
KNOWN_ERC1155_ENCODING.callbackData,
),
).to.equal(KNOWN_ERC1155_ENCODING.assetData);
});
it('should decode ERC1155 asset data', async () => {
expect(await libAssetData.decodeERC1155AssetData.callAsync(KNOWN_ERC1155_ENCODING.assetData)).to.deep.equal([
AssetProxyId.ERC1155,
KNOWN_ERC1155_ENCODING.tokenAddress,
KNOWN_ERC1155_ENCODING.tokenIds,
KNOWN_ERC1155_ENCODING.tokenValues,
KNOWN_ERC1155_ENCODING.callbackData,
]);
});
it('should encode multiasset data', async () => {
expect(
await libAssetData.encodeMultiAssetData.callAsync(
KNOWN_MULTI_ASSET_ENCODING.amounts,
KNOWN_MULTI_ASSET_ENCODING.nestedAssetData,
),
).to.equal(KNOWN_MULTI_ASSET_ENCODING.assetData);
});
it('should decode multiasset data', async () => {
expect(await libAssetData.decodeMultiAssetData.callAsync(KNOWN_MULTI_ASSET_ENCODING.assetData)).to.deep.equal([
AssetProxyId.MultiAsset,
KNOWN_MULTI_ASSET_ENCODING.amounts,
KNOWN_MULTI_ASSET_ENCODING.nestedAssetData,
]);
});
it('should query ERC20 balance by asset data', async () => {
expect(
await libAssetData.getBalance.callAsync(
tokenOwnerAddress,
await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress),
),
).to.bignumber.equal(erc20TokenTotalSupply);
});
it('should query ERC721 balance by asset data', async () => {
expect(
await libAssetData.getBalance.callAsync(
tokenOwnerAddress,
await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId),
),
).to.bignumber.equal(1);
});
it('should query ERC1155 balances by asset data', async () => {
expect(
await libAssetData.getBalance.callAsync(
tokenOwnerAddress,
await libAssetData.encodeERC1155AssetData.callAsync(
erc1155MintableAddress,
[erc1155TokenId],
[new BigNumber(1)], // token values
'0x', // callback data
),
),
).to.bignumber.equal(1);
});
it('should query multi-asset batch balance by asset data', async () => {
expect(
await libAssetData.getBalance.callAsync(
tokenOwnerAddress,
await libAssetData.encodeMultiAssetData.callAsync(
[new BigNumber(1), new BigNumber(1)],
[
await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress),
await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId),
],
),
),
).to.bignumber.equal(Math.min(erc20TokenTotalSupply.toNumber(), numberOfERC721Tokens));
});
it('should query ERC20 allowances by asset data', async () => {
await setERC20AllowanceAsync();
expect(
await libAssetData.getAllowance.callAsync(
tokenOwnerAddress,
approvedSpenderAddress,
await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress),
),
).to.bignumber.equal(1);
});
it('should query ERC721 approval by asset data', async () => {
await setERC721AllowanceAsync();
expect(
await libAssetData.getAllowance.callAsync(
tokenOwnerAddress,
approvedSpenderAddress,
await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId),
),
).to.bignumber.equal(1);
});
it('should query ERC721 approvalForAll by assetData', async () => {
await web3Wrapper.awaitTransactionSuccessAsync(
await new IERC721TokenContract(
erc721Artifacts.IERC721Token.compilerOutput.abi,
erc721TokenAddress,
provider,
).setApprovalForAll.sendTransactionAsync(anotherApprovedSpenderAddress, true, {
from: tokenOwnerAddress,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
expect(
await libAssetData.getAllowance.callAsync(
tokenOwnerAddress,
anotherApprovedSpenderAddress,
await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId),
),
).to.bignumber.equal(1);
});
it('should query ERC1155 allowances by asset data', async () => {
await web3Wrapper.awaitTransactionSuccessAsync(
await new IERC1155MintableContract(
erc1155Artifacts.IERC1155Mintable.compilerOutput.abi,
erc1155MintableAddress,
provider,
).setApprovalForAll.sendTransactionAsync(approvedSpenderAddress, true, { from: tokenOwnerAddress }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
expect(
await libAssetData.getAllowance.callAsync(
tokenOwnerAddress,
approvedSpenderAddress,
await libAssetData.encodeERC1155AssetData.callAsync(
erc1155MintableAddress,
[erc1155TokenId],
[new BigNumber(1)],
'0x',
),
),
).to.bignumber.equal(1);
});
it('should query multi-asset allowances by asset data', async () => {
await setERC20AllowanceAsync();
await setERC721AllowanceAsync();
expect(
await libAssetData.getAllowance.callAsync(
tokenOwnerAddress,
approvedSpenderAddress,
await libAssetData.encodeMultiAssetData.callAsync(
[new BigNumber(1), new BigNumber(1)],
[
await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress),
await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId),
],
),
),
).to.bignumber.equal(1);
return;
});
it('should query balances for a batch of asset data strings', async () => {
expect(
await libAssetData.getBatchBalances.callAsync(tokenOwnerAddress, [
await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress),
await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId),
]),
).to.deep.equal([new BigNumber(erc20TokenTotalSupply), new BigNumber(1)]);
});
it('should query allowances for a batch of asset data strings', async () => {
await setERC20AllowanceAsync();
await setERC721AllowanceAsync();
expect(
await libAssetData.getBatchAllowances.callAsync(tokenOwnerAddress, approvedSpenderAddress, [
await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress),
await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId),
]),
).to.deep.equal([new BigNumber(1), new BigNumber(1)]);
});
describe('getERC721TokenOwner', async () => {
it('should return the null address when tokenId is not owned', async () => {
const nonexistentTokenId = new BigNumber(1234567890);
expect(
await libAssetData.getERC721TokenOwner.callAsync(erc721TokenAddress, nonexistentTokenId),
).to.be.equal(constants.NULL_ADDRESS);
});
it('should return the owner address when tokenId is owned', async () => {
expect(
await libAssetData.getERC721TokenOwner.callAsync(erc721TokenAddress, firstERC721TokenId),
).to.be.equal(tokenOwnerAddress);
});
});
it('should query balance and allowance together, from asset data', async () => {
await setERC20AllowanceAsync();
expect(
await libAssetData.getBalanceAndAllowance.callAsync(
tokenOwnerAddress,
approvedSpenderAddress,
await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress),
),
).to.deep.equal([new BigNumber(erc20TokenTotalSupply), new BigNumber(1)]);
});
it('should query balances and allowances together, from an asset data array', async () => {
await setERC20AllowanceAsync();
expect(
await libAssetData.getBatchBalancesAndAllowances.callAsync(tokenOwnerAddress, approvedSpenderAddress, [
await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress),
]),
).to.deep.equal([[new BigNumber(erc20TokenTotalSupply)], [new BigNumber(1)]]);
});
});

View File

@ -9,6 +9,7 @@
"generated-artifacts/IAssetData.json",
"generated-artifacts/IAssetProxy.json",
"generated-artifacts/IAuthorizable.json",
"generated-artifacts/LibAssetData.json",
"generated-artifacts/MixinAuthorizable.json",
"generated-artifacts/MultiAssetProxy.json"
],

View File

@ -1,7 +1,7 @@
{
"artifactsDir": "./generated-artifacts",
"contractsDir": "./contracts",
"useDockerisedSolc": true,
"useDockerisedSolc": false,
"compilerSettings": {
"evmVersion": "constantinople",
"optimizer": {

View File

@ -1,7 +1,7 @@
{
"artifactsDir": "./generated-artifacts",
"contractsDir": "./contracts",
"useDockerisedSolc": true,
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",

View File

@ -1,7 +1,7 @@
{
"artifactsDir": "./generated-artifacts",
"contractsDir": "./contracts",
"useDockerisedSolc": true,
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",

View File

@ -1,7 +1,7 @@
{
"artifactsDir": "./generated-artifacts",
"contractsDir": "./contracts",
"useDockerisedSolc": true,
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",

View File

@ -1,7 +1,7 @@
{
"artifactsDir": "./generated-artifacts",
"contractsDir": "./contracts",
"useDockerisedSolc": true,
"useDockerisedSolc": false,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "constantinople",

View File

@ -23,11 +23,6 @@ describe('LibAddressArray', () => {
before(async () => {
await blockchainLifecycle.startAsync();
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
before(async () => {
// Deploy LibAddressArray
lib = await TestLibAddressArrayContract.deployFrom0xArtifactAsync(
artifacts.TestLibAddressArray,
@ -35,10 +30,7 @@ describe('LibAddressArray', () => {
txDefaults,
);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
after(async () => {
await blockchainLifecycle.revertAsync();
});