Skip to content

Commit

Permalink
Add default operators
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacques Dafflon committed Jul 13, 2018
1 parent 89212a6 commit c90fcf2
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 10 deletions.
29 changes: 25 additions & 4 deletions contracts/ERC777BaseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,26 @@ contract ERC777BaseToken is ERC777Token, ERC820Implementer {
mapping(address => uint) internal mBalances;
mapping(address => mapping(address => bool)) internal mAuthorized;

address[] internal mDefaultOperators;
mapping(address => bool) internal mIsDefaultOperator;
mapping(address => mapping(address => bool)) internal mRevokedDefaultOperator;

/* -- Constructor -- */
//
/// @notice Constructor to create a ReferenceToken
/// @param _name Name of the new token
/// @param _symbol Symbol of the new token.
/// @param _granularity Minimum transferable chunk.
function ERC777BaseToken(string _name, string _symbol, uint256 _granularity) internal {
function ERC777BaseToken(string _name, string _symbol, uint256 _granularity, address[] _defaultOperators) internal {
mName = _name;
mSymbol = _symbol;
mTotalSupply = 0;
require(_granularity >= 1);
mGranularity = _granularity;

mDefaultOperators = _defaultOperators;
for (uint i = 0; i < mDefaultOperators.length; i++) { mIsDefaultOperator[mDefaultOperators[i]] = true; }

setInterfaceImplementation("ERC777Token", this);
}

Expand All @@ -57,6 +64,10 @@ contract ERC777BaseToken is ERC777Token, ERC820Implementer {
/// @return the balance of `_tokenAddress`.
function balanceOf(address _tokenHolder) public constant returns (uint256) { return mBalances[_tokenHolder]; }

/// @notice Return the list of default operators
/// @return the list of all the default operators
function defaultOperators() public view returns (address[]) { return mDefaultOperators; }

/// @notice Send `_amount` of tokens to address `_to` passing `_userData` to the recipient
/// @param _to The address of the recipient
/// @param _amount The number of tokens to be sent
Expand All @@ -68,15 +79,23 @@ contract ERC777BaseToken is ERC777Token, ERC820Implementer {
/// @param _operator The operator that wants to be Authorized
function authorizeOperator(address _operator) public {
require(_operator != msg.sender);
mAuthorized[_operator][msg.sender] = true;
if (mIsDefaultOperator[_operator]) {
mRevokedDefaultOperator[_operator][msg.sender] = false;
} else {
mAuthorized[_operator][msg.sender] = true;
}
AuthorizedOperator(_operator, msg.sender);
}

/// @notice Revoke a third party `_operator`'s rights to manage (send) `msg.sender`'s tokens.
/// @param _operator The operator that wants to be Revoked
function revokeOperator(address _operator) public {
require(_operator != msg.sender);
mAuthorized[_operator][msg.sender] = false;
if (mIsDefaultOperator[_operator]) {
mRevokedDefaultOperator[_operator][msg.sender] = true;
} else {
mAuthorized[_operator][msg.sender] = false;
}
RevokedOperator(_operator, msg.sender);
}

Expand All @@ -85,7 +104,9 @@ contract ERC777BaseToken is ERC777Token, ERC820Implementer {
/// @param _tokenHolder address which holds the tokens to be managed
/// @return `true` if `_operator` is authorized for `_tokenHolder`
function isOperatorFor(address _operator, address _tokenHolder) public constant returns (bool) {
return _operator == _tokenHolder || mAuthorized[_operator][_tokenHolder];
return (_operator == _tokenHolder
|| mAuthorized[_operator][_tokenHolder]
|| (mIsDefaultOperator[_operator] && !mRevokedDefaultOperator[_operator][_tokenHolder]));
}

/// @notice Send `_amount` of tokens on behalf of the address `from` to the address `to`.
Expand Down
5 changes: 3 additions & 2 deletions contracts/ERC777ERC20BaseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ contract ERC777ERC20BaseToken is ERC20Token, ERC777BaseToken {
function ERC777ERC20BaseToken(
string _name,
string _symbol,
uint256 _granularity
uint256 _granularity,
address[] _defaultOperators
)
internal ERC777BaseToken(_name, _symbol, _granularity)
internal ERC777BaseToken(_name, _symbol, _granularity, _defaultOperators)
{
mErc20compatible = true;
setInterfaceImplementation("ERC20Token", this);
Expand Down
5 changes: 3 additions & 2 deletions contracts/ERC777Token.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ interface ERC777Token {
function balanceOf(address owner) public constant returns (uint256);

function send(address to, uint256 amount, bytes userData) public;
function operatorSend(address from, address to, uint256 amount, bytes holderData, bytes operatorData) public;

function defaultOperators() public view returns (address[]);
function isOperatorFor(address operator, address tokenHolder) public view returns (bool);
function authorizeOperator(address operator) public;
function revokeOperator(address operator) public;
function isOperatorFor(address operator, address tokenHolder) public constant returns (bool);
function operatorSend(address from, address to, uint256 amount, bytes userData, bytes operatorData) public;

event Sent(
address indexed operator,
Expand Down
6 changes: 4 additions & 2 deletions contracts/examples/ReferenceToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ contract ReferenceToken is ERC777ERC20BaseToken, Ownable {
function ReferenceToken(
string _name,
string _symbol,
uint256 _granularity
) public ERC777ERC20BaseToken(_name, _symbol, _granularity) { // solhint-disable-line no-empty-blocks
uint256 _granularity,
address[] _defaultOperators
// solhint-disable-next-line no-empty-blocks
) public ERC777ERC20BaseToken(_name, _symbol, _granularity, _defaultOperators) {
// insert custom constructor code
}

Expand Down
6 changes: 6 additions & 0 deletions test/ReferenceToken.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ contract('ReferenceToken', function(accounts) {
name: 'ReferenceToken',
symbol: 'XRT',
granularity: '0.01',
defaultOperators: [
'0xcafe000000000000000000000000000000000001',
'0x00bacafe00000000000000000000000000000002',
],
totalSupply: '0',
defaultBalance: '0',
};
Expand All @@ -32,6 +36,7 @@ contract('ReferenceToken', function(accounts) {
token.name,
token.symbol,
web3.utils.toWei(token.granularity),
token.defaultOperators,
] });

after(async function() { await web3.currentProvider.connection.close(); });
Expand Down Expand Up @@ -68,6 +73,7 @@ contract('ReferenceToken', function(accounts) {
token.name,
token.symbol,
web3.utils.toWei('0'),
token.defaultOperators,
] })
.send({ from: accounts[0], gasLimit: estimateGas })
.should.be.rejectedWith('revert');
Expand Down
78 changes: 78 additions & 0 deletions test/utils/operator.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,84 @@ exports.test = function(web3, accounts, token) {
.mintForAllAccounts(web3, accounts, token, accounts[0], '10', 100000);
});

it('should list the default operators', async function() {
const defaultOperators = await token.contract.methods
.defaultOperators()
.call();

assert.deepEqual(
defaultOperators.map(web3.utils.toChecksumAddress),
token.defaultOperators.map(web3.utils.toChecksumAddress),
);
});

for (let defaultOperator of token.defaultOperators) {
it(`should detect ${utils.formatAccount(defaultOperator)} is a default ` +
'operator for all accounts', async function() {
for (let account of accounts) {
assert.isTrue(
await token.contract.methods
.isOperatorFor(defaultOperator, account)
.call()
);
}
});
}

it(`should let ${utils.formatAccount(accounts[3])} revoke the default ` +
`operator ${utils.formatAccount(token.defaultOperators[1])}`,
async function() {
assert.isTrue(
await token.contract.methods
.isOperatorFor(token.defaultOperators[1], accounts[3])
.call()
);

await token.contract.methods
.revokeOperator(token.defaultOperators[1])
.send({ from: accounts[3], gas: 300000 });

await utils.getBlock(web3);
assert.isFalse(
await token.contract.methods
.isOperatorFor(token.defaultOperators[1], accounts[3])
.call()
);
});

it(`should let ${utils.formatAccount(accounts[4])} reauthorize the ` +
'previously revoked default operator ' +
`${utils.formatAccount(token.defaultOperators[0])}`,
async function() {
assert.isTrue(
await token.contract.methods
.isOperatorFor(token.defaultOperators[0], accounts[4])
.call()
);

await token.contract.methods
.revokeOperator(token.defaultOperators[0])
.send({ from: accounts[4], gas: 300000 });

await utils.getBlock(web3);
assert.isFalse(
await token.contract.methods
.isOperatorFor(token.defaultOperators[0], accounts[4])
.call()
);

await token.contract.methods
.authorizeOperator(token.defaultOperators[0])
.send({ from: accounts[4], gas: 300000 });

await utils.getBlock(web3);
assert.isTrue(
await token.contract.methods
.isOperatorFor(token.defaultOperators[0], accounts[4])
.call()
);
});

it(`should detect ${utils.formatAccount(accounts[3])} is not an operator ` +
`for ${utils.formatAccount(accounts[1])}`, async function() {
assert.isFalse(
Expand Down

0 comments on commit c90fcf2

Please sign in to comment.