ERC1967Utils.test.js 7.0 KB

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