ERC1967Utils.test.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. const { expectEvent, constants } = require('@openzeppelin/test-helpers');
  2. const { expectRevertCustomError } = require('../../helpers/customError');
  3. const { getAddressInSlot, setSlot, ImplementationSlot, AdminSlot, BeaconSlot } = require('../../helpers/erc1967');
  4. const { ZERO_ADDRESS } = constants;
  5. const ERC1967Utils = artifacts.require('$ERC1967Utils');
  6. const V1 = artifacts.require('DummyImplementation');
  7. const V2 = artifacts.require('CallReceiverMock');
  8. const UpgradeableBeaconMock = artifacts.require('UpgradeableBeaconMock');
  9. contract('ERC1967Utils', function (accounts) {
  10. const [, admin, anotherAccount] = accounts;
  11. const EMPTY_DATA = '0x';
  12. beforeEach('setup', async function () {
  13. this.utils = await ERC1967Utils.new();
  14. this.v1 = await V1.new();
  15. this.v2 = await V2.new();
  16. });
  17. describe('IMPLEMENTATION_SLOT', function () {
  18. beforeEach('set v1 implementation', async function () {
  19. await setSlot(this.utils, ImplementationSlot, this.v1.address);
  20. });
  21. describe('getImplementation', function () {
  22. it('returns current implementation and matches implementation slot value', async function () {
  23. expect(await this.utils.$getImplementation()).to.equal(this.v1.address);
  24. expect(await getAddressInSlot(this.utils.address, ImplementationSlot)).to.equal(this.v1.address);
  25. });
  26. });
  27. describe('upgradeToAndCall', function () {
  28. it('sets implementation in storage and emits event', async function () {
  29. const newImplementation = this.v2.address;
  30. const receipt = await this.utils.$upgradeToAndCall(newImplementation, EMPTY_DATA);
  31. expect(await getAddressInSlot(this.utils.address, ImplementationSlot)).to.equal(newImplementation);
  32. expectEvent(receipt, 'Upgraded', { implementation: newImplementation });
  33. });
  34. it('reverts when implementation does not contain code', async function () {
  35. await expectRevertCustomError(
  36. this.utils.$upgradeToAndCall(anotherAccount, EMPTY_DATA),
  37. 'ERC1967InvalidImplementation',
  38. [anotherAccount],
  39. );
  40. });
  41. describe('when data is empty', function () {
  42. it('reverts when value is sent', async function () {
  43. await expectRevertCustomError(
  44. this.utils.$upgradeToAndCall(this.v2.address, EMPTY_DATA, { value: 1 }),
  45. 'ERC1967NonPayable',
  46. [],
  47. );
  48. });
  49. });
  50. describe('when data is not empty', function () {
  51. it('delegates a call to the new implementation', async function () {
  52. const initializeData = this.v2.contract.methods.mockFunction().encodeABI();
  53. const receipt = await this.utils.$upgradeToAndCall(this.v2.address, initializeData);
  54. await expectEvent.inTransaction(receipt.tx, await V2.at(this.utils.address), 'MockFunctionCalled');
  55. });
  56. });
  57. });
  58. });
  59. describe('ADMIN_SLOT', function () {
  60. beforeEach('set admin', async function () {
  61. await setSlot(this.utils, AdminSlot, admin);
  62. });
  63. describe('getAdmin', function () {
  64. it('returns current admin and matches admin slot value', async function () {
  65. expect(await this.utils.$getAdmin()).to.equal(admin);
  66. expect(await getAddressInSlot(this.utils.address, AdminSlot)).to.equal(admin);
  67. });
  68. });
  69. describe('changeAdmin', function () {
  70. it('sets admin in storage and emits event', async function () {
  71. const newAdmin = anotherAccount;
  72. const receipt = await this.utils.$changeAdmin(newAdmin);
  73. expect(await getAddressInSlot(this.utils.address, AdminSlot)).to.equal(newAdmin);
  74. expectEvent(receipt, 'AdminChanged', { previousAdmin: admin, newAdmin: newAdmin });
  75. });
  76. it('reverts when setting the address zero as admin', async function () {
  77. await expectRevertCustomError(this.utils.$changeAdmin(ZERO_ADDRESS), 'ERC1967InvalidAdmin', [ZERO_ADDRESS]);
  78. });
  79. });
  80. });
  81. describe('BEACON_SLOT', function () {
  82. beforeEach('set beacon', async function () {
  83. this.beacon = await UpgradeableBeaconMock.new(this.v1.address);
  84. await setSlot(this.utils, BeaconSlot, this.beacon.address);
  85. });
  86. describe('getBeacon', function () {
  87. it('returns current beacon and matches beacon slot value', async function () {
  88. expect(await this.utils.$getBeacon()).to.equal(this.beacon.address);
  89. expect(await getAddressInSlot(this.utils.address, BeaconSlot)).to.equal(this.beacon.address);
  90. });
  91. });
  92. describe('upgradeBeaconToAndCall', function () {
  93. it('sets beacon in storage and emits event', async function () {
  94. const newBeacon = await UpgradeableBeaconMock.new(this.v2.address);
  95. const receipt = await this.utils.$upgradeBeaconToAndCall(newBeacon.address, EMPTY_DATA);
  96. expect(await getAddressInSlot(this.utils.address, BeaconSlot)).to.equal(newBeacon.address);
  97. expectEvent(receipt, 'BeaconUpgraded', { beacon: newBeacon.address });
  98. });
  99. it('reverts when beacon does not contain code', async function () {
  100. await expectRevertCustomError(
  101. this.utils.$upgradeBeaconToAndCall(anotherAccount, EMPTY_DATA),
  102. 'ERC1967InvalidBeacon',
  103. [anotherAccount],
  104. );
  105. });
  106. it("reverts when beacon's implementation does not contain code", async function () {
  107. const newBeacon = await UpgradeableBeaconMock.new(anotherAccount);
  108. await expectRevertCustomError(
  109. this.utils.$upgradeBeaconToAndCall(newBeacon.address, EMPTY_DATA),
  110. 'ERC1967InvalidImplementation',
  111. [anotherAccount],
  112. );
  113. });
  114. describe('when data is empty', function () {
  115. it('reverts when value is sent', async function () {
  116. const newBeacon = await UpgradeableBeaconMock.new(this.v2.address);
  117. await expectRevertCustomError(
  118. this.utils.$upgradeBeaconToAndCall(newBeacon.address, EMPTY_DATA, { value: 1 }),
  119. 'ERC1967NonPayable',
  120. [],
  121. );
  122. });
  123. });
  124. describe('when data is not empty', function () {
  125. it('delegates a call to the new implementation', async function () {
  126. const initializeData = this.v2.contract.methods.mockFunction().encodeABI();
  127. const newBeacon = await UpgradeableBeaconMock.new(this.v2.address);
  128. const receipt = await this.utils.$upgradeBeaconToAndCall(newBeacon.address, initializeData);
  129. await expectEvent.inTransaction(receipt.tx, await V2.at(this.utils.address), 'MockFunctionCalled');
  130. });
  131. });
  132. });
  133. });
  134. });