Split ERC1155 Asset Proxy from ERC1155 POC implementation - squashed
This commit is contained in:
@@ -1,4 +1,15 @@
|
||||
[
|
||||
{
|
||||
"name": "ERC1155Proxy",
|
||||
"version": "1.0.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add ERC1155Proxy implementation",
|
||||
"pr": 0,
|
||||
"networks": {
|
||||
}
|
||||
}
|
||||
]
|
||||
{
|
||||
"name": "MultiAssetProxy",
|
||||
"version": "1.0.0",
|
||||
|
||||
257
contracts/asset-proxy/contracts/src/ERC1155Proxy.sol
Normal file
257
contracts/asset-proxy/contracts/src/ERC1155Proxy.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
288
contracts/asset-proxy/test/utils/erc1155_wrapper.ts
Normal file
288
contracts/asset-proxy/test/utils/erc1155_wrapper.ts
Normal 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"',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './erc20_wrapper';
|
||||
export * from './erc721_wrapper';
|
||||
export * from './erc1155_wrapper';
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -32,6 +32,9 @@ export {
|
||||
MarketBuyOrders,
|
||||
MarketSellOrders,
|
||||
ERC721TokenIdsByOwner,
|
||||
ERC1155FungibleHoldingsByOwner,
|
||||
ERC1155NonFungibleHoldingsByOwner,
|
||||
ERC1155HoldingsByOwner,
|
||||
OrderStatus,
|
||||
AllowanceAmountScenario,
|
||||
AssetDataScenario,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user