Move ERC1820 and ERC777 tests out of drafts.
This commit is contained in:
@ -1,62 +0,0 @@
|
||||
const { shouldFail, singletons } = require('openzeppelin-test-helpers');
|
||||
const { bufferToHex, keccak256 } = require('ethereumjs-util');
|
||||
|
||||
const ERC1820ImplementerMock = artifacts.require('ERC1820ImplementerMock');
|
||||
|
||||
contract('ERC1820Implementer', function ([_, registryFunder, implementee, other]) {
|
||||
const ERC1820_ACCEPT_MAGIC = bufferToHex(keccak256('ERC1820_ACCEPT_MAGIC'));
|
||||
|
||||
beforeEach(async function () {
|
||||
this.implementer = await ERC1820ImplementerMock.new();
|
||||
this.registry = await singletons.ERC1820Registry(registryFunder);
|
||||
|
||||
this.interfaceA = bufferToHex(keccak256('interfaceA'));
|
||||
this.interfaceB = bufferToHex(keccak256('interfaceB'));
|
||||
});
|
||||
|
||||
context('with no registered interfaces', function () {
|
||||
it('returns false when interface implementation is queried', async function () {
|
||||
(await this.implementer.canImplementInterfaceForAddress(this.interfaceA, implementee))
|
||||
.should.not.equal(ERC1820_ACCEPT_MAGIC);
|
||||
});
|
||||
|
||||
it('reverts when attempting to set as implementer in the registry', async function () {
|
||||
await shouldFail.reverting.withMessage(
|
||||
this.registry.setInterfaceImplementer(
|
||||
implementee, this.interfaceA, this.implementer.address, { from: implementee }
|
||||
),
|
||||
'Does not implement the interface'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with registered interfaces', function () {
|
||||
beforeEach(async function () {
|
||||
await this.implementer.registerInterfaceForAddress(this.interfaceA, implementee);
|
||||
});
|
||||
|
||||
it('returns true when interface implementation is queried', async function () {
|
||||
(await this.implementer.canImplementInterfaceForAddress(this.interfaceA, implementee))
|
||||
.should.equal(ERC1820_ACCEPT_MAGIC);
|
||||
});
|
||||
|
||||
it('returns false when interface implementation for non-supported interfaces is queried', async function () {
|
||||
(await this.implementer.canImplementInterfaceForAddress(this.interfaceB, implementee))
|
||||
.should.not.equal(ERC1820_ACCEPT_MAGIC);
|
||||
});
|
||||
|
||||
it('returns false when interface implementation for non-supported addresses is queried', async function () {
|
||||
(await this.implementer.canImplementInterfaceForAddress(this.interfaceA, other))
|
||||
.should.not.equal(ERC1820_ACCEPT_MAGIC);
|
||||
});
|
||||
|
||||
it('can be set as an implementer for supported interfaces in the registry', async function () {
|
||||
await this.registry.setInterfaceImplementer(
|
||||
implementee, this.interfaceA, this.implementer.address, { from: implementee }
|
||||
);
|
||||
|
||||
(await this.registry.getInterfaceImplementer(implementee, this.interfaceA))
|
||||
.should.equal(this.implementer.address);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,551 +0,0 @@
|
||||
const { BN, constants, expectEvent, shouldFail } = require('openzeppelin-test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
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 shouldFail.reverting(this.token.send(recipient, balance.addn(1), data, { from: holder }));
|
||||
});
|
||||
|
||||
it('reverts when sending to the zero address', async function () {
|
||||
await shouldFail.reverting(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 shouldFail.reverting(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 shouldFail.reverting(
|
||||
this.token.operatorSend(holder, recipient, balance.addn(1), data, operatorData, { from: operator })
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when sending to the zero address', async function () {
|
||||
await shouldFail.reverting(
|
||||
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 shouldFail.reverting(
|
||||
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 shouldFail.reverting(
|
||||
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 shouldFail.reverting(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 shouldFail.reverting(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 shouldFail.reverting(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 shouldFail.reverting(
|
||||
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 shouldFail.reverting(
|
||||
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 shouldFail.reverting(
|
||||
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 shouldFail.reverting(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);
|
||||
|
||||
finalTotalSupply.should.be.bignumber.equal(initialTotalSupply);
|
||||
finalToBalance.sub(initialToBalance).should.be.bignumber.equal(amount);
|
||||
finalFromBalance.sub(initialFromBalance).should.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);
|
||||
|
||||
finalTotalSupply.sub(initialTotalSupply).should.be.bignumber.equal(amount.neg());
|
||||
finalFromBalance.sub(initialFromBalance).should.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 shouldFail.reverting(this.token.mintInternal(operator, ZERO_ADDRESS, amount, data, operatorData));
|
||||
});
|
||||
}
|
||||
|
||||
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(operator, to, amount, data, operatorData);
|
||||
|
||||
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);
|
||||
|
||||
finalTotalSupply.sub(initialTotalSupply).should.be.bignumber.equal(amount);
|
||||
finalToBalance.sub(initialToBalance).should.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 shouldFail.reverting(sendFromHolder(this.token, this.sender, this.recipient, amount, data));
|
||||
});
|
||||
|
||||
it('operatorSend reverts', async function () {
|
||||
await shouldFail.reverting(
|
||||
this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator })
|
||||
);
|
||||
});
|
||||
|
||||
it('mint (internal) reverts', async function () {
|
||||
await shouldFail.reverting(
|
||||
this.token.mintInternal(operator, this.recipient, amount, data, operatorData)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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(
|
||||
operator, this.recipient, amount, data, operatorData,
|
||||
);
|
||||
|
||||
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 shouldFail.reverting(sendFromHolder(this.token, this.sender, this.recipient, amount, data));
|
||||
});
|
||||
|
||||
it('operatorSend reverts', async function () {
|
||||
await shouldFail.reverting(
|
||||
this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator })
|
||||
);
|
||||
});
|
||||
|
||||
it('burn reverts', async function () {
|
||||
await shouldFail.reverting(burnFromHolder(this.token, this.sender, amount, data));
|
||||
});
|
||||
|
||||
it('operatorBurn reverts', async function () {
|
||||
await shouldFail.reverting(
|
||||
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 });
|
||||
(await this.token.balanceOf(holder)).should.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,
|
||||
};
|
||||
@ -1,430 +0,0 @@
|
||||
const { BN, expectEvent, shouldFail, singletons } = require('openzeppelin-test-helpers');
|
||||
|
||||
const {
|
||||
shouldBehaveLikeERC777DirectSendBurn,
|
||||
shouldBehaveLikeERC777OperatorSendBurn,
|
||||
shouldBehaveLikeERC777UnauthorizedOperatorSendBurn,
|
||||
shouldBehaveLikeERC777InternalMint,
|
||||
shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook,
|
||||
shouldBehaveLikeERC777SendBurnWithSendHook,
|
||||
} = require('./ERC777.behavior');
|
||||
|
||||
const {
|
||||
shouldBehaveLikeERC20,
|
||||
} = require('../../token/ERC20/ERC20.behavior');
|
||||
|
||||
const ERC777 = artifacts.require('ERC777Mock');
|
||||
const ERC777SenderRecipientMock = artifacts.require('ERC777SenderRecipientMock');
|
||||
|
||||
contract('ERC777', function ([
|
||||
_, registryFunder, holder, defaultOperatorA, defaultOperatorB, newOperator, anyone,
|
||||
]) {
|
||||
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);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC20('ERC777', initialSupply, holder, anyone, defaultOperatorA);
|
||||
|
||||
it.skip('does not emit AuthorizedOperator events for default operators', async function () {
|
||||
expectEvent.not.inConstructor(this.token, 'AuthorizedOperator'); // This helper needs to be implemented
|
||||
});
|
||||
|
||||
describe('basic information', function () {
|
||||
it('returns the name', async function () {
|
||||
(await this.token.name()).should.equal(name);
|
||||
});
|
||||
|
||||
it('returns the symbol', async function () {
|
||||
(await this.token.symbol()).should.equal(symbol);
|
||||
});
|
||||
|
||||
it('returns a granularity of 1', async function () {
|
||||
(await this.token.granularity()).should.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('returns the default operators', async function () {
|
||||
(await this.token.defaultOperators()).should.deep.equal(defaultOperators);
|
||||
});
|
||||
|
||||
it('default operators are operators for all accounts', async function () {
|
||||
for (const operator of defaultOperators) {
|
||||
(await this.token.isOperatorFor(operator, anyone)).should.equal(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns the total supply', async function () {
|
||||
(await this.token.totalSupply()).should.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
|
||||
it('returns 18 when decimals is called', async function () {
|
||||
(await this.token.decimals()).should.be.bignumber.equal('18');
|
||||
});
|
||||
|
||||
it('the ERC777Token interface is registered in the registry', async function () {
|
||||
(await this.erc1820.getInterfaceImplementer(this.token.address, web3.utils.soliditySha3('ERC777Token')))
|
||||
.should.equal(this.token.address);
|
||||
});
|
||||
|
||||
it('the ERC20Token interface is registered in the registry', async function () {
|
||||
(await this.erc1820.getInterfaceImplementer(this.token.address, web3.utils.soliditySha3('ERC20Token')))
|
||||
.should.equal(this.token.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
context('for an account with no tokens', function () {
|
||||
it('returns zero', async function () {
|
||||
(await this.token.balanceOf(anyone)).should.be.bignumber.equal('0');
|
||||
});
|
||||
});
|
||||
|
||||
context('for an account with tokens', function () {
|
||||
it('returns their balance', async function () {
|
||||
(await this.token.balanceOf(holder)).should.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('operator management', function () {
|
||||
it('accounts are their own operator', async function () {
|
||||
(await this.token.isOperatorFor(holder, holder)).should.equal(true);
|
||||
});
|
||||
|
||||
it('reverts when self-authorizing', async function () {
|
||||
await shouldFail.reverting.withMessage(
|
||||
this.token.authorizeOperator(holder, { from: holder }), 'ERC777: authorizing self as operator'
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts when self-revoking', async function () {
|
||||
await shouldFail.reverting.withMessage(
|
||||
this.token.revokeOperator(holder, { from: holder }), 'ERC777: revoking self as operator'
|
||||
);
|
||||
});
|
||||
|
||||
it('non-operators can be revoked', async function () {
|
||||
(await this.token.isOperatorFor(newOperator, holder)).should.equal(false);
|
||||
|
||||
const { logs } = await this.token.revokeOperator(newOperator, { from: holder });
|
||||
expectEvent.inLogs(logs, 'RevokedOperator', { operator: newOperator, tokenHolder: holder });
|
||||
|
||||
(await this.token.isOperatorFor(newOperator, holder)).should.equal(false);
|
||||
});
|
||||
|
||||
it('non-operators can be authorized', async function () {
|
||||
(await this.token.isOperatorFor(newOperator, holder)).should.equal(false);
|
||||
|
||||
const { logs } = await this.token.authorizeOperator(newOperator, { from: holder });
|
||||
expectEvent.inLogs(logs, 'AuthorizedOperator', { operator: newOperator, tokenHolder: holder });
|
||||
|
||||
(await this.token.isOperatorFor(newOperator, holder)).should.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 () {
|
||||
(await this.token.defaultOperators()).should.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 });
|
||||
|
||||
(await this.token.isOperatorFor(newOperator, holder)).should.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 });
|
||||
|
||||
(await this.token.isOperatorFor(newOperator, holder)).should.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 });
|
||||
|
||||
(await this.token.isOperatorFor(defaultOperatorA, holder)).should.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 });
|
||||
|
||||
(await this.token.isOperatorFor(defaultOperatorA, holder)).should.equal(false);
|
||||
});
|
||||
|
||||
it('cannot be revoked for themselves', async function () {
|
||||
await shouldFail.reverting.withMessage(
|
||||
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 () {
|
||||
(await this.token.isOperatorFor(defaultOperatorA, anyone)).should.equal(true);
|
||||
});
|
||||
|
||||
it('other default operators are not revoked', async function () {
|
||||
(await this.token.isOperatorFor(defaultOperatorB, holder)).should.equal(true);
|
||||
});
|
||||
|
||||
it('default operators list is not modified', async function () {
|
||||
(await this.token.defaultOperators()).should.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 });
|
||||
|
||||
(await this.token.isOperatorFor(defaultOperatorA, holder)).should.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 shouldFail.reverting.withMessage(
|
||||
this.token.send(this.recipient, amount, data, { from: holder }),
|
||||
'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
|
||||
);
|
||||
});
|
||||
|
||||
it('operatorSend reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(
|
||||
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 shouldFail.reverting.withMessage(
|
||||
this.token.mintInternal(operator, this.recipient, amount, data, operatorData),
|
||||
'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 () {
|
||||
(await this.token.defaultOperators()).should.deep.equal([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user