ERC1967Utils.test.js 7.0 KB

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