Split ERC1155 Asset Proxy from ERC1155 POC implementation - squashed

This commit is contained in:
Greg Hysen
2019-02-27 14:44:54 -08:00
parent d5d9df383e
commit 15d9e2d3d5
14 changed files with 1032 additions and 4 deletions

View File

@@ -1,4 +1,15 @@
[
{
"name": "ERC1155Proxy",
"version": "1.0.0",
"changes": [
{
"note": "Add ERC1155Proxy implementation",
"pr": 0,
"networks": {
}
}
]
{
"name": "MultiAssetProxy",
"version": "1.0.0",

View File

@@ -0,0 +1,257 @@
/*
Copyright 2018 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.4.24;
import "./MixinAuthorizable.sol";
contract ERC1155Proxy is
MixinAuthorizable
{
// Id of this proxy.
bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC1155Token(address,uint256[],uint256[],bytes)"));
function ()
external
{
// Input calldata to this function is encoded as follows:
// -- TABLE #1 --
// | Area | Offset (**) | Length | Contents |
// |----------|-------------|-------------|---------------------------------|
// | Header | 0 | 4 | function selector |
// | Params | | 4 * 32 | function parameters: |
// | | 4 | | 1. offset to assetData (*) |
// | | 36 | | 2. from |
// | | 68 | | 3. to |
// | | 100 | | 4. amount |
// | Data | | | assetData: |
// | | 132 | 32 | assetData Length |
// | | 164 | (see below) | assetData Contents |
//
//
// Asset data is encoded as follows:
// -- TABLE #2 --
// | Area | Offset | Length | Contents |
// |----------|-------------|---------|-------------------------------------|
// | Header | 0 | 4 | assetProxyId |
// | Params | | 4 * 32 | function parameters: |
// | | 4 | | 1. address of ERC1155 contract |
// | | 36 | | 2. offset to tokenIds (*) |
// | | 68 | | 3. offset to tokenValues (*) |
// | | 100 | | 4. offset to callbackData (*) |
// | Data | | | tokenIds: |
// | | 132 | 32 | 1. tokenIds Length |
// | | 164 | a | 2. tokenIds Contents |
// | | | | tokenValues: |
// | | 164 + a | 32 | 1. tokenValues Length |
// | | 196 + a | b | 2. tokenValues Contents |
// | | | | callbackData |
// | | 196 + a + b | 32 | 1. callbackData Length |
// | | 228 + a + b | c | 2. callbackData Contents |
//
//
// Calldata for target ERC155 asset is encoded for safeBatchTransferFrom:
// -- TABLE #3 --
// | Area | Offset (**) | Length | Contents |
// |----------|-------------|---------|-------------------------------------|
// | Header | 0 | 4 | safeBatchTransferFrom selector |
// | Params | | 5 * 32 | function parameters: |
// | | 4 | | 1. from address |
// | | 36 | | 2. to address |
// | | 68 | | 3. offset to tokenIds (*) |
// | | 100 | | 4. offset to tokenValues (*) |
// | | 132 | | 5. offset to callbackData (*) |
// | Data | | | tokenIds: |
// | | 164 | 32 | 1. tokenIds Length |
// | | 196 | a | 2. tokenIds Contents |
// | | | | tokenValues: |
// | | 196 + a | 32 | 1. tokenValues Length |
// | | 228 + a | b | 2. tokenValues Contents |
// | | | | callbackData |
// | | 228 + a + b | 32 | 1. callbackData Length |
// | | 260 + a + b | c | 2. callbackData Contents |
//
//
// (*): offset is computed from start of function parameters, so offset
// by an additional 4 bytes in the calldata.
//
// (**): the `Offset` column is computed assuming no calldata compression;
// offsets in the Data Area are dynamic and should be evaluated in
// real-time.
//
// WARNING: The ABIv2 specification allows additional padding between
// the Params and Data section. This will result in a larger
// offset to assetData.
//
// Note: Table #1 and Table #2 exists in Calldata. We construct Table #3 in memory.
//
//
assembly {
// The first 4 bytes of calldata holds the function selector
let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000)
// `transferFrom` will be called with the following parameters:
// assetData Encoded byte array.
// from Address to transfer asset from.
// to Address to transfer asset to.
// amount Amount of asset to transfer.
// bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4
if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) {
// To lookup a value in a mapping, we load from the storage location keccak256(k, p),
// where k is the key left padded to 32 bytes and p is the storage slot
mstore(0, caller)
mstore(32, authorized_slot)
// Revert if authorized[msg.sender] == false
if iszero(sload(keccak256(0, 64))) {
// Revert with `Error("SENDER_NOT_AUTHORIZED")`
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000)
mstore(96, 0)
revert(0, 100)
}
// Construct Table #3 in memory, starting at memory offset 0.
// The algorithm below maps asset data from Table #1 and Table #2 to Table #3, while
// scaling the `tokenValues` (Table #2) by `amount` (Table #1). Once Table #3 has
// been constructed in memory, the destination erc1155 contract is called using this
// as its calldata. This process is divided into four steps, below.
////////// STEP 1/4 //////////
// Map relevant fields from assetData (Table #2) into memory (Table #3)
// The Contents column of Table #2 is the same as Table #3,
// beginning from parameter 3 - `offset to tokenIds (*)`
// The offsets in these rows are offset by 32 bytes in Table #3.
// Strategy:
// 1. Copy the assetData into memory at offset 32
// 2. Increment by 32 the offsets to `tokenIds`, `tokenValues`, and `callbackData`
// Load offset to `assetData`
let assetDataOffset := calldataload(4)
// Load length in bytes of `assetData`, computed by:
// 4 (function selector)
// + assetDataOffset
let assetDataLength := calldataload(add(4, assetDataOffset))
// This corresponds to the beginning of the Data Area for Table #3.
// Computed by:
// 4 (function selector)
// + assetDataOffset
// + 32 (length of assetData)
calldatacopy(32, add(36, assetDataOffset), assetDataLength)
// Increment by 32 the offsets to `tokenIds`, `tokenValues`, and `callbackData`
mstore(68, add(mload(68), 32))
mstore(100, add(mload(100), 32))
mstore(132, add(mload(132), 32))
// Record the address of the destination erc1155 asset for later.
let assetAddress := and(
mload(36),
0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff
)
////////// STEP 2/4 //////////
let scaleAmount := calldataload(100)
let tokenValuesOffset := add(mload(100), 4) // add 4 for calldata offset
let tokenValuesLengthInBytes := mul(
mload(tokenValuesOffset),
32
)
let tokenValuesBegin := add(tokenValuesOffset, 32)
let tokenValuesEnd := add(tokenValuesBegin, add(tokenValuesLengthInBytes, 32))
for { let tokenValueOffset := tokenValuesBegin }
lt(tokenValueOffset, tokenValuesEnd)
{ tokenValueOffset := add(tokenValueOffset, 32) }
{
// Load token value and revert if multiplication would result in an overflow
let tokenValue := mload(tokenValueOffset)
let scaledTokenValue := mul(tokenValue, scaleAmount)
let expectedTokenValue := div(scaledTokenValue, scaleAmount)
// check for multiplication overflow
if iszero(eq(expectedTokenValue, tokenValue)) {
// Revert with `Error("UINT256_OVERFLOW")`
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
mstore(64, 0x0000001055494e543235365f4f564552464c4f57000000000000000000000000)
mstore(96, 0)
revert(0, 100)
}
// There was no overflow, update `tokenValue` with its scaled counterpart
mstore(tokenValueOffset, scaledTokenValue)
}
////////// STEP 3/4 //////////
// Store the safeBatchTransferFrom function selector,
// and copy `from`/`to` fields from Table #1 to Table #3.
// The function selector is computed using:
// bytes4(keccak256("safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"))
mstore(0, 0x2eb2c2d600000000000000000000000000000000000000000000000000000000)
// Copy `from` and `to` fields from Table #1 to Table #3
calldatacopy(4, 36, 64)
////////// STEP 4/4 //////////
// Call into the destination erc1155 contract using as calldata Table #3 (constructed in-memory above)
let success := call(
gas, // forward all gas
assetAddress, // call address of erc1155 asset
0, // don't send any ETH
0, // pointer to start of input
add(assetDataLength, 32), // length of input (Table #3) is 32 bytes longer than `assetData` (Table #2)
0, // write output over memory that won't be reused
0 // don't copy output to memory
)
// Revert with reason given by AssetProxy if `transferFrom` call failed
if iszero(success) {
returndatacopy(
0, // copy to memory at 0
0, // copy from return data at 0
returndatasize() // copy all return data
)
revert(0, returndatasize())
}
// Return if call was successful
return(0, 0)
}
// Revert if undefined function is called
revert(0, 0)
}
}
/// @dev Gets the proxy id associated with the proxy address.
/// @return Proxy id.
function getProxyId()
external
pure
returns (bytes4)
{
return PROXY_ID;
}
}

View File

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

View File

@@ -5,6 +5,9 @@
*/
import { ContractArtifact } from 'ethereum-types';
import * as DummyERC1155Receiver from '../generated-artifacts/DummyERC1155Receiver.json';
import * as ERC1155Mintable from '../generated-artifacts/ERC1155Mintable.json';
import * as ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json';
import * as ERC20Proxy from '../generated-artifacts/ERC20Proxy.json';
import * as ERC721Proxy from '../generated-artifacts/ERC721Proxy.json';
import * as IAssetData from '../generated-artifacts/IAssetData.json';
@@ -13,6 +16,9 @@ import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json';
import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json';
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
export const artifacts = {
DummyERC1155Receiver: DummyERC1155Receiver as ContractArtifact,
ERC1155Mintable: ERC1155Mintable as ContractArtifact,
ERC1155Proxy: ERC1155Proxy as ContractArtifact,
ERC20Proxy: ERC20Proxy as ContractArtifact,
ERC721Proxy: ERC721Proxy as ContractArtifact,
MixinAuthorizable: MixinAuthorizable as ContractArtifact,

View File

@@ -3,6 +3,9 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
export * from '../generated-wrappers/dummy_erc1155_receiver';
export * from '../generated-wrappers/erc1155_mintable';
export * from '../generated-wrappers/erc1155_proxy';
export * from '../generated-wrappers/erc20_proxy';
export * from '../generated-wrappers/erc721_proxy';
export * from '../generated-wrappers/i_asset_data';

View File

@@ -34,10 +34,16 @@ import {
ERC20Wrapper,
ERC721ProxyContract,
ERC721Wrapper,
ERC1155Wrapper,
ERC1155ProxyContract,
IAssetDataContract,
IAssetProxyContract,
MultiAssetProxyContract,
ERC1155MintableContract,
DummyERC1155ReceiverContract,
DummyERC1155ReceiverBatchTokenReceivedEventArgs,
} from '../src';
import values from 'ramda/es/values';
chaiSetup.configure();
const expect = chai.expect;
@@ -68,15 +74,22 @@ describe('Asset Transfer Proxies', () => {
let erc721Receiver: DummyERC721ReceiverContract;
let erc20Proxy: ERC20ProxyContract;
let erc721Proxy: ERC721ProxyContract;
let erc1155Proxy: ERC721ProxyContract;
let erc1155Receiver: DummyERC1155ReceiverContract;
let noReturnErc20Token: DummyNoReturnERC20TokenContract;
let multipleReturnErc20Token: DummyMultipleReturnERC20TokenContract;
let multiAssetProxy: MultiAssetProxyContract;
let erc20Wrapper: ERC20Wrapper;
let erc721Wrapper: ERC721Wrapper;
let erc1155Wrapper: ERC1155Wrapper;
let erc721AFromTokenId: BigNumber;
let erc721BFromTokenId: BigNumber;
let erc1155Token: ERC1155MintableContract;
let erc1155FungibleTokenIds: BigNumber[];
let erc1155NonFungibleTokenIds: BigNumber[];
before(async () => {
await blockchainLifecycle.startAsync();
});
@@ -89,10 +102,12 @@ describe('Asset Transfer Proxies', () => {
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
erc1155Wrapper = new ERC1155Wrapper(provider, usedAddresses, owner);
// Deploy AssetProxies
erc20Proxy = await erc20Wrapper.deployProxyAsync();
erc721Proxy = await erc721Wrapper.deployProxyAsync();
erc1155Proxy = await erc1155Wrapper.deployProxyAsync();
multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
artifacts.MultiAssetProxy,
provider,
@@ -127,6 +142,20 @@ describe('Asset Transfer Proxies', () => {
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Configure ERC1155Proxy
await web3Wrapper.awaitTransactionSuccessAsync(
await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
from: owner,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, {
from: owner,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Configure MultiAssetProxy
await web3Wrapper.awaitTransactionSuccessAsync(
await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
@@ -208,11 +237,21 @@ describe('Asset Transfer Proxies', () => {
provider,
txDefaults,
);
await erc721Wrapper.setBalancesAndAllowancesAsync();
const erc721Balances = await erc721Wrapper.getBalancesAsync();
erc721AFromTokenId = erc721Balances[fromAddress][erc721TokenA.address][0];
erc721BFromTokenId = erc721Balances[fromAddress][erc721TokenB.address][0];
// Deploy and configure ERC1155 tokens and receiver
[erc1155Token] = await erc1155Wrapper.deployDummyTokensAsync();
erc1155Receiver = await DummyERC1155ReceiverContract.deployFrom0xArtifactAsync(
artifacts.DummyERC1155Receiver,
provider,
txDefaults,
);
await erc1155Wrapper.setBalancesAndAllowancesAsync();
erc1155FungibleTokenIds = erc1155Wrapper.getFungibleTokenIds();
erc1155NonFungibleTokenIds = erc1155Wrapper.getNonFungibleTokenIds();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
@@ -477,7 +516,6 @@ describe('Asset Transfer Proxies', () => {
});
});
});
describe('ERC721Proxy', () => {
it('should revert if undefined function is called', async () => {
const undefinedSelector = '0x01020304';
@@ -1286,6 +1324,373 @@ describe('Asset Transfer Proxies', () => {
});
});
});
describe.only('ERC1155Proxy', () => {
it('should revert if undefined function is called', async () => {
const undefinedSelector = '0x01020304';
await expectTransactionFailedWithoutReasonAsync(
web3Wrapper.sendTransactionAsync({
from: owner,
to: erc1155Proxy.address,
value: constants.ZERO_AMOUNT,
data: undefinedSelector,
}),
);
});
it('should have an id of 0x9645780d', async () => {
const proxyId = await erc1155Proxy.getProxyId.callAsync();
// proxy computed using -- bytes4(keccak256("ERC1155Token(address,uint256[],uint256[],bytes)"));
const expectedProxyId = '0x9645780d';
expect(proxyId).to.equal(expectedProxyId);
});
describe('transferFrom', () => {
it('should successfully transfer value for a single token', async () => {
// Construct ERC1155 asset data
const callbackData = "0x";
const fungibleTokenIdToTransfer = erc1155FungibleTokenIds[0];
const tokenIdsToTransfer = [fungibleTokenIdToTransfer];
const tokenValuesToTransfer = [new BigNumber(10)];
const encodedAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Token.address, tokenIdsToTransfer, tokenValuesToTransfer, callbackData);
// Verify pre-condition
const initialHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const initialSenderBalance = initialHoldingsByOwner.fungible[fromAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const initialReceiverBalance = initialHoldingsByOwner.fungible[toAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
// Perform a transfer from fromAddress to toAddress
const perUnitValue = new BigNumber(1000);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
encodedAssetData,
fromAddress,
toAddress,
perUnitValue,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
to: erc1155Proxy.address,
data,
from: authorized,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Verify transfer was successful
const totalValueTransferred = tokenValuesToTransfer[0].times(perUnitValue);
const newHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const newSenderBalance = newHoldingsByOwner.fungible[fromAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const expectedNewSenderBalance = initialSenderBalance.minus(totalValueTransferred);
const newReceiverBalance = newHoldingsByOwner.fungible[toAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const expectedNewReceiverBalance = initialReceiverBalance.plus(totalValueTransferred);
expect(newSenderBalance).to.be.bignumber.equal(expectedNewSenderBalance);
expect(newReceiverBalance).to.be.bignumber.equal(expectedNewReceiverBalance);
});
it('should successfully transfer value for a collection of fungible tokens of the same id', async () => {
// Construct ERC1155 asset data
const callbackData = "0x";
const fungibleTokenIdToTransfer = erc1155FungibleTokenIds[0];
const tokenIdsToTransfer = [fungibleTokenIdToTransfer, fungibleTokenIdToTransfer, fungibleTokenIdToTransfer];
const tokenValuesToTransfer = [new BigNumber(10), new BigNumber(20), new BigNumber(30)];
const encodedAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Token.address, tokenIdsToTransfer, tokenValuesToTransfer, callbackData);
// Verify pre-condition
const initialHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const initialSenderBalance = initialHoldingsByOwner.fungible[fromAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const initialReceiverBalance = initialHoldingsByOwner.fungible[toAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
// Perform a transfer from fromAddress to toAddress
const perUnitValue = new BigNumber(1000);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
encodedAssetData,
fromAddress,
toAddress,
perUnitValue,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
to: erc1155Proxy.address,
data,
from: authorized,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Verify transfer was successful
const totalValueTransferred = _.reduce(tokenValuesToTransfer, (sum: BigNumber, value: BigNumber) => {return sum.plus(value)}, new BigNumber(0)).times(perUnitValue);
const newHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const newSenderBalance = newHoldingsByOwner.fungible[fromAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const expectedNewSenderBalance = initialSenderBalance.minus(totalValueTransferred);
const newReceiverBalance = newHoldingsByOwner.fungible[toAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const expectedNewReceiverBalance = initialReceiverBalance.plus(totalValueTransferred);
expect(newSenderBalance).to.be.bignumber.equal(expectedNewSenderBalance);
expect(newReceiverBalance).to.be.bignumber.equal(expectedNewReceiverBalance);
});
it('should successfully transfer value for a collection of fungible tokens of different ids', async () => {
// Construct ERC1155 asset data
const callbackData = "0x";
const tokenIdsToTransfer = erc1155FungibleTokenIds.slice(0, 2);
const tokenValuesToTransfer = [new BigNumber(10), new BigNumber(20)];
const encodedAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Token.address, tokenIdsToTransfer, tokenValuesToTransfer, callbackData);
// Verify pre-condition
const initialHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const initialSenderBalances: BigNumber[] = [];
const initialReceiverBalances: BigNumber[] = [];
_.each(tokenIdsToTransfer, (tokenIdToTransfer: BigNumber) => {
initialSenderBalances.push(initialHoldingsByOwner.fungible[fromAddress][erc1155Token.address][tokenIdToTransfer.toString()]);
initialReceiverBalances.push(initialHoldingsByOwner.fungible[toAddress][erc1155Token.address][tokenIdToTransfer.toString()]);
});
// Perform a transfer from fromAddress to toAddress
const perUnitValue = new BigNumber(1000);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
encodedAssetData,
fromAddress,
toAddress,
perUnitValue,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
to: erc1155Proxy.address,
data,
from: authorized,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Verify transfer was successful
const newHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
_.each(tokenIdsToTransfer, (tokenIdToTransfer: BigNumber, i: number) => {
const totalValueTransferred = tokenValuesToTransfer[i].times(perUnitValue);
const newSenderBalance = newHoldingsByOwner.fungible[fromAddress][erc1155Token.address][tokenIdToTransfer.toString()];
const expectedNewSenderBalance = initialSenderBalances[i].minus(totalValueTransferred);
const newReceiverBalance = newHoldingsByOwner.fungible[toAddress][erc1155Token.address][tokenIdToTransfer.toString()];
const expectedNewReceiverBalance = initialReceiverBalances[i].plus(totalValueTransferred);
expect(newSenderBalance).to.be.bignumber.equal(expectedNewSenderBalance);
expect(newReceiverBalance).to.be.bignumber.equal(expectedNewReceiverBalance);
});
});
it('should successfully transfer a non-fungible token', async () => {
// Construct ERC1155 asset data
const callbackData = "0x";
const nonFungibleTokenIdToTransfer = erc1155NonFungibleTokenIds[0];
const initialHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const nftToTransfer = initialHoldingsByOwner.nonFungible[fromAddress][erc1155Token.address][nonFungibleTokenIdToTransfer.toString()][0];
const tokenIdsToTransfer = [nftToTransfer];
const tokenValuesToTransfer = [new BigNumber(1)];
const encodedAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Token.address, tokenIdsToTransfer, tokenValuesToTransfer, callbackData);
// Verify precondition
const nftHolder = await erc1155Wrapper.ownerOfNonFungibleAsync(erc1155Token.address, nftToTransfer);
expect(nftHolder).to.be.equal(fromAddress);
// Perform a transfer from fromAddress to toAddress
const perUnitValue = new BigNumber(1);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
encodedAssetData,
fromAddress,
toAddress,
perUnitValue,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
to: erc1155Proxy.address,
data,
from: authorized,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Verify transfer was successful
const newNftHolder = await erc1155Wrapper.ownerOfNonFungibleAsync(erc1155Token.address, nftToTransfer);
expect(newNftHolder).to.be.equal(toAddress);
// Verify balances updated successfully
const newHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const newNftsForFromAddress = newHoldingsByOwner.nonFungible[fromAddress][erc1155Token.address][nonFungibleTokenIdToTransfer.toString()];
const newNftsForToAddress = newHoldingsByOwner.nonFungible[toAddress][erc1155Token.address][nonFungibleTokenIdToTransfer.toString()];
expect(_.find(newNftsForFromAddress, nftToTransfer)).to.be.undefined();
expect(_.find(newNftsForToAddress, nftToTransfer)).to.be.not.undefined();
});
it('should successfully transfer value for a combination of fungible/non-fungible tokens', async () => {
// Construct ERC1155 asset data
const callbackData = "0x";
const fungibleTokenIdToTransfer = erc1155FungibleTokenIds[0];
const nonFungibleTokenIdToTransfer = erc1155NonFungibleTokenIds[0];
const initialHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const nftToTransfer = initialHoldingsByOwner.nonFungible[fromAddress][erc1155Token.address][nonFungibleTokenIdToTransfer.toString()][0];
const tokenIdsToTransfer = [fungibleTokenIdToTransfer, nftToTransfer];
const tokenValuesToTransfer = [new BigNumber(10), new BigNumber(1)];
const encodedAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Token.address, tokenIdsToTransfer, tokenValuesToTransfer, callbackData);
// Verify precondition
const initialSenderBalance = initialHoldingsByOwner.fungible[fromAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const initialReceiverBalance = initialHoldingsByOwner.fungible[toAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const nftHolder = await erc1155Wrapper.ownerOfNonFungibleAsync(erc1155Token.address, nftToTransfer);
expect(nftHolder).to.be.equal(fromAddress);
// Perform a transfer from fromAddress to toAddress
const perUnitValue = new BigNumber(1);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
encodedAssetData,
fromAddress,
toAddress,
perUnitValue,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
to: erc1155Proxy.address,
data,
from: authorized,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Verify non-fungible transfer was successful
const newNftHolder = await erc1155Wrapper.ownerOfNonFungibleAsync(erc1155Token.address, nftToTransfer);
expect(newNftHolder).to.be.equal(toAddress);
// Verify non-fungible balances updated successfully
const newHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const newNftsForFromAddress = newHoldingsByOwner.nonFungible[fromAddress][erc1155Token.address][nonFungibleTokenIdToTransfer.toString()];
const newNftsForToAddress = newHoldingsByOwner.nonFungible[toAddress][erc1155Token.address][nonFungibleTokenIdToTransfer.toString()];
expect(_.find(newNftsForFromAddress, nftToTransfer)).to.be.undefined();
expect(_.find(newNftsForToAddress, nftToTransfer)).to.be.not.undefined();
// Verify fungible transfer was successful
const totalValueTransferred = tokenValuesToTransfer[0].times(perUnitValue);
const newSenderBalance = newHoldingsByOwner.fungible[fromAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const expectedNewSenderBalance = initialSenderBalance.minus(totalValueTransferred);
const newReceiverBalance = newHoldingsByOwner.fungible[toAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const expectedNewReceiverBalance = initialReceiverBalance.plus(totalValueTransferred);
expect(newSenderBalance).to.be.bignumber.equal(expectedNewSenderBalance);
expect(newReceiverBalance).to.be.bignumber.equal(expectedNewReceiverBalance);
});
it('should successfully transfer value and ignore extra assetData', async () => {
// Construct ERC1155 asset data
const callbackData = "0x";
const fungibleTokenIdToTransfer = erc1155FungibleTokenIds[0];
const tokenIdsToTransfer = [fungibleTokenIdToTransfer];
const tokenValuesToTransfer = [new BigNumber(10)];
const encodedAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Token.address, tokenIdsToTransfer, tokenValuesToTransfer, callbackData);
const extraData = '0102030405060708';
const encodedAssetDataPlusExtraData = `${encodedAssetData}${extraData}`;
// Verify pre-condition
const initialHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const initialSenderBalance = initialHoldingsByOwner.fungible[fromAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const initialReceiverBalance = initialHoldingsByOwner.fungible[toAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
// Perform a transfer from fromAddress to toAddress
const perUnitValue = new BigNumber(1000);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
encodedAssetDataPlusExtraData,
fromAddress,
toAddress,
perUnitValue,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await web3Wrapper.sendTransactionAsync({
to: erc1155Proxy.address,
data,
from: authorized,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Verify transfer was successful
const totalValueTransferred = tokenValuesToTransfer[0].times(perUnitValue);
const newHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const newSenderBalance = newHoldingsByOwner.fungible[fromAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const expectedNewSenderBalance = initialSenderBalance.minus(totalValueTransferred);
const newReceiverBalance = newHoldingsByOwner.fungible[toAddress][erc1155Token.address][fungibleTokenIdToTransfer.toString()];
const expectedNewReceiverBalance = initialReceiverBalance.plus(totalValueTransferred);
expect(newSenderBalance).to.be.bignumber.equal(expectedNewSenderBalance);
expect(newReceiverBalance).to.be.bignumber.equal(expectedNewReceiverBalance);
});
it.only('should successfully execute callback when transferring to a smart contract', async () => {
// Construct ERC1155 asset data
const callbackData = "0x";
const nonFungibleTokenIdToTransfer = erc1155NonFungibleTokenIds[0];
const initialHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const nftToTransfer = initialHoldingsByOwner.nonFungible[fromAddress][erc1155Token.address][nonFungibleTokenIdToTransfer.toString()][0];
const tokenIdsToTransfer = [nftToTransfer];
const tokenValuesToTransfer = [new BigNumber(1)];
const encodedAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Token.address, tokenIdsToTransfer, tokenValuesToTransfer, callbackData);
// Verify precondition
const nftHolder = await erc1155Wrapper.ownerOfNonFungibleAsync(erc1155Token.address, nftToTransfer);
expect(nftHolder).to.be.equal(fromAddress);
// Perform a transfer from fromAddress to toAddress
const perUnitValue = new BigNumber(1);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
encodedAssetData,
fromAddress,
erc1155Receiver.address,
perUnitValue,
);
const logDecoder = new LogDecoder(web3Wrapper, artifacts);
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await web3Wrapper.sendTransactionAsync({
to: erc1155Proxy.address,
data,
from: authorized,
})
);
// Verify logs
expect(txReceipt.logs.length).to.be.equal(2);
const receiverLog = txReceipt.logs[1] as LogWithDecodedArgs<DummyERC1155ReceiverBatchTokenReceivedEventArgs>;
expect(receiverLog.args.operator).to.be.equal(erc1155Proxy.address);
expect(receiverLog.args.from).to.be.equal(fromAddress);
expect(receiverLog.args.tokenIds.length).to.be.deep.equal(1);
expect(receiverLog.args.tokenIds[0]).to.be.bignumber.equal(tokenIdsToTransfer[0]);
expect(receiverLog.args.tokenValues.length).to.be.deep.equal(1);
expect(receiverLog.args.tokenValues[0]).to.be.bignumber.equal(tokenValuesToTransfer[0]);
expect(receiverLog.args.data).to.be.deep.equal(callbackData);
// Verify transfer was successful
const newNftHolder = await erc1155Wrapper.ownerOfNonFungibleAsync(erc1155Token.address, nftToTransfer);
expect(newNftHolder).to.be.equal(erc1155Receiver.address);
});
it.only('should successfully execute callback when transferring to a smart conract when there is callback data', async () => {
// Construct ERC1155 asset data
const callbackData = "0x12345678";
const nonFungibleTokenIdToTransfer = erc1155NonFungibleTokenIds[0];
const initialHoldingsByOwner = await erc1155Wrapper.getBalancesAsync();
const nftToTransfer = initialHoldingsByOwner.nonFungible[fromAddress][erc1155Token.address][nonFungibleTokenIdToTransfer.toString()][0];
const tokenIdsToTransfer = [nftToTransfer];
const tokenValuesToTransfer = [new BigNumber(1)];
const encodedAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Token.address, tokenIdsToTransfer, tokenValuesToTransfer, callbackData);
// Verify precondition
const nftHolder = await erc1155Wrapper.ownerOfNonFungibleAsync(erc1155Token.address, nftToTransfer);
expect(nftHolder).to.be.equal(fromAddress);
// Perform a transfer from fromAddress to toAddress
const perUnitValue = new BigNumber(1);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
encodedAssetData,
fromAddress,
erc1155Receiver.address,
perUnitValue,
);
const logDecoder = new LogDecoder(web3Wrapper, artifacts);
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await web3Wrapper.sendTransactionAsync({
to: erc1155Proxy.address,
data,
from: authorized,
})
);
// Verify logs
expect(txReceipt.logs.length).to.be.equal(2);
const receiverLog = txReceipt.logs[1] as LogWithDecodedArgs<DummyERC1155ReceiverBatchTokenReceivedEventArgs>;
expect(receiverLog.args.operator).to.be.equal(erc1155Proxy.address);
expect(receiverLog.args.from).to.be.equal(fromAddress);
expect(receiverLog.args.tokenIds.length).to.be.deep.equal(1);
expect(receiverLog.args.tokenIds[0]).to.be.bignumber.equal(tokenIdsToTransfer[0]);
expect(receiverLog.args.tokenValues.length).to.be.deep.equal(1);
expect(receiverLog.args.tokenValues[0]).to.be.bignumber.equal(tokenValuesToTransfer[0]);
expect(receiverLog.args.data).to.be.deep.equal(callbackData);
// Verify transfer was successful
const newNftHolder = await erc1155Wrapper.ownerOfNonFungibleAsync(erc1155Token.address, nftToTransfer);
expect(newNftHolder).to.be.equal(erc1155Receiver.address);
});
it('should propagate revert reason from erc1155 contract failure', async () => {
});
it('should revert if transferring the same non-fungible token more than once', async () => {
});
it('should revert if tansferring 0 amount of any token', async () => {
});
it('should revert if there is a multiplication overflow', async () => {
});
it('should revert if there is a multiplication overflow, when transferring multiple tokens', async () => {
});
it('should revert if transferring > 1 instances of a non-fungible token (amount field >1)', async () => {
});
it('should revert if transferring > 1 instances of a non-fungible token (value field >1)', async () => {
});
it('should revert if sender balance is insufficient', async () => {
});
it('should revert if sender allowance is insufficient', async () => {
});
it('should revert if caller is not authorized', async () => {
});
});
});
});
// tslint:enable:no-unnecessary-type-assertion
// tslint:disable:max-file-line-count

View File

@@ -0,0 +1,288 @@
import { constants, ERC1155HoldingsByOwner, ERC1155FungibleHoldingsByOwner, ERC1155NonFungibleHoldingsByOwner, LogDecoder, txDefaults } from '@0x/contracts-test-utils';
import { generatePseudoRandomSalt } from '@0x/order-utils';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import { LogWithDecodedArgs } from 'ethereum-types';
import { artifacts, ERC1155MintableContract, ERC1155ProxyContract, ERC1155MintableTransferSingleEventArgs } from '../../src';
export class ERC1155Wrapper {
private readonly _tokenOwnerAddresses: string[];
private readonly _fungibleTokenIds: string[];
private readonly _nonFungibleTokenIds: string[];
private readonly _nfts: {id: BigNumber, tokenId: BigNumber}[];
private readonly _contractOwnerAddress: string;
private readonly _web3Wrapper: Web3Wrapper;
private readonly _provider: Provider;
private readonly _logDecoder: LogDecoder;
private readonly _dummyTokenContracts: ERC1155MintableContract[];
private _proxyContract?: ERC1155ProxyContract;
private _proxyIdIfExists?: string;
private _initialTokenIdsByOwner: ERC1155HoldingsByOwner = {fungible: {}, nonFungible: {}};
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
this._web3Wrapper = new Web3Wrapper(provider);
this._provider = provider;
this._logDecoder = new LogDecoder(this._web3Wrapper, artifacts);
this._dummyTokenContracts = [];
this._tokenOwnerAddresses = tokenOwnerAddresses;
this._contractOwnerAddress = contractOwnerAddress;
this._fungibleTokenIds = [];
this._nonFungibleTokenIds = [];
this._nfts = [];
}
public async deployDummyTokensAsync(): Promise<ERC1155MintableContract[]> {
// tslint:disable-next-line:no-unused-variable
for (const i of _.times(constants.NUM_DUMMY_ERC1155_TO_DEPLOY)) {
this._dummyTokenContracts.push(
await ERC1155MintableContract.deployFrom0xArtifactAsync(
artifacts.ERC1155Mintable,
this._provider,
txDefaults,
),
);
}
return this._dummyTokenContracts;
}
public async deployProxyAsync(): Promise<ERC1155ProxyContract> {
this._proxyContract = await ERC1155ProxyContract.deployFrom0xArtifactAsync(
artifacts.ERC1155Proxy,
this._provider,
txDefaults,
);
this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
return this._proxyContract;
}
public getProxyId(): string {
this._validateProxyContractExistsOrThrow();
return this._proxyIdIfExists as string;
}
public async setBalancesAndAllowancesAsync(): Promise<ERC1155HoldingsByOwner> {
this._validateDummyTokenContractsExistOrThrow();
this._validateProxyContractExistsOrThrow();
this._initialTokenIdsByOwner = {
fungible: {} as ERC1155FungibleHoldingsByOwner,
nonFungible: {}
};
const fungibleHoldingsByOwner: ERC1155FungibleHoldingsByOwner = {};
const nonFungibleHoldingsByOwner: ERC1155NonFungibleHoldingsByOwner = {};
for (const dummyTokenContract of this._dummyTokenContracts) {
// Fungible Tokens
for (const i of _.times(constants.NUM_ERC1155_FUNGIBLE_TOKENS_MINT)) {
// Create a fungible token
const tokenUri = generatePseudoRandomSalt().toString();
const tokenIsNonFungible = false;
const tokenId = await this.createTokenAsync(dummyTokenContract.address, tokenUri, tokenIsNonFungible);
const tokenIdAsString = tokenId.toString();
this._fungibleTokenIds.push(tokenIdAsString);
// Mint tokens for each owner for this token
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
// tslint:disable-next-line:no-unused-variable
await this.mintFungibleAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress);
if (_.isUndefined(fungibleHoldingsByOwner[tokenOwnerAddress])) {
fungibleHoldingsByOwner[tokenOwnerAddress] = {};
}
if (_.isUndefined(fungibleHoldingsByOwner[tokenOwnerAddress][dummyTokenContract.address])) {
fungibleHoldingsByOwner[tokenOwnerAddress][dummyTokenContract.address] = {};
}
fungibleHoldingsByOwner[tokenOwnerAddress][dummyTokenContract.address][tokenIdAsString] = constants.INITIAL_ERC1155_FUNGIBLE_BALANCE;
await this.approveProxyAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress);
}
}
// Non-Fungible Tokens
for (const i of _.times(constants.NUM_ERC1155_NONFUNGIBLE_TOKENS_MINT)) {
const tokenUri = generatePseudoRandomSalt().toString();
const tokenIsNonFungible = true;
const tokenId = await this.createTokenAsync(dummyTokenContract.address, tokenUri, tokenIsNonFungible);
const tokenIdAsString = tokenId.toString();
this._nonFungibleTokenIds.push(tokenIdAsString);
await this.mintNonFungibleAsync(dummyTokenContract.address, tokenId, this._tokenOwnerAddresses);
let tokenNonce = 0;
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
if (_.isUndefined(nonFungibleHoldingsByOwner[tokenOwnerAddress])) {
nonFungibleHoldingsByOwner[tokenOwnerAddress] = {};
}
if (_.isUndefined(nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyTokenContract.address])) {
nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyTokenContract.address] = {};
}
if (_.isUndefined(nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyTokenContract.address][tokenIdAsString])) {
nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyTokenContract.address][tokenIdAsString] = [];
}
const nonFungibleId = tokenId.plus(++tokenNonce);
this._nfts.push({id: nonFungibleId, tokenId});
nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyTokenContract.address][tokenIdAsString].push(nonFungibleId);
await this.approveProxyAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress);
}
}
}
this._initialTokenIdsByOwner = {
fungible: fungibleHoldingsByOwner,
nonFungible: nonFungibleHoldingsByOwner,
}
return this._initialTokenIdsByOwner;
}
public async approveProxyAsync(tokenAddress: string, tokenId: BigNumber, tokenOwner: string): Promise<void> {
const proxyAddress = (this._proxyContract as ERC1155ProxyContract).address;
await this.approveProxyForAllAsync(proxyAddress, tokenAddress, tokenOwner);
}
public async approveProxyForAllAsync(to: string, tokenAddress: string, tokenOwner: string): Promise<void> {
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
await this._web3Wrapper.awaitTransactionSuccessAsync(
await tokenContract.setApprovalForAll.sendTransactionAsync(to, true, {
from: tokenOwner,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
}
public async createTokenAsync(tokenAddress: string, tokenUri: string, tokenIsNonFungible: boolean): Promise<BigNumber> {
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(
await tokenContract.create.sendTransactionAsync(tokenUri, tokenIsNonFungible),
);
const createFungibleTokenLog = txReceipt.logs[0] as LogWithDecodedArgs<ERC1155MintableTransferSingleEventArgs>;
const dummyFungibleTokenId = createFungibleTokenLog.args._id;
return dummyFungibleTokenId;
}
public async mintFungibleAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise<void> {
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
await this._web3Wrapper.awaitTransactionSuccessAsync(
await tokenContract.mintFungible.sendTransactionAsync(
tokenId,
[userAddress],
[constants.INITIAL_ERC1155_FUNGIBLE_BALANCE],
{ from: this._contractOwnerAddress }
),
constants.AWAIT_TRANSACTION_MINED_MS,
);
}
public async mintNonFungibleAsync(tokenAddress: string, tokenId: BigNumber, userAddresses: string[]): Promise<void> {
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
await this._web3Wrapper.awaitTransactionSuccessAsync(
await tokenContract.mintNonFungible.sendTransactionAsync(
tokenId,
userAddresses,
{ from: this._contractOwnerAddress }
),
constants.AWAIT_TRANSACTION_MINED_MS,
);
}
public async ownerOfNonFungibleAsync(tokenAddress: string, tokenId: BigNumber): Promise<string> {
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
const owner = await tokenContract.ownerOf.callAsync(tokenId);
return owner;
}
public async isNonFungibleOwnerAsync(userAddress: string, tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId);
const isOwner = tokenOwner === userAddress;
return isOwner;
}
public async isProxyApprovedForAllAsync(userAddress: string, tokenAddress: string): Promise<boolean> {
this._validateProxyContractExistsOrThrow();
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
const operator = (this._proxyContract as ERC1155ProxyContract).address;
const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator);
return didApproveAll;
}
public async getBalancesAsync(): Promise<ERC1155HoldingsByOwner> {
this._validateDummyTokenContractsExistOrThrow();
this._validateBalancesAndAllowancesSetOrThrow();
const tokenHoldingsByOwner: ERC1155FungibleHoldingsByOwner = {};
const nonFungibleHoldingsByOwner: ERC1155NonFungibleHoldingsByOwner = {};
for (const dummyTokenContract of this._dummyTokenContracts) {
const tokenAddress = dummyTokenContract.address;
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
// Construct batch balance call
const tokenOwners: string[] = [];
const tokenIds: BigNumber[] = [];
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
for (const tokenId of this._fungibleTokenIds) {
tokenOwners.push(tokenOwnerAddress);
tokenIds.push(new BigNumber(tokenId));
}
for (const nft of this._nfts) {
tokenOwners.push(tokenOwnerAddress);
tokenIds.push(nft.id);
}
}
const balances = await tokenContract.balanceOfBatch.callAsync(tokenOwners, tokenIds);
// Parse out balances into fungible / non-fungible token holdings
let i = 0;
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
// Fungible tokens
for (const tokenId of this._fungibleTokenIds) {
if (_.isUndefined(tokenHoldingsByOwner[tokenOwnerAddress])) {
tokenHoldingsByOwner[tokenOwnerAddress] = {};
}
if (_.isUndefined(tokenHoldingsByOwner[tokenOwnerAddress][tokenAddress])) {
tokenHoldingsByOwner[tokenOwnerAddress][tokenAddress] = {};
}
tokenHoldingsByOwner[tokenOwnerAddress][tokenAddress][tokenId] = balances[i++];
}
// Non-fungible tokens
for (const nft of this._nfts) {
if (_.isUndefined(nonFungibleHoldingsByOwner[tokenOwnerAddress])) {
nonFungibleHoldingsByOwner[tokenOwnerAddress] = {};
}
if (_.isUndefined(nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress])) {
nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress] = {};
}
if (_.isUndefined(nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress][nft.tokenId.toString()])) {
nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress][nft.tokenId.toString()] = [];
}
const isOwner = balances[i++];
if (isOwner.isEqualTo(1)) {
nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress][nft.tokenId.toString()].push(nft.id);
}
}
}
}
const holdingsByOwner = {
fungible: tokenHoldingsByOwner,
nonFungible: nonFungibleHoldingsByOwner,
}
return holdingsByOwner;
}
public getFungibleTokenIds(): BigNumber[] {
const fungibleTokenIds = _.map(this._fungibleTokenIds, (tokenIdAsString: string) => {return new BigNumber(tokenIdAsString)});
return fungibleTokenIds;
}
public getNonFungibleTokenIds(): BigNumber[] {
const nonFungibleTokenIds = _.map(this._nonFungibleTokenIds, (tokenIdAsString: string) => {return new BigNumber(tokenIdAsString)});
return nonFungibleTokenIds;
}
public getTokenOwnerAddresses(): string[] {
return this._tokenOwnerAddresses;
}
public getTokenAddresses(): string[] {
const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
return tokenAddresses;
}
private _getTokenContractFromAssetData(tokenAddress: string): ERC1155MintableContract {
const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
if (_.isUndefined(tokenContractIfExists)) {
throw new Error(`Token: ${tokenAddress} was not deployed through ERC1155Wrapper`);
}
return tokenContractIfExists;
}
private _validateDummyTokenContractsExistOrThrow(): void {
if (_.isUndefined(this._dummyTokenContracts)) {
throw new Error('Dummy ERC1155 tokens not yet deployed, please call "deployDummyTokensAsync"');
}
}
private _validateProxyContractExistsOrThrow(): void {
if (_.isUndefined(this._proxyContract)) {
throw new Error('ERC1155 proxy contract not yet deployed, please call "deployProxyAsync"');
}
}
private _validateBalancesAndAllowancesSetOrThrow(): void {
if (_.keys(this._initialTokenIdsByOwner.fungible).length === 0 || _.keys(this._initialTokenIdsByOwner.nonFungible).length === 0) {
throw new Error(
'Dummy ERC1155 balances and allowances not yet set, please call "setBalancesAndAllowancesAsync"',
);
}
}
}

View File

@@ -1,2 +1,3 @@
export * from './erc20_wrapper';
export * from './erc721_wrapper';
export * from './erc1155_wrapper';

View File

@@ -3,6 +3,9 @@
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [
"generated-artifacts/DummyERC1155Receiver.json",
"generated-artifacts/ERC1155Mintable.json",
"generated-artifacts/ERC1155Proxy.json",
"generated-artifacts/ERC20Proxy.json",
"generated-artifacts/ERC721Proxy.json",
"generated-artifacts/IAssetData.json",

View File

@@ -38,11 +38,16 @@ export const constants = {
NUM_DUMMY_ERC20_TO_DEPLOY: 3,
NUM_DUMMY_ERC721_TO_DEPLOY: 2,
NUM_ERC721_TOKENS_TO_MINT: 2,
NUM_DUMMY_ERC1155_TO_DEPLOY: 1,
NUM_ERC1155_FUNGIBLE_TOKENS_MINT: 3,
NUM_ERC1155_NONFUNGIBLE_TOKENS_MINT: 3,
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
TESTRPC_PRIVATE_KEYS: _.map(TESTRPC_PRIVATE_KEYS_STRINGS, privateKeyString => ethUtil.toBuffer(privateKeyString)),
INITIAL_ERC20_BALANCE: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18),
INITIAL_ERC20_ALLOWANCE: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18),
INITIAL_ERC1155_FUNGIBLE_BALANCE: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18),
INITIAL_ERC1155_FUNGIBLE_ALLOWANCE: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18),
STATIC_ORDER_PARAMS: {
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),

View File

@@ -32,6 +32,9 @@ export {
MarketBuyOrders,
MarketSellOrders,
ERC721TokenIdsByOwner,
ERC1155FungibleHoldingsByOwner,
ERC1155NonFungibleHoldingsByOwner,
ERC1155HoldingsByOwner,
OrderStatus,
AllowanceAmountScenario,
AssetDataScenario,

View File

@@ -14,6 +14,27 @@ export interface ERC721TokenIdsByOwner {
};
}
export interface ERC1155FungibleHoldingsByOwner {
[ownerAddress: string]: {
[tokenAddress: string]: {
[tokenId: string]: BigNumber
}
};
}
export interface ERC1155NonFungibleHoldingsByOwner {
[ownerAddress: string]: {
[tokenAddress: string]: {
[tokenId: string]: BigNumber[]
}
};
}
export interface ERC1155HoldingsByOwner {
fungible: ERC1155FungibleHoldingsByOwner,
nonFungible: ERC1155NonFungibleHoldingsByOwner,
}
export interface SubmissionContractEventArgs {
transactionId: BigNumber;
}

View File

@@ -94,6 +94,18 @@ export const assetDataUtils = {
const assetData = abiEncoder.encode(args, encodingRules);
return assetData;
},
/**
* Encodes assetData for multiple AssetProxies into a single hex encoded assetData string, usable in the makerAssetData or
* takerAssetData fields in a 0x order.
* @return The hex encoded assetData string
*/
encodeERC1155AssetData(tokenAddress: string, tokenIds: BigNumber[], tokenValues: BigNumber[], callbackData: string): string {
const abiEncoder = AbiEncoder.createMethod('ERC1155Token', ['address','uint256[]','uint256[]','bytes']);
const args = [tokenAddress, tokenIds, tokenValues, callbackData];
const assetData = abiEncoder.encode(args, encodingRules);
return assetData;
},
/**
* Decodes a MultiAsset assetData hex string into it's corresponding amounts and nestedAssetData
* @param assetData Hex encoded assetData string to decode
@@ -174,6 +186,7 @@ export const assetDataUtils = {
if (
assetProxyId !== AssetProxyId.ERC20 &&
assetProxyId !== AssetProxyId.ERC721 &&
assetProxyId !== AssetProxyId.ERC1155 &&
assetProxyId !== AssetProxyId.MultiAsset
) {
throw new Error(`Invalid assetProxyId: ${assetProxyId}`);
@@ -201,6 +214,9 @@ export const assetDataUtils = {
isMultiAssetData(decodedAssetData: SingleAssetData | MultiAssetData): decodedAssetData is MultiAssetData {
return decodedAssetData.assetProxyId === AssetProxyId.MultiAsset;
},
isER1155AssetData(decodedAssetData: SingleAssetData | MultiAssetData): decodedAssetData is MultiAssetData {
return decodedAssetData.assetProxyId === AssetProxyId.MultiAsset;
},
/**
* Throws if the length or assetProxyId are invalid for the ERC20Proxy.
* @param assetData Hex encoded assetData string

View File

@@ -163,6 +163,7 @@ export enum AssetProxyId {
ERC20 = '0xf47261b0',
ERC721 = '0x02571792',
MultiAsset = '0x94cfcdd7',
ERC1155 = '0x9645780d',
}
export interface ERC20AssetData {
@@ -176,7 +177,15 @@ export interface ERC721AssetData {
tokenId: BigNumber;
}
export type SingleAssetData = ERC20AssetData | ERC721AssetData;
export interface ERC1155AssetData {
assetProxyId: string;
tokenAddress: string;
tokenIds: BigNumber[];
tokenValues: BigNumber[];
callbackData: string;
}
export type SingleAssetData = ERC20AssetData | ERC721AssetData | ERC1155AssetData;
export interface MultiAssetData {
assetProxyId: string;