PublicRole.behavior.js 5.9 KB

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