Transpile 7bce2b72
This commit is contained in:
774
test/token/ERC1155/ERC1155.behavior.js
Normal file
774
test/token/ERC1155/ERC1155.behavior.js
Normal file
@ -0,0 +1,774 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior');
|
||||
|
||||
const ERC1155ReceiverMock = artifacts.require('ERC1155ReceiverMock');
|
||||
|
||||
function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder, multiTokenHolder, recipient, proxy]) {
|
||||
const firstTokenId = new BN(1);
|
||||
const secondTokenId = new BN(2);
|
||||
const unknownTokenId = new BN(3);
|
||||
|
||||
const firstAmount = new BN(1000);
|
||||
const secondAmount = new BN(2000);
|
||||
|
||||
const RECEIVER_SINGLE_MAGIC_VALUE = '0xf23a6e61';
|
||||
const RECEIVER_BATCH_MAGIC_VALUE = '0xbc197c81';
|
||||
|
||||
describe('like an ERC1155', function () {
|
||||
describe('balanceOf', function () {
|
||||
it('reverts when queried about the zero address', async function () {
|
||||
await expectRevert(
|
||||
this.token.balanceOf(ZERO_ADDRESS, firstTokenId),
|
||||
'ERC1155: balance query for the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
context('when accounts don\'t own tokens', function () {
|
||||
it('returns zero for given addresses', async function () {
|
||||
expect(await this.token.balanceOf(
|
||||
firstTokenHolder,
|
||||
firstTokenId,
|
||||
)).to.be.bignumber.equal('0');
|
||||
|
||||
expect(await this.token.balanceOf(
|
||||
secondTokenHolder,
|
||||
secondTokenId,
|
||||
)).to.be.bignumber.equal('0');
|
||||
|
||||
expect(await this.token.balanceOf(
|
||||
firstTokenHolder,
|
||||
unknownTokenId,
|
||||
)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('when accounts own some tokens', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(firstTokenHolder, firstTokenId, firstAmount, '0x', {
|
||||
from: minter,
|
||||
});
|
||||
await this.token.mint(
|
||||
secondTokenHolder,
|
||||
secondTokenId,
|
||||
secondAmount,
|
||||
'0x',
|
||||
{
|
||||
from: minter,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the amount of tokens owned by the given addresses', async function () {
|
||||
expect(await this.token.balanceOf(
|
||||
firstTokenHolder,
|
||||
firstTokenId,
|
||||
)).to.be.bignumber.equal(firstAmount);
|
||||
|
||||
expect(await this.token.balanceOf(
|
||||
secondTokenHolder,
|
||||
secondTokenId,
|
||||
)).to.be.bignumber.equal(secondAmount);
|
||||
|
||||
expect(await this.token.balanceOf(
|
||||
firstTokenHolder,
|
||||
unknownTokenId,
|
||||
)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('balanceOfBatch', function () {
|
||||
it('reverts when input arrays don\'t match up', async function () {
|
||||
await expectRevert(
|
||||
this.token.balanceOfBatch(
|
||||
[firstTokenHolder, secondTokenHolder, firstTokenHolder, secondTokenHolder],
|
||||
[firstTokenId, secondTokenId, unknownTokenId],
|
||||
),
|
||||
'ERC1155: accounts and ids length mismatch',
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.token.balanceOfBatch(
|
||||
[firstTokenHolder, secondTokenHolder],
|
||||
[firstTokenId, secondTokenId, unknownTokenId],
|
||||
),
|
||||
'ERC1155: accounts and ids length mismatch',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when one of the addresses is the zero address', async function () {
|
||||
await expectRevert(
|
||||
this.token.balanceOfBatch(
|
||||
[firstTokenHolder, secondTokenHolder, ZERO_ADDRESS],
|
||||
[firstTokenId, secondTokenId, unknownTokenId],
|
||||
),
|
||||
'ERC1155: balance query for the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
context('when accounts don\'t own tokens', function () {
|
||||
it('returns zeros for each account', async function () {
|
||||
const result = await this.token.balanceOfBatch(
|
||||
[firstTokenHolder, secondTokenHolder, firstTokenHolder],
|
||||
[firstTokenId, secondTokenId, unknownTokenId],
|
||||
);
|
||||
expect(result).to.be.an('array');
|
||||
expect(result[0]).to.be.a.bignumber.equal('0');
|
||||
expect(result[1]).to.be.a.bignumber.equal('0');
|
||||
expect(result[2]).to.be.a.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('when accounts own some tokens', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(firstTokenHolder, firstTokenId, firstAmount, '0x', {
|
||||
from: minter,
|
||||
});
|
||||
await this.token.mint(
|
||||
secondTokenHolder,
|
||||
secondTokenId,
|
||||
secondAmount,
|
||||
'0x',
|
||||
{
|
||||
from: minter,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('returns amounts owned by each account in order passed', async function () {
|
||||
const result = await this.token.balanceOfBatch(
|
||||
[secondTokenHolder, firstTokenHolder, firstTokenHolder],
|
||||
[secondTokenId, firstTokenId, unknownTokenId],
|
||||
);
|
||||
expect(result).to.be.an('array');
|
||||
expect(result[0]).to.be.a.bignumber.equal(secondAmount);
|
||||
expect(result[1]).to.be.a.bignumber.equal(firstAmount);
|
||||
expect(result[2]).to.be.a.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns multiple times the balance of the same address when asked', async function () {
|
||||
const result = await this.token.balanceOfBatch(
|
||||
[firstTokenHolder, secondTokenHolder, firstTokenHolder],
|
||||
[firstTokenId, secondTokenId, firstTokenId],
|
||||
);
|
||||
expect(result).to.be.an('array');
|
||||
expect(result[0]).to.be.a.bignumber.equal(result[2]);
|
||||
expect(result[0]).to.be.a.bignumber.equal(firstAmount);
|
||||
expect(result[1]).to.be.a.bignumber.equal(secondAmount);
|
||||
expect(result[2]).to.be.a.bignumber.equal(firstAmount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setApprovalForAll', function () {
|
||||
let logs;
|
||||
beforeEach(async function () {
|
||||
({ logs } = await this.token.setApprovalForAll(proxy, true, { from: multiTokenHolder }));
|
||||
});
|
||||
|
||||
it('sets approval status which can be queried via isApprovedForAll', async function () {
|
||||
expect(await this.token.isApprovedForAll(multiTokenHolder, proxy)).to.be.equal(true);
|
||||
});
|
||||
|
||||
it('emits an ApprovalForAll log', function () {
|
||||
expectEvent.inLogs(logs, 'ApprovalForAll', { account: multiTokenHolder, operator: proxy, approved: true });
|
||||
});
|
||||
|
||||
it('can unset approval for an operator', async function () {
|
||||
await this.token.setApprovalForAll(proxy, false, { from: multiTokenHolder });
|
||||
expect(await this.token.isApprovedForAll(multiTokenHolder, proxy)).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('reverts if attempting to approve self as an operator', async function () {
|
||||
await expectRevert(
|
||||
this.token.setApprovalForAll(multiTokenHolder, true, { from: multiTokenHolder }),
|
||||
'ERC1155: setting approval status for self',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('safeTransferFrom', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(multiTokenHolder, firstTokenId, firstAmount, '0x', {
|
||||
from: minter,
|
||||
});
|
||||
await this.token.mint(
|
||||
multiTokenHolder,
|
||||
secondTokenId,
|
||||
secondAmount,
|
||||
'0x',
|
||||
{
|
||||
from: minter,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when transferring more than balance', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(
|
||||
multiTokenHolder,
|
||||
recipient,
|
||||
firstTokenId,
|
||||
firstAmount.addn(1),
|
||||
'0x',
|
||||
{ from: multiTokenHolder },
|
||||
),
|
||||
'ERC1155: insufficient balance for transfer',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when transferring to zero address', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(
|
||||
multiTokenHolder,
|
||||
ZERO_ADDRESS,
|
||||
firstTokenId,
|
||||
firstAmount,
|
||||
'0x',
|
||||
{ from: multiTokenHolder },
|
||||
),
|
||||
'ERC1155: transfer to the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
function transferWasSuccessful ({ operator, from, id, value }) {
|
||||
it('debits transferred balance from sender', async function () {
|
||||
const newBalance = await this.token.balanceOf(from, id);
|
||||
expect(newBalance).to.be.a.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('credits transferred balance to receiver', async function () {
|
||||
const newBalance = await this.token.balanceOf(this.toWhom, id);
|
||||
expect(newBalance).to.be.a.bignumber.equal(value);
|
||||
});
|
||||
|
||||
it('emits a TransferSingle log', function () {
|
||||
expectEvent.inLogs(this.transferLogs, 'TransferSingle', {
|
||||
operator,
|
||||
from,
|
||||
to: this.toWhom,
|
||||
id,
|
||||
value,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
context('when called by the multiTokenHolder', async function () {
|
||||
beforeEach(async function () {
|
||||
this.toWhom = recipient;
|
||||
({ logs: this.transferLogs } =
|
||||
await this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstAmount, '0x', {
|
||||
from: multiTokenHolder,
|
||||
}));
|
||||
});
|
||||
|
||||
transferWasSuccessful.call(this, {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
id: firstTokenId,
|
||||
value: firstAmount,
|
||||
});
|
||||
|
||||
it('preserves existing balances which are not transferred by multiTokenHolder', async function () {
|
||||
const balance1 = await this.token.balanceOf(multiTokenHolder, secondTokenId);
|
||||
expect(balance1).to.be.a.bignumber.equal(secondAmount);
|
||||
|
||||
const balance2 = await this.token.balanceOf(recipient, secondTokenId);
|
||||
expect(balance2).to.be.a.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('when called by an operator on behalf of the multiTokenHolder', function () {
|
||||
context('when operator is not approved by multiTokenHolder', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.setApprovalForAll(proxy, false, { from: multiTokenHolder });
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstAmount, '0x', {
|
||||
from: proxy,
|
||||
}),
|
||||
'ERC1155: caller is not owner nor approved',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when operator is approved by multiTokenHolder', function () {
|
||||
beforeEach(async function () {
|
||||
this.toWhom = recipient;
|
||||
await this.token.setApprovalForAll(proxy, true, { from: multiTokenHolder });
|
||||
({ logs: this.transferLogs } =
|
||||
await this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstAmount, '0x', {
|
||||
from: proxy,
|
||||
}));
|
||||
});
|
||||
|
||||
transferWasSuccessful.call(this, {
|
||||
operator: proxy,
|
||||
from: multiTokenHolder,
|
||||
id: firstTokenId,
|
||||
value: firstAmount,
|
||||
});
|
||||
|
||||
it('preserves operator\'s balances not involved in the transfer', async function () {
|
||||
const balance1 = await this.token.balanceOf(proxy, firstTokenId);
|
||||
expect(balance1).to.be.a.bignumber.equal('0');
|
||||
|
||||
const balance2 = await this.token.balanceOf(proxy, secondTokenId);
|
||||
expect(balance2).to.be.a.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when sending to a valid receiver', function () {
|
||||
beforeEach(async function () {
|
||||
this.receiver = await ERC1155ReceiverMock.new(
|
||||
RECEIVER_SINGLE_MAGIC_VALUE, false,
|
||||
RECEIVER_BATCH_MAGIC_VALUE, false,
|
||||
);
|
||||
});
|
||||
|
||||
context('without data', function () {
|
||||
beforeEach(async function () {
|
||||
this.toWhom = this.receiver.address;
|
||||
this.transferReceipt = await this.token.safeTransferFrom(
|
||||
multiTokenHolder,
|
||||
this.receiver.address,
|
||||
firstTokenId,
|
||||
firstAmount,
|
||||
'0x',
|
||||
{ from: multiTokenHolder },
|
||||
);
|
||||
({ logs: this.transferLogs } = this.transferReceipt);
|
||||
});
|
||||
|
||||
transferWasSuccessful.call(this, {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
id: firstTokenId,
|
||||
value: firstAmount,
|
||||
});
|
||||
|
||||
it('calls onERC1155Received', async function () {
|
||||
await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
id: firstTokenId,
|
||||
value: firstAmount,
|
||||
data: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('with data', function () {
|
||||
const data = '0xf00dd00d';
|
||||
beforeEach(async function () {
|
||||
this.toWhom = this.receiver.address;
|
||||
this.transferReceipt = await this.token.safeTransferFrom(
|
||||
multiTokenHolder,
|
||||
this.receiver.address,
|
||||
firstTokenId,
|
||||
firstAmount,
|
||||
data,
|
||||
{ from: multiTokenHolder },
|
||||
);
|
||||
({ logs: this.transferLogs } = this.transferReceipt);
|
||||
});
|
||||
|
||||
transferWasSuccessful.call(this, {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
id: firstTokenId,
|
||||
value: firstAmount,
|
||||
});
|
||||
|
||||
it('calls onERC1155Received', async function () {
|
||||
await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
id: firstTokenId,
|
||||
value: firstAmount,
|
||||
data,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('to a receiver contract returning unexpected value', function () {
|
||||
beforeEach(async function () {
|
||||
this.receiver = await ERC1155ReceiverMock.new(
|
||||
'0x00c0ffee', false,
|
||||
RECEIVER_BATCH_MAGIC_VALUE, false,
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', {
|
||||
from: multiTokenHolder,
|
||||
}),
|
||||
'ERC1155: ERC1155Receiver rejected tokens',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('to a receiver contract that reverts', function () {
|
||||
beforeEach(async function () {
|
||||
this.receiver = await ERC1155ReceiverMock.new(
|
||||
RECEIVER_SINGLE_MAGIC_VALUE, true,
|
||||
RECEIVER_BATCH_MAGIC_VALUE, false,
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', {
|
||||
from: multiTokenHolder,
|
||||
}),
|
||||
'ERC1155ReceiverMock: reverting on receive',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('to a contract that does not implement the required function', function () {
|
||||
it('reverts', async function () {
|
||||
const invalidReceiver = this.token;
|
||||
await expectRevert.unspecified(
|
||||
this.token.safeTransferFrom(multiTokenHolder, invalidReceiver.address, firstTokenId, firstAmount, '0x', {
|
||||
from: multiTokenHolder,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('safeBatchTransferFrom', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(multiTokenHolder, firstTokenId, firstAmount, '0x', {
|
||||
from: minter,
|
||||
});
|
||||
await this.token.mint(
|
||||
multiTokenHolder,
|
||||
secondTokenId,
|
||||
secondAmount,
|
||||
'0x',
|
||||
{
|
||||
from: minter,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when transferring amount more than any of balances', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, recipient,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount.addn(1)],
|
||||
'0x', { from: multiTokenHolder },
|
||||
),
|
||||
'ERC1155: insufficient balance for transfer',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when ids array length doesn\'t match amounts array length', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, recipient,
|
||||
[firstTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: multiTokenHolder },
|
||||
),
|
||||
'ERC1155: ids and amounts length mismatch',
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, recipient,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount],
|
||||
'0x', { from: multiTokenHolder },
|
||||
),
|
||||
'ERC1155: ids and amounts length mismatch',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when transferring to zero address', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, ZERO_ADDRESS,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: multiTokenHolder },
|
||||
),
|
||||
'ERC1155: transfer to the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
function batchTransferWasSuccessful ({ operator, from, ids, values }) {
|
||||
it('debits transferred balances from sender', async function () {
|
||||
const newBalances = await this.token.balanceOfBatch(new Array(ids.length).fill(from), ids);
|
||||
for (const newBalance of newBalances) {
|
||||
expect(newBalance).to.be.a.bignumber.equal('0');
|
||||
}
|
||||
});
|
||||
|
||||
it('credits transferred balances to receiver', async function () {
|
||||
const newBalances = await this.token.balanceOfBatch(new Array(ids.length).fill(this.toWhom), ids);
|
||||
for (let i = 0; i < newBalances.length; i++) {
|
||||
expect(newBalances[i]).to.be.a.bignumber.equal(values[i]);
|
||||
}
|
||||
});
|
||||
|
||||
it('emits a TransferBatch log', function () {
|
||||
expectEvent.inLogs(this.transferLogs, 'TransferBatch', {
|
||||
operator,
|
||||
from,
|
||||
to: this.toWhom,
|
||||
// ids,
|
||||
// values,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
context('when called by the multiTokenHolder', async function () {
|
||||
beforeEach(async function () {
|
||||
this.toWhom = recipient;
|
||||
({ logs: this.transferLogs } =
|
||||
await this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, recipient,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: multiTokenHolder },
|
||||
));
|
||||
});
|
||||
|
||||
batchTransferWasSuccessful.call(this, {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
ids: [firstTokenId, secondTokenId],
|
||||
values: [firstAmount, secondAmount],
|
||||
});
|
||||
});
|
||||
|
||||
context('when called by an operator on behalf of the multiTokenHolder', function () {
|
||||
context('when operator is not approved by multiTokenHolder', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.setApprovalForAll(proxy, false, { from: multiTokenHolder });
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, recipient,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: proxy },
|
||||
),
|
||||
'ERC1155: transfer caller is not owner nor approved',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when operator is approved by multiTokenHolder', function () {
|
||||
beforeEach(async function () {
|
||||
this.toWhom = recipient;
|
||||
await this.token.setApprovalForAll(proxy, true, { from: multiTokenHolder });
|
||||
({ logs: this.transferLogs } =
|
||||
await this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, recipient,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: proxy },
|
||||
));
|
||||
});
|
||||
|
||||
batchTransferWasSuccessful.call(this, {
|
||||
operator: proxy,
|
||||
from: multiTokenHolder,
|
||||
ids: [firstTokenId, secondTokenId],
|
||||
values: [firstAmount, secondAmount],
|
||||
});
|
||||
|
||||
it('preserves operator\'s balances not involved in the transfer', async function () {
|
||||
const balance1 = await this.token.balanceOf(proxy, firstTokenId);
|
||||
expect(balance1).to.be.a.bignumber.equal('0');
|
||||
const balance2 = await this.token.balanceOf(proxy, secondTokenId);
|
||||
expect(balance2).to.be.a.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when sending to a valid receiver', function () {
|
||||
beforeEach(async function () {
|
||||
this.receiver = await ERC1155ReceiverMock.new(
|
||||
RECEIVER_SINGLE_MAGIC_VALUE, false,
|
||||
RECEIVER_BATCH_MAGIC_VALUE, false,
|
||||
);
|
||||
});
|
||||
|
||||
context('without data', function () {
|
||||
beforeEach(async function () {
|
||||
this.toWhom = this.receiver.address;
|
||||
this.transferReceipt = await this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, this.receiver.address,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: multiTokenHolder },
|
||||
);
|
||||
({ logs: this.transferLogs } = this.transferReceipt);
|
||||
});
|
||||
|
||||
batchTransferWasSuccessful.call(this, {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
ids: [firstTokenId, secondTokenId],
|
||||
values: [firstAmount, secondAmount],
|
||||
});
|
||||
|
||||
it('calls onERC1155BatchReceived', async function () {
|
||||
await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
// ids: [firstTokenId, secondTokenId],
|
||||
// values: [firstAmount, secondAmount],
|
||||
data: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('with data', function () {
|
||||
const data = '0xf00dd00d';
|
||||
beforeEach(async function () {
|
||||
this.toWhom = this.receiver.address;
|
||||
this.transferReceipt = await this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, this.receiver.address,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
data, { from: multiTokenHolder },
|
||||
);
|
||||
({ logs: this.transferLogs } = this.transferReceipt);
|
||||
});
|
||||
|
||||
batchTransferWasSuccessful.call(this, {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
ids: [firstTokenId, secondTokenId],
|
||||
values: [firstAmount, secondAmount],
|
||||
});
|
||||
|
||||
it('calls onERC1155Received', async function () {
|
||||
await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
// ids: [firstTokenId, secondTokenId],
|
||||
// values: [firstAmount, secondAmount],
|
||||
data,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('to a receiver contract returning unexpected value', function () {
|
||||
beforeEach(async function () {
|
||||
this.receiver = await ERC1155ReceiverMock.new(
|
||||
RECEIVER_SINGLE_MAGIC_VALUE, false,
|
||||
RECEIVER_SINGLE_MAGIC_VALUE, false,
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, this.receiver.address,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: multiTokenHolder },
|
||||
),
|
||||
'ERC1155: ERC1155Receiver rejected tokens',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('to a receiver contract that reverts', function () {
|
||||
beforeEach(async function () {
|
||||
this.receiver = await ERC1155ReceiverMock.new(
|
||||
RECEIVER_SINGLE_MAGIC_VALUE, false,
|
||||
RECEIVER_BATCH_MAGIC_VALUE, true,
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, this.receiver.address,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: multiTokenHolder },
|
||||
),
|
||||
'ERC1155ReceiverMock: reverting on batch receive',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('to a receiver contract that reverts only on single transfers', function () {
|
||||
beforeEach(async function () {
|
||||
this.receiver = await ERC1155ReceiverMock.new(
|
||||
RECEIVER_SINGLE_MAGIC_VALUE, true,
|
||||
RECEIVER_BATCH_MAGIC_VALUE, false,
|
||||
);
|
||||
|
||||
this.toWhom = this.receiver.address;
|
||||
this.transferReceipt = await this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, this.receiver.address,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: multiTokenHolder },
|
||||
);
|
||||
({ logs: this.transferLogs } = this.transferReceipt);
|
||||
});
|
||||
|
||||
batchTransferWasSuccessful.call(this, {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
ids: [firstTokenId, secondTokenId],
|
||||
values: [firstAmount, secondAmount],
|
||||
});
|
||||
|
||||
it('calls onERC1155BatchReceived', async function () {
|
||||
await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', {
|
||||
operator: multiTokenHolder,
|
||||
from: multiTokenHolder,
|
||||
// ids: [firstTokenId, secondTokenId],
|
||||
// values: [firstAmount, secondAmount],
|
||||
data: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('to a contract that does not implement the required function', function () {
|
||||
it('reverts', async function () {
|
||||
const invalidReceiver = this.token;
|
||||
await expectRevert.unspecified(
|
||||
this.token.safeBatchTransferFrom(
|
||||
multiTokenHolder, invalidReceiver.address,
|
||||
[firstTokenId, secondTokenId],
|
||||
[firstAmount, secondAmount],
|
||||
'0x', { from: multiTokenHolder },
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
shouldSupportInterfaces(['ERC165', 'ERC1155']);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeERC1155,
|
||||
};
|
||||
264
test/token/ERC1155/ERC1155.test.js
Normal file
264
test/token/ERC1155/ERC1155.test.js
Normal file
@ -0,0 +1,264 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const { shouldBehaveLikeERC1155 } = require('./ERC1155.behavior');
|
||||
const ERC1155Mock = artifacts.require('ERC1155Mock');
|
||||
|
||||
contract('ERC1155', function (accounts) {
|
||||
const [operator, tokenHolder, tokenBatchHolder, ...otherAccounts] = accounts;
|
||||
|
||||
const initialURI = 'https://token-cdn-domain/{id}.json';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC1155Mock.new(initialURI);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC1155(otherAccounts);
|
||||
|
||||
describe('internal functions', function () {
|
||||
const tokenId = new BN(1990);
|
||||
const mintAmount = new BN(9001);
|
||||
const burnAmount = new BN(3000);
|
||||
|
||||
const tokenBatchIds = [new BN(2000), new BN(2010), new BN(2020)];
|
||||
const mintAmounts = [new BN(5000), new BN(10000), new BN(42195)];
|
||||
const burnAmounts = [new BN(5000), new BN(9001), new BN(195)];
|
||||
|
||||
const data = '0x12345678';
|
||||
|
||||
describe('_mint', function () {
|
||||
it('reverts with a zero destination address', async function () {
|
||||
await expectRevert(
|
||||
this.token.mint(ZERO_ADDRESS, tokenId, mintAmount, data),
|
||||
'ERC1155: mint to the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
context('with minted tokens', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs: this.logs } = await this.token.mint(tokenHolder, tokenId, mintAmount, data, { from: operator }));
|
||||
});
|
||||
|
||||
it('emits a TransferSingle event', function () {
|
||||
expectEvent.inLogs(this.logs, 'TransferSingle', {
|
||||
operator,
|
||||
from: ZERO_ADDRESS,
|
||||
to: tokenHolder,
|
||||
id: tokenId,
|
||||
value: mintAmount,
|
||||
});
|
||||
});
|
||||
|
||||
it('credits the minted amount of tokens', async function () {
|
||||
expect(await this.token.balanceOf(tokenHolder, tokenId)).to.be.bignumber.equal(mintAmount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_mintBatch', function () {
|
||||
it('reverts with a zero destination address', async function () {
|
||||
await expectRevert(
|
||||
this.token.mintBatch(ZERO_ADDRESS, tokenBatchIds, mintAmounts, data),
|
||||
'ERC1155: mint to the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts if length of inputs do not match', async function () {
|
||||
await expectRevert(
|
||||
this.token.mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts.slice(1), data),
|
||||
'ERC1155: ids and amounts length mismatch',
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.token.mintBatch(tokenBatchHolder, tokenBatchIds.slice(1), mintAmounts, data),
|
||||
'ERC1155: ids and amounts length mismatch',
|
||||
);
|
||||
});
|
||||
|
||||
context('with minted batch of tokens', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs: this.logs } = await this.token.mintBatch(
|
||||
tokenBatchHolder,
|
||||
tokenBatchIds,
|
||||
mintAmounts,
|
||||
data,
|
||||
{ from: operator },
|
||||
));
|
||||
});
|
||||
|
||||
it('emits a TransferBatch event', function () {
|
||||
expectEvent.inLogs(this.logs, 'TransferBatch', {
|
||||
operator,
|
||||
from: ZERO_ADDRESS,
|
||||
to: tokenBatchHolder,
|
||||
});
|
||||
});
|
||||
|
||||
it('credits the minted batch of tokens', async function () {
|
||||
const holderBatchBalances = await this.token.balanceOfBatch(
|
||||
new Array(tokenBatchIds.length).fill(tokenBatchHolder),
|
||||
tokenBatchIds,
|
||||
);
|
||||
|
||||
for (let i = 0; i < holderBatchBalances.length; i++) {
|
||||
expect(holderBatchBalances[i]).to.be.bignumber.equal(mintAmounts[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_burn', function () {
|
||||
it('reverts when burning the zero account\'s tokens', async function () {
|
||||
await expectRevert(
|
||||
this.token.burn(ZERO_ADDRESS, tokenId, mintAmount),
|
||||
'ERC1155: burn from the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when burning a non-existent token id', async function () {
|
||||
await expectRevert(
|
||||
this.token.burn(tokenHolder, tokenId, mintAmount),
|
||||
'ERC1155: burn amount exceeds balance',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when burning more than available tokens', async function () {
|
||||
await this.token.mint(
|
||||
tokenHolder,
|
||||
tokenId,
|
||||
mintAmount,
|
||||
data,
|
||||
{ from: operator },
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.token.burn(tokenHolder, tokenId, mintAmount.addn(1)),
|
||||
'ERC1155: burn amount exceeds balance',
|
||||
);
|
||||
});
|
||||
|
||||
context('with minted-then-burnt tokens', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(tokenHolder, tokenId, mintAmount, data);
|
||||
({ logs: this.logs } = await this.token.burn(
|
||||
tokenHolder,
|
||||
tokenId,
|
||||
burnAmount,
|
||||
{ from: operator },
|
||||
));
|
||||
});
|
||||
|
||||
it('emits a TransferSingle event', function () {
|
||||
expectEvent.inLogs(this.logs, 'TransferSingle', {
|
||||
operator,
|
||||
from: tokenHolder,
|
||||
to: ZERO_ADDRESS,
|
||||
id: tokenId,
|
||||
value: burnAmount,
|
||||
});
|
||||
});
|
||||
|
||||
it('accounts for both minting and burning', async function () {
|
||||
expect(await this.token.balanceOf(
|
||||
tokenHolder,
|
||||
tokenId,
|
||||
)).to.be.bignumber.equal(mintAmount.sub(burnAmount));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_burnBatch', function () {
|
||||
it('reverts when burning the zero account\'s tokens', async function () {
|
||||
await expectRevert(
|
||||
this.token.burnBatch(ZERO_ADDRESS, tokenBatchIds, burnAmounts),
|
||||
'ERC1155: burn from the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts if length of inputs do not match', async function () {
|
||||
await expectRevert(
|
||||
this.token.burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts.slice(1)),
|
||||
'ERC1155: ids and amounts length mismatch',
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.token.burnBatch(tokenBatchHolder, tokenBatchIds.slice(1), burnAmounts),
|
||||
'ERC1155: ids and amounts length mismatch',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when burning a non-existent token id', async function () {
|
||||
await expectRevert(
|
||||
this.token.burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts),
|
||||
'ERC1155: burn amount exceeds balance',
|
||||
);
|
||||
});
|
||||
|
||||
context('with minted-then-burnt tokens', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts, data);
|
||||
({ logs: this.logs } = await this.token.burnBatch(
|
||||
tokenBatchHolder,
|
||||
tokenBatchIds,
|
||||
burnAmounts,
|
||||
{ from: operator },
|
||||
));
|
||||
});
|
||||
|
||||
it('emits a TransferBatch event', function () {
|
||||
expectEvent.inLogs(this.logs, 'TransferBatch', {
|
||||
operator,
|
||||
from: tokenBatchHolder,
|
||||
to: ZERO_ADDRESS,
|
||||
// ids: tokenBatchIds,
|
||||
// values: burnAmounts,
|
||||
});
|
||||
});
|
||||
|
||||
it('accounts for both minting and burning', async function () {
|
||||
const holderBatchBalances = await this.token.balanceOfBatch(
|
||||
new Array(tokenBatchIds.length).fill(tokenBatchHolder),
|
||||
tokenBatchIds,
|
||||
);
|
||||
|
||||
for (let i = 0; i < holderBatchBalances.length; i++) {
|
||||
expect(holderBatchBalances[i]).to.be.bignumber.equal(mintAmounts[i].sub(burnAmounts[i]));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERC1155MetadataURI', function () {
|
||||
const firstTokenID = new BN('42');
|
||||
const secondTokenID = new BN('1337');
|
||||
|
||||
it('emits no URI event in constructor', async function () {
|
||||
await expectEvent.notEmitted.inConstruction(this.token, 'URI');
|
||||
});
|
||||
|
||||
it('sets the initial URI for all token types', async function () {
|
||||
expect(await this.token.uri(firstTokenID)).to.be.equal(initialURI);
|
||||
expect(await this.token.uri(secondTokenID)).to.be.equal(initialURI);
|
||||
});
|
||||
|
||||
describe('_setURI', function () {
|
||||
const newURI = 'https://token-cdn-domain/{locale}/{id}.json';
|
||||
|
||||
it('emits no URI event', async function () {
|
||||
const receipt = await this.token.setURI(newURI);
|
||||
|
||||
expectEvent.notEmitted(receipt, 'URI');
|
||||
});
|
||||
|
||||
it('sets the new URI for all token types', async function () {
|
||||
await this.token.setURI(newURI);
|
||||
|
||||
expect(await this.token.uri(firstTokenID)).to.be.equal(newURI);
|
||||
expect(await this.token.uri(secondTokenID)).to.be.equal(newURI);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
67
test/token/ERC1155/extensions/ERC1155Burnable.test.js
Normal file
67
test/token/ERC1155/extensions/ERC1155Burnable.test.js
Normal file
@ -0,0 +1,67 @@
|
||||
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC1155BurnableMock = artifacts.require('ERC1155BurnableMock');
|
||||
|
||||
contract('ERC1155Burnable', function (accounts) {
|
||||
const [ holder, operator, other ] = accounts;
|
||||
|
||||
const uri = 'https://token.com';
|
||||
|
||||
const tokenIds = [new BN('42'), new BN('1137')];
|
||||
const amounts = [new BN('3000'), new BN('9902')];
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC1155BurnableMock.new(uri);
|
||||
|
||||
await this.token.mint(holder, tokenIds[0], amounts[0], '0x');
|
||||
await this.token.mint(holder, tokenIds[1], amounts[1], '0x');
|
||||
});
|
||||
|
||||
describe('burn', function () {
|
||||
it('holder can burn their tokens', async function () {
|
||||
await this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: holder });
|
||||
|
||||
expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('approved operators can burn the holder\'s tokens', async function () {
|
||||
await this.token.setApprovalForAll(operator, true, { from: holder });
|
||||
await this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: operator });
|
||||
|
||||
expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('unapproved accounts cannot burn the holder\'s tokens', async function () {
|
||||
await expectRevert(
|
||||
this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: other }),
|
||||
'ERC1155: caller is not owner nor approved',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('burnBatch', function () {
|
||||
it('holder can burn their tokens', async function () {
|
||||
await this.token.burnBatch(holder, tokenIds, [ amounts[0].subn(1), amounts[1].subn(2) ], { from: holder });
|
||||
|
||||
expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1');
|
||||
expect(await this.token.balanceOf(holder, tokenIds[1])).to.be.bignumber.equal('2');
|
||||
});
|
||||
|
||||
it('approved operators can burn the holder\'s tokens', async function () {
|
||||
await this.token.setApprovalForAll(operator, true, { from: holder });
|
||||
await this.token.burnBatch(holder, tokenIds, [ amounts[0].subn(1), amounts[1].subn(2) ], { from: operator });
|
||||
|
||||
expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1');
|
||||
expect(await this.token.balanceOf(holder, tokenIds[1])).to.be.bignumber.equal('2');
|
||||
});
|
||||
|
||||
it('unapproved accounts cannot burn the holder\'s tokens', async function () {
|
||||
await expectRevert(
|
||||
this.token.burnBatch(holder, tokenIds, [ amounts[0].subn(1), amounts[1].subn(2) ], { from: other }),
|
||||
'ERC1155: caller is not owner nor approved',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
108
test/token/ERC1155/extensions/ERC1155Pausable.test.js
Normal file
108
test/token/ERC1155/extensions/ERC1155Pausable.test.js
Normal file
@ -0,0 +1,108 @@
|
||||
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC1155PausableMock = artifacts.require('ERC1155PausableMock');
|
||||
|
||||
contract('ERC1155Pausable', function (accounts) {
|
||||
const [ holder, operator, receiver, other ] = accounts;
|
||||
|
||||
const uri = 'https://token.com';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC1155PausableMock.new(uri);
|
||||
});
|
||||
|
||||
context('when token is paused', function () {
|
||||
const firstTokenId = new BN('37');
|
||||
const firstTokenAmount = new BN('42');
|
||||
|
||||
const secondTokenId = new BN('19842');
|
||||
const secondTokenAmount = new BN('23');
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.setApprovalForAll(operator, true, { from: holder });
|
||||
await this.token.mint(holder, firstTokenId, firstTokenAmount, '0x');
|
||||
|
||||
await this.token.pause();
|
||||
});
|
||||
|
||||
it('reverts when trying to safeTransferFrom from holder', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenAmount, '0x', { from: holder }),
|
||||
'ERC1155Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to safeTransferFrom from operator', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenAmount, '0x', { from: operator }),
|
||||
'ERC1155Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to safeBatchTransferFrom from holder', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenAmount], '0x', { from: holder }),
|
||||
'ERC1155Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to safeBatchTransferFrom from operator', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeBatchTransferFrom(
|
||||
holder, receiver, [firstTokenId], [firstTokenAmount], '0x', { from: operator },
|
||||
),
|
||||
'ERC1155Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to mint', async function () {
|
||||
await expectRevert(
|
||||
this.token.mint(holder, secondTokenId, secondTokenAmount, '0x'),
|
||||
'ERC1155Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to mintBatch', async function () {
|
||||
await expectRevert(
|
||||
this.token.mintBatch(holder, [secondTokenId], [secondTokenAmount], '0x'),
|
||||
'ERC1155Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to burn', async function () {
|
||||
await expectRevert(
|
||||
this.token.burn(holder, firstTokenId, firstTokenAmount),
|
||||
'ERC1155Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to burnBatch', async function () {
|
||||
await expectRevert(
|
||||
this.token.burnBatch(holder, [firstTokenId], [firstTokenAmount]),
|
||||
'ERC1155Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
describe('setApprovalForAll', function () {
|
||||
it('approves an operator', async function () {
|
||||
await this.token.setApprovalForAll(other, true, { from: holder });
|
||||
expect(await this.token.isApprovedForAll(holder, other)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
it('returns the amount of tokens owned by the given address', async function () {
|
||||
const balance = await this.token.balanceOf(holder, firstTokenId);
|
||||
expect(balance).to.be.bignumber.equal(firstTokenAmount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isApprovedForAll', function () {
|
||||
it('returns the approval of the operator', async function () {
|
||||
expect(await this.token.isApprovedForAll(holder, operator)).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
111
test/token/ERC1155/extensions/ERC1155Supply.test.js
Normal file
111
test/token/ERC1155/extensions/ERC1155Supply.test.js
Normal file
@ -0,0 +1,111 @@
|
||||
const { BN } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC1155SupplyMock = artifacts.require('ERC1155SupplyMock');
|
||||
|
||||
contract('ERC1155Supply', function (accounts) {
|
||||
const [ holder ] = accounts;
|
||||
|
||||
const uri = 'https://token.com';
|
||||
|
||||
const firstTokenId = new BN('37');
|
||||
const firstTokenAmount = new BN('42');
|
||||
|
||||
const secondTokenId = new BN('19842');
|
||||
const secondTokenAmount = new BN('23');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC1155SupplyMock.new(uri);
|
||||
});
|
||||
|
||||
context('before mint', function () {
|
||||
it('exist', async function () {
|
||||
expect(await this.token.exists(firstTokenId)).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('totalSupply', async function () {
|
||||
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('after mint', function () {
|
||||
context('single', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(holder, firstTokenId, firstTokenAmount, '0x');
|
||||
});
|
||||
|
||||
it('exist', async function () {
|
||||
expect(await this.token.exists(firstTokenId)).to.be.equal(true);
|
||||
});
|
||||
|
||||
it('totalSupply', async function () {
|
||||
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal(firstTokenAmount);
|
||||
});
|
||||
});
|
||||
|
||||
context('batch', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mintBatch(
|
||||
holder,
|
||||
[ firstTokenId, secondTokenId ],
|
||||
[ firstTokenAmount, secondTokenAmount ],
|
||||
'0x',
|
||||
);
|
||||
});
|
||||
|
||||
it('exist', async function () {
|
||||
expect(await this.token.exists(firstTokenId)).to.be.equal(true);
|
||||
expect(await this.token.exists(secondTokenId)).to.be.equal(true);
|
||||
});
|
||||
|
||||
it('totalSupply', async function () {
|
||||
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal(firstTokenAmount);
|
||||
expect(await this.token.totalSupply(secondTokenId)).to.be.bignumber.equal(secondTokenAmount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('after burn', function () {
|
||||
context('single', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(holder, firstTokenId, firstTokenAmount, '0x');
|
||||
await this.token.burn(holder, firstTokenId, firstTokenAmount);
|
||||
});
|
||||
|
||||
it('exist', async function () {
|
||||
expect(await this.token.exists(firstTokenId)).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('totalSupply', async function () {
|
||||
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('batch', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mintBatch(
|
||||
holder,
|
||||
[ firstTokenId, secondTokenId ],
|
||||
[ firstTokenAmount, secondTokenAmount ],
|
||||
'0x',
|
||||
);
|
||||
await this.token.burnBatch(
|
||||
holder,
|
||||
[ firstTokenId, secondTokenId ],
|
||||
[ firstTokenAmount, secondTokenAmount ],
|
||||
);
|
||||
});
|
||||
|
||||
it('exist', async function () {
|
||||
expect(await this.token.exists(firstTokenId)).to.be.equal(false);
|
||||
expect(await this.token.exists(secondTokenId)).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('totalSupply', async function () {
|
||||
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.totalSupply(secondTokenId)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
146
test/token/ERC1155/presets/ERC1155PresetMinterPauser.test.js
Normal file
146
test/token/ERC1155/presets/ERC1155PresetMinterPauser.test.js
Normal file
@ -0,0 +1,146 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC1155PresetMinterPauser = artifacts.require('ERC1155PresetMinterPauser');
|
||||
|
||||
contract('ERC1155PresetMinterPauser', function (accounts) {
|
||||
const [ deployer, other ] = accounts;
|
||||
|
||||
const firstTokenId = new BN('845');
|
||||
const firstTokenIdAmount = new BN('5000');
|
||||
|
||||
const secondTokenId = new BN('48324');
|
||||
const secondTokenIdAmount = new BN('77875');
|
||||
|
||||
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE');
|
||||
const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE');
|
||||
|
||||
const uri = 'https://token.com';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC1155PresetMinterPauser.new(uri, { from: deployer });
|
||||
});
|
||||
|
||||
shouldSupportInterfaces(['ERC1155', 'AccessControl', 'AccessControlEnumerable']);
|
||||
|
||||
it('deployer has the default admin role', async function () {
|
||||
expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer);
|
||||
});
|
||||
|
||||
it('deployer has the minter role', async function () {
|
||||
expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer);
|
||||
});
|
||||
|
||||
it('deployer has the pauser role', async function () {
|
||||
expect(await this.token.getRoleMemberCount(PAUSER_ROLE)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.getRoleMember(PAUSER_ROLE, 0)).to.equal(deployer);
|
||||
});
|
||||
|
||||
it('minter and pauser role admin is the default admin', async function () {
|
||||
expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||
expect(await this.token.getRoleAdmin(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||
});
|
||||
|
||||
describe('minting', function () {
|
||||
it('deployer can mint tokens', async function () {
|
||||
const receipt = await this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: deployer });
|
||||
expectEvent(receipt, 'TransferSingle',
|
||||
{ operator: deployer, from: ZERO_ADDRESS, to: other, value: firstTokenIdAmount, id: firstTokenId },
|
||||
);
|
||||
|
||||
expect(await this.token.balanceOf(other, firstTokenId)).to.be.bignumber.equal(firstTokenIdAmount);
|
||||
});
|
||||
|
||||
it('other accounts cannot mint tokens', async function () {
|
||||
await expectRevert(
|
||||
this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: other }),
|
||||
'ERC1155PresetMinterPauser: must have minter role to mint',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batched minting', function () {
|
||||
it('deployer can batch mint tokens', async function () {
|
||||
const receipt = await this.token.mintBatch(
|
||||
other, [firstTokenId, secondTokenId], [firstTokenIdAmount, secondTokenIdAmount], '0x', { from: deployer },
|
||||
);
|
||||
|
||||
expectEvent(receipt, 'TransferBatch',
|
||||
{ operator: deployer, from: ZERO_ADDRESS, to: other },
|
||||
);
|
||||
|
||||
expect(await this.token.balanceOf(other, firstTokenId)).to.be.bignumber.equal(firstTokenIdAmount);
|
||||
});
|
||||
|
||||
it('other accounts cannot batch mint tokens', async function () {
|
||||
await expectRevert(
|
||||
this.token.mintBatch(
|
||||
other, [firstTokenId, secondTokenId], [firstTokenIdAmount, secondTokenIdAmount], '0x', { from: other },
|
||||
),
|
||||
'ERC1155PresetMinterPauser: must have minter role to mint',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pausing', function () {
|
||||
it('deployer can pause', async function () {
|
||||
const receipt = await this.token.pause({ from: deployer });
|
||||
expectEvent(receipt, 'Paused', { account: deployer });
|
||||
|
||||
expect(await this.token.paused()).to.equal(true);
|
||||
});
|
||||
|
||||
it('deployer can unpause', async function () {
|
||||
await this.token.pause({ from: deployer });
|
||||
|
||||
const receipt = await this.token.unpause({ from: deployer });
|
||||
expectEvent(receipt, 'Unpaused', { account: deployer });
|
||||
|
||||
expect(await this.token.paused()).to.equal(false);
|
||||
});
|
||||
|
||||
it('cannot mint while paused', async function () {
|
||||
await this.token.pause({ from: deployer });
|
||||
|
||||
await expectRevert(
|
||||
this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: deployer }),
|
||||
'ERC1155Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('other accounts cannot pause', async function () {
|
||||
await expectRevert(
|
||||
this.token.pause({ from: other }),
|
||||
'ERC1155PresetMinterPauser: must have pauser role to pause',
|
||||
);
|
||||
});
|
||||
|
||||
it('other accounts cannot unpause', async function () {
|
||||
await this.token.pause({ from: deployer });
|
||||
|
||||
await expectRevert(
|
||||
this.token.unpause({ from: other }),
|
||||
'ERC1155PresetMinterPauser: must have pauser role to unpause',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('burning', function () {
|
||||
it('holders can burn their tokens', async function () {
|
||||
await this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: deployer });
|
||||
|
||||
const receipt = await this.token.burn(other, firstTokenId, firstTokenIdAmount.subn(1), { from: other });
|
||||
expectEvent(receipt, 'TransferSingle',
|
||||
{ operator: other, from: other, to: ZERO_ADDRESS, value: firstTokenIdAmount.subn(1), id: firstTokenId },
|
||||
);
|
||||
|
||||
expect(await this.token.balanceOf(other, firstTokenId)).to.be.bignumber.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
62
test/token/ERC1155/utils/ERC1155Holder.test.js
Normal file
62
test/token/ERC1155/utils/ERC1155Holder.test.js
Normal file
@ -0,0 +1,62 @@
|
||||
const { BN } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const ERC1155Holder = artifacts.require('ERC1155Holder');
|
||||
const ERC1155Mock = artifacts.require('ERC1155Mock');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior');
|
||||
|
||||
contract('ERC1155Holder', function (accounts) {
|
||||
const [creator] = accounts;
|
||||
const uri = 'https://token-cdn-domain/{id}.json';
|
||||
const multiTokenIds = [new BN(1), new BN(2), new BN(3)];
|
||||
const multiTokenAmounts = [new BN(1000), new BN(2000), new BN(3000)];
|
||||
const transferData = '0x12345678';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.multiToken = await ERC1155Mock.new(uri, { from: creator });
|
||||
this.holder = await ERC1155Holder.new();
|
||||
await this.multiToken.mintBatch(creator, multiTokenIds, multiTokenAmounts, '0x', { from: creator });
|
||||
});
|
||||
|
||||
shouldSupportInterfaces(['ERC165', 'ERC1155Receiver']);
|
||||
|
||||
it('receives ERC1155 tokens from a single ID', async function () {
|
||||
await this.multiToken.safeTransferFrom(
|
||||
creator,
|
||||
this.holder.address,
|
||||
multiTokenIds[0],
|
||||
multiTokenAmounts[0],
|
||||
transferData,
|
||||
{ from: creator },
|
||||
);
|
||||
|
||||
expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[0]))
|
||||
.to.be.bignumber.equal(multiTokenAmounts[0]);
|
||||
|
||||
for (let i = 1; i < multiTokenIds.length; i++) {
|
||||
expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i])).to.be.bignumber.equal(new BN(0));
|
||||
}
|
||||
});
|
||||
|
||||
it('receives ERC1155 tokens from a multiple IDs', async function () {
|
||||
for (let i = 0; i < multiTokenIds.length; i++) {
|
||||
expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i])).to.be.bignumber.equal(new BN(0));
|
||||
};
|
||||
|
||||
await this.multiToken.safeBatchTransferFrom(
|
||||
creator,
|
||||
this.holder.address,
|
||||
multiTokenIds,
|
||||
multiTokenAmounts,
|
||||
transferData,
|
||||
{ from: creator },
|
||||
);
|
||||
|
||||
for (let i = 0; i < multiTokenIds.length; i++) {
|
||||
expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i]))
|
||||
.to.be.bignumber.equal(multiTokenAmounts[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
333
test/token/ERC20/ERC20.behavior.js
Normal file
333
test/token/ERC20/ERC20.behavior.js
Normal file
@ -0,0 +1,333 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { ZERO_ADDRESS, MAX_UINT256 } = constants;
|
||||
|
||||
function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recipient, anotherAccount) {
|
||||
describe('total supply', function () {
|
||||
it('returns the total amount of tokens', async function () {
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
describe('when the requested account has no tokens', function () {
|
||||
it('returns zero', async function () {
|
||||
expect(await this.token.balanceOf(anotherAccount)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the requested account has some tokens', function () {
|
||||
it('returns the total amount of tokens', async function () {
|
||||
expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transfer', function () {
|
||||
shouldBehaveLikeERC20Transfer(errorPrefix, initialHolder, recipient, initialSupply,
|
||||
function (from, to, value) {
|
||||
return this.token.transfer(to, value, { from });
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('transfer from', function () {
|
||||
const spender = recipient;
|
||||
|
||||
describe('when the token owner is not the zero address', function () {
|
||||
const tokenOwner = initialHolder;
|
||||
|
||||
describe('when the recipient is not the zero address', function () {
|
||||
const to = anotherAccount;
|
||||
|
||||
describe('when the spender has enough allowance', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, initialSupply, { from: initialHolder });
|
||||
});
|
||||
|
||||
describe('when the token owner has enough balance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
it('transfers the requested amount', async function () {
|
||||
await this.token.transferFrom(tokenOwner, to, amount, { from: spender });
|
||||
|
||||
expect(await this.token.balanceOf(tokenOwner)).to.be.bignumber.equal('0');
|
||||
|
||||
expect(await this.token.balanceOf(to)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('decreases the spender allowance', async function () {
|
||||
await this.token.transferFrom(tokenOwner, to, amount, { from: spender });
|
||||
|
||||
expect(await this.token.allowance(tokenOwner, spender)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
expectEvent(
|
||||
await this.token.transferFrom(tokenOwner, to, amount, { from: spender }),
|
||||
'Transfer',
|
||||
{ from: tokenOwner, to: to, value: amount },
|
||||
);
|
||||
});
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent(
|
||||
await this.token.transferFrom(tokenOwner, to, amount, { from: spender }),
|
||||
'Approval',
|
||||
{ owner: tokenOwner, spender: spender, value: await this.token.allowance(tokenOwner, spender) },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the token owner does not have enough balance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
beforeEach('reducing balance', async function () {
|
||||
await this.token.transfer(to, 1, { from: tokenOwner });
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.transferFrom(tokenOwner, to, amount, { from: spender }),
|
||||
`${errorPrefix}: transfer amount exceeds balance`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender does not have enough allowance', function () {
|
||||
const allowance = initialSupply.subn(1);
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, allowance, { from: tokenOwner });
|
||||
});
|
||||
|
||||
describe('when the token owner has enough balance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.transferFrom(tokenOwner, to, amount, { from: spender }),
|
||||
`${errorPrefix}: transfer amount exceeds allowance`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the token owner does not have enough balance', function () {
|
||||
const amount = allowance;
|
||||
|
||||
beforeEach('reducing balance', async function () {
|
||||
await this.token.transfer(to, 2, { from: tokenOwner });
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.transferFrom(tokenOwner, to, amount, { from: spender }),
|
||||
`${errorPrefix}: transfer amount exceeds balance`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender has unlimited allowance', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, MAX_UINT256, { from: initialHolder });
|
||||
});
|
||||
|
||||
it('does not decrease the spender allowance', async function () {
|
||||
await this.token.transferFrom(tokenOwner, to, 1, { from: spender });
|
||||
|
||||
expect(await this.token.allowance(tokenOwner, spender)).to.be.bignumber.equal(MAX_UINT256);
|
||||
});
|
||||
|
||||
it('does not emit an approval event', async function () {
|
||||
expectEvent.notEmitted(
|
||||
await this.token.transferFrom(tokenOwner, to, 1, { from: spender }),
|
||||
'Approval',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the recipient is the zero address', function () {
|
||||
const amount = initialSupply;
|
||||
const to = ZERO_ADDRESS;
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, amount, { from: tokenOwner });
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.transferFrom(
|
||||
tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer to the zero address`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the token owner is the zero address', function () {
|
||||
const amount = 0;
|
||||
const tokenOwner = ZERO_ADDRESS;
|
||||
const to = recipient;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.transferFrom(tokenOwner, to, amount, { from: spender }),
|
||||
'from the zero address',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('approve', function () {
|
||||
shouldBehaveLikeERC20Approve(errorPrefix, initialHolder, recipient, initialSupply,
|
||||
function (owner, spender, amount) {
|
||||
return this.token.approve(spender, amount, { from: owner });
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC20Transfer (errorPrefix, from, to, balance, transfer) {
|
||||
describe('when the recipient is not the zero address', function () {
|
||||
describe('when the sender does not have enough balance', function () {
|
||||
const amount = balance.addn(1);
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(transfer.call(this, from, to, amount),
|
||||
`${errorPrefix}: transfer amount exceeds balance`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender transfers all balance', function () {
|
||||
const amount = balance;
|
||||
|
||||
it('transfers the requested amount', async function () {
|
||||
await transfer.call(this, from, to, amount);
|
||||
|
||||
expect(await this.token.balanceOf(from)).to.be.bignumber.equal('0');
|
||||
|
||||
expect(await this.token.balanceOf(to)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
expectEvent(
|
||||
await transfer.call(this, from, to, amount),
|
||||
'Transfer',
|
||||
{ from, to, value: amount },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender transfers zero tokens', function () {
|
||||
const amount = new BN('0');
|
||||
|
||||
it('transfers the requested amount', async function () {
|
||||
await transfer.call(this, from, to, amount);
|
||||
|
||||
expect(await this.token.balanceOf(from)).to.be.bignumber.equal(balance);
|
||||
|
||||
expect(await this.token.balanceOf(to)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
expectEvent(
|
||||
await transfer.call(this, from, to, amount),
|
||||
'Transfer',
|
||||
{ from, to, value: amount },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the recipient is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(transfer.call(this, from, ZERO_ADDRESS, balance),
|
||||
`${errorPrefix}: transfer to the zero address`,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC20Approve (errorPrefix, owner, spender, supply, approve) {
|
||||
describe('when the spender is not the zero address', function () {
|
||||
describe('when the sender has enough balance', function () {
|
||||
const amount = supply;
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent(
|
||||
await approve.call(this, owner, spender, amount),
|
||||
'Approval',
|
||||
{ owner: owner, spender: spender, value: amount },
|
||||
);
|
||||
});
|
||||
|
||||
describe('when there was no approved amount before', function () {
|
||||
it('approves the requested amount', async function () {
|
||||
await approve.call(this, owner, spender, amount);
|
||||
|
||||
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender had an approved amount', function () {
|
||||
beforeEach(async function () {
|
||||
await approve.call(this, owner, spender, new BN(1));
|
||||
});
|
||||
|
||||
it('approves the requested amount and replaces the previous one', async function () {
|
||||
await approve.call(this, owner, spender, amount);
|
||||
|
||||
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender does not have enough balance', function () {
|
||||
const amount = supply.addn(1);
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent(
|
||||
await approve.call(this, owner, spender, amount),
|
||||
'Approval',
|
||||
{ owner: owner, spender: spender, value: amount },
|
||||
);
|
||||
});
|
||||
|
||||
describe('when there was no approved amount before', function () {
|
||||
it('approves the requested amount', async function () {
|
||||
await approve.call(this, owner, spender, amount);
|
||||
|
||||
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender had an approved amount', function () {
|
||||
beforeEach(async function () {
|
||||
await approve.call(this, owner, spender, new BN(1));
|
||||
});
|
||||
|
||||
it('approves the requested amount and replaces the previous one', async function () {
|
||||
await approve.call(this, owner, spender, amount);
|
||||
|
||||
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(approve.call(this, owner, ZERO_ADDRESS, supply),
|
||||
`${errorPrefix}: approve to the zero address`,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeERC20,
|
||||
shouldBehaveLikeERC20Transfer,
|
||||
shouldBehaveLikeERC20Approve,
|
||||
};
|
||||
309
test/token/ERC20/ERC20.test.js
Normal file
309
test/token/ERC20/ERC20.test.js
Normal file
@ -0,0 +1,309 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const {
|
||||
shouldBehaveLikeERC20,
|
||||
shouldBehaveLikeERC20Transfer,
|
||||
shouldBehaveLikeERC20Approve,
|
||||
} = require('./ERC20.behavior');
|
||||
|
||||
const ERC20Mock = artifacts.require('ERC20Mock');
|
||||
const ERC20DecimalsMock = artifacts.require('ERC20DecimalsMock');
|
||||
|
||||
contract('ERC20', function (accounts) {
|
||||
const [ initialHolder, recipient, anotherAccount ] = accounts;
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
const initialSupply = new BN(100);
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20Mock.new(name, symbol, initialHolder, initialSupply);
|
||||
});
|
||||
|
||||
it('has a name', async function () {
|
||||
expect(await this.token.name()).to.equal(name);
|
||||
});
|
||||
|
||||
it('has a symbol', async function () {
|
||||
expect(await this.token.symbol()).to.equal(symbol);
|
||||
});
|
||||
|
||||
it('has 18 decimals', async function () {
|
||||
expect(await this.token.decimals()).to.be.bignumber.equal('18');
|
||||
});
|
||||
|
||||
describe('set decimals', function () {
|
||||
const decimals = new BN(6);
|
||||
|
||||
it('can set decimals during construction', async function () {
|
||||
const token = await ERC20DecimalsMock.new(name, symbol, decimals);
|
||||
expect(await token.decimals()).to.be.bignumber.equal(decimals);
|
||||
});
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount);
|
||||
|
||||
describe('decrease allowance', function () {
|
||||
describe('when the spender is not the zero address', function () {
|
||||
const spender = recipient;
|
||||
|
||||
function shouldDecreaseApproval (amount) {
|
||||
describe('when there was no approved amount before', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.decreaseAllowance(
|
||||
spender, amount, { from: initialHolder }), 'ERC20: decreased allowance below zero',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender had an approved amount', function () {
|
||||
const approvedAmount = amount;
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, approvedAmount, { from: initialHolder });
|
||||
});
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent(
|
||||
await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder }),
|
||||
'Approval',
|
||||
{ owner: initialHolder, spender: spender, value: new BN(0) },
|
||||
);
|
||||
});
|
||||
|
||||
it('decreases the spender allowance subtracting the requested amount', async function () {
|
||||
await this.token.decreaseAllowance(spender, approvedAmount.subn(1), { from: initialHolder });
|
||||
|
||||
expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('sets the allowance to zero when all allowance is removed', async function () {
|
||||
await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder });
|
||||
expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('reverts when more than the full allowance is removed', async function () {
|
||||
await expectRevert(
|
||||
this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }),
|
||||
'ERC20: decreased allowance below zero',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('when the sender has enough balance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
shouldDecreaseApproval(amount);
|
||||
});
|
||||
|
||||
describe('when the sender does not have enough balance', function () {
|
||||
const amount = initialSupply.addn(1);
|
||||
|
||||
shouldDecreaseApproval(amount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender is the zero address', function () {
|
||||
const amount = initialSupply;
|
||||
const spender = ZERO_ADDRESS;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.decreaseAllowance(
|
||||
spender, amount, { from: initialHolder }), 'ERC20: decreased allowance below zero',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('increase allowance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
describe('when the spender is not the zero address', function () {
|
||||
const spender = recipient;
|
||||
|
||||
describe('when the sender has enough balance', function () {
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent(
|
||||
await this.token.increaseAllowance(spender, amount, { from: initialHolder }),
|
||||
'Approval',
|
||||
{ owner: initialHolder, spender: spender, value: amount },
|
||||
);
|
||||
});
|
||||
|
||||
describe('when there was no approved amount before', function () {
|
||||
it('approves the requested amount', async function () {
|
||||
await this.token.increaseAllowance(spender, amount, { from: initialHolder });
|
||||
|
||||
expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender had an approved amount', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, new BN(1), { from: initialHolder });
|
||||
});
|
||||
|
||||
it('increases the spender allowance adding the requested amount', async function () {
|
||||
await this.token.increaseAllowance(spender, amount, { from: initialHolder });
|
||||
|
||||
expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender does not have enough balance', function () {
|
||||
const amount = initialSupply.addn(1);
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent(
|
||||
await this.token.increaseAllowance(spender, amount, { from: initialHolder }),
|
||||
'Approval',
|
||||
{ owner: initialHolder, spender: spender, value: amount },
|
||||
);
|
||||
});
|
||||
|
||||
describe('when there was no approved amount before', function () {
|
||||
it('approves the requested amount', async function () {
|
||||
await this.token.increaseAllowance(spender, amount, { from: initialHolder });
|
||||
|
||||
expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender had an approved amount', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, new BN(1), { from: initialHolder });
|
||||
});
|
||||
|
||||
it('increases the spender allowance adding the requested amount', async function () {
|
||||
await this.token.increaseAllowance(spender, amount, { from: initialHolder });
|
||||
|
||||
expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender is the zero address', function () {
|
||||
const spender = ZERO_ADDRESS;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'ERC20: approve to the zero address',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_mint', function () {
|
||||
const amount = new BN(50);
|
||||
it('rejects a null account', async function () {
|
||||
await expectRevert(
|
||||
this.token.mint(ZERO_ADDRESS, amount), 'ERC20: mint to the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
describe('for a non zero account', function () {
|
||||
beforeEach('minting', async function () {
|
||||
this.receipt = await this.token.mint(recipient, amount);
|
||||
});
|
||||
|
||||
it('increments totalSupply', async function () {
|
||||
const expectedSupply = initialSupply.add(amount);
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
|
||||
});
|
||||
|
||||
it('increments recipient balance', async function () {
|
||||
expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('emits Transfer event', async function () {
|
||||
const event = expectEvent(
|
||||
this.receipt,
|
||||
'Transfer',
|
||||
{ from: ZERO_ADDRESS, to: recipient },
|
||||
);
|
||||
|
||||
expect(event.args.value).to.be.bignumber.equal(amount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_burn', function () {
|
||||
it('rejects a null account', async function () {
|
||||
await expectRevert(this.token.burn(ZERO_ADDRESS, new BN(1)),
|
||||
'ERC20: burn from the zero address');
|
||||
});
|
||||
|
||||
describe('for a non zero account', function () {
|
||||
it('rejects burning more than balance', async function () {
|
||||
await expectRevert(this.token.burn(
|
||||
initialHolder, initialSupply.addn(1)), 'ERC20: burn amount exceeds balance',
|
||||
);
|
||||
});
|
||||
|
||||
const describeBurn = function (description, amount) {
|
||||
describe(description, function () {
|
||||
beforeEach('burning', async function () {
|
||||
this.receipt = await this.token.burn(initialHolder, amount);
|
||||
});
|
||||
|
||||
it('decrements totalSupply', async function () {
|
||||
const expectedSupply = initialSupply.sub(amount);
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
|
||||
});
|
||||
|
||||
it('decrements initialHolder balance', async function () {
|
||||
const expectedBalance = initialSupply.sub(amount);
|
||||
expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
|
||||
it('emits Transfer event', async function () {
|
||||
const event = expectEvent(
|
||||
this.receipt,
|
||||
'Transfer',
|
||||
{ from: initialHolder, to: ZERO_ADDRESS },
|
||||
);
|
||||
|
||||
expect(event.args.value).to.be.bignumber.equal(amount);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describeBurn('for entire balance', initialSupply);
|
||||
describeBurn('for less amount than balance', initialSupply.subn(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe('_transfer', function () {
|
||||
shouldBehaveLikeERC20Transfer('ERC20', initialHolder, recipient, initialSupply, function (from, to, amount) {
|
||||
return this.token.transferInternal(from, to, amount);
|
||||
});
|
||||
|
||||
describe('when the sender is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.transferInternal(ZERO_ADDRESS, recipient, initialSupply),
|
||||
'ERC20: transfer from the zero address',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_approve', function () {
|
||||
shouldBehaveLikeERC20Approve('ERC20', initialHolder, recipient, initialSupply, function (owner, spender, amount) {
|
||||
return this.token.approveInternal(owner, spender, amount);
|
||||
});
|
||||
|
||||
describe('when the owner is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.approveInternal(ZERO_ADDRESS, recipient, initialSupply),
|
||||
'ERC20: approve from the zero address',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
110
test/token/ERC20/extensions/ERC20Burnable.behavior.js
Normal file
110
test/token/ERC20/extensions/ERC20Burnable.behavior.js
Normal file
@ -0,0 +1,110 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
function shouldBehaveLikeERC20Burnable (owner, initialBalance, [burner]) {
|
||||
describe('burn', function () {
|
||||
describe('when the given amount is not greater than balance of the sender', function () {
|
||||
context('for a zero amount', function () {
|
||||
shouldBurn(new BN(0));
|
||||
});
|
||||
|
||||
context('for a non-zero amount', function () {
|
||||
shouldBurn(new BN(100));
|
||||
});
|
||||
|
||||
function shouldBurn (amount) {
|
||||
beforeEach(async function () {
|
||||
({ logs: this.logs } = await this.token.burn(amount, { from: owner }));
|
||||
});
|
||||
|
||||
it('burns the requested amount', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(amount));
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
expectEvent.inLogs(this.logs, 'Transfer', {
|
||||
from: owner,
|
||||
to: ZERO_ADDRESS,
|
||||
value: amount,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('when the given amount is greater than the balance of the sender', function () {
|
||||
const amount = initialBalance.addn(1);
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.burn(amount, { from: owner }),
|
||||
'ERC20: burn amount exceeds balance',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('burnFrom', function () {
|
||||
describe('on success', function () {
|
||||
context('for a zero amount', function () {
|
||||
shouldBurnFrom(new BN(0));
|
||||
});
|
||||
|
||||
context('for a non-zero amount', function () {
|
||||
shouldBurnFrom(new BN(100));
|
||||
});
|
||||
|
||||
function shouldBurnFrom (amount) {
|
||||
const originalAllowance = amount.muln(3);
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(burner, originalAllowance, { from: owner });
|
||||
const { logs } = await this.token.burnFrom(owner, amount, { from: burner });
|
||||
this.logs = logs;
|
||||
});
|
||||
|
||||
it('burns the requested amount', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(amount));
|
||||
});
|
||||
|
||||
it('decrements allowance', async function () {
|
||||
expect(await this.token.allowance(owner, burner)).to.be.bignumber.equal(originalAllowance.sub(amount));
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
expectEvent.inLogs(this.logs, 'Transfer', {
|
||||
from: owner,
|
||||
to: ZERO_ADDRESS,
|
||||
value: amount,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('when the given amount is greater than the balance of the sender', function () {
|
||||
const amount = initialBalance.addn(1);
|
||||
|
||||
it('reverts', async function () {
|
||||
await this.token.approve(burner, amount, { from: owner });
|
||||
await expectRevert(this.token.burnFrom(owner, amount, { from: burner }),
|
||||
'ERC20: burn amount exceeds balance',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given amount is greater than the allowance', function () {
|
||||
const allowance = new BN(100);
|
||||
|
||||
it('reverts', async function () {
|
||||
await this.token.approve(burner, allowance, { from: owner });
|
||||
await expectRevert(this.token.burnFrom(owner, allowance.addn(1), { from: burner }),
|
||||
'ERC20: burn amount exceeds allowance',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeERC20Burnable,
|
||||
};
|
||||
19
test/token/ERC20/extensions/ERC20Burnable.test.js
Normal file
19
test/token/ERC20/extensions/ERC20Burnable.test.js
Normal file
@ -0,0 +1,19 @@
|
||||
const { BN } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { shouldBehaveLikeERC20Burnable } = require('./ERC20Burnable.behavior');
|
||||
const ERC20BurnableMock = artifacts.require('ERC20BurnableMock');
|
||||
|
||||
contract('ERC20Burnable', function (accounts) {
|
||||
const [ owner, ...otherAccounts ] = accounts;
|
||||
|
||||
const initialBalance = new BN(1000);
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20BurnableMock.new(name, symbol, owner, initialBalance, { from: owner });
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC20Burnable(owner, initialBalance, otherAccounts);
|
||||
});
|
||||
32
test/token/ERC20/extensions/ERC20Capped.behavior.js
Normal file
32
test/token/ERC20/extensions/ERC20Capped.behavior.js
Normal file
@ -0,0 +1,32 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
function shouldBehaveLikeERC20Capped (minter, [other], cap) {
|
||||
describe('capped token', function () {
|
||||
const from = minter;
|
||||
|
||||
it('starts with the correct cap', async function () {
|
||||
expect(await this.token.cap()).to.be.bignumber.equal(cap);
|
||||
});
|
||||
|
||||
it('mints when amount is less than cap', async function () {
|
||||
await this.token.mint(other, cap.subn(1), { from });
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(cap.subn(1));
|
||||
});
|
||||
|
||||
it('fails to mint if the amount exceeds the cap', async function () {
|
||||
await this.token.mint(other, cap.subn(1), { from });
|
||||
await expectRevert(this.token.mint(other, 2, { from }), 'ERC20Capped: cap exceeded');
|
||||
});
|
||||
|
||||
it('fails to mint after cap is reached', async function () {
|
||||
await this.token.mint(other, cap, { from });
|
||||
await expectRevert(this.token.mint(other, 1, { from }), 'ERC20Capped: cap exceeded');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeERC20Capped,
|
||||
};
|
||||
27
test/token/ERC20/extensions/ERC20Capped.test.js
Normal file
27
test/token/ERC20/extensions/ERC20Capped.test.js
Normal file
@ -0,0 +1,27 @@
|
||||
const { BN, ether, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { shouldBehaveLikeERC20Capped } = require('./ERC20Capped.behavior');
|
||||
|
||||
const ERC20Capped = artifacts.require('ERC20CappedMock');
|
||||
|
||||
contract('ERC20Capped', function (accounts) {
|
||||
const [ minter, ...otherAccounts ] = accounts;
|
||||
|
||||
const cap = ether('1000');
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
it('requires a non-zero cap', async function () {
|
||||
await expectRevert(
|
||||
ERC20Capped.new(name, symbol, new BN(0), { from: minter }), 'ERC20Capped: cap is 0',
|
||||
);
|
||||
});
|
||||
|
||||
context('once deployed', async function () {
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20Capped.new(name, symbol, cap, { from: minter });
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC20Capped(minter, otherAccounts, cap);
|
||||
});
|
||||
});
|
||||
90
test/token/ERC20/extensions/ERC20FlashMint.test.js
Normal file
90
test/token/ERC20/extensions/ERC20FlashMint.test.js
Normal file
@ -0,0 +1,90 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants;
|
||||
|
||||
const ERC20FlashMintMock = artifacts.require('ERC20FlashMintMock');
|
||||
const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock');
|
||||
|
||||
contract('ERC20FlashMint', function (accounts) {
|
||||
const [ initialHolder, other ] = accounts;
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
const initialSupply = new BN(100);
|
||||
const loanAmount = new BN(10000000000000);
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20FlashMintMock.new(name, symbol, initialHolder, initialSupply);
|
||||
});
|
||||
|
||||
describe('maxFlashLoan', function () {
|
||||
it('token match', async function () {
|
||||
expect(await this.token.maxFlashLoan(this.token.address)).to.be.bignumber.equal(MAX_UINT256.sub(initialSupply));
|
||||
});
|
||||
|
||||
it('token mismatch', async function () {
|
||||
expect(await this.token.maxFlashLoan(ZERO_ADDRESS)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('flashFee', function () {
|
||||
it('token match', async function () {
|
||||
expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('token mismatch', async function () {
|
||||
await expectRevert(this.token.flashFee(ZERO_ADDRESS, loanAmount), 'ERC20FlashMint: wrong token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('flashLoan', function () {
|
||||
it('success', async function () {
|
||||
const receiver = await ERC3156FlashBorrowerMock.new(true, true);
|
||||
const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x');
|
||||
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount });
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount });
|
||||
await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: loanAmount });
|
||||
await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add(loanAmount) });
|
||||
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
|
||||
expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it ('missing return value', async function () {
|
||||
const receiver = await ERC3156FlashBorrowerMock.new(false, true);
|
||||
await expectRevert(
|
||||
this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'),
|
||||
'ERC20FlashMint: invalid return value',
|
||||
);
|
||||
});
|
||||
|
||||
it ('missing approval', async function () {
|
||||
const receiver = await ERC3156FlashBorrowerMock.new(true, false);
|
||||
await expectRevert(
|
||||
this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'),
|
||||
'ERC20FlashMint: allowance does not allow refund',
|
||||
);
|
||||
});
|
||||
|
||||
it ('unavailable funds', async function () {
|
||||
const receiver = await ERC3156FlashBorrowerMock.new(true, true);
|
||||
const data = this.token.contract.methods.transfer(other, 10).encodeABI();
|
||||
await expectRevert(
|
||||
this.token.flashLoan(receiver.address, this.token.address, loanAmount, data),
|
||||
'ERC20: burn amount exceeds balance',
|
||||
);
|
||||
});
|
||||
|
||||
it ('more than maxFlashLoan', async function () {
|
||||
const receiver = await ERC3156FlashBorrowerMock.new(true, true);
|
||||
const data = this.token.contract.methods.transfer(other, 10).encodeABI();
|
||||
// _mint overflow reverts using a panic code. No reason string.
|
||||
await expectRevert.unspecified(this.token.flashLoan(receiver.address, this.token.address, MAX_UINT256, data));
|
||||
});
|
||||
});
|
||||
});
|
||||
134
test/token/ERC20/extensions/ERC20Pausable.test.js
Normal file
134
test/token/ERC20/extensions/ERC20Pausable.test.js
Normal file
@ -0,0 +1,134 @@
|
||||
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC20PausableMock = artifacts.require('ERC20PausableMock');
|
||||
|
||||
contract('ERC20Pausable', function (accounts) {
|
||||
const [ holder, recipient, anotherAccount ] = accounts;
|
||||
|
||||
const initialSupply = new BN(100);
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20PausableMock.new(name, symbol, holder, initialSupply);
|
||||
});
|
||||
|
||||
describe('pausable token', function () {
|
||||
describe('transfer', function () {
|
||||
it('allows to transfer when unpaused', async function () {
|
||||
await this.token.transfer(recipient, initialSupply, { from: holder });
|
||||
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
|
||||
it('allows to transfer when paused and then unpaused', async function () {
|
||||
await this.token.pause();
|
||||
await this.token.unpause();
|
||||
|
||||
await this.token.transfer(recipient, initialSupply, { from: holder });
|
||||
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
|
||||
it('reverts when trying to transfer when paused', async function () {
|
||||
await this.token.pause();
|
||||
|
||||
await expectRevert(this.token.transfer(recipient, initialSupply, { from: holder }),
|
||||
'ERC20Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transfer from', function () {
|
||||
const allowance = new BN(40);
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(anotherAccount, allowance, { from: holder });
|
||||
});
|
||||
|
||||
it('allows to transfer from when unpaused', async function () {
|
||||
await this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount });
|
||||
|
||||
expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(allowance);
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(allowance));
|
||||
});
|
||||
|
||||
it('allows to transfer when paused and then unpaused', async function () {
|
||||
await this.token.pause();
|
||||
await this.token.unpause();
|
||||
|
||||
await this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount });
|
||||
|
||||
expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(allowance);
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(allowance));
|
||||
});
|
||||
|
||||
it('reverts when trying to transfer from when paused', async function () {
|
||||
await this.token.pause();
|
||||
|
||||
await expectRevert(this.token.transferFrom(
|
||||
holder, recipient, allowance, { from: anotherAccount }), 'ERC20Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mint', function () {
|
||||
const amount = new BN('42');
|
||||
|
||||
it('allows to mint when unpaused', async function () {
|
||||
await this.token.mint(recipient, amount);
|
||||
|
||||
expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('allows to mint when paused and then unpaused', async function () {
|
||||
await this.token.pause();
|
||||
await this.token.unpause();
|
||||
|
||||
await this.token.mint(recipient, amount);
|
||||
|
||||
expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('reverts when trying to mint when paused', async function () {
|
||||
await this.token.pause();
|
||||
|
||||
await expectRevert(this.token.mint(recipient, amount),
|
||||
'ERC20Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('burn', function () {
|
||||
const amount = new BN('42');
|
||||
|
||||
it('allows to burn when unpaused', async function () {
|
||||
await this.token.burn(holder, amount);
|
||||
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(amount));
|
||||
});
|
||||
|
||||
it('allows to burn when paused and then unpaused', async function () {
|
||||
await this.token.pause();
|
||||
await this.token.unpause();
|
||||
|
||||
await this.token.burn(holder, amount);
|
||||
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(amount));
|
||||
});
|
||||
|
||||
it('reverts when trying to burn when paused', async function () {
|
||||
await this.token.pause();
|
||||
|
||||
await expectRevert(this.token.burn(holder, amount),
|
||||
'ERC20Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
204
test/token/ERC20/extensions/ERC20Snapshot.test.js
Normal file
204
test/token/ERC20/extensions/ERC20Snapshot.test.js
Normal file
@ -0,0 +1,204 @@
|
||||
const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const ERC20SnapshotMock = artifacts.require('ERC20SnapshotMock');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
contract('ERC20Snapshot', function (accounts) {
|
||||
const [ initialHolder, recipient, other ] = accounts;
|
||||
|
||||
const initialSupply = new BN(100);
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20SnapshotMock.new(name, symbol, initialHolder, initialSupply);
|
||||
});
|
||||
|
||||
describe('snapshot', function () {
|
||||
it('emits a snapshot event', async function () {
|
||||
const { logs } = await this.token.snapshot();
|
||||
expectEvent.inLogs(logs, 'Snapshot');
|
||||
});
|
||||
|
||||
it('creates increasing snapshots ids, starting from 1', async function () {
|
||||
for (const id of ['1', '2', '3', '4', '5']) {
|
||||
const { logs } = await this.token.snapshot();
|
||||
expectEvent.inLogs(logs, 'Snapshot', { id });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('totalSupplyAt', function () {
|
||||
it('reverts with a snapshot id of 0', async function () {
|
||||
await expectRevert(this.token.totalSupplyAt(0), 'ERC20Snapshot: id is 0');
|
||||
});
|
||||
|
||||
it('reverts with a not-yet-created snapshot id', async function () {
|
||||
await expectRevert(this.token.totalSupplyAt(1), 'ERC20Snapshot: nonexistent id');
|
||||
});
|
||||
|
||||
context('with initial snapshot', function () {
|
||||
beforeEach(async function () {
|
||||
this.initialSnapshotId = new BN('1');
|
||||
|
||||
const { logs } = await this.token.snapshot();
|
||||
expectEvent.inLogs(logs, 'Snapshot', { id: this.initialSnapshotId });
|
||||
});
|
||||
|
||||
context('with no supply changes after the snapshot', function () {
|
||||
it('returns the current total supply', async function () {
|
||||
expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
});
|
||||
|
||||
context('with supply changes after the snapshot', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(other, new BN('50'));
|
||||
await this.token.burn(initialHolder, new BN('20'));
|
||||
});
|
||||
|
||||
it('returns the total supply before the changes', async function () {
|
||||
expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
|
||||
context('with a second snapshot after supply changes', function () {
|
||||
beforeEach(async function () {
|
||||
this.secondSnapshotId = new BN('2');
|
||||
|
||||
const { logs } = await this.token.snapshot();
|
||||
expectEvent.inLogs(logs, 'Snapshot', { id: this.secondSnapshotId });
|
||||
});
|
||||
|
||||
it('snapshots return the supply before and after the changes', async function () {
|
||||
expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply);
|
||||
|
||||
expect(await this.token.totalSupplyAt(this.secondSnapshotId)).to.be.bignumber.equal(
|
||||
await this.token.totalSupply(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with multiple snapshots after supply changes', function () {
|
||||
beforeEach(async function () {
|
||||
this.secondSnapshotIds = ['2', '3', '4'];
|
||||
|
||||
for (const id of this.secondSnapshotIds) {
|
||||
const { logs } = await this.token.snapshot();
|
||||
expectEvent.inLogs(logs, 'Snapshot', { id });
|
||||
}
|
||||
});
|
||||
|
||||
it('all posterior snapshots return the supply after the changes', async function () {
|
||||
expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply);
|
||||
|
||||
const currentSupply = await this.token.totalSupply();
|
||||
|
||||
for (const id of this.secondSnapshotIds) {
|
||||
expect(await this.token.totalSupplyAt(id)).to.be.bignumber.equal(currentSupply);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('balanceOfAt', function () {
|
||||
it('reverts with a snapshot id of 0', async function () {
|
||||
await expectRevert(this.token.balanceOfAt(other, 0), 'ERC20Snapshot: id is 0');
|
||||
});
|
||||
|
||||
it('reverts with a not-yet-created snapshot id', async function () {
|
||||
await expectRevert(this.token.balanceOfAt(other, 1), 'ERC20Snapshot: nonexistent id');
|
||||
});
|
||||
|
||||
context('with initial snapshot', function () {
|
||||
beforeEach(async function () {
|
||||
this.initialSnapshotId = new BN('1');
|
||||
|
||||
const { logs } = await this.token.snapshot();
|
||||
expectEvent.inLogs(logs, 'Snapshot', { id: this.initialSnapshotId });
|
||||
});
|
||||
|
||||
context('with no balance changes after the snapshot', function () {
|
||||
it('returns the current balance for all accounts', async function () {
|
||||
expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId))
|
||||
.to.be.bignumber.equal(initialSupply);
|
||||
expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('with balance changes after the snapshot', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.transfer(recipient, new BN('10'), { from: initialHolder });
|
||||
await this.token.mint(other, new BN('50'));
|
||||
await this.token.burn(initialHolder, new BN('20'));
|
||||
});
|
||||
|
||||
it('returns the balances before the changes', async function () {
|
||||
expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId))
|
||||
.to.be.bignumber.equal(initialSupply);
|
||||
expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
context('with a second snapshot after supply changes', function () {
|
||||
beforeEach(async function () {
|
||||
this.secondSnapshotId = new BN('2');
|
||||
|
||||
const { logs } = await this.token.snapshot();
|
||||
expectEvent.inLogs(logs, 'Snapshot', { id: this.secondSnapshotId });
|
||||
});
|
||||
|
||||
it('snapshots return the balances before and after the changes', async function () {
|
||||
expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId))
|
||||
.to.be.bignumber.equal(initialSupply);
|
||||
expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0');
|
||||
|
||||
expect(await this.token.balanceOfAt(initialHolder, this.secondSnapshotId)).to.be.bignumber.equal(
|
||||
await this.token.balanceOf(initialHolder),
|
||||
);
|
||||
expect(await this.token.balanceOfAt(recipient, this.secondSnapshotId)).to.be.bignumber.equal(
|
||||
await this.token.balanceOf(recipient),
|
||||
);
|
||||
expect(await this.token.balanceOfAt(other, this.secondSnapshotId)).to.be.bignumber.equal(
|
||||
await this.token.balanceOf(other),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with multiple snapshots after supply changes', function () {
|
||||
beforeEach(async function () {
|
||||
this.secondSnapshotIds = ['2', '3', '4'];
|
||||
|
||||
for (const id of this.secondSnapshotIds) {
|
||||
const { logs } = await this.token.snapshot();
|
||||
expectEvent.inLogs(logs, 'Snapshot', { id });
|
||||
}
|
||||
});
|
||||
|
||||
it('all posterior snapshots return the supply after the changes', async function () {
|
||||
expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId))
|
||||
.to.be.bignumber.equal(initialSupply);
|
||||
expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0');
|
||||
|
||||
for (const id of this.secondSnapshotIds) {
|
||||
expect(await this.token.balanceOfAt(initialHolder, id)).to.be.bignumber.equal(
|
||||
await this.token.balanceOf(initialHolder),
|
||||
);
|
||||
expect(await this.token.balanceOfAt(recipient, id)).to.be.bignumber.equal(
|
||||
await this.token.balanceOf(recipient),
|
||||
);
|
||||
expect(await this.token.balanceOfAt(other, id)).to.be.bignumber.equal(
|
||||
await this.token.balanceOf(other),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
538
test/token/ERC20/extensions/ERC20Votes.test.js
Normal file
538
test/token/ERC20/extensions/ERC20Votes.test.js
Normal file
@ -0,0 +1,538 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants;
|
||||
|
||||
const { fromRpcSig } = require('ethereumjs-util');
|
||||
const ethSigUtil = require('eth-sig-util');
|
||||
const Wallet = require('ethereumjs-wallet').default;
|
||||
|
||||
const { promisify } = require('util');
|
||||
const queue = promisify(setImmediate);
|
||||
|
||||
const ERC20VotesMock = artifacts.require('ERC20VotesMock');
|
||||
|
||||
const { EIP712Domain, domainSeparator } = require('../../../helpers/eip712');
|
||||
|
||||
const Delegation = [
|
||||
{ name: 'delegatee', type: 'address' },
|
||||
{ name: 'nonce', type: 'uint256' },
|
||||
{ name: 'expiry', type: 'uint256' },
|
||||
];
|
||||
|
||||
async function countPendingTransactions() {
|
||||
return parseInt(
|
||||
await network.provider.send('eth_getBlockTransactionCountByNumber', ['pending'])
|
||||
);
|
||||
}
|
||||
|
||||
async function batchInBlock (txs) {
|
||||
try {
|
||||
// disable auto-mining
|
||||
await network.provider.send('evm_setAutomine', [false]);
|
||||
// send all transactions
|
||||
const promises = txs.map(fn => fn());
|
||||
// wait for node to have all pending transactions
|
||||
while (txs.length > await countPendingTransactions()) {
|
||||
await queue();
|
||||
}
|
||||
// mine one block
|
||||
await network.provider.send('evm_mine');
|
||||
// fetch receipts
|
||||
const receipts = await Promise.all(promises);
|
||||
// Sanity check, all tx should be in the same block
|
||||
const minedBlocks = new Set(receipts.map(({ receipt }) => receipt.blockNumber));
|
||||
expect(minedBlocks.size).to.equal(1);
|
||||
|
||||
return receipts;
|
||||
} finally {
|
||||
// enable auto-mining
|
||||
await network.provider.send('evm_setAutomine', [true]);
|
||||
}
|
||||
}
|
||||
|
||||
contract('ERC20Votes', function (accounts) {
|
||||
const [ holder, recipient, holderDelegatee, recipientDelegatee, other1, other2 ] = accounts;
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
const version = '1';
|
||||
const supply = new BN('10000000000000000000000000');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20VotesMock.new(name, symbol);
|
||||
|
||||
// We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id
|
||||
// from within the EVM as from the JSON RPC interface.
|
||||
// See https://github.com/trufflesuite/ganache-core/issues/515
|
||||
this.chainId = await this.token.getChainId();
|
||||
});
|
||||
|
||||
it('initial nonce is 0', async function () {
|
||||
expect(await this.token.nonces(holder)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('domain separator', async function () {
|
||||
expect(
|
||||
await this.token.DOMAIN_SEPARATOR(),
|
||||
).to.equal(
|
||||
await domainSeparator(name, version, this.chainId, this.token.address),
|
||||
);
|
||||
});
|
||||
|
||||
it('minting restriction', async function () {
|
||||
const amount = new BN('2').pow(new BN('224'));
|
||||
await expectRevert(
|
||||
this.token.mint(holder, amount),
|
||||
'ERC20Votes: total supply risks overflowing votes',
|
||||
);
|
||||
});
|
||||
|
||||
describe('set delegation', function () {
|
||||
describe('call', function () {
|
||||
it('delegation with balance', async function () {
|
||||
await this.token.mint(holder, supply);
|
||||
expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS);
|
||||
|
||||
const { receipt } = await this.token.delegate(holder, { from: holder });
|
||||
expectEvent(receipt, 'DelegateChanged', {
|
||||
delegator: holder,
|
||||
fromDelegate: ZERO_ADDRESS,
|
||||
toDelegate: holder,
|
||||
});
|
||||
expectEvent(receipt, 'DelegateVotesChanged', {
|
||||
delegate: holder,
|
||||
previousBalance: '0',
|
||||
newBalance: supply,
|
||||
});
|
||||
|
||||
expect(await this.token.delegates(holder)).to.be.equal(holder);
|
||||
|
||||
expect(await this.token.getVotes(holder)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPastVotes(holder, receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPastVotes(holder, receipt.blockNumber)).to.be.bignumber.equal(supply);
|
||||
});
|
||||
|
||||
it('delegation without balance', async function () {
|
||||
expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS);
|
||||
|
||||
const { receipt } = await this.token.delegate(holder, { from: holder });
|
||||
expectEvent(receipt, 'DelegateChanged', {
|
||||
delegator: holder,
|
||||
fromDelegate: ZERO_ADDRESS,
|
||||
toDelegate: holder,
|
||||
});
|
||||
expectEvent.notEmitted(receipt, 'DelegateVotesChanged');
|
||||
|
||||
expect(await this.token.delegates(holder)).to.be.equal(holder);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with signature', function () {
|
||||
const delegator = Wallet.generate();
|
||||
const delegatorAddress = web3.utils.toChecksumAddress(delegator.getAddressString());
|
||||
const nonce = 0;
|
||||
|
||||
const buildData = (chainId, verifyingContract, message) => ({ data: {
|
||||
primaryType: 'Delegation',
|
||||
types: { EIP712Domain, Delegation },
|
||||
domain: { name, version, chainId, verifyingContract },
|
||||
message,
|
||||
}});
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(delegatorAddress, supply);
|
||||
});
|
||||
|
||||
it('accept signed delegation', async function () {
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry: MAX_UINT256,
|
||||
}),
|
||||
));
|
||||
|
||||
expect(await this.token.delegates(delegatorAddress)).to.be.equal(ZERO_ADDRESS);
|
||||
|
||||
const { receipt } = await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s);
|
||||
expectEvent(receipt, 'DelegateChanged', {
|
||||
delegator: delegatorAddress,
|
||||
fromDelegate: ZERO_ADDRESS,
|
||||
toDelegate: delegatorAddress,
|
||||
});
|
||||
expectEvent(receipt, 'DelegateVotesChanged', {
|
||||
delegate: delegatorAddress,
|
||||
previousBalance: '0',
|
||||
newBalance: supply,
|
||||
});
|
||||
|
||||
expect(await this.token.delegates(delegatorAddress)).to.be.equal(delegatorAddress);
|
||||
|
||||
expect(await this.token.getVotes(delegatorAddress)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPastVotes(delegatorAddress, receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPastVotes(delegatorAddress, receipt.blockNumber)).to.be.bignumber.equal(supply);
|
||||
});
|
||||
|
||||
it('rejects reused signature', async function () {
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry: MAX_UINT256,
|
||||
}),
|
||||
));
|
||||
|
||||
await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s);
|
||||
|
||||
await expectRevert(
|
||||
this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s),
|
||||
'ERC20Votes: invalid nonce',
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects bad delegatee', async function () {
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry: MAX_UINT256,
|
||||
}),
|
||||
));
|
||||
|
||||
const { logs } = await this.token.delegateBySig(holderDelegatee, nonce, MAX_UINT256, v, r, s);
|
||||
const { args } = logs.find(({ event }) => event == 'DelegateChanged');
|
||||
expect(args.delegator).to.not.be.equal(delegatorAddress);
|
||||
expect(args.fromDelegate).to.be.equal(ZERO_ADDRESS);
|
||||
expect(args.toDelegate).to.be.equal(holderDelegatee);
|
||||
});
|
||||
|
||||
it('rejects bad nonce', async function () {
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry: MAX_UINT256,
|
||||
}),
|
||||
));
|
||||
await expectRevert(
|
||||
this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s),
|
||||
'ERC20Votes: invalid nonce',
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects expired permit', async function () {
|
||||
const expiry = (await time.latest()) - time.duration.weeks(1);
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry,
|
||||
}),
|
||||
));
|
||||
|
||||
await expectRevert(
|
||||
this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s),
|
||||
'ERC20Votes: signature expired',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('change delegation', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(holder, supply);
|
||||
await this.token.delegate(holder, { from: holder });
|
||||
});
|
||||
|
||||
it('call', async function () {
|
||||
expect(await this.token.delegates(holder)).to.be.equal(holder);
|
||||
|
||||
const { receipt } = await this.token.delegate(holderDelegatee, { from: holder });
|
||||
expectEvent(receipt, 'DelegateChanged', {
|
||||
delegator: holder,
|
||||
fromDelegate: holder,
|
||||
toDelegate: holderDelegatee,
|
||||
});
|
||||
expectEvent(receipt, 'DelegateVotesChanged', {
|
||||
delegate: holder,
|
||||
previousBalance: supply,
|
||||
newBalance: '0',
|
||||
});
|
||||
expectEvent(receipt, 'DelegateVotesChanged', {
|
||||
delegate: holderDelegatee,
|
||||
previousBalance: '0',
|
||||
newBalance: supply,
|
||||
});
|
||||
|
||||
expect(await this.token.delegates(holder)).to.be.equal(holderDelegatee);
|
||||
|
||||
expect(await this.token.getVotes(holder)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getVotes(holderDelegatee)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPastVotes(holder, receipt.blockNumber - 1)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPastVotes(holderDelegatee, receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPastVotes(holder, receipt.blockNumber)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPastVotes(holderDelegatee, receipt.blockNumber)).to.be.bignumber.equal(supply);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transfers', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(holder, supply);
|
||||
});
|
||||
|
||||
it('no delegation', async function () {
|
||||
const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
|
||||
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
|
||||
expectEvent.notEmitted(receipt, 'DelegateVotesChanged');
|
||||
|
||||
this.holderVotes = '0';
|
||||
this.recipientVotes = '0';
|
||||
});
|
||||
|
||||
it('sender delegation', async function () {
|
||||
await this.token.delegate(holder, { from: holder });
|
||||
|
||||
const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
|
||||
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) });
|
||||
|
||||
const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
|
||||
expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true);
|
||||
|
||||
this.holderVotes = supply.subn(1);
|
||||
this.recipientVotes = '0';
|
||||
});
|
||||
|
||||
it('receiver delegation', async function () {
|
||||
await this.token.delegate(recipient, { from: recipient });
|
||||
|
||||
const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
|
||||
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' });
|
||||
|
||||
const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
|
||||
expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true);
|
||||
|
||||
this.holderVotes = '0';
|
||||
this.recipientVotes = '1';
|
||||
});
|
||||
|
||||
it('full delegation', async function () {
|
||||
await this.token.delegate(holder, { from: holder });
|
||||
await this.token.delegate(recipient, { from: recipient });
|
||||
|
||||
const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
|
||||
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' });
|
||||
|
||||
const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
|
||||
expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true);
|
||||
|
||||
this.holderVotes = supply.subn(1);
|
||||
this.recipientVotes = '1';
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
expect(await this.token.getVotes(holder)).to.be.bignumber.equal(this.holderVotes);
|
||||
expect(await this.token.getVotes(recipient)).to.be.bignumber.equal(this.recipientVotes);
|
||||
|
||||
// need to advance 2 blocks to see the effect of a transfer on "getPastVotes"
|
||||
const blockNumber = await time.latestBlock();
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPastVotes(holder, blockNumber)).to.be.bignumber.equal(this.holderVotes);
|
||||
expect(await this.token.getPastVotes(recipient, blockNumber)).to.be.bignumber.equal(this.recipientVotes);
|
||||
});
|
||||
});
|
||||
|
||||
// The following tests are a adaptation of https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js.
|
||||
describe('Compound test suite', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(holder, supply);
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
it('grants to initial account', async function () {
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('numCheckpoints', function () {
|
||||
it('returns the number of checkpoints for a delegate', async function () {
|
||||
await this.token.transfer(recipient, '100', { from: holder }); //give an account a few tokens for readability
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0');
|
||||
|
||||
const t1 = await this.token.delegate(other1, { from: recipient });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1');
|
||||
|
||||
const t2 = await this.token.transfer(other2, 10, { from: recipient });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2');
|
||||
|
||||
const t3 = await this.token.transfer(other2, 10, { from: recipient });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('3');
|
||||
|
||||
const t4 = await this.token.transfer(recipient, 20, { from: holder });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('4');
|
||||
|
||||
expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([ t1.receipt.blockNumber.toString(), '100' ]);
|
||||
expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([ t2.receipt.blockNumber.toString(), '90' ]);
|
||||
expect(await this.token.checkpoints(other1, 2)).to.be.deep.equal([ t3.receipt.blockNumber.toString(), '80' ]);
|
||||
expect(await this.token.checkpoints(other1, 3)).to.be.deep.equal([ t4.receipt.blockNumber.toString(), '100' ]);
|
||||
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPastVotes(other1, t1.receipt.blockNumber)).to.be.bignumber.equal('100');
|
||||
expect(await this.token.getPastVotes(other1, t2.receipt.blockNumber)).to.be.bignumber.equal('90');
|
||||
expect(await this.token.getPastVotes(other1, t3.receipt.blockNumber)).to.be.bignumber.equal('80');
|
||||
expect(await this.token.getPastVotes(other1, t4.receipt.blockNumber)).to.be.bignumber.equal('100');
|
||||
});
|
||||
|
||||
it('does not add more than one checkpoint in a block', async function () {
|
||||
await this.token.transfer(recipient, '100', { from: holder });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0');
|
||||
|
||||
const [ t1, t2, t3 ] = await batchInBlock([
|
||||
() => this.token.delegate(other1, { from: recipient, gas: 100000 }),
|
||||
() => this.token.transfer(other2, 10, { from: recipient, gas: 100000 }),
|
||||
() => this.token.transfer(other2, 10, { from: recipient, gas: 100000 }),
|
||||
]);
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([ t1.receipt.blockNumber.toString(), '80' ]);
|
||||
// expectReve(await this.token.checkpoints(other1, 1)).to.be.deep.equal([ '0', '0' ]); // Reverts due to array overflow check
|
||||
// expect(await this.token.checkpoints(other1, 2)).to.be.deep.equal([ '0', '0' ]); // Reverts due to array overflow check
|
||||
|
||||
const t4 = await this.token.transfer(recipient, 20, { from: holder });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2');
|
||||
expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([ t4.receipt.blockNumber.toString(), '100' ]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPastVotes', function () {
|
||||
it('reverts if block number >= current block', async function () {
|
||||
await expectRevert(
|
||||
this.token.getPastVotes(other1, 5e10),
|
||||
'ERC20Votes: block not yet mined',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns 0 if there are no checkpoints', async function () {
|
||||
expect(await this.token.getPastVotes(other1, 0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns the latest block if >= last checkpoint block', async function () {
|
||||
const t1 = await this.token.delegate(other1, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPastVotes(other1, t1.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastVotes(other1, t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
|
||||
it('returns zero if < first checkpoint block', async function () {
|
||||
await time.advanceBlock();
|
||||
const t1 = await this.token.delegate(other1, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPastVotes(other1, t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPastVotes(other1, t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
|
||||
it('generally returns the voting balance at the appropriate checkpoint', async function () {
|
||||
const t1 = await this.token.delegate(other1, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t2 = await this.token.transfer(other2, 10, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t3 = await this.token.transfer(other2, 10, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t4 = await this.token.transfer(holder, 20, { from: other2 });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPastVotes(other1, t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPastVotes(other1, t1.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastVotes(other1, t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastVotes(other1, t2.receipt.blockNumber)).to.be.bignumber.equal('9999999999999999999999990');
|
||||
expect(await this.token.getPastVotes(other1, t2.receipt.blockNumber + 1)).to.be.bignumber.equal('9999999999999999999999990');
|
||||
expect(await this.token.getPastVotes(other1, t3.receipt.blockNumber)).to.be.bignumber.equal('9999999999999999999999980');
|
||||
expect(await this.token.getPastVotes(other1, t3.receipt.blockNumber + 1)).to.be.bignumber.equal('9999999999999999999999980');
|
||||
expect(await this.token.getPastVotes(other1, t4.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastVotes(other1, t4.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPastTotalSupply', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.delegate(holder, { from: holder });
|
||||
});
|
||||
|
||||
it('reverts if block number >= current block', async function () {
|
||||
await expectRevert(
|
||||
this.token.getPastTotalSupply(5e10),
|
||||
'ERC20Votes: block not yet mined',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns 0 if there are no checkpoints', async function () {
|
||||
expect(await this.token.getPastTotalSupply(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns the latest block if >= last checkpoint block', async function () {
|
||||
t1 = await this.token.mint(holder, supply);
|
||||
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal(supply);
|
||||
});
|
||||
|
||||
it('returns zero if < first checkpoint block', async function () {
|
||||
await time.advanceBlock();
|
||||
const t1 = await this.token.mint(holder, supply);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
|
||||
it('generally returns the voting balance at the appropriate checkpoint', async function () {
|
||||
const t1 = await this.token.mint(holder, supply);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t2 = await this.token.burn(holder, 10);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t3 = await this.token.burn(holder, 10);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t4 = await this.token.mint(holder, 20);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastTotalSupply(t2.receipt.blockNumber)).to.be.bignumber.equal('9999999999999999999999990');
|
||||
expect(await this.token.getPastTotalSupply(t2.receipt.blockNumber + 1)).to.be.bignumber.equal('9999999999999999999999990');
|
||||
expect(await this.token.getPastTotalSupply(t3.receipt.blockNumber)).to.be.bignumber.equal('9999999999999999999999980');
|
||||
expect(await this.token.getPastTotalSupply(t3.receipt.blockNumber + 1)).to.be.bignumber.equal('9999999999999999999999980');
|
||||
expect(await this.token.getPastTotalSupply(t4.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastTotalSupply(t4.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
});
|
||||
});
|
||||
529
test/token/ERC20/extensions/ERC20VotesComp.test.js
Normal file
529
test/token/ERC20/extensions/ERC20VotesComp.test.js
Normal file
@ -0,0 +1,529 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants;
|
||||
|
||||
const { fromRpcSig } = require('ethereumjs-util');
|
||||
const ethSigUtil = require('eth-sig-util');
|
||||
const Wallet = require('ethereumjs-wallet').default;
|
||||
|
||||
const { promisify } = require('util');
|
||||
const queue = promisify(setImmediate);
|
||||
|
||||
const ERC20VotesCompMock = artifacts.require('ERC20VotesCompMock');
|
||||
|
||||
const { EIP712Domain, domainSeparator } = require('../../../helpers/eip712');
|
||||
|
||||
const Delegation = [
|
||||
{ name: 'delegatee', type: 'address' },
|
||||
{ name: 'nonce', type: 'uint256' },
|
||||
{ name: 'expiry', type: 'uint256' },
|
||||
];
|
||||
|
||||
async function countPendingTransactions() {
|
||||
return parseInt(
|
||||
await network.provider.send('eth_getBlockTransactionCountByNumber', ['pending'])
|
||||
);
|
||||
}
|
||||
|
||||
async function batchInBlock (txs) {
|
||||
try {
|
||||
// disable auto-mining
|
||||
await network.provider.send('evm_setAutomine', [false]);
|
||||
// send all transactions
|
||||
const promises = txs.map(fn => fn());
|
||||
// wait for node to have all pending transactions
|
||||
while (txs.length > await countPendingTransactions()) {
|
||||
await queue();
|
||||
}
|
||||
// mine one block
|
||||
await network.provider.send('evm_mine');
|
||||
// fetch receipts
|
||||
const receipts = await Promise.all(promises);
|
||||
// Sanity check, all tx should be in the same block
|
||||
const minedBlocks = new Set(receipts.map(({ receipt }) => receipt.blockNumber));
|
||||
expect(minedBlocks.size).to.equal(1);
|
||||
|
||||
return receipts;
|
||||
} finally {
|
||||
// enable auto-mining
|
||||
await network.provider.send('evm_setAutomine', [true]);
|
||||
}
|
||||
}
|
||||
|
||||
contract('ERC20VotesComp', function (accounts) {
|
||||
const [ holder, recipient, holderDelegatee, recipientDelegatee, other1, other2 ] = accounts;
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
const version = '1';
|
||||
const supply = new BN('10000000000000000000000000');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20VotesCompMock.new(name, symbol);
|
||||
|
||||
// We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id
|
||||
// from within the EVM as from the JSON RPC interface.
|
||||
// See https://github.com/trufflesuite/ganache-core/issues/515
|
||||
this.chainId = await this.token.getChainId();
|
||||
});
|
||||
|
||||
it('initial nonce is 0', async function () {
|
||||
expect(await this.token.nonces(holder)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('domain separator', async function () {
|
||||
expect(
|
||||
await this.token.DOMAIN_SEPARATOR(),
|
||||
).to.equal(
|
||||
await domainSeparator(name, version, this.chainId, this.token.address),
|
||||
);
|
||||
});
|
||||
|
||||
it('minting restriction', async function () {
|
||||
const amount = new BN('2').pow(new BN('96'));
|
||||
await expectRevert(
|
||||
this.token.mint(holder, amount),
|
||||
'ERC20Votes: total supply risks overflowing votes',
|
||||
);
|
||||
});
|
||||
|
||||
describe('set delegation', function () {
|
||||
describe('call', function () {
|
||||
it('delegation with balance', async function () {
|
||||
await this.token.mint(holder, supply);
|
||||
expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS);
|
||||
|
||||
const { receipt } = await this.token.delegate(holder, { from: holder });
|
||||
expectEvent(receipt, 'DelegateChanged', {
|
||||
delegator: holder,
|
||||
fromDelegate: ZERO_ADDRESS,
|
||||
toDelegate: holder,
|
||||
});
|
||||
expectEvent(receipt, 'DelegateVotesChanged', {
|
||||
delegate: holder,
|
||||
previousBalance: '0',
|
||||
newBalance: supply,
|
||||
});
|
||||
|
||||
expect(await this.token.delegates(holder)).to.be.equal(holder);
|
||||
|
||||
expect(await this.token.getCurrentVotes(holder)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPriorVotes(holder, receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPriorVotes(holder, receipt.blockNumber)).to.be.bignumber.equal(supply);
|
||||
});
|
||||
|
||||
it('delegation without balance', async function () {
|
||||
expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS);
|
||||
|
||||
const { receipt } = await this.token.delegate(holder, { from: holder });
|
||||
expectEvent(receipt, 'DelegateChanged', {
|
||||
delegator: holder,
|
||||
fromDelegate: ZERO_ADDRESS,
|
||||
toDelegate: holder,
|
||||
});
|
||||
expectEvent.notEmitted(receipt, 'DelegateVotesChanged');
|
||||
|
||||
expect(await this.token.delegates(holder)).to.be.equal(holder);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with signature', function () {
|
||||
const delegator = Wallet.generate();
|
||||
const delegatorAddress = web3.utils.toChecksumAddress(delegator.getAddressString());
|
||||
const nonce = 0;
|
||||
|
||||
const buildData = (chainId, verifyingContract, message) => ({ data: {
|
||||
primaryType: 'Delegation',
|
||||
types: { EIP712Domain, Delegation },
|
||||
domain: { name, version, chainId, verifyingContract },
|
||||
message,
|
||||
}});
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(delegatorAddress, supply);
|
||||
});
|
||||
|
||||
it('accept signed delegation', async function () {
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry: MAX_UINT256,
|
||||
}),
|
||||
));
|
||||
|
||||
expect(await this.token.delegates(delegatorAddress)).to.be.equal(ZERO_ADDRESS);
|
||||
|
||||
const { receipt } = await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s);
|
||||
expectEvent(receipt, 'DelegateChanged', {
|
||||
delegator: delegatorAddress,
|
||||
fromDelegate: ZERO_ADDRESS,
|
||||
toDelegate: delegatorAddress,
|
||||
});
|
||||
expectEvent(receipt, 'DelegateVotesChanged', {
|
||||
delegate: delegatorAddress,
|
||||
previousBalance: '0',
|
||||
newBalance: supply,
|
||||
});
|
||||
|
||||
expect(await this.token.delegates(delegatorAddress)).to.be.equal(delegatorAddress);
|
||||
|
||||
expect(await this.token.getCurrentVotes(delegatorAddress)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPriorVotes(delegatorAddress, receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPriorVotes(delegatorAddress, receipt.blockNumber)).to.be.bignumber.equal(supply);
|
||||
});
|
||||
|
||||
it('rejects reused signature', async function () {
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry: MAX_UINT256,
|
||||
}),
|
||||
));
|
||||
|
||||
await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s);
|
||||
|
||||
await expectRevert(
|
||||
this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s),
|
||||
'ERC20Votes: invalid nonce',
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects bad delegatee', async function () {
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry: MAX_UINT256,
|
||||
}),
|
||||
));
|
||||
|
||||
const { logs } = await this.token.delegateBySig(holderDelegatee, nonce, MAX_UINT256, v, r, s);
|
||||
const { args } = logs.find(({ event }) => event == 'DelegateChanged');
|
||||
expect(args.delegator).to.not.be.equal(delegatorAddress);
|
||||
expect(args.fromDelegate).to.be.equal(ZERO_ADDRESS);
|
||||
expect(args.toDelegate).to.be.equal(holderDelegatee);
|
||||
});
|
||||
|
||||
it('rejects bad nonce', async function () {
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry: MAX_UINT256,
|
||||
}),
|
||||
));
|
||||
await expectRevert(
|
||||
this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s),
|
||||
'ERC20Votes: invalid nonce',
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects expired permit', async function () {
|
||||
const expiry = (await time.latest()) - time.duration.weeks(1);
|
||||
const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
||||
delegator.getPrivateKey(),
|
||||
buildData(this.chainId, this.token.address, {
|
||||
delegatee: delegatorAddress,
|
||||
nonce,
|
||||
expiry,
|
||||
}),
|
||||
));
|
||||
|
||||
await expectRevert(
|
||||
this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s),
|
||||
'ERC20Votes: signature expired',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('change delegation', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(holder, supply);
|
||||
await this.token.delegate(holder, { from: holder });
|
||||
});
|
||||
|
||||
it('call', async function () {
|
||||
expect(await this.token.delegates(holder)).to.be.equal(holder);
|
||||
|
||||
const { receipt } = await this.token.delegate(holderDelegatee, { from: holder });
|
||||
expectEvent(receipt, 'DelegateChanged', {
|
||||
delegator: holder,
|
||||
fromDelegate: holder,
|
||||
toDelegate: holderDelegatee,
|
||||
});
|
||||
expectEvent(receipt, 'DelegateVotesChanged', {
|
||||
delegate: holder,
|
||||
previousBalance: supply,
|
||||
newBalance: '0',
|
||||
});
|
||||
expectEvent(receipt, 'DelegateVotesChanged', {
|
||||
delegate: holderDelegatee,
|
||||
previousBalance: '0',
|
||||
newBalance: supply,
|
||||
});
|
||||
|
||||
expect(await this.token.delegates(holder)).to.be.equal(holderDelegatee);
|
||||
|
||||
expect(await this.token.getCurrentVotes(holder)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getCurrentVotes(holderDelegatee)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPriorVotes(holder, receipt.blockNumber - 1)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPriorVotes(holderDelegatee, receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPriorVotes(holder, receipt.blockNumber)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPriorVotes(holderDelegatee, receipt.blockNumber)).to.be.bignumber.equal(supply);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transfers', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(holder, supply);
|
||||
});
|
||||
|
||||
it('no delegation', async function () {
|
||||
const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
|
||||
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
|
||||
expectEvent.notEmitted(receipt, 'DelegateVotesChanged');
|
||||
|
||||
this.holderVotes = '0';
|
||||
this.recipientVotes = '0';
|
||||
});
|
||||
|
||||
it('sender delegation', async function () {
|
||||
await this.token.delegate(holder, { from: holder });
|
||||
|
||||
const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
|
||||
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) });
|
||||
|
||||
this.holderVotes = supply.subn(1);
|
||||
this.recipientVotes = '0';
|
||||
});
|
||||
|
||||
it('receiver delegation', async function () {
|
||||
await this.token.delegate(recipient, { from: recipient });
|
||||
|
||||
const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
|
||||
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' });
|
||||
|
||||
this.holderVotes = '0';
|
||||
this.recipientVotes = '1';
|
||||
});
|
||||
|
||||
it('full delegation', async function () {
|
||||
await this.token.delegate(holder, { from: holder });
|
||||
await this.token.delegate(recipient, { from: recipient });
|
||||
|
||||
const { receipt } = await this.token.transfer(recipient, 1, { from: holder });
|
||||
expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' });
|
||||
|
||||
this.holderVotes = supply.subn(1);
|
||||
this.recipientVotes = '1';
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
expect(await this.token.getCurrentVotes(holder)).to.be.bignumber.equal(this.holderVotes);
|
||||
expect(await this.token.getCurrentVotes(recipient)).to.be.bignumber.equal(this.recipientVotes);
|
||||
|
||||
// need to advance 2 blocks to see the effect of a transfer on "getPriorVotes"
|
||||
const blockNumber = await time.latestBlock();
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPriorVotes(holder, blockNumber)).to.be.bignumber.equal(this.holderVotes);
|
||||
expect(await this.token.getPriorVotes(recipient, blockNumber)).to.be.bignumber.equal(this.recipientVotes);
|
||||
});
|
||||
});
|
||||
|
||||
// The following tests are a adaptation of https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js.
|
||||
describe('Compound test suite', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(holder, supply);
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
it('grants to initial account', async function () {
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('numCheckpoints', function () {
|
||||
it('returns the number of checkpoints for a delegate', async function () {
|
||||
await this.token.transfer(recipient, '100', { from: holder }); //give an account a few tokens for readability
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0');
|
||||
|
||||
const t1 = await this.token.delegate(other1, { from: recipient });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1');
|
||||
|
||||
const t2 = await this.token.transfer(other2, 10, { from: recipient });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2');
|
||||
|
||||
const t3 = await this.token.transfer(other2, 10, { from: recipient });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('3');
|
||||
|
||||
const t4 = await this.token.transfer(recipient, 20, { from: holder });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('4');
|
||||
|
||||
expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([ t1.receipt.blockNumber.toString(), '100' ]);
|
||||
expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([ t2.receipt.blockNumber.toString(), '90' ]);
|
||||
expect(await this.token.checkpoints(other1, 2)).to.be.deep.equal([ t3.receipt.blockNumber.toString(), '80' ]);
|
||||
expect(await this.token.checkpoints(other1, 3)).to.be.deep.equal([ t4.receipt.blockNumber.toString(), '100' ]);
|
||||
|
||||
await time.advanceBlock();
|
||||
expect(await this.token.getPriorVotes(other1, t1.receipt.blockNumber)).to.be.bignumber.equal('100');
|
||||
expect(await this.token.getPriorVotes(other1, t2.receipt.blockNumber)).to.be.bignumber.equal('90');
|
||||
expect(await this.token.getPriorVotes(other1, t3.receipt.blockNumber)).to.be.bignumber.equal('80');
|
||||
expect(await this.token.getPriorVotes(other1, t4.receipt.blockNumber)).to.be.bignumber.equal('100');
|
||||
});
|
||||
|
||||
it('does not add more than one checkpoint in a block', async function () {
|
||||
await this.token.transfer(recipient, '100', { from: holder });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0');
|
||||
|
||||
const [ t1, t2, t3 ] = await batchInBlock([
|
||||
() => this.token.delegate(other1, { from: recipient, gas: 100000 }),
|
||||
() => this.token.transfer(other2, 10, { from: recipient, gas: 100000 }),
|
||||
() => this.token.transfer(other2, 10, { from: recipient, gas: 100000 }),
|
||||
]);
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([ t1.receipt.blockNumber.toString(), '80' ]);
|
||||
// expectReve(await this.token.checkpoints(other1, 1)).to.be.deep.equal([ '0', '0' ]); // Reverts due to array overflow check
|
||||
// expect(await this.token.checkpoints(other1, 2)).to.be.deep.equal([ '0', '0' ]); // Reverts due to array overflow check
|
||||
|
||||
const t4 = await this.token.transfer(recipient, 20, { from: holder });
|
||||
expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2');
|
||||
expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([ t4.receipt.blockNumber.toString(), '100' ]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPriorVotes', function () {
|
||||
it('reverts if block number >= current block', async function () {
|
||||
await expectRevert(
|
||||
this.token.getPriorVotes(other1, 5e10),
|
||||
'ERC20Votes: block not yet mined',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns 0 if there are no checkpoints', async function () {
|
||||
expect(await this.token.getPriorVotes(other1, 0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns the latest block if >= last checkpoint block', async function () {
|
||||
const t1 = await this.token.delegate(other1, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPriorVotes(other1, t1.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPriorVotes(other1, t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
|
||||
it('returns zero if < first checkpoint block', async function () {
|
||||
await time.advanceBlock();
|
||||
const t1 = await this.token.delegate(other1, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPriorVotes(other1, t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPriorVotes(other1, t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
|
||||
it('generally returns the voting balance at the appropriate checkpoint', async function () {
|
||||
const t1 = await this.token.delegate(other1, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t2 = await this.token.transfer(other2, 10, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t3 = await this.token.transfer(other2, 10, { from: holder });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t4 = await this.token.transfer(holder, 20, { from: other2 });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPriorVotes(other1, t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPriorVotes(other1, t1.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPriorVotes(other1, t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPriorVotes(other1, t2.receipt.blockNumber)).to.be.bignumber.equal('9999999999999999999999990');
|
||||
expect(await this.token.getPriorVotes(other1, t2.receipt.blockNumber + 1)).to.be.bignumber.equal('9999999999999999999999990');
|
||||
expect(await this.token.getPriorVotes(other1, t3.receipt.blockNumber)).to.be.bignumber.equal('9999999999999999999999980');
|
||||
expect(await this.token.getPriorVotes(other1, t3.receipt.blockNumber + 1)).to.be.bignumber.equal('9999999999999999999999980');
|
||||
expect(await this.token.getPriorVotes(other1, t4.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPriorVotes(other1, t4.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPastTotalSupply', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.delegate(holder, { from: holder });
|
||||
});
|
||||
|
||||
it('reverts if block number >= current block', async function () {
|
||||
await expectRevert(
|
||||
this.token.getPastTotalSupply(5e10),
|
||||
'ERC20Votes: block not yet mined',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns 0 if there are no checkpoints', async function () {
|
||||
expect(await this.token.getPastTotalSupply(0)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('returns the latest block if >= last checkpoint block', async function () {
|
||||
t1 = await this.token.mint(holder, supply);
|
||||
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber)).to.be.bignumber.equal(supply);
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal(supply);
|
||||
});
|
||||
|
||||
it('returns zero if < first checkpoint block', async function () {
|
||||
await time.advanceBlock();
|
||||
const t1 = await this.token.mint(holder, supply);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
|
||||
it('generally returns the voting balance at the appropriate checkpoint', async function () {
|
||||
const t1 = await this.token.mint(holder, supply);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t2 = await this.token.burn(holder, 10);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t3 = await this.token.burn(holder, 10);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t4 = await this.token.mint(holder, 20);
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastTotalSupply(t2.receipt.blockNumber)).to.be.bignumber.equal('9999999999999999999999990');
|
||||
expect(await this.token.getPastTotalSupply(t2.receipt.blockNumber + 1)).to.be.bignumber.equal('9999999999999999999999990');
|
||||
expect(await this.token.getPastTotalSupply(t3.receipt.blockNumber)).to.be.bignumber.equal('9999999999999999999999980');
|
||||
expect(await this.token.getPastTotalSupply(t3.receipt.blockNumber + 1)).to.be.bignumber.equal('9999999999999999999999980');
|
||||
expect(await this.token.getPastTotalSupply(t4.receipt.blockNumber)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
expect(await this.token.getPastTotalSupply(t4.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000');
|
||||
});
|
||||
});
|
||||
});
|
||||
181
test/token/ERC20/extensions/ERC20Wrapper.test.js
Normal file
181
test/token/ERC20/extensions/ERC20Wrapper.test.js
Normal file
@ -0,0 +1,181 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { ZERO_ADDRESS, MAX_UINT256 } = constants;
|
||||
|
||||
const { shouldBehaveLikeERC20 } = require('../ERC20.behavior');
|
||||
|
||||
const ERC20Mock = artifacts.require('ERC20Mock');
|
||||
const ERC20WrapperMock = artifacts.require('ERC20WrapperMock');
|
||||
|
||||
contract('ERC20', function (accounts) {
|
||||
const [ initialHolder, recipient, anotherAccount ] = accounts;
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
const initialSupply = new BN(100);
|
||||
|
||||
beforeEach(async function () {
|
||||
this.underlying = await ERC20Mock.new(name, symbol, initialHolder, initialSupply);
|
||||
this.token = await ERC20WrapperMock.new(this.underlying.address, `Wrapped ${name}`, `W${symbol}`);
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
expect(await this.underlying.balanceOf(this.token.address)).to.be.bignumber.equal(await this.token.totalSupply());
|
||||
});
|
||||
|
||||
it('has a name', async function () {
|
||||
expect(await this.token.name()).to.equal(`Wrapped ${name}`);
|
||||
});
|
||||
|
||||
it('has a symbol', async function () {
|
||||
expect(await this.token.symbol()).to.equal(`W${symbol}`);
|
||||
});
|
||||
|
||||
it('has 18 decimals', async function () {
|
||||
expect(await this.token.decimals()).to.be.bignumber.equal('18');
|
||||
});
|
||||
|
||||
it('has underlying', async function () {
|
||||
expect(await this.token.underlying()).to.be.bignumber.equal(this.underlying.address);
|
||||
});
|
||||
|
||||
describe('deposit', function () {
|
||||
it('valid', async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
const { tx } = await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: this.token.address,
|
||||
value: initialSupply,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to: initialHolder,
|
||||
value: initialSupply,
|
||||
});
|
||||
});
|
||||
|
||||
it('missing approval', async function () {
|
||||
await expectRevert(
|
||||
this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }),
|
||||
'ERC20: transfer amount exceeds allowance',
|
||||
);
|
||||
});
|
||||
|
||||
it('missing balance', async function () {
|
||||
await this.underlying.approve(this.token.address, MAX_UINT256, { from: initialHolder });
|
||||
await expectRevert(
|
||||
this.token.depositFor(initialHolder, MAX_UINT256, { from: initialHolder }),
|
||||
'ERC20: transfer amount exceeds balance',
|
||||
);
|
||||
});
|
||||
|
||||
it('to other account', async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
const { tx } = await this.token.depositFor(anotherAccount, initialSupply, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: this.token.address,
|
||||
value: initialSupply,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to: anotherAccount,
|
||||
value: initialSupply,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('withdraw', function () {
|
||||
beforeEach(async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
|
||||
});
|
||||
|
||||
it('missing balance', async function () {
|
||||
await expectRevert(
|
||||
this.token.withdrawTo(initialHolder, MAX_UINT256, { from: initialHolder }),
|
||||
'ERC20: burn amount exceeds balance',
|
||||
);
|
||||
});
|
||||
|
||||
it('valid', async function () {
|
||||
const value = new BN(42);
|
||||
|
||||
const { tx } = await this.token.withdrawTo(initialHolder, value, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: this.token.address,
|
||||
to: initialHolder,
|
||||
value: value,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: ZERO_ADDRESS,
|
||||
value: value,
|
||||
});
|
||||
});
|
||||
|
||||
it('entire balance', async function () {
|
||||
const { tx } = await this.token.withdrawTo(initialHolder, initialSupply, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: this.token.address,
|
||||
to: initialHolder,
|
||||
value: initialSupply,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: ZERO_ADDRESS,
|
||||
value: initialSupply,
|
||||
});
|
||||
});
|
||||
|
||||
it('to other account', async function () {
|
||||
const { tx } = await this.token.withdrawTo(anotherAccount, initialSupply, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: this.token.address,
|
||||
to: anotherAccount,
|
||||
value: initialSupply,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: ZERO_ADDRESS,
|
||||
value: initialSupply,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('recover', function () {
|
||||
it('nothing to recover', async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
|
||||
|
||||
const { tx } = await this.token.recover(anotherAccount);
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to: anotherAccount,
|
||||
value: '0',
|
||||
});
|
||||
});
|
||||
|
||||
it('something to recover', async function () {
|
||||
await this.underlying.transfer(this.token.address, initialSupply, { from: initialHolder });
|
||||
|
||||
const { tx } = await this.token.recover(anotherAccount);
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to: anotherAccount,
|
||||
value: initialSupply,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('erc20 behaviour', function () {
|
||||
beforeEach(async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount);
|
||||
});
|
||||
});
|
||||
117
test/token/ERC20/extensions/draft-ERC20Permit.test.js
Normal file
117
test/token/ERC20/extensions/draft-ERC20Permit.test.js
Normal file
@ -0,0 +1,117 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants;
|
||||
|
||||
const { fromRpcSig } = require('ethereumjs-util');
|
||||
const ethSigUtil = require('eth-sig-util');
|
||||
const Wallet = require('ethereumjs-wallet').default;
|
||||
|
||||
const ERC20PermitMock = artifacts.require('ERC20PermitMock');
|
||||
|
||||
const { EIP712Domain, domainSeparator } = require('../../../helpers/eip712');
|
||||
|
||||
const Permit = [
|
||||
{ name: 'owner', type: 'address' },
|
||||
{ name: 'spender', type: 'address' },
|
||||
{ name: 'value', type: 'uint256' },
|
||||
{ name: 'nonce', type: 'uint256' },
|
||||
{ name: 'deadline', type: 'uint256' },
|
||||
];
|
||||
|
||||
contract('ERC20Permit', function (accounts) {
|
||||
const [ initialHolder, spender, recipient, other ] = accounts;
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
const version = '1';
|
||||
|
||||
const initialSupply = new BN(100);
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20PermitMock.new(name, symbol, initialHolder, initialSupply);
|
||||
|
||||
// We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id
|
||||
// from within the EVM as from the JSON RPC interface.
|
||||
// See https://github.com/trufflesuite/ganache-core/issues/515
|
||||
this.chainId = await this.token.getChainId();
|
||||
});
|
||||
|
||||
it('initial nonce is 0', async function () {
|
||||
expect(await this.token.nonces(initialHolder)).to.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('domain separator', async function () {
|
||||
expect(
|
||||
await this.token.DOMAIN_SEPARATOR(),
|
||||
).to.equal(
|
||||
await domainSeparator(name, version, this.chainId, this.token.address),
|
||||
);
|
||||
});
|
||||
|
||||
describe('permit', function () {
|
||||
const wallet = Wallet.generate();
|
||||
|
||||
const owner = wallet.getAddressString();
|
||||
const value = new BN(42);
|
||||
const nonce = 0;
|
||||
const maxDeadline = MAX_UINT256;
|
||||
|
||||
const buildData = (chainId, verifyingContract, deadline = maxDeadline) => ({
|
||||
primaryType: 'Permit',
|
||||
types: { EIP712Domain, Permit },
|
||||
domain: { name, version, chainId, verifyingContract },
|
||||
message: { owner, spender, value, nonce, deadline },
|
||||
});
|
||||
|
||||
it('accepts owner signature', async function () {
|
||||
const data = buildData(this.chainId, this.token.address);
|
||||
const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
|
||||
const { v, r, s } = fromRpcSig(signature);
|
||||
|
||||
const receipt = await this.token.permit(owner, spender, value, maxDeadline, v, r, s);
|
||||
|
||||
expect(await this.token.nonces(owner)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value);
|
||||
});
|
||||
|
||||
it('rejects reused signature', async function () {
|
||||
const data = buildData(this.chainId, this.token.address);
|
||||
const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
|
||||
const { v, r, s } = fromRpcSig(signature);
|
||||
|
||||
await this.token.permit(owner, spender, value, maxDeadline, v, r, s);
|
||||
|
||||
await expectRevert(
|
||||
this.token.permit(owner, spender, value, maxDeadline, v, r, s),
|
||||
'ERC20Permit: invalid signature',
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects other signature', async function () {
|
||||
const otherWallet = Wallet.generate();
|
||||
const data = buildData(this.chainId, this.token.address);
|
||||
const signature = ethSigUtil.signTypedMessage(otherWallet.getPrivateKey(), { data });
|
||||
const { v, r, s } = fromRpcSig(signature);
|
||||
|
||||
await expectRevert(
|
||||
this.token.permit(owner, spender, value, maxDeadline, v, r, s),
|
||||
'ERC20Permit: invalid signature',
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects expired permit', async function () {
|
||||
const deadline = (await time.latest()) - time.duration.weeks(1);
|
||||
|
||||
const data = buildData(this.chainId, this.token.address, deadline);
|
||||
const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
|
||||
const { v, r, s } = fromRpcSig(signature);
|
||||
|
||||
await expectRevert(
|
||||
this.token.permit(owner, spender, value, deadline, v, r, s),
|
||||
'ERC20Permit: expired deadline',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
42
test/token/ERC20/presets/ERC20PresetFixedSupply.test.js
Normal file
42
test/token/ERC20/presets/ERC20PresetFixedSupply.test.js
Normal file
@ -0,0 +1,42 @@
|
||||
const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC20PresetFixedSupply = artifacts.require('ERC20PresetFixedSupply');
|
||||
|
||||
contract('ERC20PresetFixedSupply', function (accounts) {
|
||||
const [deployer, owner] = accounts;
|
||||
|
||||
const name = 'PresetFixedSupply';
|
||||
const symbol = 'PFS';
|
||||
|
||||
const initialSupply = new BN('50000');
|
||||
const amount = new BN('10000');
|
||||
|
||||
before(async function () {
|
||||
this.token = await ERC20PresetFixedSupply.new(name, symbol, initialSupply, owner, { from: deployer });
|
||||
});
|
||||
|
||||
it('deployer has the balance equal to initial supply', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
|
||||
it('total supply is equal to initial supply', async function () {
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
|
||||
describe('burning', function () {
|
||||
it('holders can burn their tokens', async function () {
|
||||
const remainingBalance = initialSupply.sub(amount);
|
||||
const receipt = await this.token.burn(amount, { from: owner });
|
||||
expectEvent(receipt, 'Transfer', { from: owner, to: ZERO_ADDRESS, value: amount });
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(remainingBalance);
|
||||
});
|
||||
|
||||
it('decrements totalSupply', async function () {
|
||||
const expectedSupply = initialSupply.sub(amount);
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
|
||||
});
|
||||
});
|
||||
});
|
||||
113
test/token/ERC20/presets/ERC20PresetMinterPauser.test.js
Normal file
113
test/token/ERC20/presets/ERC20PresetMinterPauser.test.js
Normal file
@ -0,0 +1,113 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC20PresetMinterPauser = artifacts.require('ERC20PresetMinterPauser');
|
||||
|
||||
contract('ERC20PresetMinterPauser', function (accounts) {
|
||||
const [ deployer, other ] = accounts;
|
||||
|
||||
const name = 'MinterPauserToken';
|
||||
const symbol = 'DRT';
|
||||
|
||||
const amount = new BN('5000');
|
||||
|
||||
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE');
|
||||
const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20PresetMinterPauser.new(name, symbol, { from: deployer });
|
||||
});
|
||||
|
||||
it('deployer has the default admin role', async function () {
|
||||
expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer);
|
||||
});
|
||||
|
||||
it('deployer has the minter role', async function () {
|
||||
expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer);
|
||||
});
|
||||
|
||||
it('deployer has the pauser role', async function () {
|
||||
expect(await this.token.getRoleMemberCount(PAUSER_ROLE)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.getRoleMember(PAUSER_ROLE, 0)).to.equal(deployer);
|
||||
});
|
||||
|
||||
it('minter and pauser role admin is the default admin', async function () {
|
||||
expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||
expect(await this.token.getRoleAdmin(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||
});
|
||||
|
||||
describe('minting', function () {
|
||||
it('deployer can mint tokens', async function () {
|
||||
const receipt = await this.token.mint(other, amount, { from: deployer });
|
||||
expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, value: amount });
|
||||
|
||||
expect(await this.token.balanceOf(other)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('other accounts cannot mint tokens', async function () {
|
||||
await expectRevert(
|
||||
this.token.mint(other, amount, { from: other }),
|
||||
'ERC20PresetMinterPauser: must have minter role to mint',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pausing', function () {
|
||||
it('deployer can pause', async function () {
|
||||
const receipt = await this.token.pause({ from: deployer });
|
||||
expectEvent(receipt, 'Paused', { account: deployer });
|
||||
|
||||
expect(await this.token.paused()).to.equal(true);
|
||||
});
|
||||
|
||||
it('deployer can unpause', async function () {
|
||||
await this.token.pause({ from: deployer });
|
||||
|
||||
const receipt = await this.token.unpause({ from: deployer });
|
||||
expectEvent(receipt, 'Unpaused', { account: deployer });
|
||||
|
||||
expect(await this.token.paused()).to.equal(false);
|
||||
});
|
||||
|
||||
it('cannot mint while paused', async function () {
|
||||
await this.token.pause({ from: deployer });
|
||||
|
||||
await expectRevert(
|
||||
this.token.mint(other, amount, { from: deployer }),
|
||||
'ERC20Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('other accounts cannot pause', async function () {
|
||||
await expectRevert(
|
||||
this.token.pause({ from: other }),
|
||||
'ERC20PresetMinterPauser: must have pauser role to pause',
|
||||
);
|
||||
});
|
||||
|
||||
it('other accounts cannot unpause', async function () {
|
||||
await this.token.pause({ from: deployer });
|
||||
|
||||
await expectRevert(
|
||||
this.token.unpause({ from: other }),
|
||||
'ERC20PresetMinterPauser: must have pauser role to unpause',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('burning', function () {
|
||||
it('holders can burn their tokens', async function () {
|
||||
await this.token.mint(other, amount, { from: deployer });
|
||||
|
||||
const receipt = await this.token.burn(amount.subn(1), { from: other });
|
||||
expectEvent(receipt, 'Transfer', { from: other, to: ZERO_ADDRESS, value: amount.subn(1) });
|
||||
|
||||
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
135
test/token/ERC20/utils/SafeERC20.test.js
Normal file
135
test/token/ERC20/utils/SafeERC20.test.js
Normal file
@ -0,0 +1,135 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const ERC20ReturnFalseMock = artifacts.require('ERC20ReturnFalseMock');
|
||||
const ERC20ReturnTrueMock = artifacts.require('ERC20ReturnTrueMock');
|
||||
const ERC20NoReturnMock = artifacts.require('ERC20NoReturnMock');
|
||||
const SafeERC20Wrapper = artifacts.require('SafeERC20Wrapper');
|
||||
|
||||
contract('SafeERC20', function (accounts) {
|
||||
const [ hasNoCode ] = accounts;
|
||||
|
||||
describe('with address that has no contract code', function () {
|
||||
beforeEach(async function () {
|
||||
this.wrapper = await SafeERC20Wrapper.new(hasNoCode);
|
||||
});
|
||||
|
||||
shouldRevertOnAllCalls('Address: call to non-contract');
|
||||
});
|
||||
|
||||
describe('with token that returns false on all calls', function () {
|
||||
beforeEach(async function () {
|
||||
this.wrapper = await SafeERC20Wrapper.new((await ERC20ReturnFalseMock.new()).address);
|
||||
});
|
||||
|
||||
shouldRevertOnAllCalls('SafeERC20: ERC20 operation did not succeed');
|
||||
});
|
||||
|
||||
describe('with token that returns true on all calls', function () {
|
||||
beforeEach(async function () {
|
||||
this.wrapper = await SafeERC20Wrapper.new((await ERC20ReturnTrueMock.new()).address);
|
||||
});
|
||||
|
||||
shouldOnlyRevertOnErrors();
|
||||
});
|
||||
|
||||
describe('with token that returns no boolean values', function () {
|
||||
beforeEach(async function () {
|
||||
this.wrapper = await SafeERC20Wrapper.new((await ERC20NoReturnMock.new()).address);
|
||||
});
|
||||
|
||||
shouldOnlyRevertOnErrors();
|
||||
});
|
||||
});
|
||||
|
||||
function shouldRevertOnAllCalls (reason) {
|
||||
it('reverts on transfer', async function () {
|
||||
await expectRevert(this.wrapper.transfer(), reason);
|
||||
});
|
||||
|
||||
it('reverts on transferFrom', async function () {
|
||||
await expectRevert(this.wrapper.transferFrom(), reason);
|
||||
});
|
||||
|
||||
it('reverts on approve', async function () {
|
||||
await expectRevert(this.wrapper.approve(0), reason);
|
||||
});
|
||||
|
||||
it('reverts on increaseAllowance', async function () {
|
||||
// [TODO] make sure it's reverting for the right reason
|
||||
await expectRevert.unspecified(this.wrapper.increaseAllowance(0));
|
||||
});
|
||||
|
||||
it('reverts on decreaseAllowance', async function () {
|
||||
// [TODO] make sure it's reverting for the right reason
|
||||
await expectRevert.unspecified(this.wrapper.decreaseAllowance(0));
|
||||
});
|
||||
}
|
||||
|
||||
function shouldOnlyRevertOnErrors () {
|
||||
it('doesn\'t revert on transfer', async function () {
|
||||
await this.wrapper.transfer();
|
||||
});
|
||||
|
||||
it('doesn\'t revert on transferFrom', async function () {
|
||||
await this.wrapper.transferFrom();
|
||||
});
|
||||
|
||||
describe('approvals', function () {
|
||||
context('with zero allowance', function () {
|
||||
beforeEach(async function () {
|
||||
await this.wrapper.setAllowance(0);
|
||||
});
|
||||
|
||||
it('doesn\'t revert when approving a non-zero allowance', async function () {
|
||||
await this.wrapper.approve(100);
|
||||
});
|
||||
|
||||
it('doesn\'t revert when approving a zero allowance', async function () {
|
||||
await this.wrapper.approve(0);
|
||||
});
|
||||
|
||||
it('doesn\'t revert when increasing the allowance', async function () {
|
||||
await this.wrapper.increaseAllowance(10);
|
||||
});
|
||||
|
||||
it('reverts when decreasing the allowance', async function () {
|
||||
await expectRevert(
|
||||
this.wrapper.decreaseAllowance(10),
|
||||
'SafeERC20: decreased allowance below zero',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with non-zero allowance', function () {
|
||||
beforeEach(async function () {
|
||||
await this.wrapper.setAllowance(100);
|
||||
});
|
||||
|
||||
it('reverts when approving a non-zero allowance', async function () {
|
||||
await expectRevert(
|
||||
this.wrapper.approve(20),
|
||||
'SafeERC20: approve from non-zero to non-zero allowance',
|
||||
);
|
||||
});
|
||||
|
||||
it('doesn\'t revert when approving a zero allowance', async function () {
|
||||
await this.wrapper.approve(0);
|
||||
});
|
||||
|
||||
it('doesn\'t revert when increasing the allowance', async function () {
|
||||
await this.wrapper.increaseAllowance(10);
|
||||
});
|
||||
|
||||
it('doesn\'t revert when decreasing the allowance to a positive value', async function () {
|
||||
await this.wrapper.decreaseAllowance(50);
|
||||
});
|
||||
|
||||
it('reverts when decreasing the allowance to a negative value', async function () {
|
||||
await expectRevert(
|
||||
this.wrapper.decreaseAllowance(200),
|
||||
'SafeERC20: decreased allowance below zero',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
71
test/token/ERC20/utils/TokenTimelock.test.js
Normal file
71
test/token/ERC20/utils/TokenTimelock.test.js
Normal file
@ -0,0 +1,71 @@
|
||||
const { BN, expectRevert, time } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC20Mock = artifacts.require('ERC20Mock');
|
||||
const TokenTimelock = artifacts.require('TokenTimelock');
|
||||
|
||||
contract('TokenTimelock', function (accounts) {
|
||||
const [ beneficiary ] = accounts;
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
const amount = new BN(100);
|
||||
|
||||
context('with token', function () {
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC20Mock.new(name, symbol, beneficiary, 0); // We're not using the preminted tokens
|
||||
});
|
||||
|
||||
it('rejects a release time in the past', async function () {
|
||||
const pastReleaseTime = (await time.latest()).sub(time.duration.years(1));
|
||||
await expectRevert(
|
||||
TokenTimelock.new(this.token.address, beneficiary, pastReleaseTime),
|
||||
'TokenTimelock: release time is before current time',
|
||||
);
|
||||
});
|
||||
|
||||
context('once deployed', function () {
|
||||
beforeEach(async function () {
|
||||
this.releaseTime = (await time.latest()).add(time.duration.years(1));
|
||||
this.timelock = await TokenTimelock.new(this.token.address, beneficiary, this.releaseTime);
|
||||
await this.token.mint(this.timelock.address, amount);
|
||||
});
|
||||
|
||||
it('can get state', async function () {
|
||||
expect(await this.timelock.token()).to.equal(this.token.address);
|
||||
expect(await this.timelock.beneficiary()).to.equal(beneficiary);
|
||||
expect(await this.timelock.releaseTime()).to.be.bignumber.equal(this.releaseTime);
|
||||
});
|
||||
|
||||
it('cannot be released before time limit', async function () {
|
||||
await expectRevert(this.timelock.release(), 'TokenTimelock: current time is before release time');
|
||||
});
|
||||
|
||||
it('cannot be released just before time limit', async function () {
|
||||
await time.increaseTo(this.releaseTime.sub(time.duration.seconds(3)));
|
||||
await expectRevert(this.timelock.release(), 'TokenTimelock: current time is before release time');
|
||||
});
|
||||
|
||||
it('can be released just after limit', async function () {
|
||||
await time.increaseTo(this.releaseTime.add(time.duration.seconds(1)));
|
||||
await this.timelock.release();
|
||||
expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('can be released after time limit', async function () {
|
||||
await time.increaseTo(this.releaseTime.add(time.duration.years(1)));
|
||||
await this.timelock.release();
|
||||
expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('cannot be released twice', async function () {
|
||||
await time.increaseTo(this.releaseTime.add(time.duration.years(1)));
|
||||
await this.timelock.release();
|
||||
await expectRevert(this.timelock.release(), 'TokenTimelock: no tokens to release');
|
||||
expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
945
test/token/ERC721/ERC721.behavior.js
Normal file
945
test/token/ERC721/ERC721.behavior.js
Normal file
@ -0,0 +1,945 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior');
|
||||
|
||||
const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock');
|
||||
|
||||
const Error = [ 'None', 'RevertWithMessage', 'RevertWithoutMessage', 'Panic' ]
|
||||
.reduce((acc, entry, idx) => Object.assign({ [entry]: idx }, acc), {});
|
||||
|
||||
const firstTokenId = new BN('5042');
|
||||
const secondTokenId = new BN('79217');
|
||||
const nonExistentTokenId = new BN('13');
|
||||
const fourthTokenId = new BN(4);
|
||||
const baseURI = 'https://api.example.com/v1/';
|
||||
|
||||
const RECEIVER_MAGIC_VALUE = '0x150b7a02';
|
||||
|
||||
function shouldBehaveLikeERC721 (errorPrefix, owner, newOwner, approved, anotherApproved, operator, other) {
|
||||
shouldSupportInterfaces([
|
||||
'ERC165',
|
||||
'ERC721',
|
||||
]);
|
||||
|
||||
context('with minted tokens', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(owner, firstTokenId);
|
||||
await this.token.mint(owner, secondTokenId);
|
||||
this.toWhom = other; // default to other for toWhom in context-dependent tests
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
context('when the given address owns some tokens', function () {
|
||||
it('returns the amount of tokens owned by the given address', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('2');
|
||||
});
|
||||
});
|
||||
|
||||
context('when the given address does not own any tokens', function () {
|
||||
it('returns 0', async function () {
|
||||
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('when querying the zero address', function () {
|
||||
it('throws', async function () {
|
||||
await expectRevert(
|
||||
this.token.balanceOf(ZERO_ADDRESS), 'ERC721: balance query for the zero address',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ownerOf', function () {
|
||||
context('when the given token ID was tracked by this token', function () {
|
||||
const tokenId = firstTokenId;
|
||||
|
||||
it('returns the owner of the given token ID', async function () {
|
||||
expect(await this.token.ownerOf(tokenId)).to.be.equal(owner);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the given token ID was not tracked by this token', function () {
|
||||
const tokenId = nonExistentTokenId;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.ownerOf(tokenId), 'ERC721: owner query for nonexistent token',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transfers', function () {
|
||||
const tokenId = firstTokenId;
|
||||
const data = '0x42';
|
||||
|
||||
let logs = null;
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(approved, tokenId, { from: owner });
|
||||
await this.token.setApprovalForAll(operator, true, { from: owner });
|
||||
});
|
||||
|
||||
const transferWasSuccessful = function ({ owner, tokenId, approved }) {
|
||||
it('transfers the ownership of the given token ID to the given address', async function () {
|
||||
expect(await this.token.ownerOf(tokenId)).to.be.equal(this.toWhom);
|
||||
});
|
||||
|
||||
it('emits a Transfer event', async function () {
|
||||
expectEvent.inLogs(logs, 'Transfer', { from: owner, to: this.toWhom, tokenId: tokenId });
|
||||
});
|
||||
|
||||
it('clears the approval for the token ID', async function () {
|
||||
expect(await this.token.getApproved(tokenId)).to.be.equal(ZERO_ADDRESS);
|
||||
});
|
||||
|
||||
it('emits an Approval event', async function () {
|
||||
expectEvent.inLogs(logs, 'Approval', { owner, approved: ZERO_ADDRESS, tokenId: tokenId });
|
||||
});
|
||||
|
||||
it('adjusts owners balances', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('adjusts owners tokens by index', async function () {
|
||||
if (!this.token.tokenOfOwnerByIndex) return;
|
||||
|
||||
expect(await this.token.tokenOfOwnerByIndex(this.toWhom, 0)).to.be.bignumber.equal(tokenId);
|
||||
|
||||
expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.not.equal(tokenId);
|
||||
});
|
||||
};
|
||||
|
||||
const shouldTransferTokensByUsers = function (transferFunction) {
|
||||
context('when called by the owner', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs } = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: owner }));
|
||||
});
|
||||
transferWasSuccessful({ owner, tokenId, approved });
|
||||
});
|
||||
|
||||
context('when called by the approved individual', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs } = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: approved }));
|
||||
});
|
||||
transferWasSuccessful({ owner, tokenId, approved });
|
||||
});
|
||||
|
||||
context('when called by the operator', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs } = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: operator }));
|
||||
});
|
||||
transferWasSuccessful({ owner, tokenId, approved });
|
||||
});
|
||||
|
||||
context('when called by the owner without an approved user', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner });
|
||||
({ logs } = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: operator }));
|
||||
});
|
||||
transferWasSuccessful({ owner, tokenId, approved: null });
|
||||
});
|
||||
|
||||
context('when sent to the owner', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs } = await transferFunction.call(this, owner, owner, tokenId, { from: owner }));
|
||||
});
|
||||
|
||||
it('keeps ownership of the token', async function () {
|
||||
expect(await this.token.ownerOf(tokenId)).to.be.equal(owner);
|
||||
});
|
||||
|
||||
it('clears the approval for the token ID', async function () {
|
||||
expect(await this.token.getApproved(tokenId)).to.be.equal(ZERO_ADDRESS);
|
||||
});
|
||||
|
||||
it('emits only a transfer event', async function () {
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from: owner,
|
||||
to: owner,
|
||||
tokenId: tokenId,
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the owner balance', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('2');
|
||||
});
|
||||
|
||||
it('keeps same tokens by index', async function () {
|
||||
if (!this.token.tokenOfOwnerByIndex) return;
|
||||
const tokensListed = await Promise.all(
|
||||
[0, 1].map(i => this.token.tokenOfOwnerByIndex(owner, i)),
|
||||
);
|
||||
expect(tokensListed.map(t => t.toNumber())).to.have.members(
|
||||
[firstTokenId.toNumber(), secondTokenId.toNumber()],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the address of the previous owner is incorrect', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
transferFunction.call(this, other, other, tokenId, { from: owner }),
|
||||
'ERC721: transfer from incorrect owner',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the sender is not authorized for the token id', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
transferFunction.call(this, owner, other, tokenId, { from: other }),
|
||||
'ERC721: transfer caller is not owner nor approved',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the given token ID does not exist', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
transferFunction.call(this, owner, other, nonExistentTokenId, { from: owner }),
|
||||
'ERC721: operator query for nonexistent token',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the address to transfer the token to is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
transferFunction.call(this, owner, ZERO_ADDRESS, tokenId, { from: owner }),
|
||||
'ERC721: transfer to the zero address',
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('via transferFrom', function () {
|
||||
shouldTransferTokensByUsers(function (from, to, tokenId, opts) {
|
||||
return this.token.transferFrom(from, to, tokenId, opts);
|
||||
});
|
||||
});
|
||||
|
||||
describe('via safeTransferFrom', function () {
|
||||
const safeTransferFromWithData = function (from, to, tokenId, opts) {
|
||||
return this.token.methods['safeTransferFrom(address,address,uint256,bytes)'](from, to, tokenId, data, opts);
|
||||
};
|
||||
|
||||
const safeTransferFromWithoutData = function (from, to, tokenId, opts) {
|
||||
return this.token.methods['safeTransferFrom(address,address,uint256)'](from, to, tokenId, opts);
|
||||
};
|
||||
|
||||
const shouldTransferSafely = function (transferFun, data) {
|
||||
describe('to a user account', function () {
|
||||
shouldTransferTokensByUsers(transferFun);
|
||||
});
|
||||
|
||||
describe('to a valid receiver contract', function () {
|
||||
beforeEach(async function () {
|
||||
this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.None);
|
||||
this.toWhom = this.receiver.address;
|
||||
});
|
||||
|
||||
shouldTransferTokensByUsers(transferFun);
|
||||
|
||||
it('calls onERC721Received', async function () {
|
||||
const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner });
|
||||
|
||||
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
|
||||
operator: owner,
|
||||
from: owner,
|
||||
tokenId: tokenId,
|
||||
data: data,
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onERC721Received from approved', async function () {
|
||||
const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: approved });
|
||||
|
||||
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
|
||||
operator: approved,
|
||||
from: owner,
|
||||
tokenId: tokenId,
|
||||
data: data,
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an invalid token id', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
transferFun.call(
|
||||
this,
|
||||
owner,
|
||||
this.receiver.address,
|
||||
nonExistentTokenId,
|
||||
{ from: owner },
|
||||
),
|
||||
'ERC721: operator query for nonexistent token',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('with data', function () {
|
||||
shouldTransferSafely(safeTransferFromWithData, data);
|
||||
});
|
||||
|
||||
describe('without data', function () {
|
||||
shouldTransferSafely(safeTransferFromWithoutData, null);
|
||||
});
|
||||
|
||||
describe('to a receiver contract returning unexpected value', function () {
|
||||
it('reverts', async function () {
|
||||
const invalidReceiver = await ERC721ReceiverMock.new('0x42', Error.None);
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner }),
|
||||
'ERC721: transfer to non ERC721Receiver implementer',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('to a receiver contract that reverts with message', function () {
|
||||
it('reverts', async function () {
|
||||
const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithMessage);
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }),
|
||||
'ERC721ReceiverMock: reverting',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('to a receiver contract that reverts without message', function () {
|
||||
it('reverts', async function () {
|
||||
const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithoutMessage);
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }),
|
||||
'ERC721: transfer to non ERC721Receiver implementer',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('to a receiver contract that panics', function () {
|
||||
it('reverts', async function () {
|
||||
const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.Panic);
|
||||
await expectRevert.unspecified(
|
||||
this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('to a contract that does not implement the required function', function () {
|
||||
it('reverts', async function () {
|
||||
const nonReceiver = this.token;
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(owner, nonReceiver.address, tokenId, { from: owner }),
|
||||
'ERC721: transfer to non ERC721Receiver implementer',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('safe mint', function () {
|
||||
const tokenId = fourthTokenId;
|
||||
const data = '0x42';
|
||||
|
||||
describe('via safeMint', function () { // regular minting is tested in ERC721Mintable.test.js and others
|
||||
it('calls onERC721Received — with data', async function () {
|
||||
this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.None);
|
||||
const receipt = await this.token.safeMint(this.receiver.address, tokenId, data);
|
||||
|
||||
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
|
||||
from: ZERO_ADDRESS,
|
||||
tokenId: tokenId,
|
||||
data: data,
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onERC721Received — without data', async function () {
|
||||
this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.None);
|
||||
const receipt = await this.token.safeMint(this.receiver.address, tokenId);
|
||||
|
||||
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
|
||||
from: ZERO_ADDRESS,
|
||||
tokenId: tokenId,
|
||||
});
|
||||
});
|
||||
|
||||
context('to a receiver contract returning unexpected value', function () {
|
||||
it('reverts', async function () {
|
||||
const invalidReceiver = await ERC721ReceiverMock.new('0x42', Error.None);
|
||||
await expectRevert(
|
||||
this.token.safeMint(invalidReceiver.address, tokenId),
|
||||
'ERC721: transfer to non ERC721Receiver implementer',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('to a receiver contract that reverts with message', function () {
|
||||
it('reverts', async function () {
|
||||
const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithMessage);
|
||||
await expectRevert(
|
||||
this.token.safeMint(revertingReceiver.address, tokenId),
|
||||
'ERC721ReceiverMock: reverting',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('to a receiver contract that reverts without message', function () {
|
||||
it('reverts', async function () {
|
||||
const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithoutMessage);
|
||||
await expectRevert(
|
||||
this.token.safeMint(revertingReceiver.address, tokenId),
|
||||
'ERC721: transfer to non ERC721Receiver implementer',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('to a receiver contract that panics', function () {
|
||||
it('reverts', async function () {
|
||||
const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.Panic);
|
||||
await expectRevert.unspecified(
|
||||
this.token.safeMint(revertingReceiver.address, tokenId),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('to a contract that does not implement the required function', function () {
|
||||
it('reverts', async function () {
|
||||
const nonReceiver = this.token;
|
||||
await expectRevert(
|
||||
this.token.safeMint(nonReceiver.address, tokenId),
|
||||
'ERC721: transfer to non ERC721Receiver implementer',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('approve', function () {
|
||||
const tokenId = firstTokenId;
|
||||
|
||||
let logs = null;
|
||||
|
||||
const itClearsApproval = function () {
|
||||
it('clears approval for the token', async function () {
|
||||
expect(await this.token.getApproved(tokenId)).to.be.equal(ZERO_ADDRESS);
|
||||
});
|
||||
};
|
||||
|
||||
const itApproves = function (address) {
|
||||
it('sets the approval for the target address', async function () {
|
||||
expect(await this.token.getApproved(tokenId)).to.be.equal(address);
|
||||
});
|
||||
};
|
||||
|
||||
const itEmitsApprovalEvent = function (address) {
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent.inLogs(logs, 'Approval', {
|
||||
owner: owner,
|
||||
approved: address,
|
||||
tokenId: tokenId,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
context('when clearing approval', function () {
|
||||
context('when there was no prior approval', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs } = await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner }));
|
||||
});
|
||||
|
||||
itClearsApproval();
|
||||
itEmitsApprovalEvent(ZERO_ADDRESS);
|
||||
});
|
||||
|
||||
context('when there was a prior approval', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(approved, tokenId, { from: owner });
|
||||
({ logs } = await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner }));
|
||||
});
|
||||
|
||||
itClearsApproval();
|
||||
itEmitsApprovalEvent(ZERO_ADDRESS);
|
||||
});
|
||||
});
|
||||
|
||||
context('when approving a non-zero address', function () {
|
||||
context('when there was no prior approval', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs } = await this.token.approve(approved, tokenId, { from: owner }));
|
||||
});
|
||||
|
||||
itApproves(approved);
|
||||
itEmitsApprovalEvent(approved);
|
||||
});
|
||||
|
||||
context('when there was a prior approval to the same address', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(approved, tokenId, { from: owner });
|
||||
({ logs } = await this.token.approve(approved, tokenId, { from: owner }));
|
||||
});
|
||||
|
||||
itApproves(approved);
|
||||
itEmitsApprovalEvent(approved);
|
||||
});
|
||||
|
||||
context('when there was a prior approval to a different address', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(anotherApproved, tokenId, { from: owner });
|
||||
({ logs } = await this.token.approve(anotherApproved, tokenId, { from: owner }));
|
||||
});
|
||||
|
||||
itApproves(anotherApproved);
|
||||
itEmitsApprovalEvent(anotherApproved);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the address that receives the approval is the owner', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.approve(owner, tokenId, { from: owner }), 'ERC721: approval to current owner',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the sender does not own the given token ID', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.approve(approved, tokenId, { from: other }),
|
||||
'ERC721: approve caller is not owner nor approved');
|
||||
});
|
||||
});
|
||||
|
||||
context('when the sender is approved for the given token ID', function () {
|
||||
it('reverts', async function () {
|
||||
await this.token.approve(approved, tokenId, { from: owner });
|
||||
await expectRevert(this.token.approve(anotherApproved, tokenId, { from: approved }),
|
||||
'ERC721: approve caller is not owner nor approved for all');
|
||||
});
|
||||
});
|
||||
|
||||
context('when the sender is an operator', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.setApprovalForAll(operator, true, { from: owner });
|
||||
({ logs } = await this.token.approve(approved, tokenId, { from: operator }));
|
||||
});
|
||||
|
||||
itApproves(approved);
|
||||
itEmitsApprovalEvent(approved);
|
||||
});
|
||||
|
||||
context('when the given token ID does not exist', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.approve(approved, nonExistentTokenId, { from: operator }),
|
||||
'ERC721: owner query for nonexistent token');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setApprovalForAll', function () {
|
||||
context('when the operator willing to approve is not the owner', function () {
|
||||
context('when there is no operator approval set by the sender', function () {
|
||||
it('approves the operator', async function () {
|
||||
await this.token.setApprovalForAll(operator, true, { from: owner });
|
||||
|
||||
expect(await this.token.isApprovedForAll(owner, operator)).to.equal(true);
|
||||
});
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
const { logs } = await this.token.setApprovalForAll(operator, true, { from: owner });
|
||||
|
||||
expectEvent.inLogs(logs, 'ApprovalForAll', {
|
||||
owner: owner,
|
||||
operator: operator,
|
||||
approved: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when the operator was set as not approved', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.setApprovalForAll(operator, false, { from: owner });
|
||||
});
|
||||
|
||||
it('approves the operator', async function () {
|
||||
await this.token.setApprovalForAll(operator, true, { from: owner });
|
||||
|
||||
expect(await this.token.isApprovedForAll(owner, operator)).to.equal(true);
|
||||
});
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
const { logs } = await this.token.setApprovalForAll(operator, true, { from: owner });
|
||||
|
||||
expectEvent.inLogs(logs, 'ApprovalForAll', {
|
||||
owner: owner,
|
||||
operator: operator,
|
||||
approved: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('can unset the operator approval', async function () {
|
||||
await this.token.setApprovalForAll(operator, false, { from: owner });
|
||||
|
||||
expect(await this.token.isApprovedForAll(owner, operator)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the operator was already approved', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.setApprovalForAll(operator, true, { from: owner });
|
||||
});
|
||||
|
||||
it('keeps the approval to the given address', async function () {
|
||||
await this.token.setApprovalForAll(operator, true, { from: owner });
|
||||
|
||||
expect(await this.token.isApprovedForAll(owner, operator)).to.equal(true);
|
||||
});
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
const { logs } = await this.token.setApprovalForAll(operator, true, { from: owner });
|
||||
|
||||
expectEvent.inLogs(logs, 'ApprovalForAll', {
|
||||
owner: owner,
|
||||
operator: operator,
|
||||
approved: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when the operator is the owner', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.setApprovalForAll(owner, true, { from: owner }),
|
||||
'ERC721: approve to caller');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getApproved', async function () {
|
||||
context('when token is not minted', async function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.getApproved(nonExistentTokenId),
|
||||
'ERC721: approved query for nonexistent token',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when token has been minted ', async function () {
|
||||
it('should return the zero address', async function () {
|
||||
expect(await this.token.getApproved(firstTokenId)).to.be.equal(
|
||||
ZERO_ADDRESS,
|
||||
);
|
||||
});
|
||||
|
||||
context('when account has been approved', async function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(approved, firstTokenId, { from: owner });
|
||||
});
|
||||
|
||||
it('returns approved account', async function () {
|
||||
expect(await this.token.getApproved(firstTokenId)).to.be.equal(approved);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_mint(address, uint256)', function () {
|
||||
it('reverts with a null destination address', async function () {
|
||||
await expectRevert(
|
||||
this.token.mint(ZERO_ADDRESS, firstTokenId), 'ERC721: mint to the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
context('with minted token', async function () {
|
||||
beforeEach(async function () {
|
||||
({ logs: this.logs } = await this.token.mint(owner, firstTokenId));
|
||||
});
|
||||
|
||||
it('emits a Transfer event', function () {
|
||||
expectEvent.inLogs(this.logs, 'Transfer', { from: ZERO_ADDRESS, to: owner, tokenId: firstTokenId });
|
||||
});
|
||||
|
||||
it('creates the token', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.ownerOf(firstTokenId)).to.equal(owner);
|
||||
});
|
||||
|
||||
it('reverts when adding a token id that already exists', async function () {
|
||||
await expectRevert(this.token.mint(owner, firstTokenId), 'ERC721: token already minted');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_burn', function () {
|
||||
it('reverts when burning a non-existent token id', async function () {
|
||||
await expectRevert(
|
||||
this.token.burn(nonExistentTokenId), 'ERC721: owner query for nonexistent token',
|
||||
);
|
||||
});
|
||||
|
||||
context('with minted tokens', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(owner, firstTokenId);
|
||||
await this.token.mint(owner, secondTokenId);
|
||||
});
|
||||
|
||||
context('with burnt token', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs: this.logs } = await this.token.burn(firstTokenId));
|
||||
});
|
||||
|
||||
it('emits a Transfer event', function () {
|
||||
expectEvent.inLogs(this.logs, 'Transfer', { from: owner, to: ZERO_ADDRESS, tokenId: firstTokenId });
|
||||
});
|
||||
|
||||
it('emits an Approval event', function () {
|
||||
expectEvent.inLogs(this.logs, 'Approval', { owner, approved: ZERO_ADDRESS, tokenId: firstTokenId });
|
||||
});
|
||||
|
||||
it('deletes the token', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1');
|
||||
await expectRevert(
|
||||
this.token.ownerOf(firstTokenId), 'ERC721: owner query for nonexistent token',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when burning a token id that has been deleted', async function () {
|
||||
await expectRevert(
|
||||
this.token.burn(firstTokenId), 'ERC721: owner query for nonexistent token',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC721Enumerable (errorPrefix, owner, newOwner, approved, anotherApproved, operator, other) {
|
||||
shouldSupportInterfaces([
|
||||
'ERC721Enumerable',
|
||||
]);
|
||||
|
||||
context('with minted tokens', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(owner, firstTokenId);
|
||||
await this.token.mint(owner, secondTokenId);
|
||||
this.toWhom = other; // default to other for toWhom in context-dependent tests
|
||||
});
|
||||
|
||||
describe('totalSupply', function () {
|
||||
it('returns total token supply', async function () {
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal('2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('tokenOfOwnerByIndex', function () {
|
||||
describe('when the given index is lower than the amount of tokens owned by the given address', function () {
|
||||
it('returns the token ID placed at the given index', async function () {
|
||||
expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(firstTokenId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the index is greater than or equal to the total tokens owned by the given address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.tokenOfOwnerByIndex(owner, 2), 'ERC721Enumerable: owner index out of bounds',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given address does not own any token', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.tokenOfOwnerByIndex(other, 0), 'ERC721Enumerable: owner index out of bounds',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('after transferring all tokens to another user', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.transferFrom(owner, other, firstTokenId, { from: owner });
|
||||
await this.token.transferFrom(owner, other, secondTokenId, { from: owner });
|
||||
});
|
||||
|
||||
it('returns correct token IDs for target', async function () {
|
||||
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('2');
|
||||
const tokensListed = await Promise.all(
|
||||
[0, 1].map(i => this.token.tokenOfOwnerByIndex(other, i)),
|
||||
);
|
||||
expect(tokensListed.map(t => t.toNumber())).to.have.members([firstTokenId.toNumber(),
|
||||
secondTokenId.toNumber()]);
|
||||
});
|
||||
|
||||
it('returns empty collection for original owner', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('0');
|
||||
await expectRevert(
|
||||
this.token.tokenOfOwnerByIndex(owner, 0), 'ERC721Enumerable: owner index out of bounds',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tokenByIndex', function () {
|
||||
it('returns all tokens', async function () {
|
||||
const tokensListed = await Promise.all(
|
||||
[0, 1].map(i => this.token.tokenByIndex(i)),
|
||||
);
|
||||
expect(tokensListed.map(t => t.toNumber())).to.have.members([firstTokenId.toNumber(),
|
||||
secondTokenId.toNumber()]);
|
||||
});
|
||||
|
||||
it('reverts if index is greater than supply', async function () {
|
||||
await expectRevert(
|
||||
this.token.tokenByIndex(2), 'ERC721Enumerable: global index out of bounds',
|
||||
);
|
||||
});
|
||||
|
||||
[firstTokenId, secondTokenId].forEach(function (tokenId) {
|
||||
it(`returns all tokens after burning token ${tokenId} and minting new tokens`, async function () {
|
||||
const newTokenId = new BN(300);
|
||||
const anotherNewTokenId = new BN(400);
|
||||
|
||||
await this.token.burn(tokenId);
|
||||
await this.token.mint(newOwner, newTokenId);
|
||||
await this.token.mint(newOwner, anotherNewTokenId);
|
||||
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal('3');
|
||||
|
||||
const tokensListed = await Promise.all(
|
||||
[0, 1, 2].map(i => this.token.tokenByIndex(i)),
|
||||
);
|
||||
const expectedTokens = [firstTokenId, secondTokenId, newTokenId, anotherNewTokenId].filter(
|
||||
x => (x !== tokenId),
|
||||
);
|
||||
expect(tokensListed.map(t => t.toNumber())).to.have.members(expectedTokens.map(t => t.toNumber()));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_mint(address, uint256)', function () {
|
||||
it('reverts with a null destination address', async function () {
|
||||
await expectRevert(
|
||||
this.token.mint(ZERO_ADDRESS, firstTokenId), 'ERC721: mint to the zero address',
|
||||
);
|
||||
});
|
||||
|
||||
context('with minted token', async function () {
|
||||
beforeEach(async function () {
|
||||
({ logs: this.logs } = await this.token.mint(owner, firstTokenId));
|
||||
});
|
||||
|
||||
it('adjusts owner tokens by index', async function () {
|
||||
expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(firstTokenId);
|
||||
});
|
||||
|
||||
it('adjusts all tokens list', async function () {
|
||||
expect(await this.token.tokenByIndex(0)).to.be.bignumber.equal(firstTokenId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_burn', function () {
|
||||
it('reverts when burning a non-existent token id', async function () {
|
||||
await expectRevert(
|
||||
this.token.burn(firstTokenId), 'ERC721: owner query for nonexistent token',
|
||||
);
|
||||
});
|
||||
|
||||
context('with minted tokens', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(owner, firstTokenId);
|
||||
await this.token.mint(owner, secondTokenId);
|
||||
});
|
||||
|
||||
context('with burnt token', function () {
|
||||
beforeEach(async function () {
|
||||
({ logs: this.logs } = await this.token.burn(firstTokenId));
|
||||
});
|
||||
|
||||
it('removes that token from the token list of the owner', async function () {
|
||||
expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(secondTokenId);
|
||||
});
|
||||
|
||||
it('adjusts all tokens list', async function () {
|
||||
expect(await this.token.tokenByIndex(0)).to.be.bignumber.equal(secondTokenId);
|
||||
});
|
||||
|
||||
it('burns all tokens', async function () {
|
||||
await this.token.burn(secondTokenId, { from: owner });
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal('0');
|
||||
await expectRevert(
|
||||
this.token.tokenByIndex(0), 'ERC721Enumerable: global index out of bounds',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC721Metadata (errorPrefix, name, symbol, owner) {
|
||||
shouldSupportInterfaces([
|
||||
'ERC721Metadata',
|
||||
]);
|
||||
|
||||
describe('metadata', function () {
|
||||
it('has a name', async function () {
|
||||
expect(await this.token.name()).to.be.equal(name);
|
||||
});
|
||||
|
||||
it('has a symbol', async function () {
|
||||
expect(await this.token.symbol()).to.be.equal(symbol);
|
||||
});
|
||||
|
||||
describe('token URI', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(owner, firstTokenId);
|
||||
});
|
||||
|
||||
it('return empty string by default', async function () {
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal('');
|
||||
});
|
||||
|
||||
it('reverts when queried for non existent token id', async function () {
|
||||
await expectRevert(
|
||||
this.token.tokenURI(nonExistentTokenId), 'ERC721Metadata: URI query for nonexistent token',
|
||||
);
|
||||
});
|
||||
|
||||
describe('base URI', function () {
|
||||
beforeEach(function () {
|
||||
if (this.token.setBaseURI === undefined) {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
it('base URI can be set', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
expect(await this.token.baseURI()).to.equal(baseURI);
|
||||
});
|
||||
|
||||
it('base URI is added as a prefix to the token URI', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + firstTokenId.toString());
|
||||
});
|
||||
|
||||
it('token URI can be changed by changing the base URI', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
const newBaseURI = 'https://api.example.com/v2/';
|
||||
await this.token.setBaseURI(newBaseURI);
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + firstTokenId.toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeERC721,
|
||||
shouldBehaveLikeERC721Enumerable,
|
||||
shouldBehaveLikeERC721Metadata,
|
||||
};
|
||||
18
test/token/ERC721/ERC721.test.js
Normal file
18
test/token/ERC721/ERC721.test.js
Normal file
@ -0,0 +1,18 @@
|
||||
const {
|
||||
shouldBehaveLikeERC721,
|
||||
shouldBehaveLikeERC721Metadata,
|
||||
} = require('./ERC721.behavior');
|
||||
|
||||
const ERC721Mock = artifacts.require('ERC721Mock');
|
||||
|
||||
contract('ERC721', function (accounts) {
|
||||
const name = 'Non Fungible Token';
|
||||
const symbol = 'NFT';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC721Mock.new(name, symbol);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC721('ERC721', ...accounts);
|
||||
shouldBehaveLikeERC721Metadata('ERC721', name, symbol, ...accounts);
|
||||
});
|
||||
20
test/token/ERC721/ERC721Enumerable.test.js
Normal file
20
test/token/ERC721/ERC721Enumerable.test.js
Normal file
@ -0,0 +1,20 @@
|
||||
const {
|
||||
shouldBehaveLikeERC721,
|
||||
shouldBehaveLikeERC721Metadata,
|
||||
shouldBehaveLikeERC721Enumerable,
|
||||
} = require('./ERC721.behavior');
|
||||
|
||||
const ERC721Mock = artifacts.require('ERC721EnumerableMock');
|
||||
|
||||
contract('ERC721Enumerable', function (accounts) {
|
||||
const name = 'Non Fungible Token';
|
||||
const symbol = 'NFT';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC721Mock.new(name, symbol);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC721('ERC721', ...accounts);
|
||||
shouldBehaveLikeERC721Metadata('ERC721', name, symbol, ...accounts);
|
||||
shouldBehaveLikeERC721Enumerable('ERC721', ...accounts);
|
||||
});
|
||||
80
test/token/ERC721/extensions/ERC721Burnable.test.js
Normal file
80
test/token/ERC721/extensions/ERC721Burnable.test.js
Normal file
@ -0,0 +1,80 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC721BurnableMock = artifacts.require('ERC721BurnableMock');
|
||||
|
||||
contract('ERC721Burnable', function (accounts) {
|
||||
const [owner, approved] = accounts;
|
||||
|
||||
const firstTokenId = new BN(1);
|
||||
const secondTokenId = new BN(2);
|
||||
const unknownTokenId = new BN(3);
|
||||
|
||||
const name = 'Non Fungible Token';
|
||||
const symbol = 'NFT';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC721BurnableMock.new(name, symbol);
|
||||
});
|
||||
|
||||
describe('like a burnable ERC721', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(owner, firstTokenId);
|
||||
await this.token.mint(owner, secondTokenId);
|
||||
});
|
||||
|
||||
describe('burn', function () {
|
||||
const tokenId = firstTokenId;
|
||||
let logs = null;
|
||||
|
||||
describe('when successful', function () {
|
||||
beforeEach(async function () {
|
||||
const result = await this.token.burn(tokenId, { from: owner });
|
||||
logs = result.logs;
|
||||
});
|
||||
|
||||
it('burns the given token ID and adjusts the balance of the owner', async function () {
|
||||
await expectRevert(
|
||||
this.token.ownerOf(tokenId),
|
||||
'ERC721: owner query for nonexistent token',
|
||||
);
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('emits a burn event', async function () {
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from: owner,
|
||||
to: ZERO_ADDRESS,
|
||||
tokenId: tokenId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is a previous approval burned', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(approved, tokenId, { from: owner });
|
||||
const result = await this.token.burn(tokenId, { from: owner });
|
||||
logs = result.logs;
|
||||
});
|
||||
|
||||
context('getApproved', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.getApproved(tokenId), 'ERC721: approved query for nonexistent token',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given token ID was not tracked by this contract', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.burn(unknownTokenId, { from: owner }), 'ERC721: operator query for nonexistent token',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
98
test/token/ERC721/extensions/ERC721Pausable.test.js
Normal file
98
test/token/ERC721/extensions/ERC721Pausable.test.js
Normal file
@ -0,0 +1,98 @@
|
||||
const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC721PausableMock = artifacts.require('ERC721PausableMock');
|
||||
|
||||
contract('ERC721Pausable', function (accounts) {
|
||||
const [ owner, receiver, operator ] = accounts;
|
||||
|
||||
const name = 'Non Fungible Token';
|
||||
const symbol = 'NFT';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC721PausableMock.new(name, symbol);
|
||||
});
|
||||
|
||||
context('when token is paused', function () {
|
||||
const firstTokenId = new BN(1);
|
||||
const secondTokenId = new BN(1337);
|
||||
|
||||
const mockData = '0x42';
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(owner, firstTokenId, { from: owner });
|
||||
await this.token.pause();
|
||||
});
|
||||
|
||||
it('reverts when trying to transferFrom', async function () {
|
||||
await expectRevert(
|
||||
this.token.transferFrom(owner, receiver, firstTokenId, { from: owner }),
|
||||
'ERC721Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to safeTransferFrom', async function () {
|
||||
await expectRevert(
|
||||
this.token.safeTransferFrom(owner, receiver, firstTokenId, { from: owner }),
|
||||
'ERC721Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to safeTransferFrom with data', async function () {
|
||||
await expectRevert(
|
||||
this.token.methods['safeTransferFrom(address,address,uint256,bytes)'](
|
||||
owner, receiver, firstTokenId, mockData, { from: owner },
|
||||
), 'ERC721Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to mint', async function () {
|
||||
await expectRevert(
|
||||
this.token.mint(receiver, secondTokenId),
|
||||
'ERC721Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when trying to burn', async function () {
|
||||
await expectRevert(
|
||||
this.token.burn(firstTokenId),
|
||||
'ERC721Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
describe('getApproved', function () {
|
||||
it('returns approved address', async function () {
|
||||
const approvedAccount = await this.token.getApproved(firstTokenId);
|
||||
expect(approvedAccount).to.equal(ZERO_ADDRESS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
it('returns the amount of tokens owned by the given address', async function () {
|
||||
const balance = await this.token.balanceOf(owner);
|
||||
expect(balance).to.be.bignumber.equal('1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ownerOf', function () {
|
||||
it('returns the amount of tokens owned by the given address', async function () {
|
||||
const ownerOfToken = await this.token.ownerOf(firstTokenId);
|
||||
expect(ownerOfToken).to.equal(owner);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', function () {
|
||||
it('returns token existence', async function () {
|
||||
expect(await this.token.exists(firstTokenId)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isApprovedForAll', function () {
|
||||
it('returns the approval of the operator', async function () {
|
||||
expect(await this.token.isApprovedForAll(owner, operator)).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
40
test/token/ERC721/extensions/ERC721Royalty.test.js
Normal file
40
test/token/ERC721/extensions/ERC721Royalty.test.js
Normal file
@ -0,0 +1,40 @@
|
||||
const { BN, constants } = require('@openzeppelin/test-helpers');
|
||||
const ERC721RoyaltyMock = artifacts.require('ERC721RoyaltyMock');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { shouldBehaveLikeERC2981 } = require('../../common/ERC2981.behavior');
|
||||
|
||||
contract('ERC721Royalty', function (accounts) {
|
||||
const [ account1, account2 ] = accounts;
|
||||
const tokenId1 = new BN('1');
|
||||
const tokenId2 = new BN('2');
|
||||
const royalty = new BN('200');
|
||||
const salePrice = new BN('1000');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC721RoyaltyMock.new('My Token', 'TKN');
|
||||
|
||||
await this.token.mint(account1, tokenId1);
|
||||
await this.token.mint(account1, tokenId2);
|
||||
this.account1 = account1;
|
||||
this.account2 = account2;
|
||||
this.tokenId1 = tokenId1;
|
||||
this.tokenId2 = tokenId2;
|
||||
this.salePrice = salePrice;
|
||||
});
|
||||
|
||||
describe('token specific functions', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.setTokenRoyalty(tokenId1, account1, royalty);
|
||||
});
|
||||
|
||||
it('removes royalty information after burn', async function () {
|
||||
await this.token.burn(tokenId1);
|
||||
const tokenInfo = await this.token.royaltyInfo(tokenId1, salePrice);
|
||||
|
||||
expect(tokenInfo[0]).to.be.equal(ZERO_ADDRESS);
|
||||
expect(tokenInfo[1]).to.be.bignumber.equal(new BN('0'));
|
||||
});
|
||||
});
|
||||
shouldBehaveLikeERC2981();
|
||||
});
|
||||
96
test/token/ERC721/extensions/ERC721URIStorage.test.js
Normal file
96
test/token/ERC721/extensions/ERC721URIStorage.test.js
Normal file
@ -0,0 +1,96 @@
|
||||
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC721URIStorageMock = artifacts.require('ERC721URIStorageMock');
|
||||
|
||||
contract('ERC721URIStorage', function (accounts) {
|
||||
const [ owner ] = accounts;
|
||||
|
||||
const name = 'Non Fungible Token';
|
||||
const symbol = 'NFT';
|
||||
|
||||
const firstTokenId = new BN('5042');
|
||||
const nonExistentTokenId = new BN('13');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC721URIStorageMock.new(name, symbol);
|
||||
});
|
||||
|
||||
describe('token URI', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.mint(owner, firstTokenId);
|
||||
});
|
||||
|
||||
const baseURI = 'https://api.example.com/v1/';
|
||||
const sampleUri = 'mock://mytoken';
|
||||
|
||||
it('it is empty by default', async function () {
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal('');
|
||||
});
|
||||
|
||||
it('reverts when queried for non existent token id', async function () {
|
||||
await expectRevert(
|
||||
this.token.tokenURI(nonExistentTokenId), 'ERC721URIStorage: URI query for nonexistent token',
|
||||
);
|
||||
});
|
||||
|
||||
it('can be set for a token id', async function () {
|
||||
await this.token.setTokenURI(firstTokenId, sampleUri);
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(sampleUri);
|
||||
});
|
||||
|
||||
it('reverts when setting for non existent token id', async function () {
|
||||
await expectRevert(
|
||||
this.token.setTokenURI(nonExistentTokenId, sampleUri), 'ERC721URIStorage: URI set of nonexistent token',
|
||||
);
|
||||
});
|
||||
|
||||
it('base URI can be set', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
expect(await this.token.baseURI()).to.equal(baseURI);
|
||||
});
|
||||
|
||||
it('base URI is added as a prefix to the token URI', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
await this.token.setTokenURI(firstTokenId, sampleUri);
|
||||
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri);
|
||||
});
|
||||
|
||||
it('token URI can be changed by changing the base URI', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
await this.token.setTokenURI(firstTokenId, sampleUri);
|
||||
|
||||
const newBaseURI = 'https://api.example.com/v2/';
|
||||
await this.token.setBaseURI(newBaseURI);
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + sampleUri);
|
||||
});
|
||||
|
||||
it('tokenId is appended to base URI for tokens with no URI', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + firstTokenId);
|
||||
});
|
||||
|
||||
it('tokens without URI can be burnt ', async function () {
|
||||
await this.token.burn(firstTokenId, { from: owner });
|
||||
|
||||
expect(await this.token.exists(firstTokenId)).to.equal(false);
|
||||
await expectRevert(
|
||||
this.token.tokenURI(firstTokenId), 'ERC721URIStorage: URI query for nonexistent token',
|
||||
);
|
||||
});
|
||||
|
||||
it('tokens with URI can be burnt ', async function () {
|
||||
await this.token.setTokenURI(firstTokenId, sampleUri);
|
||||
|
||||
await this.token.burn(firstTokenId, { from: owner });
|
||||
|
||||
expect(await this.token.exists(firstTokenId)).to.equal(false);
|
||||
await expectRevert(
|
||||
this.token.tokenURI(firstTokenId), 'ERC721URIStorage: URI query for nonexistent token',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
174
test/token/ERC721/extensions/ERC721Votes.test.js
Normal file
174
test/token/ERC721/extensions/ERC721Votes.test.js
Normal file
@ -0,0 +1,174 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const { BN, expectEvent, time } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const { promisify } = require('util');
|
||||
const queue = promisify(setImmediate);
|
||||
|
||||
const ERC721VotesMock = artifacts.require('ERC721VotesMock');
|
||||
|
||||
const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior');
|
||||
|
||||
contract('ERC721Votes', function (accounts) {
|
||||
const [ account1, account2, account1Delegatee, other1, other2 ] = accounts;
|
||||
this.name = 'My Vote';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
beforeEach(async function () {
|
||||
this.votes = await ERC721VotesMock.new(name, symbol);
|
||||
|
||||
// We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id
|
||||
// from within the EVM as from the JSON RPC interface.
|
||||
// See https://github.com/trufflesuite/ganache-core/issues/515
|
||||
this.chainId = await this.votes.getChainId();
|
||||
|
||||
this.NFT0 = new BN('10000000000000000000000000');
|
||||
this.NFT1 = new BN('10');
|
||||
this.NFT2 = new BN('20');
|
||||
this.NFT3 = new BN('30');
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
beforeEach(async function () {
|
||||
await this.votes.mint(account1, this.NFT0);
|
||||
await this.votes.mint(account1, this.NFT1);
|
||||
await this.votes.mint(account1, this.NFT2);
|
||||
await this.votes.mint(account1, this.NFT3);
|
||||
});
|
||||
|
||||
it('grants to initial account', async function () {
|
||||
expect(await this.votes.balanceOf(account1)).to.be.bignumber.equal('4');
|
||||
});
|
||||
});
|
||||
|
||||
describe('transfers', function () {
|
||||
beforeEach(async function () {
|
||||
await this.votes.mint(account1, this.NFT0);
|
||||
});
|
||||
|
||||
it('no delegation', async function () {
|
||||
const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 });
|
||||
expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.NFT0 });
|
||||
expectEvent.notEmitted(receipt, 'DelegateVotesChanged');
|
||||
|
||||
this.account1Votes = '0';
|
||||
this.account2Votes = '0';
|
||||
});
|
||||
|
||||
it('sender delegation', async function () {
|
||||
await this.votes.delegate(account1, { from: account1 });
|
||||
|
||||
const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 });
|
||||
expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.NFT0 });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousBalance: '1', newBalance: '0' });
|
||||
|
||||
const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
|
||||
expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true);
|
||||
|
||||
this.account1Votes = '0';
|
||||
this.account2Votes = '0';
|
||||
});
|
||||
|
||||
it('receiver delegation', async function () {
|
||||
await this.votes.delegate(account2, { from: account2 });
|
||||
|
||||
const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 });
|
||||
expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.NFT0 });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousBalance: '0', newBalance: '1' });
|
||||
|
||||
const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
|
||||
expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true);
|
||||
|
||||
this.account1Votes = '0';
|
||||
this.account2Votes = '1';
|
||||
});
|
||||
|
||||
it('full delegation', async function () {
|
||||
await this.votes.delegate(account1, { from: account1 });
|
||||
await this.votes.delegate(account2, { from: account2 });
|
||||
|
||||
const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 });
|
||||
expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.NFT0 });
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousBalance: '1', newBalance: '0'});
|
||||
expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousBalance: '0', newBalance: '1' });
|
||||
|
||||
const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer');
|
||||
expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true);
|
||||
|
||||
this.account1Votes = '0';
|
||||
this.account2Votes = '1';
|
||||
});
|
||||
|
||||
it('returns the same total supply on transfers', async function () {
|
||||
await this.votes.delegate(account1, { from: account1 });
|
||||
|
||||
const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 });
|
||||
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.votes.getPastTotalSupply(receipt.blockNumber - 1)).to.be.bignumber.equal('1');
|
||||
expect(await this.votes.getPastTotalSupply(receipt.blockNumber + 1)).to.be.bignumber.equal('1');
|
||||
|
||||
this.account1Votes = '0';
|
||||
this.account2Votes = '0';
|
||||
});
|
||||
|
||||
it('generally returns the voting balance at the appropriate checkpoint', async function () {
|
||||
await this.votes.mint(account1, this.NFT1);
|
||||
await this.votes.mint(account1, this.NFT2);
|
||||
await this.votes.mint(account1, this.NFT3);
|
||||
|
||||
const total = await this.votes.balanceOf(account1);
|
||||
|
||||
const t1 = await this.votes.delegate(other1, { from: account1 });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t2 = await this.votes.transferFrom(account1, other2, this.NFT0, { from: account1 });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t3 = await this.votes.transferFrom(account1, other2, this.NFT2, { from: account1 });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
const t4 = await this.votes.transferFrom(other2, account1, this.NFT2, { from: other2 });
|
||||
await time.advanceBlock();
|
||||
await time.advanceBlock();
|
||||
|
||||
expect(await this.votes.getPastVotes(other1, t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
|
||||
expect(await this.votes.getPastVotes(other1, t1.receipt.blockNumber)).to.be.bignumber.equal(total);
|
||||
expect(await this.votes.getPastVotes(other1, t1.receipt.blockNumber + 1)).to.be.bignumber.equal(total);
|
||||
expect(await this.votes.getPastVotes(other1, t2.receipt.blockNumber)).to.be.bignumber.equal('3');
|
||||
expect(await this.votes.getPastVotes(other1, t2.receipt.blockNumber + 1)).to.be.bignumber.equal('3');
|
||||
expect(await this.votes.getPastVotes(other1, t3.receipt.blockNumber)).to.be.bignumber.equal('2');
|
||||
expect(await this.votes.getPastVotes(other1, t3.receipt.blockNumber + 1)).to.be.bignumber.equal('2');
|
||||
expect(await this.votes.getPastVotes(other1, t4.receipt.blockNumber)).to.be.bignumber.equal('3');
|
||||
expect(await this.votes.getPastVotes(other1, t4.receipt.blockNumber + 1)).to.be.bignumber.equal('3');
|
||||
|
||||
this.account1Votes = '0';
|
||||
this.account2Votes = '0';
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
expect(await this.votes.getVotes(account1)).to.be.bignumber.equal(this.account1Votes);
|
||||
expect(await this.votes.getVotes(account2)).to.be.bignumber.equal(this.account2Votes);
|
||||
|
||||
// need to advance 2 blocks to see the effect of a transfer on "getPastVotes"
|
||||
const blockNumber = await time.latestBlock();
|
||||
await time.advanceBlock();
|
||||
expect(await this.votes.getPastVotes(account1, blockNumber)).to.be.bignumber.equal(this.account1Votes);
|
||||
expect(await this.votes.getPastVotes(account2, blockNumber)).to.be.bignumber.equal(this.account2Votes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Voting workflow', function () {
|
||||
beforeEach(async function () {
|
||||
this.account1 = account1;
|
||||
this.account1Delegatee = account1Delegatee;
|
||||
this.account2 = account2;
|
||||
this.name = 'My Vote';
|
||||
});
|
||||
|
||||
shouldBehaveLikeVotes();
|
||||
});
|
||||
});
|
||||
125
test/token/ERC721/presets/ERC721PresetMinterPauserAutoId.test.js
Normal file
125
test/token/ERC721/presets/ERC721PresetMinterPauserAutoId.test.js
Normal file
@ -0,0 +1,125 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC721PresetMinterPauserAutoId = artifacts.require('ERC721PresetMinterPauserAutoId');
|
||||
|
||||
contract('ERC721PresetMinterPauserAutoId', function (accounts) {
|
||||
const [ deployer, other ] = accounts;
|
||||
|
||||
const name = 'MinterAutoIDToken';
|
||||
const symbol = 'MAIT';
|
||||
const baseURI = 'my.app/';
|
||||
|
||||
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC721PresetMinterPauserAutoId.new(name, symbol, baseURI, { from: deployer });
|
||||
});
|
||||
|
||||
shouldSupportInterfaces(['ERC721', 'ERC721Enumerable', 'AccessControl', 'AccessControlEnumerable']);
|
||||
|
||||
it('token has correct name', async function () {
|
||||
expect(await this.token.name()).to.equal(name);
|
||||
});
|
||||
|
||||
it('token has correct symbol', async function () {
|
||||
expect(await this.token.symbol()).to.equal(symbol);
|
||||
});
|
||||
|
||||
it('deployer has the default admin role', async function () {
|
||||
expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer);
|
||||
});
|
||||
|
||||
it('deployer has the minter role', async function () {
|
||||
expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer);
|
||||
});
|
||||
|
||||
it('minter role admin is the default admin', async function () {
|
||||
expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||
});
|
||||
|
||||
describe('minting', function () {
|
||||
it('deployer can mint tokens', async function () {
|
||||
const tokenId = new BN('0');
|
||||
|
||||
const receipt = await this.token.mint(other, { from: deployer });
|
||||
expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, tokenId });
|
||||
|
||||
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1');
|
||||
expect(await this.token.ownerOf(tokenId)).to.equal(other);
|
||||
|
||||
expect(await this.token.tokenURI(tokenId)).to.equal(baseURI + tokenId);
|
||||
});
|
||||
|
||||
it('other accounts cannot mint tokens', async function () {
|
||||
await expectRevert(
|
||||
this.token.mint(other, { from: other }),
|
||||
'ERC721PresetMinterPauserAutoId: must have minter role to mint',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pausing', function () {
|
||||
it('deployer can pause', async function () {
|
||||
const receipt = await this.token.pause({ from: deployer });
|
||||
expectEvent(receipt, 'Paused', { account: deployer });
|
||||
|
||||
expect(await this.token.paused()).to.equal(true);
|
||||
});
|
||||
|
||||
it('deployer can unpause', async function () {
|
||||
await this.token.pause({ from: deployer });
|
||||
|
||||
const receipt = await this.token.unpause({ from: deployer });
|
||||
expectEvent(receipt, 'Unpaused', { account: deployer });
|
||||
|
||||
expect(await this.token.paused()).to.equal(false);
|
||||
});
|
||||
|
||||
it('cannot mint while paused', async function () {
|
||||
await this.token.pause({ from: deployer });
|
||||
|
||||
await expectRevert(
|
||||
this.token.mint(other, { from: deployer }),
|
||||
'ERC721Pausable: token transfer while paused',
|
||||
);
|
||||
});
|
||||
|
||||
it('other accounts cannot pause', async function () {
|
||||
await expectRevert(
|
||||
this.token.pause({ from: other }),
|
||||
'ERC721PresetMinterPauserAutoId: must have pauser role to pause',
|
||||
);
|
||||
});
|
||||
|
||||
it('other accounts cannot unpause', async function () {
|
||||
await this.token.pause({ from: deployer });
|
||||
|
||||
await expectRevert(
|
||||
this.token.unpause({ from: other }),
|
||||
'ERC721PresetMinterPauserAutoId: must have pauser role to unpause',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('burning', function () {
|
||||
it('holders can burn their tokens', async function () {
|
||||
const tokenId = new BN('0');
|
||||
|
||||
await this.token.mint(other, { from: deployer });
|
||||
|
||||
const receipt = await this.token.burn(tokenId, { from: other });
|
||||
|
||||
expectEvent(receipt, 'Transfer', { from: other, to: ZERO_ADDRESS, tokenId });
|
||||
|
||||
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
24
test/token/ERC721/utils/ERC721Holder.test.js
Normal file
24
test/token/ERC721/utils/ERC721Holder.test.js
Normal file
@ -0,0 +1,24 @@
|
||||
const { BN } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC721Holder = artifacts.require('ERC721Holder');
|
||||
const ERC721Mock = artifacts.require('ERC721Mock');
|
||||
|
||||
contract('ERC721Holder', function (accounts) {
|
||||
const [ owner ] = accounts;
|
||||
|
||||
const name = 'Non Fungible Token';
|
||||
const symbol = 'NFT';
|
||||
|
||||
it('receives an ERC721 token', async function () {
|
||||
const token = await ERC721Mock.new(name, symbol);
|
||||
const tokenId = new BN(1);
|
||||
await token.mint(owner, tokenId);
|
||||
|
||||
const receiver = await ERC721Holder.new();
|
||||
await token.safeTransferFrom(owner, receiver.address, tokenId, { from: owner });
|
||||
|
||||
expect(await token.ownerOf(tokenId)).to.be.equal(receiver.address);
|
||||
});
|
||||
});
|
||||
555
test/token/ERC777/ERC777.behavior.js
Normal file
555
test/token/ERC777/ERC777.behavior.js
Normal file
@ -0,0 +1,555 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC777SenderRecipientMock = artifacts.require('ERC777SenderRecipientMock');
|
||||
|
||||
function shouldBehaveLikeERC777DirectSendBurn (holder, recipient, data) {
|
||||
shouldBehaveLikeERC777DirectSend(holder, recipient, data);
|
||||
shouldBehaveLikeERC777DirectBurn(holder, data);
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777OperatorSendBurn (holder, recipient, operator, data, operatorData) {
|
||||
shouldBehaveLikeERC777OperatorSend(holder, recipient, operator, data, operatorData);
|
||||
shouldBehaveLikeERC777OperatorBurn(holder, operator, data, operatorData);
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777UnauthorizedOperatorSendBurn (holder, recipient, operator, data, operatorData) {
|
||||
shouldBehaveLikeERC777UnauthorizedOperatorSend(holder, recipient, operator, data, operatorData);
|
||||
shouldBehaveLikeERC777UnauthorizedOperatorBurn(holder, operator, data, operatorData);
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777DirectSend (holder, recipient, data) {
|
||||
describe('direct send', function () {
|
||||
context('when the sender has tokens', function () {
|
||||
shouldDirectSendTokens(holder, recipient, new BN('0'), data);
|
||||
shouldDirectSendTokens(holder, recipient, new BN('1'), data);
|
||||
|
||||
it('reverts when sending more than the balance', async function () {
|
||||
const balance = await this.token.balanceOf(holder);
|
||||
await expectRevert.unspecified(this.token.send(recipient, balance.addn(1), data, { from: holder }));
|
||||
});
|
||||
|
||||
it('reverts when sending to the zero address', async function () {
|
||||
await expectRevert.unspecified(this.token.send(ZERO_ADDRESS, new BN('1'), data, { from: holder }));
|
||||
});
|
||||
});
|
||||
|
||||
context('when the sender has no tokens', function () {
|
||||
removeBalance(holder);
|
||||
|
||||
shouldDirectSendTokens(holder, recipient, new BN('0'), data);
|
||||
|
||||
it('reverts when sending a non-zero amount', async function () {
|
||||
await expectRevert.unspecified(this.token.send(recipient, new BN('1'), data, { from: holder }));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777OperatorSend (holder, recipient, operator, data, operatorData) {
|
||||
describe('operator send', function () {
|
||||
context('when the sender has tokens', async function () {
|
||||
shouldOperatorSendTokens(holder, operator, recipient, new BN('0'), data, operatorData);
|
||||
shouldOperatorSendTokens(holder, operator, recipient, new BN('1'), data, operatorData);
|
||||
|
||||
it('reverts when sending more than the balance', async function () {
|
||||
const balance = await this.token.balanceOf(holder);
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorSend(holder, recipient, balance.addn(1), data, operatorData, { from: operator }),
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when sending to the zero address', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorSend(
|
||||
holder, ZERO_ADDRESS, new BN('1'), data, operatorData, { from: operator },
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the sender has no tokens', function () {
|
||||
removeBalance(holder);
|
||||
|
||||
shouldOperatorSendTokens(holder, operator, recipient, new BN('0'), data, operatorData);
|
||||
|
||||
it('reverts when sending a non-zero amount', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorSend(holder, recipient, new BN('1'), data, operatorData, { from: operator }),
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when sending from the zero address', async function () {
|
||||
// This is not yet reflected in the spec
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorSend(
|
||||
ZERO_ADDRESS, recipient, new BN('0'), data, operatorData, { from: operator },
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777UnauthorizedOperatorSend (holder, recipient, operator, data, operatorData) {
|
||||
describe('operator send', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(this.token.operatorSend(holder, recipient, new BN('0'), data, operatorData));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777DirectBurn (holder, data) {
|
||||
describe('direct burn', function () {
|
||||
context('when the sender has tokens', function () {
|
||||
shouldDirectBurnTokens(holder, new BN('0'), data);
|
||||
shouldDirectBurnTokens(holder, new BN('1'), data);
|
||||
|
||||
it('reverts when burning more than the balance', async function () {
|
||||
const balance = await this.token.balanceOf(holder);
|
||||
await expectRevert.unspecified(this.token.burn(balance.addn(1), data, { from: holder }));
|
||||
});
|
||||
});
|
||||
|
||||
context('when the sender has no tokens', function () {
|
||||
removeBalance(holder);
|
||||
|
||||
shouldDirectBurnTokens(holder, new BN('0'), data);
|
||||
|
||||
it('reverts when burning a non-zero amount', async function () {
|
||||
await expectRevert.unspecified(this.token.burn(new BN('1'), data, { from: holder }));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777OperatorBurn (holder, operator, data, operatorData) {
|
||||
describe('operator burn', function () {
|
||||
context('when the sender has tokens', async function () {
|
||||
shouldOperatorBurnTokens(holder, operator, new BN('0'), data, operatorData);
|
||||
shouldOperatorBurnTokens(holder, operator, new BN('1'), data, operatorData);
|
||||
|
||||
it('reverts when burning more than the balance', async function () {
|
||||
const balance = await this.token.balanceOf(holder);
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorBurn(holder, balance.addn(1), data, operatorData, { from: operator }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when the sender has no tokens', function () {
|
||||
removeBalance(holder);
|
||||
|
||||
shouldOperatorBurnTokens(holder, operator, new BN('0'), data, operatorData);
|
||||
|
||||
it('reverts when burning a non-zero amount', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorBurn(holder, new BN('1'), data, operatorData, { from: operator }),
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when burning from the zero address', async function () {
|
||||
// This is not yet reflected in the spec
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorBurn(
|
||||
ZERO_ADDRESS, new BN('0'), data, operatorData, { from: operator },
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777UnauthorizedOperatorBurn (holder, operator, data, operatorData) {
|
||||
describe('operator burn', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(this.token.operatorBurn(holder, new BN('0'), data, operatorData));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldDirectSendTokens (from, to, amount, data) {
|
||||
shouldSendTokens(from, null, to, amount, data, null);
|
||||
}
|
||||
|
||||
function shouldOperatorSendTokens (from, operator, to, amount, data, operatorData) {
|
||||
shouldSendTokens(from, operator, to, amount, data, operatorData);
|
||||
}
|
||||
|
||||
function shouldSendTokens (from, operator, to, amount, data, operatorData) {
|
||||
const operatorCall = operator !== null;
|
||||
|
||||
it(`${operatorCall ? 'operator ' : ''}can send an amount of ${amount}`, async function () {
|
||||
const initialTotalSupply = await this.token.totalSupply();
|
||||
const initialFromBalance = await this.token.balanceOf(from);
|
||||
const initialToBalance = await this.token.balanceOf(to);
|
||||
|
||||
let logs;
|
||||
if (!operatorCall) {
|
||||
({ logs } = await this.token.send(to, amount, data, { from }));
|
||||
expectEvent.inLogs(logs, 'Sent', {
|
||||
operator: from,
|
||||
from,
|
||||
to,
|
||||
amount,
|
||||
data,
|
||||
operatorData: null,
|
||||
});
|
||||
} else {
|
||||
({ logs } = await this.token.operatorSend(from, to, amount, data, operatorData, { from: operator }));
|
||||
expectEvent.inLogs(logs, 'Sent', {
|
||||
operator,
|
||||
from,
|
||||
to,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
});
|
||||
}
|
||||
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from,
|
||||
to,
|
||||
value: amount,
|
||||
});
|
||||
|
||||
const finalTotalSupply = await this.token.totalSupply();
|
||||
const finalFromBalance = await this.token.balanceOf(from);
|
||||
const finalToBalance = await this.token.balanceOf(to);
|
||||
|
||||
expect(finalTotalSupply).to.be.bignumber.equal(initialTotalSupply);
|
||||
expect(finalToBalance.sub(initialToBalance)).to.be.bignumber.equal(amount);
|
||||
expect(finalFromBalance.sub(initialFromBalance)).to.be.bignumber.equal(amount.neg());
|
||||
});
|
||||
}
|
||||
|
||||
function shouldDirectBurnTokens (from, amount, data) {
|
||||
shouldBurnTokens(from, null, amount, data, null);
|
||||
}
|
||||
|
||||
function shouldOperatorBurnTokens (from, operator, amount, data, operatorData) {
|
||||
shouldBurnTokens(from, operator, amount, data, operatorData);
|
||||
}
|
||||
|
||||
function shouldBurnTokens (from, operator, amount, data, operatorData) {
|
||||
const operatorCall = operator !== null;
|
||||
|
||||
it(`${operatorCall ? 'operator ' : ''}can burn an amount of ${amount}`, async function () {
|
||||
const initialTotalSupply = await this.token.totalSupply();
|
||||
const initialFromBalance = await this.token.balanceOf(from);
|
||||
|
||||
let logs;
|
||||
if (!operatorCall) {
|
||||
({ logs } = await this.token.burn(amount, data, { from }));
|
||||
expectEvent.inLogs(logs, 'Burned', {
|
||||
operator: from,
|
||||
from,
|
||||
amount,
|
||||
data,
|
||||
operatorData: null,
|
||||
});
|
||||
} else {
|
||||
({ logs } = await this.token.operatorBurn(from, amount, data, operatorData, { from: operator }));
|
||||
expectEvent.inLogs(logs, 'Burned', {
|
||||
operator,
|
||||
from,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
});
|
||||
}
|
||||
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from,
|
||||
to: ZERO_ADDRESS,
|
||||
value: amount,
|
||||
});
|
||||
|
||||
const finalTotalSupply = await this.token.totalSupply();
|
||||
const finalFromBalance = await this.token.balanceOf(from);
|
||||
|
||||
expect(finalTotalSupply.sub(initialTotalSupply)).to.be.bignumber.equal(amount.neg());
|
||||
expect(finalFromBalance.sub(initialFromBalance)).to.be.bignumber.equal(amount.neg());
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777InternalMint (recipient, operator, amount, data, operatorData) {
|
||||
shouldInternalMintTokens(operator, recipient, new BN('0'), data, operatorData);
|
||||
shouldInternalMintTokens(operator, recipient, amount, data, operatorData);
|
||||
|
||||
it('reverts when minting tokens for the zero address', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.token.mintInternal(ZERO_ADDRESS, amount, data, operatorData, { from: operator }),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function shouldInternalMintTokens (operator, to, amount, data, operatorData) {
|
||||
it(`can (internal) mint an amount of ${amount}`, async function () {
|
||||
const initialTotalSupply = await this.token.totalSupply();
|
||||
const initialToBalance = await this.token.balanceOf(to);
|
||||
|
||||
const { logs } = await this.token.mintInternal(to, amount, data, operatorData, { from: operator });
|
||||
|
||||
expectEvent.inLogs(logs, 'Minted', {
|
||||
operator,
|
||||
to,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
});
|
||||
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to,
|
||||
value: amount,
|
||||
});
|
||||
|
||||
const finalTotalSupply = await this.token.totalSupply();
|
||||
const finalToBalance = await this.token.balanceOf(to);
|
||||
|
||||
expect(finalTotalSupply.sub(initialTotalSupply)).to.be.bignumber.equal(amount);
|
||||
expect(finalToBalance.sub(initialToBalance)).to.be.bignumber.equal(amount);
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook (operator, amount, data, operatorData) {
|
||||
context('when TokensRecipient reverts', function () {
|
||||
beforeEach(async function () {
|
||||
await this.tokensRecipientImplementer.setShouldRevertReceive(true);
|
||||
});
|
||||
|
||||
it('send reverts', async function () {
|
||||
await expectRevert.unspecified(sendFromHolder(this.token, this.sender, this.recipient, amount, data));
|
||||
});
|
||||
|
||||
it('operatorSend reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator }),
|
||||
);
|
||||
});
|
||||
|
||||
it('mint (internal) reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.token.mintInternal(this.recipient, amount, data, operatorData, { from: operator }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when TokensRecipient does not revert', function () {
|
||||
beforeEach(async function () {
|
||||
await this.tokensRecipientImplementer.setShouldRevertSend(false);
|
||||
});
|
||||
|
||||
it('TokensRecipient receives send data and is called after state mutation', async function () {
|
||||
const { tx } = await sendFromHolder(this.token, this.sender, this.recipient, amount, data);
|
||||
|
||||
const postSenderBalance = await this.token.balanceOf(this.sender);
|
||||
const postRecipientBalance = await this.token.balanceOf(this.recipient);
|
||||
|
||||
await assertTokensReceivedCalled(
|
||||
this.token,
|
||||
tx,
|
||||
this.sender,
|
||||
this.sender,
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
null,
|
||||
postSenderBalance,
|
||||
postRecipientBalance,
|
||||
);
|
||||
});
|
||||
|
||||
it('TokensRecipient receives operatorSend data and is called after state mutation', async function () {
|
||||
const { tx } = await this.token.operatorSend(
|
||||
this.sender, this.recipient, amount, data, operatorData,
|
||||
{ from: operator },
|
||||
);
|
||||
|
||||
const postSenderBalance = await this.token.balanceOf(this.sender);
|
||||
const postRecipientBalance = await this.token.balanceOf(this.recipient);
|
||||
|
||||
await assertTokensReceivedCalled(
|
||||
this.token,
|
||||
tx,
|
||||
operator,
|
||||
this.sender,
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
postSenderBalance,
|
||||
postRecipientBalance,
|
||||
);
|
||||
});
|
||||
|
||||
it('TokensRecipient receives mint (internal) data and is called after state mutation', async function () {
|
||||
const { tx } = await this.token.mintInternal(
|
||||
this.recipient, amount, data, operatorData, { from: operator },
|
||||
);
|
||||
|
||||
const postRecipientBalance = await this.token.balanceOf(this.recipient);
|
||||
|
||||
await assertTokensReceivedCalled(
|
||||
this.token,
|
||||
tx,
|
||||
operator,
|
||||
ZERO_ADDRESS,
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
new BN('0'),
|
||||
postRecipientBalance,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC777SendBurnWithSendHook (operator, amount, data, operatorData) {
|
||||
context('when TokensSender reverts', function () {
|
||||
beforeEach(async function () {
|
||||
await this.tokensSenderImplementer.setShouldRevertSend(true);
|
||||
});
|
||||
|
||||
it('send reverts', async function () {
|
||||
await expectRevert.unspecified(sendFromHolder(this.token, this.sender, this.recipient, amount, data));
|
||||
});
|
||||
|
||||
it('operatorSend reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator }),
|
||||
);
|
||||
});
|
||||
|
||||
it('burn reverts', async function () {
|
||||
await expectRevert.unspecified(burnFromHolder(this.token, this.sender, amount, data));
|
||||
});
|
||||
|
||||
it('operatorBurn reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.token.operatorBurn(this.sender, amount, data, operatorData, { from: operator }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('when TokensSender does not revert', function () {
|
||||
beforeEach(async function () {
|
||||
await this.tokensSenderImplementer.setShouldRevertSend(false);
|
||||
});
|
||||
|
||||
it('TokensSender receives send data and is called before state mutation', async function () {
|
||||
const preSenderBalance = await this.token.balanceOf(this.sender);
|
||||
const preRecipientBalance = await this.token.balanceOf(this.recipient);
|
||||
|
||||
const { tx } = await sendFromHolder(this.token, this.sender, this.recipient, amount, data);
|
||||
|
||||
await assertTokensToSendCalled(
|
||||
this.token,
|
||||
tx,
|
||||
this.sender,
|
||||
this.sender,
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
null,
|
||||
preSenderBalance,
|
||||
preRecipientBalance,
|
||||
);
|
||||
});
|
||||
|
||||
it('TokensSender receives operatorSend data and is called before state mutation', async function () {
|
||||
const preSenderBalance = await this.token.balanceOf(this.sender);
|
||||
const preRecipientBalance = await this.token.balanceOf(this.recipient);
|
||||
|
||||
const { tx } = await this.token.operatorSend(
|
||||
this.sender, this.recipient, amount, data, operatorData,
|
||||
{ from: operator },
|
||||
);
|
||||
|
||||
await assertTokensToSendCalled(
|
||||
this.token,
|
||||
tx,
|
||||
operator,
|
||||
this.sender,
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
preSenderBalance,
|
||||
preRecipientBalance,
|
||||
);
|
||||
});
|
||||
|
||||
it('TokensSender receives burn data and is called before state mutation', async function () {
|
||||
const preSenderBalance = await this.token.balanceOf(this.sender);
|
||||
|
||||
const { tx } = await burnFromHolder(this.token, this.sender, amount, data, { from: this.sender });
|
||||
|
||||
await assertTokensToSendCalled(
|
||||
this.token, tx, this.sender, this.sender, ZERO_ADDRESS, amount, data, null, preSenderBalance,
|
||||
);
|
||||
});
|
||||
|
||||
it('TokensSender receives operatorBurn data and is called before state mutation', async function () {
|
||||
const preSenderBalance = await this.token.balanceOf(this.sender);
|
||||
|
||||
const { tx } = await this.token.operatorBurn(this.sender, amount, data, operatorData, { from: operator });
|
||||
|
||||
await assertTokensToSendCalled(
|
||||
this.token, tx, operator, this.sender, ZERO_ADDRESS, amount, data, operatorData, preSenderBalance,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function removeBalance (holder) {
|
||||
beforeEach(async function () {
|
||||
await this.token.burn(await this.token.balanceOf(holder), '0x', { from: holder });
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0');
|
||||
});
|
||||
}
|
||||
|
||||
async function assertTokensReceivedCalled (token, txHash, operator, from, to, amount, data, operatorData, fromBalance,
|
||||
toBalance = '0') {
|
||||
await expectEvent.inTransaction(txHash, ERC777SenderRecipientMock, 'TokensReceivedCalled', {
|
||||
operator, from, to, amount, data, operatorData, token: token.address, fromBalance, toBalance,
|
||||
});
|
||||
}
|
||||
|
||||
async function assertTokensToSendCalled (token, txHash, operator, from, to, amount, data, operatorData, fromBalance,
|
||||
toBalance = '0') {
|
||||
await expectEvent.inTransaction(txHash, ERC777SenderRecipientMock, 'TokensToSendCalled', {
|
||||
operator, from, to, amount, data, operatorData, token: token.address, fromBalance, toBalance,
|
||||
});
|
||||
}
|
||||
|
||||
async function sendFromHolder (token, holder, to, amount, data) {
|
||||
if ((await web3.eth.getCode(holder)).length <= '0x'.length) {
|
||||
return token.send(to, amount, data, { from: holder });
|
||||
} else {
|
||||
// assume holder is ERC777SenderRecipientMock contract
|
||||
return (await ERC777SenderRecipientMock.at(holder)).send(token.address, to, amount, data);
|
||||
}
|
||||
}
|
||||
|
||||
async function burnFromHolder (token, holder, amount, data) {
|
||||
if ((await web3.eth.getCode(holder)).length <= '0x'.length) {
|
||||
return token.burn(amount, data, { from: holder });
|
||||
} else {
|
||||
// assume holder is ERC777SenderRecipientMock contract
|
||||
return (await ERC777SenderRecipientMock.at(holder)).burn(token.address, amount, data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeERC777DirectSendBurn,
|
||||
shouldBehaveLikeERC777OperatorSendBurn,
|
||||
shouldBehaveLikeERC777UnauthorizedOperatorSendBurn,
|
||||
shouldBehaveLikeERC777InternalMint,
|
||||
shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook,
|
||||
shouldBehaveLikeERC777SendBurnWithSendHook,
|
||||
};
|
||||
610
test/token/ERC777/ERC777.test.js
Normal file
610
test/token/ERC777/ERC777.test.js
Normal file
@ -0,0 +1,610 @@
|
||||
const { BN, constants, expectEvent, expectRevert, singletons } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const {
|
||||
shouldBehaveLikeERC777DirectSendBurn,
|
||||
shouldBehaveLikeERC777OperatorSendBurn,
|
||||
shouldBehaveLikeERC777UnauthorizedOperatorSendBurn,
|
||||
shouldBehaveLikeERC777InternalMint,
|
||||
shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook,
|
||||
shouldBehaveLikeERC777SendBurnWithSendHook,
|
||||
} = require('./ERC777.behavior');
|
||||
|
||||
const {
|
||||
shouldBehaveLikeERC20,
|
||||
shouldBehaveLikeERC20Approve,
|
||||
} = require('../ERC20/ERC20.behavior');
|
||||
|
||||
const ERC777 = artifacts.require('ERC777Mock');
|
||||
const ERC777SenderRecipientMock = artifacts.require('ERC777SenderRecipientMock');
|
||||
|
||||
contract('ERC777', function (accounts) {
|
||||
const [ registryFunder, holder, defaultOperatorA, defaultOperatorB, newOperator, anyone ] = accounts;
|
||||
|
||||
const initialSupply = new BN('10000');
|
||||
const name = 'ERC777Test';
|
||||
const symbol = '777T';
|
||||
const data = web3.utils.sha3('OZ777TestData');
|
||||
const operatorData = web3.utils.sha3('OZ777TestOperatorData');
|
||||
|
||||
const defaultOperators = [defaultOperatorA, defaultOperatorB];
|
||||
|
||||
beforeEach(async function () {
|
||||
this.erc1820 = await singletons.ERC1820Registry(registryFunder);
|
||||
});
|
||||
|
||||
context('with default operators', function () {
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC777.new(holder, initialSupply, name, symbol, defaultOperators);
|
||||
});
|
||||
|
||||
describe('as an ERC20 token', function () {
|
||||
shouldBehaveLikeERC20('ERC777', initialSupply, holder, anyone, defaultOperatorA);
|
||||
|
||||
describe('_approve', function () {
|
||||
shouldBehaveLikeERC20Approve('ERC777', holder, anyone, initialSupply, function (owner, spender, amount) {
|
||||
return this.token.approveInternal(owner, spender, amount);
|
||||
});
|
||||
|
||||
describe('when the owner is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert(this.token.approveInternal(ZERO_ADDRESS, anyone, initialSupply),
|
||||
'ERC777: approve from the zero address',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not emit AuthorizedOperator events for default operators', async function () {
|
||||
await expectEvent.notEmitted.inConstruction(this.token, 'AuthorizedOperator');
|
||||
});
|
||||
|
||||
describe('basic information', function () {
|
||||
it('returns the name', async function () {
|
||||
expect(await this.token.name()).to.equal(name);
|
||||
});
|
||||
|
||||
it('returns the symbol', async function () {
|
||||
expect(await this.token.symbol()).to.equal(symbol);
|
||||
});
|
||||
|
||||
it('returns a granularity of 1', async function () {
|
||||
expect(await this.token.granularity()).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('returns the default operators', async function () {
|
||||
expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators);
|
||||
});
|
||||
|
||||
it('default operators are operators for all accounts', async function () {
|
||||
for (const operator of defaultOperators) {
|
||||
expect(await this.token.isOperatorFor(operator, anyone)).to.equal(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns the total supply', async function () {
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
|
||||
it('returns 18 when decimals is called', async function () {
|
||||
expect(await this.token.decimals()).to.be.bignumber.equal('18');
|
||||
});
|
||||
|
||||
it('the ERC777Token interface is registered in the registry', async function () {
|
||||
expect(await this.erc1820.getInterfaceImplementer(this.token.address, web3.utils.soliditySha3('ERC777Token')))
|
||||
.to.equal(this.token.address);
|
||||
});
|
||||
|
||||
it('the ERC20Token interface is registered in the registry', async function () {
|
||||
expect(await this.erc1820.getInterfaceImplementer(this.token.address, web3.utils.soliditySha3('ERC20Token')))
|
||||
.to.equal(this.token.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
context('for an account with no tokens', function () {
|
||||
it('returns zero', async function () {
|
||||
expect(await this.token.balanceOf(anyone)).to.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('for an account with tokens', function () {
|
||||
it('returns their balance', async function () {
|
||||
expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('with no ERC777TokensSender and no ERC777TokensRecipient implementers', function () {
|
||||
describe('send/burn', function () {
|
||||
shouldBehaveLikeERC777DirectSendBurn(holder, anyone, data);
|
||||
|
||||
context('with self operator', function () {
|
||||
shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, holder, data, operatorData);
|
||||
});
|
||||
|
||||
context('with first default operator', function () {
|
||||
shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, defaultOperatorA, data, operatorData);
|
||||
});
|
||||
|
||||
context('with second default operator', function () {
|
||||
shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, defaultOperatorB, data, operatorData);
|
||||
});
|
||||
|
||||
context('before authorizing a new operator', function () {
|
||||
shouldBehaveLikeERC777UnauthorizedOperatorSendBurn(holder, anyone, newOperator, data, operatorData);
|
||||
});
|
||||
|
||||
context('with new authorized operator', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.authorizeOperator(newOperator, { from: holder });
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, newOperator, data, operatorData);
|
||||
|
||||
context('with revoked operator', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.revokeOperator(newOperator, { from: holder });
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC777UnauthorizedOperatorSendBurn(holder, anyone, newOperator, data, operatorData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mint (internal)', function () {
|
||||
const to = anyone;
|
||||
const amount = new BN('5');
|
||||
|
||||
context('with default operator', function () {
|
||||
const operator = defaultOperatorA;
|
||||
|
||||
shouldBehaveLikeERC777InternalMint(to, operator, amount, data, operatorData);
|
||||
});
|
||||
|
||||
context('with non operator', function () {
|
||||
const operator = newOperator;
|
||||
|
||||
shouldBehaveLikeERC777InternalMint(to, operator, amount, data, operatorData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mint (internal extended)', function () {
|
||||
const amount = new BN('5');
|
||||
|
||||
context('to anyone', function () {
|
||||
beforeEach(async function () {
|
||||
this.recipient = anyone;
|
||||
});
|
||||
|
||||
context('with default operator', function () {
|
||||
const operator = defaultOperatorA;
|
||||
|
||||
it('without requireReceptionAck', async function () {
|
||||
await this.token.mintInternalExtended(
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
false,
|
||||
{ from: operator },
|
||||
);
|
||||
});
|
||||
|
||||
it('with requireReceptionAck', async function () {
|
||||
await this.token.mintInternalExtended(
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
true,
|
||||
{ from: operator },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with non operator', function () {
|
||||
const operator = newOperator;
|
||||
|
||||
it('without requireReceptionAck', async function () {
|
||||
await this.token.mintInternalExtended(
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
false,
|
||||
{ from: operator },
|
||||
);
|
||||
});
|
||||
|
||||
it('with requireReceptionAck', async function () {
|
||||
await this.token.mintInternalExtended(
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
true,
|
||||
{ from: operator },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('to non ERC777TokensRecipient implementer', function () {
|
||||
beforeEach(async function () {
|
||||
this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
|
||||
this.recipient = this.tokensRecipientImplementer.address;
|
||||
});
|
||||
|
||||
context('with default operator', function () {
|
||||
const operator = defaultOperatorA;
|
||||
|
||||
it('without requireReceptionAck', async function () {
|
||||
await this.token.mintInternalExtended(
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
false,
|
||||
{ from: operator },
|
||||
);
|
||||
});
|
||||
|
||||
it('with requireReceptionAck', async function () {
|
||||
await expectRevert(
|
||||
this.token.mintInternalExtended(
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
true,
|
||||
{ from: operator },
|
||||
),
|
||||
'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with non operator', function () {
|
||||
const operator = newOperator;
|
||||
|
||||
it('without requireReceptionAck', async function () {
|
||||
await this.token.mintInternalExtended(
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
false,
|
||||
{ from: operator },
|
||||
);
|
||||
});
|
||||
|
||||
it('with requireReceptionAck', async function () {
|
||||
await expectRevert(
|
||||
this.token.mintInternalExtended(
|
||||
this.recipient,
|
||||
amount,
|
||||
data,
|
||||
operatorData,
|
||||
true,
|
||||
{ from: operator },
|
||||
),
|
||||
'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operator management', function () {
|
||||
it('accounts are their own operator', async function () {
|
||||
expect(await this.token.isOperatorFor(holder, holder)).to.equal(true);
|
||||
});
|
||||
|
||||
it('reverts when self-authorizing', async function () {
|
||||
await expectRevert(
|
||||
this.token.authorizeOperator(holder, { from: holder }), 'ERC777: authorizing self as operator',
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when self-revoking', async function () {
|
||||
await expectRevert(
|
||||
this.token.revokeOperator(holder, { from: holder }), 'ERC777: revoking self as operator',
|
||||
);
|
||||
});
|
||||
|
||||
it('non-operators can be revoked', async function () {
|
||||
expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false);
|
||||
|
||||
const { logs } = await this.token.revokeOperator(newOperator, { from: holder });
|
||||
expectEvent.inLogs(logs, 'RevokedOperator', { operator: newOperator, tokenHolder: holder });
|
||||
|
||||
expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false);
|
||||
});
|
||||
|
||||
it('non-operators can be authorized', async function () {
|
||||
expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false);
|
||||
|
||||
const { logs } = await this.token.authorizeOperator(newOperator, { from: holder });
|
||||
expectEvent.inLogs(logs, 'AuthorizedOperator', { operator: newOperator, tokenHolder: holder });
|
||||
|
||||
expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(true);
|
||||
});
|
||||
|
||||
describe('new operators', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.authorizeOperator(newOperator, { from: holder });
|
||||
});
|
||||
|
||||
it('are not added to the default operators list', async function () {
|
||||
expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators);
|
||||
});
|
||||
|
||||
it('can be re-authorized', async function () {
|
||||
const { logs } = await this.token.authorizeOperator(newOperator, { from: holder });
|
||||
expectEvent.inLogs(logs, 'AuthorizedOperator', { operator: newOperator, tokenHolder: holder });
|
||||
|
||||
expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(true);
|
||||
});
|
||||
|
||||
it('can be revoked', async function () {
|
||||
const { logs } = await this.token.revokeOperator(newOperator, { from: holder });
|
||||
expectEvent.inLogs(logs, 'RevokedOperator', { operator: newOperator, tokenHolder: holder });
|
||||
|
||||
expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('default operators', function () {
|
||||
it('can be re-authorized', async function () {
|
||||
const { logs } = await this.token.authorizeOperator(defaultOperatorA, { from: holder });
|
||||
expectEvent.inLogs(logs, 'AuthorizedOperator', { operator: defaultOperatorA, tokenHolder: holder });
|
||||
|
||||
expect(await this.token.isOperatorFor(defaultOperatorA, holder)).to.equal(true);
|
||||
});
|
||||
|
||||
it('can be revoked', async function () {
|
||||
const { logs } = await this.token.revokeOperator(defaultOperatorA, { from: holder });
|
||||
expectEvent.inLogs(logs, 'RevokedOperator', { operator: defaultOperatorA, tokenHolder: holder });
|
||||
|
||||
expect(await this.token.isOperatorFor(defaultOperatorA, holder)).to.equal(false);
|
||||
});
|
||||
|
||||
it('cannot be revoked for themselves', async function () {
|
||||
await expectRevert(
|
||||
this.token.revokeOperator(defaultOperatorA, { from: defaultOperatorA }),
|
||||
'ERC777: revoking self as operator',
|
||||
);
|
||||
});
|
||||
|
||||
context('with revoked default operator', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.revokeOperator(defaultOperatorA, { from: holder });
|
||||
});
|
||||
|
||||
it('default operator is not revoked for other holders', async function () {
|
||||
expect(await this.token.isOperatorFor(defaultOperatorA, anyone)).to.equal(true);
|
||||
});
|
||||
|
||||
it('other default operators are not revoked', async function () {
|
||||
expect(await this.token.isOperatorFor(defaultOperatorB, holder)).to.equal(true);
|
||||
});
|
||||
|
||||
it('default operators list is not modified', async function () {
|
||||
expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators);
|
||||
});
|
||||
|
||||
it('revoked default operator can be re-authorized', async function () {
|
||||
const { logs } = await this.token.authorizeOperator(defaultOperatorA, { from: holder });
|
||||
expectEvent.inLogs(logs, 'AuthorizedOperator', { operator: defaultOperatorA, tokenHolder: holder });
|
||||
|
||||
expect(await this.token.isOperatorFor(defaultOperatorA, holder)).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('send and receive hooks', function () {
|
||||
const amount = new BN('1');
|
||||
const operator = defaultOperatorA;
|
||||
// sender and recipient are stored inside 'this', since in some tests their addresses are determined dynamically
|
||||
|
||||
describe('tokensReceived', function () {
|
||||
beforeEach(function () {
|
||||
this.sender = holder;
|
||||
});
|
||||
|
||||
context('with no ERC777TokensRecipient implementer', function () {
|
||||
context('with contract recipient', function () {
|
||||
beforeEach(async function () {
|
||||
this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
|
||||
this.recipient = this.tokensRecipientImplementer.address;
|
||||
|
||||
// Note that tokensRecipientImplementer doesn't implement the recipient interface for the recipient
|
||||
});
|
||||
|
||||
it('send reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.send(this.recipient, amount, data, { from: holder }),
|
||||
'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
|
||||
);
|
||||
});
|
||||
|
||||
it('operatorSend reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator }),
|
||||
'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
|
||||
);
|
||||
});
|
||||
|
||||
it('mint (internal) reverts', async function () {
|
||||
await expectRevert(
|
||||
this.token.mintInternal(this.recipient, amount, data, operatorData, { from: operator }),
|
||||
'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
|
||||
);
|
||||
});
|
||||
|
||||
it('(ERC20) transfer succeeds', async function () {
|
||||
await this.token.transfer(this.recipient, amount, { from: holder });
|
||||
});
|
||||
|
||||
it('(ERC20) transferFrom succeeds', async function () {
|
||||
const approved = anyone;
|
||||
await this.token.approve(approved, amount, { from: this.sender });
|
||||
await this.token.transferFrom(this.sender, this.recipient, amount, { from: approved });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('with ERC777TokensRecipient implementer', function () {
|
||||
context('with contract as implementer for an externally owned account', function () {
|
||||
beforeEach(async function () {
|
||||
this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
|
||||
this.recipient = anyone;
|
||||
|
||||
await this.tokensRecipientImplementer.recipientFor(this.recipient);
|
||||
|
||||
await this.erc1820.setInterfaceImplementer(
|
||||
this.recipient,
|
||||
web3.utils.soliditySha3('ERC777TokensRecipient'), this.tokensRecipientImplementer.address,
|
||||
{ from: this.recipient },
|
||||
);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData);
|
||||
});
|
||||
|
||||
context('with contract as implementer for another contract', function () {
|
||||
beforeEach(async function () {
|
||||
this.recipientContract = await ERC777SenderRecipientMock.new();
|
||||
this.recipient = this.recipientContract.address;
|
||||
|
||||
this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
|
||||
await this.tokensRecipientImplementer.recipientFor(this.recipient);
|
||||
await this.recipientContract.registerRecipient(this.tokensRecipientImplementer.address);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData);
|
||||
});
|
||||
|
||||
context('with contract as implementer for itself', function () {
|
||||
beforeEach(async function () {
|
||||
this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
|
||||
this.recipient = this.tokensRecipientImplementer.address;
|
||||
|
||||
await this.tokensRecipientImplementer.recipientFor(this.recipient);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tokensToSend', function () {
|
||||
beforeEach(function () {
|
||||
this.recipient = anyone;
|
||||
});
|
||||
|
||||
context('with a contract as implementer for an externally owned account', function () {
|
||||
beforeEach(async function () {
|
||||
this.tokensSenderImplementer = await ERC777SenderRecipientMock.new();
|
||||
this.sender = holder;
|
||||
|
||||
await this.tokensSenderImplementer.senderFor(this.sender);
|
||||
|
||||
await this.erc1820.setInterfaceImplementer(
|
||||
this.sender,
|
||||
web3.utils.soliditySha3('ERC777TokensSender'), this.tokensSenderImplementer.address,
|
||||
{ from: this.sender },
|
||||
);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData);
|
||||
});
|
||||
|
||||
context('with contract as implementer for another contract', function () {
|
||||
beforeEach(async function () {
|
||||
this.senderContract = await ERC777SenderRecipientMock.new();
|
||||
this.sender = this.senderContract.address;
|
||||
|
||||
this.tokensSenderImplementer = await ERC777SenderRecipientMock.new();
|
||||
await this.tokensSenderImplementer.senderFor(this.sender);
|
||||
await this.senderContract.registerSender(this.tokensSenderImplementer.address);
|
||||
|
||||
// For the contract to be able to receive tokens (that it can later send), it must also implement the
|
||||
// recipient interface.
|
||||
|
||||
await this.senderContract.recipientFor(this.sender);
|
||||
await this.token.send(this.sender, amount, data, { from: holder });
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData);
|
||||
});
|
||||
|
||||
context('with a contract as implementer for itself', function () {
|
||||
beforeEach(async function () {
|
||||
this.tokensSenderImplementer = await ERC777SenderRecipientMock.new();
|
||||
this.sender = this.tokensSenderImplementer.address;
|
||||
|
||||
await this.tokensSenderImplementer.senderFor(this.sender);
|
||||
|
||||
// For the contract to be able to receive tokens (that it can later send), it must also implement the
|
||||
// recipient interface.
|
||||
|
||||
await this.tokensSenderImplementer.recipientFor(this.sender);
|
||||
await this.token.send(this.sender, amount, data, { from: holder });
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('with no default operators', function () {
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC777.new(holder, initialSupply, name, symbol, []);
|
||||
});
|
||||
|
||||
it('default operators list is empty', async function () {
|
||||
expect(await this.token.defaultOperators()).to.deep.equal([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('relative order of hooks', function () {
|
||||
beforeEach(async function () {
|
||||
await singletons.ERC1820Registry(registryFunder);
|
||||
this.sender = await ERC777SenderRecipientMock.new();
|
||||
await this.sender.registerRecipient(this.sender.address);
|
||||
await this.sender.registerSender(this.sender.address);
|
||||
this.token = await ERC777.new(holder, initialSupply, name, symbol, []);
|
||||
await this.token.send(this.sender.address, 1, '0x', { from: holder });
|
||||
});
|
||||
|
||||
it('send', async function () {
|
||||
const { receipt } = await this.sender.send(this.token.address, anyone, 1, '0x');
|
||||
|
||||
const internalBeforeHook = receipt.logs.findIndex(l => l.event === 'BeforeTokenTransfer');
|
||||
expect(internalBeforeHook).to.be.gte(0);
|
||||
const externalSendHook = receipt.logs.findIndex(l => l.event === 'TokensToSendCalled');
|
||||
expect(externalSendHook).to.be.gte(0);
|
||||
|
||||
expect(externalSendHook).to.be.lt(internalBeforeHook);
|
||||
});
|
||||
|
||||
it('burn', async function () {
|
||||
const { receipt } = await this.sender.burn(this.token.address, 1, '0x');
|
||||
|
||||
const internalBeforeHook = receipt.logs.findIndex(l => l.event === 'BeforeTokenTransfer');
|
||||
expect(internalBeforeHook).to.be.gte(0);
|
||||
const externalSendHook = receipt.logs.findIndex(l => l.event === 'TokensToSendCalled');
|
||||
expect(externalSendHook).to.be.gte(0);
|
||||
|
||||
expect(externalSendHook).to.be.lt(internalBeforeHook);
|
||||
});
|
||||
});
|
||||
});
|
||||
49
test/token/ERC777/presets/ERC777PresetFixedSupply.test.js
Normal file
49
test/token/ERC777/presets/ERC777PresetFixedSupply.test.js
Normal file
@ -0,0 +1,49 @@
|
||||
const { BN, singletons } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const ERC777PresetFixedSupply = artifacts.require('ERC777PresetFixedSupply');
|
||||
|
||||
contract('ERC777PresetFixedSupply', function (accounts) {
|
||||
const [registryFunder, owner, defaultOperatorA, defaultOperatorB, anyone] = accounts;
|
||||
|
||||
const initialSupply = new BN('10000');
|
||||
const name = 'ERC777Preset';
|
||||
const symbol = '777P';
|
||||
|
||||
const defaultOperators = [defaultOperatorA, defaultOperatorB];
|
||||
|
||||
before(async function () {
|
||||
await singletons.ERC1820Registry(registryFunder);
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await ERC777PresetFixedSupply.new(name, symbol, defaultOperators, initialSupply, owner);
|
||||
});
|
||||
|
||||
it('returns the name', async function () {
|
||||
expect(await this.token.name()).to.equal(name);
|
||||
});
|
||||
|
||||
it('returns the symbol', async function () {
|
||||
expect(await this.token.symbol()).to.equal(symbol);
|
||||
});
|
||||
|
||||
it('returns the default operators', async function () {
|
||||
expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators);
|
||||
});
|
||||
|
||||
it('default operators are operators for all accounts', async function () {
|
||||
for (const operator of defaultOperators) {
|
||||
expect(await this.token.isOperatorFor(operator, anyone)).to.equal(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns the total supply equal to initial supply', async function () {
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
|
||||
it('returns the balance of owner equal to initial supply', async function () {
|
||||
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
});
|
||||
160
test/token/common/ERC2981.behavior.js
Normal file
160
test/token/common/ERC2981.behavior.js
Normal file
@ -0,0 +1,160 @@
|
||||
const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior');
|
||||
|
||||
function shouldBehaveLikeERC2981 () {
|
||||
const royaltyFraction = new BN('10');
|
||||
|
||||
shouldSupportInterfaces(['ERC2981']);
|
||||
|
||||
describe('default royalty', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.setDefaultRoyalty(this.account1, royaltyFraction);
|
||||
});
|
||||
|
||||
it('checks royalty is set', async function () {
|
||||
const royalty = new BN((this.salePrice * royaltyFraction) / 10000);
|
||||
|
||||
const initInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice);
|
||||
|
||||
expect(initInfo[0]).to.be.equal(this.account1);
|
||||
expect(initInfo[1]).to.be.bignumber.equal(royalty);
|
||||
});
|
||||
|
||||
it('updates royalty amount', async function () {
|
||||
const newPercentage = new BN('25');
|
||||
|
||||
// Updated royalty check
|
||||
await this.token.setDefaultRoyalty(this.account1, newPercentage);
|
||||
const royalty = new BN((this.salePrice * newPercentage) / 10000);
|
||||
const newInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice);
|
||||
|
||||
expect(newInfo[0]).to.be.equal(this.account1);
|
||||
expect(newInfo[1]).to.be.bignumber.equal(royalty);
|
||||
});
|
||||
|
||||
it('holds same royalty value for different tokens', async function () {
|
||||
const newPercentage = new BN('20');
|
||||
await this.token.setDefaultRoyalty(this.account1, newPercentage);
|
||||
|
||||
const token1Info = await this.token.royaltyInfo(this.tokenId1, this.salePrice);
|
||||
const token2Info = await this.token.royaltyInfo(this.tokenId2, this.salePrice);
|
||||
|
||||
expect(token1Info[1]).to.be.bignumber.equal(token2Info[1]);
|
||||
});
|
||||
|
||||
it('Remove royalty information', async function () {
|
||||
const newValue = new BN('0');
|
||||
await this.token.deleteDefaultRoyalty();
|
||||
|
||||
const token1Info = await this.token.royaltyInfo(this.tokenId1, this.salePrice);
|
||||
const token2Info = await this.token.royaltyInfo(this.tokenId2, this.salePrice);
|
||||
// Test royalty info is still persistent across all tokens
|
||||
expect(token1Info[0]).to.be.bignumber.equal(token2Info[0]);
|
||||
expect(token1Info[1]).to.be.bignumber.equal(token2Info[1]);
|
||||
// Test information was deleted
|
||||
expect(token1Info[0]).to.be.equal(ZERO_ADDRESS);
|
||||
expect(token1Info[1]).to.be.bignumber.equal(newValue);
|
||||
});
|
||||
|
||||
it('reverts if invalid parameters', async function () {
|
||||
await expectRevert(
|
||||
this.token.setDefaultRoyalty(ZERO_ADDRESS, royaltyFraction),
|
||||
'ERC2981: invalid receiver',
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.token.setTokenRoyalty(this.tokenId1, this.account1, new BN('11000')),
|
||||
'ERC2981: royalty fee will exceed salePrice',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('token based royalty', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.setTokenRoyalty(this.tokenId1, this.account1, royaltyFraction);
|
||||
});
|
||||
|
||||
it('updates royalty amount', async function () {
|
||||
const newPercentage = new BN('25');
|
||||
let royalty = new BN((this.salePrice * royaltyFraction) / 10000);
|
||||
// Initial royalty check
|
||||
const initInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice);
|
||||
|
||||
expect(initInfo[0]).to.be.equal(this.account1);
|
||||
expect(initInfo[1]).to.be.bignumber.equal(royalty);
|
||||
|
||||
// Updated royalty check
|
||||
await this.token.setTokenRoyalty(this.tokenId1, this.account1, newPercentage);
|
||||
royalty = new BN((this.salePrice * newPercentage) / 10000);
|
||||
const newInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice);
|
||||
|
||||
expect(newInfo[0]).to.be.equal(this.account1);
|
||||
expect(newInfo[1]).to.be.bignumber.equal(royalty);
|
||||
});
|
||||
|
||||
it('holds different values for different tokens', async function () {
|
||||
const newPercentage = new BN('20');
|
||||
await this.token.setTokenRoyalty(this.tokenId2, this.account1, newPercentage);
|
||||
|
||||
const token1Info = await this.token.royaltyInfo(this.tokenId1, this.salePrice);
|
||||
const token2Info = await this.token.royaltyInfo(this.tokenId2, this.salePrice);
|
||||
|
||||
// must be different even at the same this.salePrice
|
||||
expect(token1Info[1]).to.not.be.equal(token2Info.royaltyFraction);
|
||||
});
|
||||
|
||||
it('reverts if invalid parameters', async function () {
|
||||
await expectRevert(
|
||||
this.token.setTokenRoyalty(this.tokenId1, ZERO_ADDRESS, royaltyFraction),
|
||||
'ERC2981: Invalid parameters',
|
||||
);
|
||||
|
||||
await expectRevert(
|
||||
this.token.setTokenRoyalty(this.tokenId1, this.account1, new BN('11000')),
|
||||
'ERC2981: royalty fee will exceed salePrice',
|
||||
);
|
||||
});
|
||||
|
||||
it('can reset token after setting royalty', async function () {
|
||||
const newPercentage = new BN('30');
|
||||
const royalty = new BN((this.salePrice * newPercentage) / 10000);
|
||||
await this.token.setTokenRoyalty(this.tokenId1, this.account2, newPercentage);
|
||||
|
||||
const tokenInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice);
|
||||
|
||||
// Tokens must have own information
|
||||
expect(tokenInfo[1]).to.be.bignumber.equal(royalty);
|
||||
expect(tokenInfo[0]).to.be.equal(this.account2);
|
||||
|
||||
await this.token.setTokenRoyalty(this.tokenId2, this.account1, new BN('0'));
|
||||
const result = await this.token.royaltyInfo(this.tokenId2, this.salePrice);
|
||||
// Token must not share default information
|
||||
expect(result[0]).to.be.equal(this.account1);
|
||||
expect(result[1]).to.be.bignumber.equal(new BN('0'));
|
||||
});
|
||||
|
||||
it('can hold default and token royalty information', async function () {
|
||||
const newPercentage = new BN('30');
|
||||
const royalty = new BN((this.salePrice * newPercentage) / 10000);
|
||||
|
||||
await this.token.setTokenRoyalty(this.tokenId2, this.account2, newPercentage);
|
||||
|
||||
const token1Info = await this.token.royaltyInfo(this.tokenId1, this.salePrice);
|
||||
const token2Info = await this.token.royaltyInfo(this.tokenId2, this.salePrice);
|
||||
// Tokens must not have same values
|
||||
expect(token1Info[1]).to.not.be.bignumber.equal(token2Info[1]);
|
||||
expect(token1Info[0]).to.not.be.equal(token2Info[0]);
|
||||
|
||||
// Updated token must have new values
|
||||
expect(token2Info[0]).to.be.equal(this.account2);
|
||||
expect(token2Info[1]).to.be.bignumber.equal(royalty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeERC2981,
|
||||
};
|
||||
Reference in New Issue
Block a user