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:
Nicolás Venturo
2019-05-08 13:13:19 -03:00
committed by GitHub
parent 86f214b7a3
commit aa4c9feabd
7 changed files with 501 additions and 316 deletions

View File

@ -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);

View File

@ -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 });
});
});
});