BeaconProxy.test.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { getAddressInSlot, BeaconSlot } = require('../../helpers/erc1967');
  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', async 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.address);
  24. });
  25. it('non-compliant beacon', async function () {
  26. const badBeacon = await ethers.deployContract('BadBeaconNoImpl');
  27. await expect(this.newBeaconProxy(badBeacon, '0x')).to.be.revertedWithoutReason;
  28. });
  29. it('non-contract implementation', async function () {
  30. const badBeacon = await ethers.deployContract('BadBeaconNotContract');
  31. await expect(this.newBeaconProxy(badBeacon, '0x'))
  32. .to.be.revertedWithCustomError(this.factory, 'ERC1967InvalidImplementation')
  33. .withArgs(await badBeacon.implementation());
  34. });
  35. });
  36. describe('initialization', function () {
  37. async function assertInitialized({ value, balance }) {
  38. const beaconAddress = await getAddressInSlot(this.proxy, BeaconSlot);
  39. expect(beaconAddress).to.equal(this.beacon.target);
  40. const dummy = this.v1.attach(this.proxy);
  41. expect(await dummy.value()).to.equal(value);
  42. expect(await ethers.provider.getBalance(this.proxy)).to.equal(balance);
  43. }
  44. it('no initialization', async function () {
  45. this.proxy = await this.newBeaconProxy(this.beacon, '0x');
  46. await assertInitialized.bind(this)({ value: 0n, balance: 0n });
  47. });
  48. it('non-payable initialization', async function () {
  49. const value = 55n;
  50. const data = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value]);
  51. this.proxy = await this.newBeaconProxy(this.beacon, data);
  52. await assertInitialized.bind(this)({ value, balance: 0n });
  53. });
  54. it('payable initialization', async function () {
  55. const value = 55n;
  56. const data = this.v1.interface.encodeFunctionData('initializePayableWithValue', [value]);
  57. const balance = 100n;
  58. this.proxy = await this.newBeaconProxy(this.beacon, data, { value: balance });
  59. await assertInitialized.bind(this)({ value, balance });
  60. });
  61. it('reverting initialization due to value', async function () {
  62. await expect(this.newBeaconProxy(this.beacon, '0x', { value: 1n })).to.be.revertedWithCustomError(
  63. this.factory,
  64. 'ERC1967NonPayable',
  65. );
  66. });
  67. it('reverting initialization function', async function () {
  68. const data = this.v1.interface.encodeFunctionData('reverts');
  69. await expect(this.newBeaconProxy(this.beacon, data)).to.be.revertedWith('DummyImplementation reverted');
  70. });
  71. });
  72. describe('upgrade', async function () {
  73. it('upgrade a proxy by upgrading its beacon', async function () {
  74. const value = 10n;
  75. const data = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value]);
  76. const proxy = await this.newBeaconProxy(this.beacon, data).then(instance => this.v1.attach(instance));
  77. // test initial values
  78. expect(await proxy.value()).to.equal(value);
  79. // test initial version
  80. expect(await proxy.version()).to.equal('V1');
  81. // upgrade beacon
  82. await this.beacon.connect(this.admin).upgradeTo(this.v2);
  83. // test upgraded version
  84. expect(await proxy.version()).to.equal('V2');
  85. });
  86. it('upgrade 2 proxies by upgrading shared beacon', async function () {
  87. const value1 = 10n;
  88. const data1 = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value1]);
  89. const proxy1 = await this.newBeaconProxy(this.beacon, data1).then(instance => this.v1.attach(instance));
  90. const value2 = 42n;
  91. const data2 = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value2]);
  92. const proxy2 = await this.newBeaconProxy(this.beacon, data2).then(instance => this.v1.attach(instance));
  93. // test initial values
  94. expect(await proxy1.value()).to.equal(value1);
  95. expect(await proxy2.value()).to.equal(value2);
  96. // test initial version
  97. expect(await proxy1.version()).to.equal('V1');
  98. expect(await proxy2.version()).to.equal('V1');
  99. // upgrade beacon
  100. await this.beacon.connect(this.admin).upgradeTo(this.v2);
  101. // test upgraded version
  102. expect(await proxy1.version()).to.equal('V2');
  103. expect(await proxy2.version()).to.equal('V2');
  104. });
  105. });
  106. });