PublicRole.behavior.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. const { shouldFail, constants, expectEvent } = require('openzeppelin-test-helpers');
  2. const { ZERO_ADDRESS } = constants;
  3. function capitalize (str) {
  4. return str.replace(/\b\w/g, l => l.toUpperCase());
  5. }
  6. // Tests that a role complies with the standard role interface, that is:
  7. // * an onlyRole modifier
  8. // * an isRole function
  9. // * an addRole function, accessible to role havers
  10. // * a renounceRole function
  11. // * roleAdded and roleRemoved events
  12. // Both the modifier and an additional internal remove function are tested through a mock contract that exposes them.
  13. // This mock contract should be stored in this.contract.
  14. //
  15. // @param authorized an account that has the role
  16. // @param otherAuthorized another account that also has the role
  17. // @param other an account that doesn't have the role, passed inside an array for ergonomics
  18. // @param rolename a string with the name of the role
  19. // @param manager undefined for regular roles, or a manager account for managed roles. In these, only the manager
  20. // account can create and remove new role bearers.
  21. function shouldBehaveLikePublicRole (authorized, otherAuthorized, [other], rolename, manager) {
  22. rolename = capitalize(rolename);
  23. describe('should behave like public role', function () {
  24. beforeEach('check preconditions', async function () {
  25. (await this.contract[`is${rolename}`](authorized)).should.equal(true);
  26. (await this.contract[`is${rolename}`](otherAuthorized)).should.equal(true);
  27. (await this.contract[`is${rolename}`](other)).should.equal(false);
  28. });
  29. if (manager === undefined) { // Managed roles are only assigned by the manager, and none are set at construction
  30. it('emits events during construction', async function () {
  31. await expectEvent.inConstruction(this.contract, `${rolename}Added`, {
  32. account: authorized,
  33. });
  34. });
  35. }
  36. it('reverts when querying roles for the null account', async function () {
  37. await shouldFail.reverting(this.contract[`is${rolename}`](ZERO_ADDRESS));
  38. });
  39. describe('access control', function () {
  40. context('from authorized account', function () {
  41. const from = authorized;
  42. it('allows access', async function () {
  43. await this.contract[`only${rolename}Mock`]({ from });
  44. });
  45. });
  46. context('from unauthorized account', function () {
  47. const from = other;
  48. it('reverts', async function () {
  49. await shouldFail.reverting(this.contract[`only${rolename}Mock`]({ from }));
  50. });
  51. });
  52. });
  53. describe('add', function () {
  54. const from = manager === undefined ? authorized : manager;
  55. context(`from ${manager ? 'the manager' : 'a role-haver'} account`, function () {
  56. it('adds role to a new account', async function () {
  57. await this.contract[`add${rolename}`](other, { from });
  58. (await this.contract[`is${rolename}`](other)).should.equal(true);
  59. });
  60. it(`emits a ${rolename}Added event`, async function () {
  61. const { logs } = await this.contract[`add${rolename}`](other, { from });
  62. expectEvent.inLogs(logs, `${rolename}Added`, { account: other });
  63. });
  64. it('reverts when adding role to an already assigned account', async function () {
  65. await shouldFail.reverting(this.contract[`add${rolename}`](authorized, { from }));
  66. });
  67. it('reverts when adding role to the null account', async function () {
  68. await shouldFail.reverting(this.contract[`add${rolename}`](ZERO_ADDRESS, { from }));
  69. });
  70. });
  71. });
  72. describe('remove', function () {
  73. // Non-managed roles have no restrictions on the mocked '_remove' function (exposed via 'remove').
  74. const from = manager || other;
  75. context(`from ${manager ? 'the manager' : 'any'} account`, function () {
  76. it('removes role from an already assigned account', async function () {
  77. await this.contract[`remove${rolename}`](authorized, { from });
  78. (await this.contract[`is${rolename}`](authorized)).should.equal(false);
  79. (await this.contract[`is${rolename}`](otherAuthorized)).should.equal(true);
  80. });
  81. it(`emits a ${rolename}Removed event`, async function () {
  82. const { logs } = await this.contract[`remove${rolename}`](authorized, { from });
  83. expectEvent.inLogs(logs, `${rolename}Removed`, { account: authorized });
  84. });
  85. it('reverts when removing from an unassigned account', async function () {
  86. await shouldFail.reverting(this.contract[`remove${rolename}`](other, { from }));
  87. });
  88. it('reverts when removing role from the null account', async function () {
  89. await shouldFail.reverting(this.contract[`remove${rolename}`](ZERO_ADDRESS, { from }));
  90. });
  91. });
  92. });
  93. describe('renouncing roles', function () {
  94. it('renounces an assigned role', async function () {
  95. await this.contract[`renounce${rolename}`]({ from: authorized });
  96. (await this.contract[`is${rolename}`](authorized)).should.equal(false);
  97. });
  98. it(`emits a ${rolename}Removed event`, async function () {
  99. const { logs } = await this.contract[`renounce${rolename}`]({ from: authorized });
  100. expectEvent.inLogs(logs, `${rolename}Removed`, { account: authorized });
  101. });
  102. it('reverts when renouncing unassigned role', async function () {
  103. await shouldFail.reverting(this.contract[`renounce${rolename}`]({ from: other }));
  104. });
  105. });
  106. });
  107. }
  108. module.exports = {
  109. shouldBehaveLikePublicRole,
  110. };