Add ERC20 compatibility to ERC777. (#1735)
* Add ERC20 compatibility. * Reusing ERC20 tests for ERC777. * Improve documentation. * Add changelog entry. * Improved ERC20 behavior tests. * Add revert reasons to ERC777. * ERC20 methods allow sending tokens to contracts with no interface. * Register ERC20 interface. * Add comment about avoidLockingTokens. * Improve revert reason string. * Make ERC777 implement IERC20. * Fix test revert string. * Remove unnecesary require. * Add private _transfer. * Update contracts/drafts/ERC777/ERC777.sol Co-Authored-By: nventuro <nicolas.venturo@gmail.com> * Update private helper names.
This commit is contained in:
@ -184,8 +184,9 @@ function shouldSendTokens (from, operator, to, amount, data, operatorData) {
|
||||
const initialFromBalance = await this.token.balanceOf(from);
|
||||
const initialToBalance = await this.token.balanceOf(to);
|
||||
|
||||
let logs;
|
||||
if (!operatorCall) {
|
||||
const { logs } = await this.token.send(to, amount, data, { from });
|
||||
({ logs } = await this.token.send(to, amount, data, { from }));
|
||||
expectEvent.inLogs(logs, 'Sent', {
|
||||
operator: from,
|
||||
from,
|
||||
@ -195,7 +196,7 @@ function shouldSendTokens (from, operator, to, amount, data, operatorData) {
|
||||
operatorData: null,
|
||||
});
|
||||
} else {
|
||||
const { logs } = await this.token.operatorSend(from, to, amount, data, operatorData, { from: operator });
|
||||
({ logs } = await this.token.operatorSend(from, to, amount, data, operatorData, { from: operator }));
|
||||
expectEvent.inLogs(logs, 'Sent', {
|
||||
operator,
|
||||
from,
|
||||
@ -206,6 +207,12 @@ function shouldSendTokens (from, operator, 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);
|
||||
@ -231,8 +238,9 @@ function shouldBurnTokens (from, operator, amount, data, operatorData) {
|
||||
const initialTotalSupply = await this.token.totalSupply();
|
||||
const initialFromBalance = await this.token.balanceOf(from);
|
||||
|
||||
let logs;
|
||||
if (!operatorCall) {
|
||||
const { logs } = await this.token.burn(amount, data, { from });
|
||||
({ logs } = await this.token.burn(amount, data, { from }));
|
||||
expectEvent.inLogs(logs, 'Burned', {
|
||||
operator: from,
|
||||
from,
|
||||
@ -241,7 +249,7 @@ function shouldBurnTokens (from, operator, amount, data, operatorData) {
|
||||
operatorData: null,
|
||||
});
|
||||
} else {
|
||||
const { logs } = await this.token.operatorBurn(from, amount, data, operatorData, { from: operator });
|
||||
({ logs } = await this.token.operatorBurn(from, amount, data, operatorData, { from: operator }));
|
||||
expectEvent.inLogs(logs, 'Burned', {
|
||||
operator,
|
||||
from,
|
||||
@ -251,6 +259,12 @@ function shouldBurnTokens (from, operator, 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);
|
||||
|
||||
@ -274,6 +288,7 @@ function shouldInternalMintTokens (operator, to, amount, data, operatorData) {
|
||||
const initialToBalance = await this.token.balanceOf(to);
|
||||
|
||||
const { logs } = await this.token.mintInternal(operator, to, amount, data, operatorData);
|
||||
|
||||
expectEvent.inLogs(logs, 'Minted', {
|
||||
operator,
|
||||
to,
|
||||
@ -282,6 +297,12 @@ function shouldInternalMintTokens (operator, to, amount, data, operatorData) {
|
||||
operatorData,
|
||||
});
|
||||
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to,
|
||||
value: amount,
|
||||
});
|
||||
|
||||
const finalTotalSupply = await this.token.totalSupply();
|
||||
const finalToBalance = await this.token.balanceOf(to);
|
||||
|
||||
|
||||
@ -9,6 +9,10 @@ const {
|
||||
shouldBehaveLikeERC777SendBurnWithSendHook,
|
||||
} = require('./ERC777.behavior');
|
||||
|
||||
const {
|
||||
shouldBehaveLikeERC20,
|
||||
} = require('../../token/ERC20/ERC20.behavior');
|
||||
|
||||
const ERC777 = artifacts.require('ERC777Mock');
|
||||
const ERC777SenderRecipientMock = artifacts.require('ERC777SenderRecipientMock');
|
||||
|
||||
@ -32,6 +36,8 @@ contract('ERC777', 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
|
||||
});
|
||||
@ -45,7 +51,7 @@ contract('ERC777', function ([
|
||||
(await this.token.symbol()).should.equal(symbol);
|
||||
});
|
||||
|
||||
it('has a granularity of 1', async function () {
|
||||
it('returns a granularity of 1', async function () {
|
||||
(await this.token.granularity()).should.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
@ -59,14 +65,23 @@ contract('ERC777', function ([
|
||||
}
|
||||
});
|
||||
|
||||
it('returns thte total supply', async function () {
|
||||
it('returns the total supply', async function () {
|
||||
(await this.token.totalSupply()).should.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
|
||||
it('is registered in the registry', async function () {
|
||||
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 () {
|
||||
@ -144,11 +159,15 @@ contract('ERC777', function ([
|
||||
});
|
||||
|
||||
it('reverts when self-authorizing', async function () {
|
||||
await shouldFail.reverting(this.token.authorizeOperator(holder, { from: holder }));
|
||||
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(this.token.revokeOperator(holder, { from: holder }));
|
||||
await shouldFail.reverting.withMessage(
|
||||
this.token.revokeOperator(holder, { from: holder }), 'ERC777: revoking self as operator'
|
||||
);
|
||||
});
|
||||
|
||||
it('non-operators can be revoked', async function () {
|
||||
@ -209,7 +228,10 @@ contract('ERC777', function ([
|
||||
});
|
||||
|
||||
it('cannot be revoked for themselves', async function () {
|
||||
await shouldFail.reverting(this.token.revokeOperator(defaultOperatorA, { from: defaultOperatorA }));
|
||||
await shouldFail.reverting.withMessage(
|
||||
this.token.revokeOperator(defaultOperatorA, { from: defaultOperatorA }),
|
||||
'ERC777: revoking self as operator'
|
||||
);
|
||||
});
|
||||
|
||||
context('with revoked default operator', function () {
|
||||
@ -259,20 +281,35 @@ contract('ERC777', function ([
|
||||
});
|
||||
|
||||
it('send reverts', async function () {
|
||||
await shouldFail.reverting(this.token.send(this.recipient, amount, data));
|
||||
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(
|
||||
this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator })
|
||||
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(
|
||||
this.token.mintInternal(operator, this.recipient, amount, data, operatorData)
|
||||
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 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user