const { ethers } = require('hardhat'); const { expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const ImplV1 = artifacts.require('DummyImplementation'); const ImplV2 = artifacts.require('DummyImplementationV2'); const ProxyAdmin = artifacts.require('ProxyAdmin'); const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeableProxy'); const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableProxy'); const { getAddressInSlot, ImplementationSlot } = require('../../helpers/erc1967'); const { expectRevertCustomError } = require('../../helpers/customError'); contract('ProxyAdmin', function (accounts) { const [proxyAdminOwner, anotherAccount] = accounts; before('set implementations', async function () { this.implementationV1 = await ImplV1.new(); this.implementationV2 = await ImplV2.new(); }); beforeEach(async function () { const initializeData = Buffer.from(''); const proxy = await TransparentUpgradeableProxy.new(this.implementationV1.address, proxyAdminOwner, initializeData); const proxyNonce = await web3.eth.getTransactionCount(proxy.address); const proxyAdminAddress = ethers.getCreateAddress({ from: proxy.address, nonce: proxyNonce - 1 }); // Nonce already used this.proxyAdmin = await ProxyAdmin.at(proxyAdminAddress); this.proxy = await ITransparentUpgradeableProxy.at(proxy.address); }); it('has an owner', async function () { expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner); }); it('has an interface version', async function () { expect(await this.proxyAdmin.UPGRADE_INTERFACE_VERSION()).to.equal('5.0.0'); }); describe('without data', function () { context('with unauthorized account', function () { it('fails to upgrade', async function () { await expectRevertCustomError( this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, '0x', { from: anotherAccount, }), 'OwnableUnauthorizedAccount', [anotherAccount], ); }); }); context('with authorized account', function () { it('upgrades implementation', async function () { await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, '0x', { from: proxyAdminOwner, }); const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); expect(implementationAddress).to.be.equal(this.implementationV2.address); }); }); }); describe('with data', function () { context('with unauthorized account', function () { it('fails to upgrade', async function () { const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI(); await expectRevertCustomError( this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, { from: anotherAccount, }), 'OwnableUnauthorizedAccount', [anotherAccount], ); }); }); context('with authorized account', function () { context('with invalid callData', function () { it('fails to upgrade', async function () { const callData = '0x12345678'; await expectRevert.unspecified( this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, { from: proxyAdminOwner, }), ); }); }); context('with valid callData', function () { it('upgrades implementation', async function () { const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI(); await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, { from: proxyAdminOwner, }); const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); expect(implementationAddress).to.be.equal(this.implementationV2.address); }); }); }); }); });