This commit is contained in:
dexorder
2024-10-17 02:42:28 -04:00
commit 25def69c66
878 changed files with 112489 additions and 0 deletions

View File

@@ -0,0 +1,763 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs');
const { RevertType } = require('../../helpers/enums');
const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior');
function shouldBehaveLikeERC1155() {
const firstTokenId = 1n;
const secondTokenId = 2n;
const unknownTokenId = 3n;
const firstTokenValue = 1000n;
const secondTokenValue = 2000n;
const RECEIVER_SINGLE_MAGIC_VALUE = '0xf23a6e61';
const RECEIVER_BATCH_MAGIC_VALUE = '0xbc197c81';
beforeEach(async function () {
[this.recipient, this.proxy, this.alice, this.bruce] = this.otherAccounts;
});
describe('like an ERC1155', function () {
describe('balanceOf', function () {
it('should return 0 when queried about the zero address', async function () {
expect(await this.token.balanceOf(ethers.ZeroAddress, firstTokenId)).to.equal(0n);
});
describe("when accounts don't own tokens", function () {
it('returns zero for given addresses', async function () {
expect(await this.token.balanceOf(this.alice, firstTokenId)).to.equal(0n);
expect(await this.token.balanceOf(this.bruce, secondTokenId)).to.equal(0n);
expect(await this.token.balanceOf(this.alice, unknownTokenId)).to.equal(0n);
});
});
describe('when accounts own some tokens', function () {
beforeEach(async function () {
await this.token.$_mint(this.alice, firstTokenId, firstTokenValue, '0x');
await this.token.$_mint(this.bruce, secondTokenId, secondTokenValue, '0x');
});
it('returns the amount of tokens owned by the given addresses', async function () {
expect(await this.token.balanceOf(this.alice, firstTokenId)).to.equal(firstTokenValue);
expect(await this.token.balanceOf(this.bruce, secondTokenId)).to.equal(secondTokenValue);
expect(await this.token.balanceOf(this.alice, unknownTokenId)).to.equal(0n);
});
});
});
describe('balanceOfBatch', function () {
it("reverts when input arrays don't match up", async function () {
const accounts1 = [this.alice, this.bruce, this.alice, this.bruce];
const ids1 = [firstTokenId, secondTokenId, unknownTokenId];
await expect(this.token.balanceOfBatch(accounts1, ids1))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
.withArgs(ids1.length, accounts1.length);
const accounts2 = [this.alice, this.bruce];
const ids2 = [firstTokenId, secondTokenId, unknownTokenId];
await expect(this.token.balanceOfBatch(accounts2, ids2))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
.withArgs(ids2.length, accounts2.length);
});
it('should return 0 as the balance when one of the addresses is the zero address', async function () {
const result = await this.token.balanceOfBatch(
[this.alice, this.bruce, ethers.ZeroAddress],
[firstTokenId, secondTokenId, unknownTokenId],
);
expect(result).to.deep.equal([0n, 0n, 0n]);
});
describe("when accounts don't own tokens", function () {
it('returns zeros for each account', async function () {
const result = await this.token.balanceOfBatch(
[this.alice, this.bruce, this.alice],
[firstTokenId, secondTokenId, unknownTokenId],
);
expect(result).to.deep.equal([0n, 0n, 0n]);
});
});
describe('when accounts own some tokens', function () {
beforeEach(async function () {
await this.token.$_mint(this.alice, firstTokenId, firstTokenValue, '0x');
await this.token.$_mint(this.bruce, secondTokenId, secondTokenValue, '0x');
});
it('returns amounts owned by each account in order passed', async function () {
const result = await this.token.balanceOfBatch(
[this.bruce, this.alice, this.alice],
[secondTokenId, firstTokenId, unknownTokenId],
);
expect(result).to.deep.equal([secondTokenValue, firstTokenValue, 0n]);
});
it('returns multiple times the balance of the same address when asked', async function () {
const result = await this.token.balanceOfBatch(
[this.alice, this.bruce, this.alice],
[firstTokenId, secondTokenId, firstTokenId],
);
expect(result).to.deep.equal([firstTokenValue, secondTokenValue, firstTokenValue]);
});
});
});
describe('setApprovalForAll', function () {
beforeEach(async function () {
this.tx = await this.token.connect(this.holder).setApprovalForAll(this.proxy, true);
});
it('sets approval status which can be queried via isApprovedForAll', async function () {
expect(await this.token.isApprovedForAll(this.holder, this.proxy)).to.be.true;
});
it('emits an ApprovalForAll log', async function () {
await expect(this.tx).to.emit(this.token, 'ApprovalForAll').withArgs(this.holder, this.proxy, true);
});
it('can unset approval for an operator', async function () {
await this.token.connect(this.holder).setApprovalForAll(this.proxy, false);
expect(await this.token.isApprovedForAll(this.holder, this.proxy)).to.be.false;
});
it('reverts if attempting to approve zero address as an operator', async function () {
await expect(this.token.connect(this.holder).setApprovalForAll(ethers.ZeroAddress, true))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidOperator')
.withArgs(ethers.ZeroAddress);
});
});
describe('safeTransferFrom', function () {
beforeEach(async function () {
await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x');
await this.token.$_mint(this.holder, secondTokenId, secondTokenValue, '0x');
});
it('reverts when transferring more than balance', async function () {
await expect(
this.token
.connect(this.holder)
.safeTransferFrom(this.holder, this.recipient, firstTokenId, firstTokenValue + 1n, '0x'),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance')
.withArgs(this.holder, firstTokenValue, firstTokenValue + 1n, firstTokenId);
});
it('reverts when transferring to zero address', async function () {
await expect(
this.token
.connect(this.holder)
.safeTransferFrom(this.holder, ethers.ZeroAddress, firstTokenId, firstTokenValue, '0x'),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(ethers.ZeroAddress);
});
function transferWasSuccessful() {
it('debits transferred balance from sender', async function () {
expect(await this.token.balanceOf(this.args.from, this.args.id)).to.equal(0n);
});
it('credits transferred balance to receiver', async function () {
expect(await this.token.balanceOf(this.args.to, this.args.id)).to.equal(this.args.value);
});
it('emits a TransferSingle log', async function () {
await expect(this.tx)
.to.emit(this.token, 'TransferSingle')
.withArgs(this.args.operator, this.args.from, this.args.to, this.args.id, this.args.value);
});
}
describe('when called by the holder', function () {
beforeEach(async function () {
this.args = {
operator: this.holder,
from: this.holder,
to: this.recipient,
id: firstTokenId,
value: firstTokenValue,
data: '0x',
};
this.tx = await this.token
.connect(this.args.operator)
.safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data);
});
transferWasSuccessful();
it('preserves existing balances which are not transferred by holder', async function () {
expect(await this.token.balanceOf(this.holder, secondTokenId)).to.equal(secondTokenValue);
expect(await this.token.balanceOf(this.recipient, secondTokenId)).to.equal(0n);
});
});
describe('when called by an operator on behalf of the holder', function () {
describe('when operator is not approved by holder', function () {
beforeEach(async function () {
await this.token.connect(this.holder).setApprovalForAll(this.proxy, false);
});
it('reverts', async function () {
await expect(
this.token
.connect(this.proxy)
.safeTransferFrom(this.holder, this.recipient, firstTokenId, firstTokenValue, '0x'),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll')
.withArgs(this.proxy, this.holder);
});
});
describe('when operator is approved by holder', function () {
beforeEach(async function () {
await this.token.connect(this.holder).setApprovalForAll(this.proxy, true);
this.args = {
operator: this.proxy,
from: this.holder,
to: this.recipient,
id: firstTokenId,
value: firstTokenValue,
data: '0x',
};
this.tx = await this.token
.connect(this.args.operator)
.safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data);
});
transferWasSuccessful();
it("preserves operator's balances not involved in the transfer", async function () {
expect(await this.token.balanceOf(this.proxy, firstTokenId)).to.equal(0n);
expect(await this.token.balanceOf(this.proxy, secondTokenId)).to.equal(0n);
});
});
});
describe('when sending to a valid receiver', function () {
beforeEach(async function () {
this.receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.None,
]);
});
describe('without data', function () {
beforeEach(async function () {
this.args = {
operator: this.holder,
from: this.holder,
to: this.receiver,
id: firstTokenId,
value: firstTokenValue,
data: '0x',
};
this.tx = await this.token
.connect(this.args.operator)
.safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data);
});
transferWasSuccessful();
it('calls onERC1155Received', async function () {
await expect(this.tx)
.to.emit(this.receiver, 'Received')
.withArgs(this.args.operator, this.args.from, this.args.id, this.args.value, this.args.data, anyValue);
});
});
describe('with data', function () {
beforeEach(async function () {
this.args = {
operator: this.holder,
from: this.holder,
to: this.receiver,
id: firstTokenId,
value: firstTokenValue,
data: '0xf00dd00d',
};
this.tx = await this.token
.connect(this.args.operator)
.safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data);
});
transferWasSuccessful();
it('calls onERC1155Received', async function () {
await expect(this.tx)
.to.emit(this.receiver, 'Received')
.withArgs(this.args.operator, this.args.from, this.args.id, this.args.value, this.args.data, anyValue);
});
});
});
describe('to a receiver contract returning unexpected value', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
'0x00c0ffee',
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.None,
]);
await expect(
this.token
.connect(this.holder)
.safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(receiver);
});
});
describe('to a receiver contract that reverts', function () {
describe('with a revert string', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.RevertWithMessage,
]);
await expect(
this.token
.connect(this.holder)
.safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
).to.be.revertedWith('ERC1155ReceiverMock: reverting on receive');
});
});
describe('without a revert string', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.RevertWithoutMessage,
]);
await expect(
this.token
.connect(this.holder)
.safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(receiver);
});
});
describe('with a custom error', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.RevertWithCustomError,
]);
await expect(
this.token
.connect(this.holder)
.safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
)
.to.be.revertedWithCustomError(receiver, 'CustomError')
.withArgs(RECEIVER_SINGLE_MAGIC_VALUE);
});
});
describe('with a panic', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.Panic,
]);
await expect(
this.token
.connect(this.holder)
.safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
).to.be.revertedWithPanic();
});
});
});
describe('to a contract that does not implement the required function', function () {
it('reverts', async function () {
const invalidReceiver = this.token;
await expect(
this.token
.connect(this.holder)
.safeTransferFrom(this.holder, invalidReceiver, firstTokenId, firstTokenValue, '0x'),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(invalidReceiver);
});
});
});
describe('safeBatchTransferFrom', function () {
beforeEach(async function () {
await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x');
await this.token.$_mint(this.holder, secondTokenId, secondTokenValue, '0x');
});
it('reverts when transferring value more than any of balances', async function () {
await expect(
this.token
.connect(this.holder)
.safeBatchTransferFrom(
this.holder,
this.recipient,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue + 1n],
'0x',
),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance')
.withArgs(this.holder, secondTokenValue, secondTokenValue + 1n, secondTokenId);
});
it("reverts when ids array length doesn't match values array length", async function () {
const ids1 = [firstTokenId];
const tokenValues1 = [firstTokenValue, secondTokenValue];
await expect(
this.token.connect(this.holder).safeBatchTransferFrom(this.holder, this.recipient, ids1, tokenValues1, '0x'),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
.withArgs(ids1.length, tokenValues1.length);
const ids2 = [firstTokenId, secondTokenId];
const tokenValues2 = [firstTokenValue];
await expect(
this.token.connect(this.holder).safeBatchTransferFrom(this.holder, this.recipient, ids2, tokenValues2, '0x'),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
.withArgs(ids2.length, tokenValues2.length);
});
it('reverts when transferring to zero address', async function () {
await expect(
this.token
.connect(this.holder)
.safeBatchTransferFrom(
this.holder,
ethers.ZeroAddress,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(ethers.ZeroAddress);
});
it('reverts when transferring from zero address', async function () {
await expect(
this.token.$_safeBatchTransferFrom(ethers.ZeroAddress, this.holder, [firstTokenId], [firstTokenValue], '0x'),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidSender')
.withArgs(ethers.ZeroAddress);
});
function batchTransferWasSuccessful() {
it('debits transferred balances from sender', async function () {
const newBalances = await this.token.balanceOfBatch(
this.args.ids.map(() => this.args.from),
this.args.ids,
);
expect(newBalances).to.deep.equal(this.args.ids.map(() => 0n));
});
it('credits transferred balances to receiver', async function () {
const newBalances = await this.token.balanceOfBatch(
this.args.ids.map(() => this.args.to),
this.args.ids,
);
expect(newBalances).to.deep.equal(this.args.values);
});
it('emits a TransferBatch log', async function () {
await expect(this.tx)
.to.emit(this.token, 'TransferBatch')
.withArgs(this.args.operator, this.args.from, this.args.to, this.args.ids, this.args.values);
});
}
describe('when called by the holder', function () {
beforeEach(async function () {
this.args = {
operator: this.holder,
from: this.holder,
to: this.recipient,
ids: [firstTokenId, secondTokenId],
values: [firstTokenValue, secondTokenValue],
data: '0x',
};
this.tx = await this.token
.connect(this.args.operator)
.safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data);
});
batchTransferWasSuccessful();
});
describe('when called by an operator on behalf of the holder', function () {
describe('when operator is not approved by holder', function () {
beforeEach(async function () {
await this.token.connect(this.holder).setApprovalForAll(this.proxy, false);
});
it('reverts', async function () {
await expect(
this.token
.connect(this.proxy)
.safeBatchTransferFrom(
this.holder,
this.recipient,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll')
.withArgs(this.proxy, this.holder);
});
});
describe('when operator is approved by holder', function () {
beforeEach(async function () {
await this.token.connect(this.holder).setApprovalForAll(this.proxy, true);
this.args = {
operator: this.proxy,
from: this.holder,
to: this.recipient,
ids: [firstTokenId, secondTokenId],
values: [firstTokenValue, secondTokenValue],
data: '0x',
};
this.tx = await this.token
.connect(this.args.operator)
.safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data);
});
batchTransferWasSuccessful();
it("preserves operator's balances not involved in the transfer", async function () {
expect(await this.token.balanceOf(this.proxy, firstTokenId)).to.equal(0n);
expect(await this.token.balanceOf(this.proxy, secondTokenId)).to.equal(0n);
});
});
});
describe('when sending to a valid receiver', function () {
beforeEach(async function () {
this.receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.None,
]);
});
describe('without data', function () {
beforeEach(async function () {
this.args = {
operator: this.holder,
from: this.holder,
to: this.receiver,
ids: [firstTokenId, secondTokenId],
values: [firstTokenValue, secondTokenValue],
data: '0x',
};
this.tx = await this.token
.connect(this.args.operator)
.safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data);
});
batchTransferWasSuccessful();
it('calls onERC1155BatchReceived', async function () {
await expect(this.tx)
.to.emit(this.receiver, 'BatchReceived')
.withArgs(this.holder, this.holder, this.args.ids, this.args.values, this.args.data, anyValue);
});
});
describe('with data', function () {
beforeEach(async function () {
this.args = {
operator: this.holder,
from: this.holder,
to: this.receiver,
ids: [firstTokenId, secondTokenId],
values: [firstTokenValue, secondTokenValue],
data: '0xf00dd00d',
};
this.tx = await this.token
.connect(this.args.operator)
.safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data);
});
batchTransferWasSuccessful();
it('calls onERC1155Received', async function () {
await expect(this.tx)
.to.emit(this.receiver, 'BatchReceived')
.withArgs(this.holder, this.holder, this.args.ids, this.args.values, this.args.data, anyValue);
});
});
});
describe('to a receiver contract returning unexpected value', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_SINGLE_MAGIC_VALUE,
RevertType.None,
]);
await expect(
this.token
.connect(this.holder)
.safeBatchTransferFrom(
this.holder,
receiver,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(receiver);
});
});
describe('to a receiver contract that reverts', function () {
describe('with a revert string', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.RevertWithMessage,
]);
await expect(
this.token
.connect(this.holder)
.safeBatchTransferFrom(
this.holder,
receiver,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
).to.be.revertedWith('ERC1155ReceiverMock: reverting on batch receive');
});
});
describe('without a revert string', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.RevertWithoutMessage,
]);
await expect(
this.token
.connect(this.holder)
.safeBatchTransferFrom(
this.holder,
receiver,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(receiver);
});
});
describe('with a custom error', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.RevertWithCustomError,
]);
await expect(
this.token
.connect(this.holder)
.safeBatchTransferFrom(
this.holder,
receiver,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(receiver, 'CustomError')
.withArgs(RECEIVER_SINGLE_MAGIC_VALUE);
});
});
describe('with a panic', function () {
it('reverts', async function () {
const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
RECEIVER_SINGLE_MAGIC_VALUE,
RECEIVER_BATCH_MAGIC_VALUE,
RevertType.Panic,
]);
await expect(
this.token
.connect(this.holder)
.safeBatchTransferFrom(
this.holder,
receiver,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
).to.be.revertedWithPanic();
});
});
});
describe('to a contract that does not implement the required function', function () {
it('reverts', async function () {
const invalidReceiver = this.token;
await expect(
this.token
.connect(this.holder)
.safeBatchTransferFrom(
this.holder,
invalidReceiver,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(invalidReceiver);
});
});
});
shouldSupportInterfaces(['ERC1155', 'ERC1155MetadataURI']);
});
}
module.exports = {
shouldBehaveLikeERC1155,
};

View File

@@ -0,0 +1,213 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { zip } = require('../../helpers/iterate');
const { shouldBehaveLikeERC1155 } = require('./ERC1155.behavior');
const initialURI = 'https://token-cdn-domain/{id}.json';
async function fixture() {
const [operator, holder, ...otherAccounts] = await ethers.getSigners();
const token = await ethers.deployContract('$ERC1155', [initialURI]);
return { token, operator, holder, otherAccounts };
}
describe('ERC1155', function () {
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
shouldBehaveLikeERC1155();
describe('internal functions', function () {
const tokenId = 1990n;
const mintValue = 9001n;
const burnValue = 3000n;
const tokenBatchIds = [2000n, 2010n, 2020n];
const mintValues = [5000n, 10000n, 42195n];
const burnValues = [5000n, 9001n, 195n];
const data = '0x12345678';
describe('_mint', function () {
it('reverts with a zero destination address', async function () {
await expect(this.token.$_mint(ethers.ZeroAddress, tokenId, mintValue, data))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(ethers.ZeroAddress);
});
describe('with minted tokens', function () {
beforeEach(async function () {
this.tx = await this.token.connect(this.operator).$_mint(this.holder, tokenId, mintValue, data);
});
it('emits a TransferSingle event', async function () {
await expect(this.tx)
.to.emit(this.token, 'TransferSingle')
.withArgs(this.operator, ethers.ZeroAddress, this.holder, tokenId, mintValue);
});
it('credits the minted token value', async function () {
expect(await this.token.balanceOf(this.holder, tokenId)).to.equal(mintValue);
});
});
});
describe('_mintBatch', function () {
it('reverts with a zero destination address', async function () {
await expect(this.token.$_mintBatch(ethers.ZeroAddress, tokenBatchIds, mintValues, data))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
.withArgs(ethers.ZeroAddress);
});
it('reverts if length of inputs do not match', async function () {
await expect(this.token.$_mintBatch(this.holder, tokenBatchIds, mintValues.slice(1), data))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
.withArgs(tokenBatchIds.length, mintValues.length - 1);
await expect(this.token.$_mintBatch(this.holder, tokenBatchIds.slice(1), mintValues, data))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
.withArgs(tokenBatchIds.length - 1, mintValues.length);
});
describe('with minted batch of tokens', function () {
beforeEach(async function () {
this.tx = await this.token.connect(this.operator).$_mintBatch(this.holder, tokenBatchIds, mintValues, data);
});
it('emits a TransferBatch event', async function () {
await expect(this.tx)
.to.emit(this.token, 'TransferBatch')
.withArgs(this.operator, ethers.ZeroAddress, this.holder, tokenBatchIds, mintValues);
});
it('credits the minted batch of tokens', async function () {
const holderBatchBalances = await this.token.balanceOfBatch(
tokenBatchIds.map(() => this.holder),
tokenBatchIds,
);
expect(holderBatchBalances).to.deep.equal(mintValues);
});
});
});
describe('_burn', function () {
it("reverts when burning the zero account's tokens", async function () {
await expect(this.token.$_burn(ethers.ZeroAddress, tokenId, mintValue))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidSender')
.withArgs(ethers.ZeroAddress);
});
it('reverts when burning a non-existent token id', async function () {
await expect(this.token.$_burn(this.holder, tokenId, mintValue))
.to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance')
.withArgs(this.holder, 0, mintValue, tokenId);
});
it('reverts when burning more than available tokens', async function () {
await this.token.connect(this.operator).$_mint(this.holder, tokenId, mintValue, data);
await expect(this.token.$_burn(this.holder, tokenId, mintValue + 1n))
.to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance')
.withArgs(this.holder, mintValue, mintValue + 1n, tokenId);
});
describe('with minted-then-burnt tokens', function () {
beforeEach(async function () {
await this.token.$_mint(this.holder, tokenId, mintValue, data);
this.tx = await this.token.connect(this.operator).$_burn(this.holder, tokenId, burnValue);
});
it('emits a TransferSingle event', async function () {
await expect(this.tx)
.to.emit(this.token, 'TransferSingle')
.withArgs(this.operator, this.holder, ethers.ZeroAddress, tokenId, burnValue);
});
it('accounts for both minting and burning', async function () {
expect(await this.token.balanceOf(this.holder, tokenId)).to.equal(mintValue - burnValue);
});
});
});
describe('_burnBatch', function () {
it("reverts when burning the zero account's tokens", async function () {
await expect(this.token.$_burnBatch(ethers.ZeroAddress, tokenBatchIds, burnValues))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidSender')
.withArgs(ethers.ZeroAddress);
});
it('reverts if length of inputs do not match', async function () {
await expect(this.token.$_burnBatch(this.holder, tokenBatchIds, burnValues.slice(1)))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
.withArgs(tokenBatchIds.length, burnValues.length - 1);
await expect(this.token.$_burnBatch(this.holder, tokenBatchIds.slice(1), burnValues))
.to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
.withArgs(tokenBatchIds.length - 1, burnValues.length);
});
it('reverts when burning a non-existent token id', async function () {
await expect(this.token.$_burnBatch(this.holder, tokenBatchIds, burnValues))
.to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance')
.withArgs(this.holder, 0, burnValues[0], tokenBatchIds[0]);
});
describe('with minted-then-burnt tokens', function () {
beforeEach(async function () {
await this.token.$_mintBatch(this.holder, tokenBatchIds, mintValues, data);
this.tx = await this.token.connect(this.operator).$_burnBatch(this.holder, tokenBatchIds, burnValues);
});
it('emits a TransferBatch event', async function () {
await expect(this.tx)
.to.emit(this.token, 'TransferBatch')
.withArgs(this.operator, this.holder, ethers.ZeroAddress, tokenBatchIds, burnValues);
});
it('accounts for both minting and burning', async function () {
const holderBatchBalances = await this.token.balanceOfBatch(
tokenBatchIds.map(() => this.holder),
tokenBatchIds,
);
expect(holderBatchBalances).to.deep.equal(
zip(mintValues, burnValues).map(([mintValue, burnValue]) => mintValue - burnValue),
);
});
});
});
});
describe('ERC1155MetadataURI', function () {
const firstTokenID = 42n;
const secondTokenID = 1337n;
it('emits no URI event in constructor', async function () {
await expect(this.token.deploymentTransaction()).to.not.emit(this.token, 'URI');
});
it('sets the initial URI for all token types', async function () {
expect(await this.token.uri(firstTokenID)).to.equal(initialURI);
expect(await this.token.uri(secondTokenID)).to.equal(initialURI);
});
describe('_setURI', function () {
const newURI = 'https://token-cdn-domain/{locale}/{id}.json';
it('emits no URI event', async function () {
await expect(this.token.$_setURI(newURI)).to.not.emit(this.token, 'URI');
});
it('sets the new URI for all token types', async function () {
await this.token.$_setURI(newURI);
expect(await this.token.uri(firstTokenID)).to.equal(newURI);
expect(await this.token.uri(secondTokenID)).to.equal(newURI);
});
});
});
});

View File

@@ -0,0 +1,66 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const ids = [42n, 1137n];
const values = [3000n, 9902n];
async function fixture() {
const [holder, operator, other] = await ethers.getSigners();
const token = await ethers.deployContract('$ERC1155Burnable', ['https://token-cdn-domain/{id}.json']);
await token.$_mint(holder, ids[0], values[0], '0x');
await token.$_mint(holder, ids[1], values[1], '0x');
return { token, holder, operator, other };
}
describe('ERC1155Burnable', function () {
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
describe('burn', function () {
it('holder can burn their tokens', async function () {
await this.token.connect(this.holder).burn(this.holder, ids[0], values[0] - 1n);
expect(await this.token.balanceOf(this.holder, ids[0])).to.equal(1n);
});
it("approved operators can burn the holder's tokens", async function () {
await this.token.connect(this.holder).setApprovalForAll(this.operator, true);
await this.token.connect(this.operator).burn(this.holder, ids[0], values[0] - 1n);
expect(await this.token.balanceOf(this.holder, ids[0])).to.equal(1n);
});
it("unapproved accounts cannot burn the holder's tokens", async function () {
await expect(this.token.connect(this.other).burn(this.holder, ids[0], values[0] - 1n))
.to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll')
.withArgs(this.other, this.holder);
});
});
describe('burnBatch', function () {
it('holder can burn their tokens', async function () {
await this.token.connect(this.holder).burnBatch(this.holder, ids, [values[0] - 1n, values[1] - 2n]);
expect(await this.token.balanceOf(this.holder, ids[0])).to.equal(1n);
expect(await this.token.balanceOf(this.holder, ids[1])).to.equal(2n);
});
it("approved operators can burn the holder's tokens", async function () {
await this.token.connect(this.holder).setApprovalForAll(this.operator, true);
await this.token.connect(this.operator).burnBatch(this.holder, ids, [values[0] - 1n, values[1] - 2n]);
expect(await this.token.balanceOf(this.holder, ids[0])).to.equal(1n);
expect(await this.token.balanceOf(this.holder, ids[1])).to.equal(2n);
});
it("unapproved accounts cannot burn the holder's tokens", async function () {
await expect(this.token.connect(this.other).burnBatch(this.holder, ids, [values[0] - 1n, values[1] - 2n]))
.to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll')
.withArgs(this.other, this.holder);
});
});
});

View File

@@ -0,0 +1,105 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
async function fixture() {
const [holder, operator, receiver, other] = await ethers.getSigners();
const token = await ethers.deployContract('$ERC1155Pausable', ['https://token-cdn-domain/{id}.json']);
return { token, holder, operator, receiver, other };
}
describe('ERC1155Pausable', function () {
const firstTokenId = 37n;
const firstTokenValue = 42n;
const secondTokenId = 19842n;
const secondTokenValue = 23n;
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
describe('when token is paused', function () {
beforeEach(async function () {
await this.token.connect(this.holder).setApprovalForAll(this.operator, true);
await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x');
await this.token.$_pause();
});
it('reverts when trying to safeTransferFrom from holder', async function () {
await expect(
this.token
.connect(this.holder)
.safeTransferFrom(this.holder, this.receiver, firstTokenId, firstTokenValue, '0x'),
).to.be.revertedWithCustomError(this.token, 'EnforcedPause');
});
it('reverts when trying to safeTransferFrom from operator', async function () {
await expect(
this.token
.connect(this.operator)
.safeTransferFrom(this.holder, this.receiver, firstTokenId, firstTokenValue, '0x'),
).to.be.revertedWithCustomError(this.token, 'EnforcedPause');
});
it('reverts when trying to safeBatchTransferFrom from holder', async function () {
await expect(
this.token
.connect(this.holder)
.safeBatchTransferFrom(this.holder, this.receiver, [firstTokenId], [firstTokenValue], '0x'),
).to.be.revertedWithCustomError(this.token, 'EnforcedPause');
});
it('reverts when trying to safeBatchTransferFrom from operator', async function () {
await expect(
this.token
.connect(this.operator)
.safeBatchTransferFrom(this.holder, this.receiver, [firstTokenId], [firstTokenValue], '0x'),
).to.be.revertedWithCustomError(this.token, 'EnforcedPause');
});
it('reverts when trying to mint', async function () {
await expect(this.token.$_mint(this.holder, secondTokenId, secondTokenValue, '0x')).to.be.revertedWithCustomError(
this.token,
'EnforcedPause',
);
});
it('reverts when trying to mintBatch', async function () {
await expect(
this.token.$_mintBatch(this.holder, [secondTokenId], [secondTokenValue], '0x'),
).to.be.revertedWithCustomError(this.token, 'EnforcedPause');
});
it('reverts when trying to burn', async function () {
await expect(this.token.$_burn(this.holder, firstTokenId, firstTokenValue)).to.be.revertedWithCustomError(
this.token,
'EnforcedPause',
);
});
it('reverts when trying to burnBatch', async function () {
await expect(
this.token.$_burnBatch(this.holder, [firstTokenId], [firstTokenValue]),
).to.be.revertedWithCustomError(this.token, 'EnforcedPause');
});
describe('setApprovalForAll', function () {
it('approves an operator', async function () {
await this.token.connect(this.holder).setApprovalForAll(this.other, true);
expect(await this.token.isApprovedForAll(this.holder, this.other)).to.be.true;
});
});
describe('balanceOf', function () {
it('returns the token value owned by the given address', async function () {
expect(await this.token.balanceOf(this.holder, firstTokenId)).to.equal(firstTokenValue);
});
});
describe('isApprovedForAll', function () {
it('returns the approval of the operator', async function () {
expect(await this.token.isApprovedForAll(this.holder, this.operator)).to.be.true;
});
});
});
});

View File

@@ -0,0 +1,119 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
async function fixture() {
const [holder] = await ethers.getSigners();
const token = await ethers.deployContract('$ERC1155Supply', ['https://token-cdn-domain/{id}.json']);
return { token, holder };
}
describe('ERC1155Supply', function () {
const firstTokenId = 37n;
const firstTokenValue = 42n;
const secondTokenId = 19842n;
const secondTokenValue = 23n;
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
describe('before mint', function () {
it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.false;
});
it('totalSupply', async function () {
expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(0n);
expect(await this.token.totalSupply()).to.equal(0n);
});
});
describe('after mint', function () {
describe('single', function () {
beforeEach(async function () {
await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x');
});
it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.true;
});
it('totalSupply', async function () {
expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(firstTokenValue);
expect(await this.token.totalSupply()).to.equal(firstTokenValue);
});
});
describe('batch', function () {
beforeEach(async function () {
await this.token.$_mintBatch(
this.holder,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
);
});
it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.true;
expect(await this.token.exists(secondTokenId)).to.be.true;
});
it('totalSupply', async function () {
expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(firstTokenValue);
expect(await this.token.totalSupply(ethers.Typed.uint256(secondTokenId))).to.equal(secondTokenValue);
expect(await this.token.totalSupply()).to.equal(firstTokenValue + secondTokenValue);
});
});
});
describe('after burn', function () {
describe('single', function () {
beforeEach(async function () {
await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x');
await this.token.$_burn(this.holder, firstTokenId, firstTokenValue);
});
it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.false;
});
it('totalSupply', async function () {
expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(0n);
expect(await this.token.totalSupply()).to.equal(0n);
});
});
describe('batch', function () {
beforeEach(async function () {
await this.token.$_mintBatch(
this.holder,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
);
await this.token.$_burnBatch(this.holder, [firstTokenId, secondTokenId], [firstTokenValue, secondTokenValue]);
});
it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.false;
expect(await this.token.exists(secondTokenId)).to.be.false;
});
it('totalSupply', async function () {
expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(0n);
expect(await this.token.totalSupply(ethers.Typed.uint256(secondTokenId))).to.equal(0n);
expect(await this.token.totalSupply()).to.equal(0n);
});
});
});
describe('other', function () {
it('supply unaffected by no-op', async function () {
await this.token.$_update(ethers.ZeroAddress, ethers.ZeroAddress, [firstTokenId], [firstTokenValue]);
expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(0n);
expect(await this.token.totalSupply()).to.equal(0n);
});
});
});

View File

@@ -0,0 +1,70 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const erc1155Uri = 'https://token.com/nfts/';
const baseUri = 'https://token.com/';
const tokenId = 1n;
const value = 3000n;
describe('ERC1155URIStorage', function () {
describe('with base uri set', function () {
async function fixture() {
const [holder] = await ethers.getSigners();
const token = await ethers.deployContract('$ERC1155URIStorage', [erc1155Uri]);
await token.$_setBaseURI(baseUri);
await token.$_mint(holder, tokenId, value, '0x');
return { token, holder };
}
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
it('can request the token uri, returning the erc1155 uri if no token uri was set', async function () {
expect(await this.token.uri(tokenId)).to.equal(erc1155Uri);
});
it('can request the token uri, returning the concatenated uri if a token uri was set', async function () {
const tokenUri = '1234/';
const expectedUri = `${baseUri}${tokenUri}`;
await expect(this.token.$_setURI(ethers.Typed.uint256(tokenId), tokenUri))
.to.emit(this.token, 'URI')
.withArgs(expectedUri, tokenId);
expect(await this.token.uri(tokenId)).to.equal(expectedUri);
});
});
describe('with base uri set to the empty string', function () {
async function fixture() {
const [holder] = await ethers.getSigners();
const token = await ethers.deployContract('$ERC1155URIStorage', ['']);
await token.$_mint(holder, tokenId, value, '0x');
return { token, holder };
}
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
it('can request the token uri, returning an empty string if no token uri was set', async function () {
expect(await this.token.uri(tokenId)).to.equal('');
});
it('can request the token uri, returning the token uri if a token uri was set', async function () {
const tokenUri = 'ipfs://1234/';
await expect(this.token.$_setURI(ethers.Typed.uint256(tokenId), tokenUri))
.to.emit(this.token, 'URI')
.withArgs(tokenUri, tokenId);
expect(await this.token.uri(tokenId)).to.equal(tokenUri);
});
});
});

View File

@@ -0,0 +1,56 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior');
const ids = [1n, 2n, 3n];
const values = [1000n, 2000n, 3000n];
const data = '0x12345678';
async function fixture() {
const [owner] = await ethers.getSigners();
const token = await ethers.deployContract('$ERC1155', ['https://token-cdn-domain/{id}.json']);
const mock = await ethers.deployContract('$ERC1155Holder');
await token.$_mintBatch(owner, ids, values, '0x');
return { owner, token, mock };
}
describe('ERC1155Holder', function () {
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
shouldSupportInterfaces(['ERC1155Receiver']);
it('receives ERC1155 tokens from a single ID', async function () {
await this.token.connect(this.owner).safeTransferFrom(this.owner, this.mock, ids[0], values[0], data);
expect(await this.token.balanceOf(this.mock, ids[0])).to.equal(values[0]);
for (let i = 1; i < ids.length; i++) {
expect(await this.token.balanceOf(this.mock, ids[i])).to.equal(0n);
}
});
it('receives ERC1155 tokens from a multiple IDs', async function () {
expect(
await this.token.balanceOfBatch(
ids.map(() => this.mock),
ids,
),
).to.deep.equal(ids.map(() => 0n));
await this.token.connect(this.owner).safeBatchTransferFrom(this.owner, this.mock, ids, values, data);
expect(
await this.token.balanceOfBatch(
ids.map(() => this.mock),
ids,
),
).to.deep.equal(values);
});
});

View File

@@ -0,0 +1,299 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { RevertType } = require('../../../helpers/enums');
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
const firstTokenId = 1n;
const secondTokenId = 2n;
const firstTokenValue = 1000n;
const secondTokenValue = 1000n;
const RECEIVER_SINGLE_MAGIC_VALUE = '0xf23a6e61';
const RECEIVER_BATCH_MAGIC_VALUE = '0xbc197c81';
const deployReceiver = (
revertType,
returnValueSingle = RECEIVER_SINGLE_MAGIC_VALUE,
returnValueBatched = RECEIVER_BATCH_MAGIC_VALUE,
) => ethers.deployContract('$ERC1155ReceiverMock', [returnValueSingle, returnValueBatched, revertType]);
const fixture = async () => {
const [eoa, operator, owner] = await ethers.getSigners();
const utils = await ethers.deployContract('$ERC1155Utils');
const receivers = {
correct: await deployReceiver(RevertType.None),
invalid: await deployReceiver(RevertType.None, '0xdeadbeef', '0xdeadbeef'),
message: await deployReceiver(RevertType.RevertWithMessage),
empty: await deployReceiver(RevertType.RevertWithoutMessage),
customError: await deployReceiver(RevertType.RevertWithCustomError),
panic: await deployReceiver(RevertType.Panic),
nonReceiver: await ethers.deployContract('CallReceiverMock'),
eoa,
};
return { operator, owner, utils, receivers };
};
describe('ERC1155Utils', function () {
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
describe('onERC1155Received', function () {
it('succeeds when called by an EOA', async function () {
await expect(
this.utils.$checkOnERC1155Received(
this.operator,
this.owner,
this.receivers.eoa,
firstTokenId,
firstTokenValue,
'0x',
),
).to.not.be.reverted;
});
it('succeeds when data is passed', async function () {
const data = '0x12345678';
await expect(
this.utils.$checkOnERC1155Received(
this.operator,
this.owner,
this.receivers.correct,
firstTokenId,
firstTokenValue,
data,
),
).to.not.be.reverted;
});
it('succeeds when data is empty', async function () {
await expect(
this.utils.$checkOnERC1155Received(
this.operator,
this.owner,
this.receivers.correct,
firstTokenId,
firstTokenValue,
'0x',
),
).to.not.be.reverted;
});
it('reverts when receiver returns invalid value', async function () {
await expect(
this.utils.$checkOnERC1155Received(
this.operator,
this.owner,
this.receivers.invalid,
firstTokenId,
firstTokenValue,
'0x',
),
)
.to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver')
.withArgs(this.receivers.invalid);
});
it('reverts when receiver reverts with message', async function () {
await expect(
this.utils.$checkOnERC1155Received(
this.operator,
this.owner,
this.receivers.message,
firstTokenId,
firstTokenValue,
'0x',
),
).to.be.revertedWith('ERC1155ReceiverMock: reverting on receive');
});
it('reverts when receiver reverts without message', async function () {
await expect(
this.utils.$checkOnERC1155Received(
this.operator,
this.owner,
this.receivers.empty,
firstTokenId,
firstTokenValue,
'0x',
),
)
.to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver')
.withArgs(this.receivers.empty);
});
it('reverts when receiver reverts with custom error', async function () {
await expect(
this.utils.$checkOnERC1155Received(
this.operator,
this.owner,
this.receivers.customError,
firstTokenId,
firstTokenValue,
'0x',
),
)
.to.be.revertedWithCustomError(this.receivers.customError, 'CustomError')
.withArgs(RECEIVER_SINGLE_MAGIC_VALUE);
});
it('reverts when receiver panics', async function () {
await expect(
this.utils.$checkOnERC1155Received(
this.operator,
this.owner,
this.receivers.panic,
firstTokenId,
firstTokenValue,
'0x',
),
).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO);
});
it('reverts when receiver does not implement onERC1155Received', async function () {
await expect(
this.utils.$checkOnERC1155Received(
this.operator,
this.owner,
this.receivers.nonReceiver,
firstTokenId,
firstTokenValue,
'0x',
),
)
.to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver')
.withArgs(this.receivers.nonReceiver);
});
});
describe('onERC1155BatchReceived', function () {
it('succeeds when called by an EOA', async function () {
await expect(
this.utils.$checkOnERC1155BatchReceived(
this.operator,
this.owner,
this.receivers.eoa,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
).to.not.be.reverted;
});
it('succeeds when data is passed', async function () {
const data = '0x12345678';
await expect(
this.utils.$checkOnERC1155BatchReceived(
this.operator,
this.owner,
this.receivers.correct,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
data,
),
).to.not.be.reverted;
});
it('succeeds when data is empty', async function () {
await expect(
this.utils.$checkOnERC1155BatchReceived(
this.operator,
this.owner,
this.receivers.correct,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
).to.not.be.reverted;
});
it('reverts when receiver returns invalid value', async function () {
await expect(
this.utils.$checkOnERC1155BatchReceived(
this.operator,
this.owner,
this.receivers.invalid,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver')
.withArgs(this.receivers.invalid);
});
it('reverts when receiver reverts with message', async function () {
await expect(
this.utils.$checkOnERC1155BatchReceived(
this.operator,
this.owner,
this.receivers.message,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
).to.be.revertedWith('ERC1155ReceiverMock: reverting on batch receive');
});
it('reverts when receiver reverts without message', async function () {
await expect(
this.utils.$checkOnERC1155BatchReceived(
this.operator,
this.owner,
this.receivers.empty,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver')
.withArgs(this.receivers.empty);
});
it('reverts when receiver reverts with custom error', async function () {
await expect(
this.utils.$checkOnERC1155BatchReceived(
this.operator,
this.owner,
this.receivers.customError,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(this.receivers.customError, 'CustomError')
.withArgs(RECEIVER_SINGLE_MAGIC_VALUE);
});
it('reverts when receiver panics', async function () {
await expect(
this.utils.$checkOnERC1155BatchReceived(
this.operator,
this.owner,
this.receivers.panic,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO);
});
it('reverts when receiver does not implement onERC1155BatchReceived', async function () {
await expect(
this.utils.$checkOnERC1155BatchReceived(
this.operator,
this.owner,
this.receivers.nonReceiver,
[firstTokenId, secondTokenId],
[firstTokenValue, secondTokenValue],
'0x',
),
)
.to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver')
.withArgs(this.receivers.nonReceiver);
});
});
});