Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MintableToken using Roles #1236

Merged
merged 38 commits into from
Sep 4, 2018
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4fb9bb7
Minor test style improvements (#1219)
nventuro Aug 22, 2018
2adb491
Add ERC165Query library (#1086)
ldub Aug 22, 2018
d58fac8
Added mint and burn tests for zero amounts. (#1230)
nventuro Aug 23, 2018
48fe7b8
Changed .eq to .equal. (#1231)
nventuro Aug 23, 2018
9e6307f
ERC721 pausable token (#1154)
urvalla Aug 24, 2018
a466e76
Add some detail to releasing steps (#1190)
frangio Aug 24, 2018
a9f910d
Increase test coverage (#1237)
nventuro Aug 24, 2018
524a276
ci: trigger docs update on tag (#1186)
Aug 28, 2018
4b3b6b6
Roles can now be transfered.
nventuro Aug 23, 2018
6ea040f
MintableToken now uses Roles.
nventuro Aug 22, 2018
9bc946c
Fixed FinalizableCrowdsale test.
nventuro Aug 23, 2018
32b84b9
Fixed tests related to MintableToken.
nventuro Aug 23, 2018
be6b78e
Removed Roles.check.
nventuro Aug 23, 2018
7a3dace
Renamed transferMintPermission.
nventuro Aug 23, 2018
5c71cf5
Moved MinterRole
nventuro Aug 23, 2018
cf57fbd
Fixed RBAC.
nventuro Aug 23, 2018
72aa867
Adressed review comments.
nventuro Aug 24, 2018
d080b3a
Addressed review comments
nventuro Aug 28, 2018
10b10a4
Fixed linter errors.
nventuro Aug 28, 2018
132994d
Added Events tests of Pausable contract (#1207)
viquezclaudio Aug 28, 2018
4e7d150
Fixed roles tests.
nventuro Aug 28, 2018
1c05324
Rename events to past-tense (#1181)
Aug 29, 2018
1c57637
fix: refactor sign.js and related tests (#1243)
shrugs Aug 29, 2018
b694326
Added "_" sufix to internal variables (#1171)
viquezclaudio Aug 29, 2018
c54681a
Added PublicRole test.
nventuro Aug 30, 2018
894fbed
Fixed crowdsale tests.
nventuro Aug 31, 2018
2e0713b
Rename ERC interfaces to I prefix (#1252)
frangio Aug 31, 2018
4385fd5
added explicit visibility (#1261)
vyomshm Sep 1, 2018
964bc40
Remove underscores from event parameters. (#1258)
Sep 3, 2018
2441fd7
Move contracts to subdirectories (#1253)
Sep 3, 2018
bd994a8
Remove HasNoEther, HasNoTokens, HasNoContracts, and NoOwner (#1254)
frangio Sep 3, 2018
fbfd1fe
Functions in interfaces changed to "external" (#1263)
Miraj98 Sep 3, 2018
b0f20d4
Add a leading underscore to internal and private functions. (#1257)
Sep 3, 2018
f4eb51a
Improve encapsulation on SignatureBouncer, Whitelist and RBAC example…
Sep 3, 2018
69f7f49
Merge branch 'master' into rbac-to-roles
nventuro Sep 4, 2018
41d0c08
Addressed review comments.
nventuro Sep 4, 2018
2136be9
Merge branch 'rbac-migration' into rbac-to-roles
nventuro Sep 4, 2018
b2c48b6
Fixed build error.
nventuro Sep 4, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions contracts/access/rbac/MinterRole.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
pragma solidity ^0.4.24;

import "./Roles.sol";


contract MinterRole {
using Roles for Roles.Role;

Roles.Role private minters;

constructor(address[] _minters) public {
minters.addMany(_minters);
}

function transferMinter(address _account) public {
minters.transfer(_account);
}

function renounceMinter() public {
minters.renounce();
}

function isMinter(address _account) public view returns (bool) {
frangio marked this conversation as resolved.
Show resolved Hide resolved
return minters.has(_account);
}

modifier onlyMinter() {
require(isMinter(msg.sender));
_;
}

function _addMinter(address _account) internal {
minters.add(_account);
}

function _removeMinter(address _account) internal {
minters.remove(_account);
}
}
2 changes: 1 addition & 1 deletion contracts/access/rbac/RBAC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract RBAC {
public
view
{
roles[_role].check(_operator);
require(roles[_role].has(_operator));
}

/**
Expand Down
8 changes: 0 additions & 8 deletions contracts/access/rbac/Roles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,6 @@ library Roles {
remove(_role, msg.sender);
}

/**
* @dev check if an account has this role
* // reverts
*/
function check(Role storage _role, address _account) internal view {
require(has(_role, _account));
}

/**
* @dev check if an account has this role
* @return bool
Expand Down
5 changes: 5 additions & 0 deletions contracts/examples/SampleCrowdsale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ contract SampleCrowdsaleToken is MintableToken {
string public constant symbol = "SCT";
uint8 public constant decimals = 18;

constructor(address[] _minters)
MintableToken(_minters)
public
{
}
}


Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/FinalizableCrowdsaleImpl.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pragma solidity ^0.4.24;

import "../token/ERC20/MintableToken.sol";
import "../token/ERC20/ERC20.sol";
import "../crowdsale/distribution/FinalizableCrowdsale.sol";


Expand All @@ -11,7 +11,7 @@ contract FinalizableCrowdsaleImpl is FinalizableCrowdsale {
uint256 _closingTime,
uint256 _rate,
address _wallet,
MintableToken _token
ERC20 _token
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is IERC20 now in master. It won't be a merge error and the tests will pass, so we should keep an eye on it when we merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I expected the merge errors to cover most of these.

)
public
Crowdsale(_rate, _wallet, _token)
Expand Down
21 changes: 21 additions & 0 deletions contracts/mocks/MintableTokenMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
pragma solidity ^0.4.24;

import "../token/ERC20/MintableToken.sol";


// Mock contract exposing internal methods
contract MintableTokenMock is MintableToken {
constructor(address[] minters) MintableToken(minters) public {
}

function addMinter(address _account) public {
_addMinter(_account);
}

function removeMinter(address _account) public {
_removeMinter(_account);
}

function onlyMinterMock() public view onlyMinter {
}
}
4 changes: 0 additions & 4 deletions contracts/mocks/RolesMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ contract RolesMock {
dummyRole.transfer(_account);
}

function check(address _account) public view {
dummyRole.check(_account);
}

function has(address _account) public view returns (bool) {
return dummyRole.has(_account);
}
Expand Down
5 changes: 4 additions & 1 deletion contracts/token/ERC20/CappedToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ contract CappedToken is MintableToken {

uint256 public cap;

constructor(uint256 _cap) public {
constructor(uint256 _cap, address[] _minters)
public
MintableToken(_minters)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: does solium complain that the public modifier has to be above MintableToken here? Convention is the inherited contract instantiation and then the visibility modifier

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't complain either if I place it first or last. We don't seem to follow that convention though, in some of our contracts visibility is indeed first, we should get a Solium rule and fix that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting. I'd support inheritances before visibility for constructors, since that seems to be the convention in contracts I've seen.

{
require(_cap > 0);
cap = _cap;
}
Expand Down
18 changes: 9 additions & 9 deletions contracts/token/ERC20/MintableToken.sol
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
pragma solidity ^0.4.24;

import "./StandardToken.sol";
import "../../ownership/Ownable.sol";
import "../../access/rbac/MinterRole.sol";


/**
* @title Mintable token
* @dev Simple ERC20 Token example, with mintable token creation
* Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol
*/
contract MintableToken is StandardToken, Ownable {
contract MintableToken is StandardToken, MinterRole {
event Mint(address indexed to, uint256 amount);
event MintFinished();

bool public mintingFinished = false;

constructor(address[] _minters)
MinterRole(_minters)
public
{
}

modifier canMint() {
require(!mintingFinished);
_;
}

modifier hasMintPermission() {
require(msg.sender == owner);
_;
}

/**
* @dev Function to mint tokens
* @param _to The address that will receive the minted tokens.
Expand All @@ -37,7 +37,7 @@ contract MintableToken is StandardToken, Ownable {
uint256 _amount
)
public
hasMintPermission
onlyMinter
canMint
returns (bool)
{
Expand All @@ -50,7 +50,7 @@ contract MintableToken is StandardToken, Ownable {
* @dev Function to stop minting new tokens.
* @return True if the operation was successful.
*/
function finishMinting() public onlyOwner canMint returns (bool) {
function finishMinting() public onlyMinter canMint returns (bool) {
mintingFinished = true;
emit MintFinished();
return true;
Expand Down
98 changes: 98 additions & 0 deletions test/access/rbac/PublicRole.behavior.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const { assertRevert } = require('../../helpers/assertRevert');

require('chai')
.should();

function capitalize (str) {
return str.replace(/\b\w/g, l => l.toUpperCase());
}

function shouldBehaveLikePublicRole (authorized, otherAuthorized, [anyone], rolename) {
rolename = capitalize(rolename);
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';

describe(`role ${rolename}`, function () {
beforeEach('check preconditions', async function () {
(await this.contract[`is${rolename}`](authorized)).should.equal(true);
(await this.contract[`is${rolename}`](otherAuthorized)).should.equal(true);
(await this.contract[`is${rolename}`](anyone)).should.equal(false);
});

describe('add', function () {
it('adds role to a new account', async function () {
await this.contract[`add${rolename}`](authorized);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be adding the role to anyone?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, looks like I forgot to update those tests after I added the preconditions :/

(await this.contract[`is${rolename}`](authorized)).should.equal(true);
(await this.contract[`is${rolename}`](anyone)).should.equal(false);
});

it('adds role to an already-assigned account', async function () {
await this.contract[`add${rolename}`](authorized);
await this.contract[`add${rolename}`](authorized);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have a precondition that authorized is authorized, is it necessary to run add twice?

(await this.contract[`is${rolename}`](authorized)).should.equal(true);
});

it('doesn\'t revert when adding role to the null account', async function () {
frangio marked this conversation as resolved.
Show resolved Hide resolved
await this.contract[`add${rolename}`](ZERO_ADDRESS);
});
});

describe('remove', function () {
it('removes role from an already assgined account', async function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in assigned.

await this.contract[`remove${rolename}`](authorized);
(await this.contract[`is${rolename}`](authorized)).should.equal(false);
(await this.contract[`is${rolename}`](otherAuthorized)).should.equal(true);
});

it('doesn\'t revert when removing from an unassigned account', async function () {
await this.contract[`remove${rolename}`](anyone);
});

it('doesn\'t revert when removing role from the null account', async function () {
await this.contract[`remove${rolename}`](ZERO_ADDRESS);
});
});

describe('transfering', function () {
context('from account with role', function () {
const from = authorized;

it('transfers to other account without the role', async function () {
await this.contract[`transfer${rolename}`](anyone, { from });
(await this.contract[`is${rolename}`](anyone)).should.equal(true);
(await this.contract[`is${rolename}`](authorized)).should.equal(false);
});

it('reverts when transfering to an account with role', async function () {
await assertRevert(this.contract[`transfer${rolename}`](otherAuthorized, { from }));
});

it('reverts when transfering to the null account', async function () {
await assertRevert(this.contract[`transfer${rolename}`](ZERO_ADDRESS, { from }));
});
});

context('from account without role', function () {
const from = anyone;

it('reverts', async function () {
await assertRevert(this.contract[`transfer${rolename}`](anyone, { from }));
});
});
});

describe('renouncing roles', function () {
it('renounces an assigned role', async function () {
await this.contract[`renounce${rolename}`]({ from: authorized });
(await this.contract[`is${rolename}`](authorized)).should.equal(false);
});

it('doesn\'t revert when renouncing unassigned role', async function () {
await this.contract[`renounce${rolename}`]({ from: anyone });
});
});
});
}

module.exports = {
shouldBehaveLikePublicRole,
};
37 changes: 14 additions & 23 deletions test/access/rbac/Roles.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,37 @@ contract('Roles', function ([_, authorized, otherAuthorized, anyone]) {

beforeEach(async function () {
this.roles = await RolesMock.new();
this.testRole = async (account, expected) => {
if (expected) {
(await this.roles.has(account)).should.equal(true);
await this.roles.check(account); // this call shouldn't revert, but is otherwise a no-op
} else {
(await this.roles.has(account)).should.equal(false);
await assertRevert(this.roles.check(account));
}
};
});

context('initially', function () {
it('doesn\'t pre-assign roles', async function () {
await this.testRole(authorized, false);
await this.testRole(otherAuthorized, false);
await this.testRole(anyone, false);
(await this.roles.has(authorized)).should.equal(false);
(await this.roles.has(otherAuthorized)).should.equal(false);
(await this.roles.has(anyone)).should.equal(false);
});

describe('adding roles', function () {
it('adds roles to a single account', async function () {
await this.roles.add(authorized);
await this.testRole(authorized, true);
await this.testRole(anyone, false);
(await this.roles.has(authorized)).should.equal(true);
(await this.roles.has(anyone)).should.equal(false);
});

it('adds roles to an already-assigned account', async function () {
await this.roles.add(authorized);
await this.roles.add(authorized);
await this.testRole(authorized, true);
(await this.roles.has(authorized)).should.equal(true);
});

it('adds roles to multiple accounts', async function () {
await this.roles.addMany([authorized, otherAuthorized]);
await this.testRole(authorized, true);
await this.testRole(otherAuthorized, true);
(await this.roles.has(authorized)).should.equal(true);
(await this.roles.has(otherAuthorized)).should.equal(true);
});

it('adds roles to multiple identical accounts', async function () {
await this.roles.addMany([authorized, authorized]);
await this.testRole(authorized, true);
(await this.roles.has(authorized)).should.equal(true);
});

it('doesn\'t revert when adding roles to the null account', async function () {
Expand All @@ -66,8 +57,8 @@ contract('Roles', function ([_, authorized, otherAuthorized, anyone]) {
describe('removing roles', function () {
it('removes a single role', async function () {
await this.roles.remove(authorized);
await this.testRole(authorized, false);
await this.testRole(otherAuthorized, true);
(await this.roles.has(authorized)).should.equal(false);
(await this.roles.has(otherAuthorized)).should.equal(true);
});

it('doesn\'t revert when removing unassigned roles', async function () {
Expand All @@ -85,8 +76,8 @@ contract('Roles', function ([_, authorized, otherAuthorized, anyone]) {

it('transfers to other account with no role', async function () {
await this.roles.transfer(anyone, { from });
await this.testRole(anyone, true);
await this.testRole(authorized, false);
(await this.roles.has(anyone)).should.equal(true);
(await this.roles.has(authorized)).should.equal(false);
});

it('reverts when transfering to an account with role', async function () {
Expand All @@ -110,7 +101,7 @@ contract('Roles', function ([_, authorized, otherAuthorized, anyone]) {
describe('renouncing roles', function () {
it('renounces an assigned role', async function () {
await this.roles.renounce({ from: authorized });
await this.testRole(authorized, false);
(await this.roles.has(authorized)).should.equal(false);
});

it('doesn\'t revert when renouncing unassigned role', async function () {
Expand Down
Loading