@animoca/ethereum-contracts-assets
Version:
Base assets contracts
388 lines (347 loc) • 16.5 kB
JavaScript
const {artifacts, accounts, web3} = require('hardhat');
const {createFixtureLoader} = require('@animoca/ethereum-contracts-core/test/utils/fixture');
const {expectEventWithParamsOverride} = require('@animoca/ethereum-contracts-core/test/utils/events');
const {BN, expectEvent, expectRevert} = require('@openzeppelin/test-helpers');
const {One, ZeroAddress, MaxUInt256, Zero} = require('@animoca/ethereum-contracts-core/src/constants');
const ReceiverType = require('../../ReceiverType');
const {makeFungibleCollectionId, makeNonFungibleCollectionId, makeNonFungibleTokenId, isNonFungibleToken, isFungible, getNonFungibleCollectionId} =
require('@animoca/blockchain-inventory_metadata').inventoryIds;
const ERC1155TokenReceiverMock = artifacts.require('ERC1155TokenReceiverMock');
const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock');
function shouldBehaveLikeERC1155721Deliverable({
contractName,
nfMaskLength,
revertMessages,
eventParamsOverrides,
interfaces,
methods,
deploy,
mint,
}) {
const [deployer, owner, _operator, _approved, other] = accounts;
const {'safeDeliver(address[],uint256[],uint256[],bytes)': safeDeliver} = methods;
if (safeDeliver === undefined) {
console.log(
`ERC1155721InventoryDeliverable: non-standard ERC1155721 method safeDeliver(address[],uint256[],uint256[],bytes)` +
` is not supported by ${contractName}, associated tests will be skipped`
);
}
const fCollection1 = {
id: makeFungibleCollectionId(1),
supply: 10,
};
const fCollection2 = {
id: makeFungibleCollectionId(2),
supply: 11,
};
const fCollection3 = {
id: makeFungibleCollectionId(3),
supply: 12,
};
const unknownFCollection = {
id: makeFungibleCollectionId(4),
supply: 0,
};
const nfCollection = makeNonFungibleCollectionId(1, nfMaskLength);
const nfCollectionOther = makeNonFungibleCollectionId(2, nfMaskLength);
const unknownNFCollection = makeNonFungibleCollectionId(99, nfMaskLength);
const nft1 = makeNonFungibleTokenId(1, 1, nfMaskLength);
const nft2 = makeNonFungibleTokenId(2, 1, nfMaskLength);
const nftOtherCollection = makeNonFungibleTokenId(1, 2, nfMaskLength);
const unknownNft = makeNonFungibleTokenId(99, 99, nfMaskLength);
describe('like a deliverable ERC1155721Inventory', function () {
const fixtureLoader = createFixtureLoader(accounts, web3.eth.currentProvider);
const fixture = async function () {
this.token = await deploy(deployer);
await mint(this.token, other, unknownFCollection.id, MaxUInt256, {from: deployer});
this.receiver721 = await ERC721ReceiverMock.new(true, this.token.address);
this.receiver1155 = await ERC1155TokenReceiverMock.new(true, this.token.address);
this.refusingReceiver1155 = await ERC1155TokenReceiverMock.new(false, this.token.address);
};
beforeEach(async function () {
await fixtureLoader(fixture, this);
});
const computeDeliveryContent = function (tokens) {
const result = {
supplies: {},
balances: {},
nftBalances: {},
};
for (const [recipient, id, value] of tokens) {
if (result.supplies[id] == undefined) {
result.supplies[id] = new BN(value);
} else {
result.supplies[id] = result.supplies[id].add(new BN(value));
}
if (result.balances[recipient] == undefined) {
result.balances[recipient] = {};
}
if (result.balances[recipient][id] == undefined) {
result.balances[recipient][id] = new BN(value);
} else {
result.balances[recipient][id] = result.balances[recipient][id].add(new BN(value));
}
if (isNonFungibleToken(id, nfMaskLength)) {
if (result.nftBalances[recipient] == undefined) {
result.nftBalances[recipient] = One;
} else {
result.nftBalances[recipient] = result.nftBalances[recipient].add(One);
}
const nfc = getNonFungibleCollectionId(id, nfMaskLength);
if (result.supplies[nfc] == undefined) {
result.supplies[nfc] = new BN(value);
} else {
result.supplies[nfc] = result.supplies[nfc].add(new BN(value));
}
if (result.balances[recipient] == undefined) {
result.balances[recipient] = {};
}
if (result.balances[recipient][nfc] == undefined) {
result.balances[recipient][nfc] = new BN(value);
} else {
result.balances[recipient][nfc] = result.balances[recipient][nfc].add(new BN(value));
}
}
}
return result;
};
const getRecipientAddress = function (recipient) {
if (recipient == ReceiverType.NON_RECEIVER) {
return this.token.address;
} else if (recipient == ReceiverType.ERC721_RECEIVER) {
return this.receiver721.address;
} else if (recipient == ReceiverType.ERC1155_RECEIVER) {
return this.receiver1155.address;
}
return recipient;
};
const deliveryWasSuccessful = function (recipients, ids, values, data, options) {
const tokens = recipients.map((recipient, i) => [recipient, ids[i], values[i]]);
const fungibleTokens = tokens.filter(([_recipient, id]) => isFungible(id));
const nonFungibleTokens = tokens.filter(([_recipient, id]) => isNonFungibleToken(id, nfMaskLength));
const delivered = computeDeliveryContent(tokens);
const nftsSentToERC721Receiver = nonFungibleTokens.filter(([recipient]) => recipient == ReceiverType.ERC721_RECEIVER);
const tokensSentToERC1155Receiver = tokens.filter(([recipient]) => recipient == ReceiverType.ERC1155_RECEIVER);
if (tokens.length != 0) {
it('increases the recipient(s) balance(s)', async function () {
for (const [recipient, id] of tokens) {
const recipientAddress = getRecipientAddress.call(this, recipient);
(await this.token.balanceOf(recipientAddress, id)).should.be.bignumber.equal(delivered.balances[recipient][id]);
}
});
if (interfaces.ERC1155InventoryTotalSupply) {
it('[ERC1155InventoryTotalSupply] increases the token(s) total supply', async function () {
for (const [_recipient, id, _value] of tokens) {
(await this.token.totalSupply(id)).should.be.bignumber.equal(delivered.supplies[id]);
}
});
}
if (nonFungibleTokens.length != 0) {
if (interfaces.ERC721 || interfaces.ERC1155Inventory) {
it('[ERC721/ERC1155Inventory] gives the ownership of the Non-Fungible Token(s) to the recipient', async function () {
for (const [recipient, id] of nonFungibleTokens) {
(await this.token.ownerOf(id)).should.be.equal(getRecipientAddress.call(this, recipient));
}
});
}
if (interfaces.ERC1155Inventory) {
it('[ERC1155Inventory] increases the recipient Non-Fungible Collection(s) balance(s)', async function () {
for (const [recipient, id] of nonFungibleTokens) {
const nfc = getNonFungibleCollectionId(id, nfMaskLength);
(await this.token.balanceOf(getRecipientAddress.call(this, recipient), nfc)).should.be.bignumber.equal(
delivered.balances[recipient][nfc]
);
}
});
if (interfaces.ERC1155InventoryTotalSupply) {
it('[ERC1155Inventory/ERC1155InventoryTotalSupply] increases the Non-Fungible Collection(s) total supply', async function () {
for (const [_recipient, id] of nonFungibleTokens) {
const nfc = getNonFungibleCollectionId(id, nfMaskLength);
(await this.token.totalSupply(nfc)).should.be.bignumber.equal(delivered.supplies[nfc]);
}
});
it('[ERC1155Inventory/ERC1155InventoryTotalSupply] sets the Non-Fungible Token(s) total supply to 1', async function () {
for (const [_recipient, id] of nonFungibleTokens) {
(await this.token.totalSupply(id)).should.be.bignumber.equal('1');
}
});
}
}
if (interfaces.ERC721 && nonFungibleTokens.length != 0) {
it('[ERC721] sets an empty approval for the Non-Fungible Token(s)', async function () {
for (const [_recipient, id, _value] of nonFungibleTokens) {
(await this.token.getApproved(id)).should.be.equal(ZeroAddress);
}
});
it('[ERC721] increases the recipient NFTs balance', async function () {
for (const [recipient] of nonFungibleTokens) {
(await this.token.balanceOf(getRecipientAddress.call(this, recipient))).should.be.bignumber.equal(delivered.nftBalances[recipient]);
}
});
it('[ERC721] emits Transfer event(s) for Non-Fungible Tokens', function () {
for (const [recipient, id, _value] of nonFungibleTokens) {
expectEventWithParamsOverride(
this.receipt,
'Transfer',
{
_from: ZeroAddress,
_to: getRecipientAddress.call(this, recipient),
_tokenId: id,
},
eventParamsOverrides
);
}
});
if (nftsSentToERC721Receiver.length != 0) {
it('[ERC721] should call onERC721Received', async function () {
for (const [_recipient, id, _value] of nftsSentToERC721Receiver) {
await expectEvent.inTransaction(this.receipt.tx, ERC721ReceiverMock, 'Received', {
operator: options.from,
from: ZeroAddress,
tokenId: id,
data: data,
});
}
});
}
}
}
it('emits TransferSingle events', function () {
for (const [recipient, id, value] of tokens) {
expectEventWithParamsOverride(
this.receipt,
'TransferSingle',
{
_operator: options.from,
_from: ZeroAddress,
_to: getRecipientAddress.call(this, recipient),
_id: id,
_value: value,
},
eventParamsOverrides
);
}
});
if (tokensSentToERC1155Receiver.length != 0) {
it('[ERC1155] should call onERC1155Received', async function () {
for (const [_recipient, id, value] of tokensSentToERC1155Receiver) {
await expectEvent.inTransaction(this.receipt.tx, ERC1155TokenReceiverMock, 'ReceivedSingle', {
operator: options.from,
from: ZeroAddress,
id: id,
value: value,
data: data,
});
}
});
}
}
};
describe('safeDeliver(address[],uint256[],uint256[],bytes)', function () {
if (safeDeliver === undefined) {
return;
}
const data = '0x42';
const options = {from: deployer};
describe('Pre-conditions', function () {
it('reverts if the sender is not a Minter', async function () {
await expectRevert(safeDeliver(this.token, [owner], [nft1], [1], data, {from: other}), revertMessages.NotMinter);
});
it('reverts with inconsistent arrays', async function () {
await expectRevert(safeDeliver(this.token, [owner], [nft1, nft2], [1, 1], data, {from: deployer}), revertMessages.InconsistentArrays);
await expectRevert(safeDeliver(this.token, [owner, owner], [nft1, nft2], [1], data, {from: deployer}), revertMessages.InconsistentArrays);
await expectRevert(safeDeliver(this.token, [owner, owner], [nft1], [1], data, {from: deployer}), revertMessages.InconsistentArrays);
});
it('reverts if transferred to the zero address', async function () {
await expectRevert(safeDeliver(this.token, [ZeroAddress], [nft1], [1], data, options), revertMessages.MintToZero);
});
it('reverts if a Fungible Token has a value equal 0', async function () {
await expectRevert(safeDeliver(this.token, [other], [fCollection1.id], [0], data, options), revertMessages.ZeroValue);
});
it('reverts if a Fungible Token has an overflowing supply', async function () {
await expectRevert(safeDeliver(this.token, [other], [unknownFCollection.id], [1], data, options), revertMessages.SupplyOverflow);
});
it('reverts if a Non-Fungible Token has a value different from 1', async function () {
await expectRevert(safeDeliver(this.token, [other], [nft1], [0], data, options), revertMessages.WrongNFTValue);
await expectRevert(safeDeliver(this.token, [other], [nft1], [2], data, options), revertMessages.WrongNFTValue);
});
it('reverts with an existing Non-Fungible Token', async function () {
await safeDeliver(this.token, [owner], [unknownNft], [1], data, options);
await expectRevert(safeDeliver(this.token, [owner], [unknownNft], [1], data, options), revertMessages.ExistingOrBurntNFT);
});
if (interfaces.ERC1155Inventory) {
it('[ERC1155Inventory] reverts if the id is a Non-Fungible Collection', async function () {
await expectRevert(safeDeliver(this.token, [owner], [nfCollection], [1], data, options), revertMessages.NotToken);
});
}
it('reverts when sent to a non-receiver contract', async function () {
await expectRevert.unspecified(safeDeliver(this.token, [this.token.address], [nft1], [1], data, options));
});
it('reverts when sent to an ERC1155TokenReceiver which refuses the transfer', async function () {
await expectRevert(
safeDeliver(this.token, [this.refusingReceiver1155.address], [nft1], [1], data, options),
revertMessages.TransferRejected
);
});
});
const shouldSafeDeliver = function (recipients, ids, values, data, options) {
beforeEach(async function () {
this.receipt = await safeDeliver(
this.token,
recipients.map((recipient) => getRecipientAddress.call(this, recipient)),
ids,
values,
data,
options
);
});
deliveryWasSuccessful(recipients, ids, values, data, options);
};
context('with an empty list of tokens', function () {
shouldSafeDeliver([], [], [], data, options);
});
context('with Fungible Tokens', function () {
context('single minting', function () {
shouldSafeDeliver([owner], [fCollection1.id], [1], data, options);
});
context('multiple minting', function () {
shouldSafeDeliver(
[owner, ReceiverType.ERC1155_RECEIVER, owner],
[fCollection1.id, fCollection2.id, fCollection3.id],
[fCollection1.supply, 1, fCollection3.supply],
data,
options
);
});
});
context('with Non-Fungible Tokens', function () {
context('single token transfer', function () {
shouldSafeDeliver([owner], [nft1], [1], data, options);
});
context('multiple tokens transfer', function () {
shouldSafeDeliver(
[owner, ReceiverType.ERC1155_RECEIVER, ReceiverType.ERC721_RECEIVER],
[nft1, nft2, nftOtherCollection],
[1, 1, 1],
data,
options
);
});
});
context('with Fungible and Non-Fungible Tokens', function () {
context('multiple tokens sorted by Non-Fungible Collection transfer', function () {
shouldSafeDeliver(
[owner, owner, ReceiverType.ERC1155_RECEIVER, ReceiverType.ERC1155_RECEIVER, ReceiverType.ERC721_RECEIVER],
[fCollection1.id, nft1, fCollection2.id, nftOtherCollection, nft2],
[2, 1, fCollection2.supply, 1, 1],
data,
options
);
});
});
});
});
}
module.exports = {
shouldBehaveLikeERC1155721Deliverable,
};