PublicRole.behavior.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. const { expectRevert, 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 expectRevert(this.contract[`is${rolename}`](ZERO_ADDRESS),
  38. 'Roles: account is the zero address'
  39. );
  40. });
  41. describe('access control', function () {
  42. context('from authorized account', function () {
  43. const from = authorized;
  44. it('allows access', async function () {
  45. await this.contract[`only${rolename}Mock`]({ from });
  46. });
  47. });
  48. context('from unauthorized account', function () {
  49. const from = other;
  50. it('reverts', async function () {
  51. await expectRevert(this.contract[`only${rolename}Mock`]({ from }),
  52. `${rolename}Role: caller does not have the ${rolename} role`
  53. );
  54. });
  55. });
  56. });
  57. describe('add', function () {
  58. const from = manager === undefined ? authorized : manager;
  59. context(`from ${manager ? 'the manager' : 'a role-haver'} account`, function () {
  60. it('adds role to a new account', async function () {
  61. await this.contract[`add${rolename}`](other, { from });
  62. (await this.contract[`is${rolename}`](other)).should.equal(true);
  63. });
  64. it(`emits a ${rolename}Added event`, async function () {
  65. const { logs } = await this.contract[`add${rolename}`](other, { from });
  66. expectEvent.inLogs(logs, `${rolename}Added`, { account: other });
  67. });
  68. it('reverts when adding role to an already assigned account', async function () {
  69. await expectRevert(this.contract[`add${rolename}`](authorized, { from }),
  70. 'Roles: account already has role'
  71. );
  72. });
  73. it('reverts when adding role to the null account', async function () {
  74. await expectRevert(this.contract[`add${rolename}`](ZERO_ADDRESS, { from }),
  75. 'Roles: account is the zero address'
  76. );
  77. });
  78. });
  79. });
  80. describe('remove', function () {
  81. // Non-managed roles have no restrictions on the mocked '_remove' function (exposed via 'remove').
  82. const from = manager || other;
  83. context(`from ${manager ? 'the manager' : 'any'} account`, function () {
  84. it('removes role from an already assigned account', async function () {
  85. await this.contract[`remove${rolename}`](authorized, { from });
  86. (await this.contract[`is${rolename}`](authorized)).should.equal(false);
  87. (await this.contract[`is${rolename}`](otherAuthorized)).should.equal(true);
  88. });
  89. it(`emits a ${rolename}Removed event`, async function () {
  90. const { logs } = await this.contract[`remove${rolename}`](authorized, { from });
  91. expectEvent.inLogs(logs, `${rolename}Removed`, { account: authorized });
  92. });
  93. it('reverts when removing from an unassigned account', async function () {
  94. await expectRevert(this.contract[`remove${rolename}`](other, { from }),
  95. 'Roles: account does not have role'
  96. );
  97. });
  98. it('reverts when removing role from the null account', async function () {
  99. await expectRevert(this.contract[`remove${rolename}`](ZERO_ADDRESS, { from }),
  100. 'Roles: account is the zero address'
  101. );
  102. });
  103. });
  104. });
  105. describe('renouncing roles', function () {
  106. it('renounces an assigned role', async function () {
  107. await this.contract[`renounce${rolename}`]({ from: authorized });
  108. (await this.contract[`is${rolename}`](authorized)).should.equal(false);
  109. });
  110. it(`emits a ${rolename}Removed event`, async function () {
  111. const { logs } = await this.contract[`renounce${rolename}`]({ from: authorized });
  112. expectEvent.inLogs(logs, `${rolename}Removed`, { account: authorized });
  113. });
  114. it('reverts when renouncing unassigned role', async function () {
  115. await expectRevert(this.contract[`renounce${rolename}`]({ from: other }),
  116. 'Roles: account does not have role'
  117. );
  118. });
  119. });
  120. });
  121. }
  122. module.exports = {
  123. shouldBehaveLikePublicRole,
  124. };