BeaconProxy.test.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { getAddressInSlot, BeaconSlot } = require('../../helpers/storage');
  5. async function fixture() {
  6. const [admin, other] = await ethers.getSigners();
  7. const v1 = await ethers.deployContract('DummyImplementation');
  8. const v2 = await ethers.deployContract('DummyImplementationV2');
  9. const factory = await ethers.getContractFactory('BeaconProxy');
  10. const beacon = await ethers.deployContract('UpgradeableBeacon', [v1, admin]);
  11. const newBeaconProxy = (beacon, data, opts = {}) => factory.deploy(beacon, data, opts);
  12. return { admin, other, factory, beacon, v1, v2, newBeaconProxy };
  13. }
  14. describe('BeaconProxy', function () {
  15. beforeEach(async function () {
  16. Object.assign(this, await loadFixture(fixture));
  17. });
  18. describe('bad beacon is not accepted', function () {
  19. it('non-contract beacon', async function () {
  20. const notBeacon = this.other;
  21. await expect(this.newBeaconProxy(notBeacon, '0x'))
  22. .to.be.revertedWithCustomError(this.factory, 'ERC1967InvalidBeacon')
  23. .withArgs(notBeacon);
  24. });
  25. it('non-compliant beacon', async function () {
  26. const badBeacon = await ethers.deployContract('BadBeaconNoImpl');
  27. // BadBeaconNoImpl does not provide `implementation()` and has no fallback.
  28. // This causes ERC1967Utils._setBeacon to revert.
  29. await expect(this.newBeaconProxy(badBeacon, '0x')).to.be.revertedWithoutReason();
  30. });
  31. it('non-contract implementation', async function () {
  32. const badBeacon = await ethers.deployContract('BadBeaconNotContract');
  33. await expect(this.newBeaconProxy(badBeacon, '0x'))
  34. .to.be.revertedWithCustomError(this.factory, 'ERC1967InvalidImplementation')
  35. .withArgs(await badBeacon.implementation());
  36. });
  37. });
  38. describe('initialization', function () {
  39. async function assertInitialized({ value, balance }) {
  40. const beaconAddress = await getAddressInSlot(this.proxy, BeaconSlot);
  41. expect(beaconAddress).to.equal(this.beacon);
  42. const dummy = this.v1.attach(this.proxy);
  43. expect(await dummy.value()).to.equal(value);
  44. expect(await ethers.provider.getBalance(this.proxy)).to.equal(balance);
  45. }
  46. it('no initialization', async function () {
  47. this.proxy = await this.newBeaconProxy(this.beacon, '0x');
  48. await assertInitialized.bind(this)({ value: 0n, balance: 0n });
  49. });
  50. it('non-payable initialization', async function () {
  51. const value = 55n;
  52. const data = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value]);
  53. this.proxy = await this.newBeaconProxy(this.beacon, data);
  54. await assertInitialized.bind(this)({ value, balance: 0n });
  55. });
  56. it('payable initialization', async function () {
  57. const value = 55n;
  58. const data = this.v1.interface.encodeFunctionData('initializePayableWithValue', [value]);
  59. const balance = 100n;
  60. this.proxy = await this.newBeaconProxy(this.beacon, data, { value: balance });
  61. await assertInitialized.bind(this)({ value, balance });
  62. });
  63. it('reverting initialization due to value', async function () {
  64. await expect(this.newBeaconProxy(this.beacon, '0x', { value: 1n })).to.be.revertedWithCustomError(
  65. this.factory,
  66. 'ERC1967NonPayable',
  67. );
  68. });
  69. it('reverting initialization function', async function () {
  70. const data = this.v1.interface.encodeFunctionData('reverts');
  71. await expect(this.newBeaconProxy(this.beacon, data)).to.be.revertedWith('DummyImplementation reverted');
  72. });
  73. });
  74. describe('upgrade', function () {
  75. it('upgrade a proxy by upgrading its beacon', async function () {
  76. const value = 10n;
  77. const data = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value]);
  78. const proxy = await this.newBeaconProxy(this.beacon, data).then(instance => this.v1.attach(instance));
  79. // test initial values
  80. expect(await proxy.value()).to.equal(value);
  81. // test initial version
  82. expect(await proxy.version()).to.equal('V1');
  83. // upgrade beacon
  84. await this.beacon.connect(this.admin).upgradeTo(this.v2);
  85. // test upgraded version
  86. expect(await proxy.version()).to.equal('V2');
  87. });
  88. it('upgrade 2 proxies by upgrading shared beacon', async function () {
  89. const value1 = 10n;
  90. const data1 = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value1]);
  91. const proxy1 = await this.newBeaconProxy(this.beacon, data1).then(instance => this.v1.attach(instance));
  92. const value2 = 42n;
  93. const data2 = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value2]);
  94. const proxy2 = await this.newBeaconProxy(this.beacon, data2).then(instance => this.v1.attach(instance));
  95. // test initial values
  96. expect(await proxy1.value()).to.equal(value1);
  97. expect(await proxy2.value()).to.equal(value2);
  98. // test initial version
  99. expect(await proxy1.version()).to.equal('V1');
  100. expect(await proxy2.version()).to.equal('V1');
  101. // upgrade beacon
  102. await this.beacon.connect(this.admin).upgradeTo(this.v2);
  103. // test upgraded version
  104. expect(await proxy1.version()).to.equal('V2');
  105. expect(await proxy2.version()).to.equal('V2');
  106. });
  107. });
  108. });