Make TransparentUpgradeableProxy deploy its ProxyAdmin and optimize proxy interfaces (#4382)
Co-authored-by: Francisco <fg@frang.io> Co-authored-by: Eric Lau <ericglau@outlook.com> Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
const { expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { computeCreate2Address } = require('../helpers/create2');
|
||||
const { computeCreate2Address } = require('../helpers/create');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
|
||||
@ -3,11 +3,10 @@ const shouldBehaveLikeProxy = require('../Proxy.behaviour');
|
||||
const ERC1967Proxy = artifacts.require('ERC1967Proxy');
|
||||
|
||||
contract('ERC1967Proxy', function (accounts) {
|
||||
const [proxyAdminOwner] = accounts;
|
||||
|
||||
const createProxy = async function (implementation, _admin, initData, opts) {
|
||||
return ERC1967Proxy.new(implementation, initData, opts);
|
||||
// `undefined`, `null` and other false-ish opts will not be forwarded.
|
||||
const createProxy = async function (implementation, initData, opts) {
|
||||
return ERC1967Proxy.new(implementation, initData, ...[opts].filter(Boolean));
|
||||
};
|
||||
|
||||
shouldBehaveLikeProxy(createProxy, undefined, proxyAdminOwner);
|
||||
shouldBehaveLikeProxy(createProxy, accounts);
|
||||
});
|
||||
|
||||
160
test/proxy/ERC1967/ERC1967Utils.test.js
Normal file
160
test/proxy/ERC1967/ERC1967Utils.test.js
Normal file
@ -0,0 +1,160 @@
|
||||
const { expectEvent, constants } = require('@openzeppelin/test-helpers');
|
||||
const { expectRevertCustomError } = require('../../helpers/customError');
|
||||
const { getAddressInSlot, setSlot, ImplementationSlot, AdminSlot, BeaconSlot } = require('../../helpers/erc1967');
|
||||
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const ERC1967Utils = artifacts.require('$ERC1967Utils');
|
||||
|
||||
const V1 = artifacts.require('DummyImplementation');
|
||||
const V2 = artifacts.require('CallReceiverMock');
|
||||
const UpgradeableBeaconMock = artifacts.require('UpgradeableBeaconMock');
|
||||
|
||||
contract('ERC1967Utils', function (accounts) {
|
||||
const [, admin, anotherAccount] = accounts;
|
||||
const EMPTY_DATA = '0x';
|
||||
|
||||
beforeEach('setup', async function () {
|
||||
this.utils = await ERC1967Utils.new();
|
||||
this.v1 = await V1.new();
|
||||
this.v2 = await V2.new();
|
||||
});
|
||||
|
||||
describe('IMPLEMENTATION_SLOT', function () {
|
||||
beforeEach('set v1 implementation', async function () {
|
||||
await setSlot(this.utils, ImplementationSlot, this.v1.address);
|
||||
});
|
||||
|
||||
describe('getImplementation', function () {
|
||||
it('returns current implementation and matches implementation slot value', async function () {
|
||||
expect(await this.utils.$getImplementation()).to.equal(this.v1.address);
|
||||
expect(await getAddressInSlot(this.utils.address, ImplementationSlot)).to.equal(this.v1.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe('upgradeToAndCall', function () {
|
||||
it('sets implementation in storage and emits event', async function () {
|
||||
const newImplementation = this.v2.address;
|
||||
const receipt = await this.utils.$upgradeToAndCall(newImplementation, EMPTY_DATA);
|
||||
|
||||
expect(await getAddressInSlot(this.utils.address, ImplementationSlot)).to.equal(newImplementation);
|
||||
expectEvent(receipt, 'Upgraded', { implementation: newImplementation });
|
||||
});
|
||||
|
||||
it('reverts when implementation does not contain code', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.utils.$upgradeToAndCall(anotherAccount, EMPTY_DATA),
|
||||
'ERC1967InvalidImplementation',
|
||||
[anotherAccount],
|
||||
);
|
||||
});
|
||||
|
||||
describe('when data is empty', function () {
|
||||
it('reverts when value is sent', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.utils.$upgradeToAndCall(this.v2.address, EMPTY_DATA, { value: 1 }),
|
||||
'ERC1967NonPayable',
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when data is not empty', function () {
|
||||
it('delegates a call to the new implementation', async function () {
|
||||
const initializeData = this.v2.contract.methods.mockFunction().encodeABI();
|
||||
const receipt = await this.utils.$upgradeToAndCall(this.v2.address, initializeData);
|
||||
await expectEvent.inTransaction(receipt.tx, await V2.at(this.utils.address), 'MockFunctionCalled');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ADMIN_SLOT', function () {
|
||||
beforeEach('set admin', async function () {
|
||||
await setSlot(this.utils, AdminSlot, admin);
|
||||
});
|
||||
|
||||
describe('getAdmin', function () {
|
||||
it('returns current admin and matches admin slot value', async function () {
|
||||
expect(await this.utils.$getAdmin()).to.equal(admin);
|
||||
expect(await getAddressInSlot(this.utils.address, AdminSlot)).to.equal(admin);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeAdmin', function () {
|
||||
it('sets admin in storage and emits event', async function () {
|
||||
const newAdmin = anotherAccount;
|
||||
const receipt = await this.utils.$changeAdmin(newAdmin);
|
||||
|
||||
expect(await getAddressInSlot(this.utils.address, AdminSlot)).to.equal(newAdmin);
|
||||
expectEvent(receipt, 'AdminChanged', { previousAdmin: admin, newAdmin: newAdmin });
|
||||
});
|
||||
|
||||
it('reverts when setting the address zero as admin', async function () {
|
||||
await expectRevertCustomError(this.utils.$changeAdmin(ZERO_ADDRESS), 'ERC1967InvalidAdmin', [ZERO_ADDRESS]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('BEACON_SLOT', function () {
|
||||
beforeEach('set beacon', async function () {
|
||||
this.beacon = await UpgradeableBeaconMock.new(this.v1.address);
|
||||
await setSlot(this.utils, BeaconSlot, this.beacon.address);
|
||||
});
|
||||
|
||||
describe('getBeacon', function () {
|
||||
it('returns current beacon and matches beacon slot value', async function () {
|
||||
expect(await this.utils.$getBeacon()).to.equal(this.beacon.address);
|
||||
expect(await getAddressInSlot(this.utils.address, BeaconSlot)).to.equal(this.beacon.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe('upgradeBeaconToAndCall', function () {
|
||||
it('sets beacon in storage and emits event', async function () {
|
||||
const newBeacon = await UpgradeableBeaconMock.new(this.v2.address);
|
||||
const receipt = await this.utils.$upgradeBeaconToAndCall(newBeacon.address, EMPTY_DATA);
|
||||
|
||||
expect(await getAddressInSlot(this.utils.address, BeaconSlot)).to.equal(newBeacon.address);
|
||||
expectEvent(receipt, 'BeaconUpgraded', { beacon: newBeacon.address });
|
||||
});
|
||||
|
||||
it('reverts when beacon does not contain code', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.utils.$upgradeBeaconToAndCall(anotherAccount, EMPTY_DATA),
|
||||
'ERC1967InvalidBeacon',
|
||||
[anotherAccount],
|
||||
);
|
||||
});
|
||||
|
||||
it("reverts when beacon's implementation does not contain code", async function () {
|
||||
const newBeacon = await UpgradeableBeaconMock.new(anotherAccount);
|
||||
|
||||
await expectRevertCustomError(
|
||||
this.utils.$upgradeBeaconToAndCall(newBeacon.address, EMPTY_DATA),
|
||||
'ERC1967InvalidImplementation',
|
||||
[anotherAccount],
|
||||
);
|
||||
});
|
||||
|
||||
describe('when data is empty', function () {
|
||||
it('reverts when value is sent', async function () {
|
||||
const newBeacon = await UpgradeableBeaconMock.new(this.v2.address);
|
||||
await expectRevertCustomError(
|
||||
this.utils.$upgradeBeaconToAndCall(newBeacon.address, EMPTY_DATA, { value: 1 }),
|
||||
'ERC1967NonPayable',
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when data is not empty', function () {
|
||||
it('delegates a call to the new implementation', async function () {
|
||||
const initializeData = this.v2.contract.methods.mockFunction().encodeABI();
|
||||
const newBeacon = await UpgradeableBeaconMock.new(this.v2.address);
|
||||
const receipt = await this.utils.$upgradeBeaconToAndCall(newBeacon.address, initializeData);
|
||||
await expectEvent.inTransaction(receipt.tx, await V2.at(this.utils.address), 'MockFunctionCalled');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -2,18 +2,15 @@ const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { getSlot, ImplementationSlot } = require('../helpers/erc1967');
|
||||
|
||||
const { expect } = require('chai');
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
|
||||
const DummyImplementation = artifacts.require('DummyImplementation');
|
||||
|
||||
module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, proxyCreator) {
|
||||
module.exports = function shouldBehaveLikeProxy(createProxy, accounts) {
|
||||
it('cannot be initialized with a non-contract address', async function () {
|
||||
const nonContractAddress = proxyCreator;
|
||||
const nonContractAddress = accounts[0];
|
||||
const initializeData = Buffer.from('');
|
||||
await expectRevert.unspecified(
|
||||
createProxy(nonContractAddress, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
}),
|
||||
);
|
||||
await expectRevert.unspecified(createProxy(nonContractAddress, initializeData));
|
||||
});
|
||||
|
||||
before('deploy implementation', async function () {
|
||||
@ -42,11 +39,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
this.proxy = (await createProxy(this.implementation, initializeData)).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({ value: 0, balance: 0 });
|
||||
@ -55,16 +48,13 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
describe('when sending some balance', function () {
|
||||
const value = 10e5;
|
||||
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
value,
|
||||
})
|
||||
).address;
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(
|
||||
createProxy(this.implementation, initializeData, { value }),
|
||||
'ERC1967NonPayable',
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
assertProxyInitialization({ value: 0, balance: value });
|
||||
});
|
||||
});
|
||||
|
||||
@ -75,11 +65,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
this.proxy = (await createProxy(this.implementation, initializeData)).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
@ -92,9 +78,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
const value = 10e5;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }),
|
||||
);
|
||||
await expectRevert.unspecified(createProxy(this.implementation, initializeData, { value }));
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -105,11 +89,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
this.proxy = (await createProxy(this.implementation, initializeData)).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
@ -122,12 +102,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
const value = 10e5;
|
||||
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
value,
|
||||
})
|
||||
).address;
|
||||
this.proxy = (await createProxy(this.implementation, initializeData, { value })).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
@ -147,11 +122,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
this.proxy = (await createProxy(this.implementation, initializeData)).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
@ -164,9 +135,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
const value = 10e5;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }),
|
||||
);
|
||||
await expectRevert.unspecified(createProxy(this.implementation, initializeData, { value }));
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -179,11 +148,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
this.proxy = (await createProxy(this.implementation, initializeData)).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
@ -196,12 +161,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
const value = 10e5;
|
||||
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
value,
|
||||
})
|
||||
).address;
|
||||
this.proxy = (await createProxy(this.implementation, initializeData, { value })).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
@ -215,10 +175,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress,
|
||||
const initializeData = new DummyImplementation('').contract.methods.reverts().encodeABI();
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator }),
|
||||
'DummyImplementation reverted',
|
||||
);
|
||||
await expectRevert(createProxy(this.implementation, initializeData), 'DummyImplementation reverted');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -59,9 +59,8 @@ contract('BeaconProxy', function (accounts) {
|
||||
|
||||
it('no initialization', async function () {
|
||||
const data = Buffer.from('');
|
||||
const balance = '10';
|
||||
this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance });
|
||||
await this.assertInitialized({ value: '0', balance });
|
||||
this.proxy = await BeaconProxy.new(this.beacon.address, data);
|
||||
await this.assertInitialized({ value: '0', balance: '0' });
|
||||
});
|
||||
|
||||
it('non-payable initialization', async function () {
|
||||
@ -79,7 +78,16 @@ contract('BeaconProxy', function (accounts) {
|
||||
await this.assertInitialized({ value, balance });
|
||||
});
|
||||
|
||||
it('reverting initialization', async function () {
|
||||
it('reverting initialization due to value', async function () {
|
||||
const data = Buffer.from('');
|
||||
await expectRevertCustomError(
|
||||
BeaconProxy.new(this.beacon.address, data, { value: '1' }),
|
||||
'ERC1967NonPayable',
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it('reverting initialization function', async function () {
|
||||
const data = this.implementationV0.contract.methods.reverts().encodeABI();
|
||||
await expectRevert(BeaconProxy.new(this.beacon.address, data), 'DummyImplementation reverted');
|
||||
});
|
||||
|
||||
@ -8,6 +8,7 @@ const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableP
|
||||
|
||||
const { getAddressInSlot, ImplementationSlot } = require('../../helpers/erc1967');
|
||||
const { expectRevertCustomError } = require('../../helpers/customError');
|
||||
const { computeCreateAddress } = require('../../helpers/create');
|
||||
|
||||
contract('ProxyAdmin', function (accounts) {
|
||||
const [proxyAdminOwner, anotherAccount] = accounts;
|
||||
@ -19,12 +20,12 @@ contract('ProxyAdmin', function (accounts) {
|
||||
|
||||
beforeEach(async function () {
|
||||
const initializeData = Buffer.from('');
|
||||
this.proxyAdmin = await ProxyAdmin.new(proxyAdminOwner);
|
||||
const proxy = await TransparentUpgradeableProxy.new(
|
||||
this.implementationV1.address,
|
||||
this.proxyAdmin.address,
|
||||
initializeData,
|
||||
);
|
||||
const proxy = await TransparentUpgradeableProxy.new(this.implementationV1.address, proxyAdminOwner, initializeData);
|
||||
|
||||
const proxyNonce = await web3.eth.getTransactionCount(proxy.address);
|
||||
const proxyAdminAddress = computeCreateAddress(proxy.address, proxyNonce - 1); // Nonce already used
|
||||
this.proxyAdmin = await ProxyAdmin.at(proxyAdminAddress);
|
||||
|
||||
this.proxy = await ITransparentUpgradeableProxy.at(proxy.address);
|
||||
});
|
||||
|
||||
@ -32,11 +33,17 @@ contract('ProxyAdmin', function (accounts) {
|
||||
expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner);
|
||||
});
|
||||
|
||||
describe('#upgrade', function () {
|
||||
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.upgrade(this.proxy.address, this.implementationV2.address, { from: anotherAccount }),
|
||||
this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, '0x', {
|
||||
from: anotherAccount,
|
||||
}),
|
||||
'OwnableUnauthorizedAccount',
|
||||
[anotherAccount],
|
||||
);
|
||||
@ -45,7 +52,9 @@ contract('ProxyAdmin', function (accounts) {
|
||||
|
||||
context('with authorized account', function () {
|
||||
it('upgrades implementation', async function () {
|
||||
await this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: proxyAdminOwner });
|
||||
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);
|
||||
@ -53,7 +62,7 @@ contract('ProxyAdmin', function (accounts) {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#upgradeAndCall', function () {
|
||||
describe('with data', function () {
|
||||
context('with unauthorized account', function () {
|
||||
it('fails to upgrade', async function () {
|
||||
const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI();
|
||||
|
||||
@ -5,6 +5,8 @@ const { expectRevertCustomError } = require('../../helpers/customError');
|
||||
|
||||
const { expect } = require('chai');
|
||||
const { web3 } = require('hardhat');
|
||||
const { computeCreateAddress } = require('../../helpers/create');
|
||||
const { impersonate } = require('../../helpers/account');
|
||||
|
||||
const Implementation1 = artifacts.require('Implementation1');
|
||||
const Implementation2 = artifacts.require('Implementation2');
|
||||
@ -16,9 +18,23 @@ const MigratableMockV3 = artifacts.require('MigratableMockV3');
|
||||
const InitializableMock = artifacts.require('InitializableMock');
|
||||
const DummyImplementation = artifacts.require('DummyImplementation');
|
||||
const ClashingImplementation = artifacts.require('ClashingImplementation');
|
||||
const Ownable = artifacts.require('Ownable');
|
||||
|
||||
module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProxy, accounts) {
|
||||
const [proxyAdminAddress, proxyAdminOwner, anotherAccount] = accounts;
|
||||
module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProxy, initialOwner, accounts) {
|
||||
const [anotherAccount] = accounts;
|
||||
|
||||
async function createProxyWithImpersonatedProxyAdmin(logic, initData, opts = undefined) {
|
||||
const proxy = await createProxy(logic, initData, opts);
|
||||
|
||||
// Expect proxy admin to be the first and only contract created by the proxy
|
||||
const proxyAdminAddress = computeCreateAddress(proxy.address, 1);
|
||||
await impersonate(proxyAdminAddress);
|
||||
|
||||
return {
|
||||
proxy,
|
||||
proxyAdminAddress,
|
||||
};
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
this.implementationV0 = (await DummyImplementation.new()).address;
|
||||
@ -27,10 +43,12 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
beforeEach(async function () {
|
||||
const initializeData = Buffer.from('');
|
||||
this.proxy = await createProxy(this.implementationV0, proxyAdminAddress, initializeData, {
|
||||
from: proxyAdminOwner,
|
||||
});
|
||||
this.proxyAddress = this.proxy.address;
|
||||
const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin(
|
||||
this.implementationV0,
|
||||
initializeData,
|
||||
);
|
||||
this.proxy = proxy;
|
||||
this.proxyAdminAddress = proxyAdminAddress;
|
||||
});
|
||||
|
||||
describe('implementation', function () {
|
||||
@ -40,7 +58,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
});
|
||||
|
||||
it('delegates to the implementation', async function () {
|
||||
const dummy = new DummyImplementation(this.proxyAddress);
|
||||
const dummy = new DummyImplementation(this.proxy.address);
|
||||
const value = await dummy.get();
|
||||
|
||||
expect(value).to.equal(true);
|
||||
@ -49,64 +67,33 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
describe('proxy admin', function () {
|
||||
it('emits AdminChanged event during construction', async function () {
|
||||
expectEvent.inConstruction(this.proxy, 'AdminChanged', {
|
||||
await expectEvent.inConstruction(this.proxy, 'AdminChanged', {
|
||||
previousAdmin: ZERO_ADDRESS,
|
||||
newAdmin: proxyAdminAddress,
|
||||
newAdmin: this.proxyAdminAddress,
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the admin in the storage', async function () {
|
||||
expect(await getAddressInSlot(this.proxy, AdminSlot)).to.be.equal(proxyAdminAddress);
|
||||
it('sets the proxy admin in storage with the correct initial owner', async function () {
|
||||
expect(await getAddressInSlot(this.proxy, AdminSlot)).to.be.equal(this.proxyAdminAddress);
|
||||
const proxyAdmin = await Ownable.at(this.proxyAdminAddress);
|
||||
expect(await proxyAdmin.owner()).to.be.equal(initialOwner);
|
||||
});
|
||||
|
||||
it('can overwrite the admin by the implementation', async function () {
|
||||
const dummy = new DummyImplementation(this.proxyAddress);
|
||||
const dummy = new DummyImplementation(this.proxy.address);
|
||||
await dummy.unsafeOverrideAdmin(anotherAccount);
|
||||
const ERC1967AdminSlotValue = await getAddressInSlot(this.proxy, AdminSlot);
|
||||
expect(ERC1967AdminSlotValue).to.be.equal(anotherAccount);
|
||||
|
||||
// Still allows previous admin to execute admin operations
|
||||
expect(ERC1967AdminSlotValue).to.not.equal(proxyAdminAddress);
|
||||
expectEvent(await this.proxy.upgradeTo(this.implementationV1, { from: proxyAdminAddress }), 'Upgraded', {
|
||||
implementation: this.implementationV1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('upgradeTo', function () {
|
||||
describe('when the sender is the admin', function () {
|
||||
const from = proxyAdminAddress;
|
||||
|
||||
describe('when the given implementation is different from the current one', function () {
|
||||
it('upgrades to the requested implementation', async function () {
|
||||
await this.proxy.upgradeTo(this.implementationV1, { from });
|
||||
|
||||
const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot);
|
||||
expect(implementationAddress).to.be.equal(this.implementationV1);
|
||||
});
|
||||
|
||||
it('emits an event', async function () {
|
||||
expectEvent(await this.proxy.upgradeTo(this.implementationV1, { from }), 'Upgraded', {
|
||||
implementation: this.implementationV1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given implementation is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(this.proxy.upgradeTo(ZERO_ADDRESS, { from }), 'ERC1967InvalidImplementation', [
|
||||
ZERO_ADDRESS,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender is not the admin', function () {
|
||||
const from = anotherAccount;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(this.proxy.upgradeTo(this.implementationV1, { from }));
|
||||
});
|
||||
expect(ERC1967AdminSlotValue).to.not.equal(this.proxyAdminAddress);
|
||||
expectEvent(
|
||||
await this.proxy.upgradeToAndCall(this.implementationV1, '0x', { from: this.proxyAdminAddress }),
|
||||
'Upgraded',
|
||||
{
|
||||
implementation: this.implementationV1,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -120,11 +107,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
const initializeData = new InitializableMock('').contract.methods['initializeWithX(uint256)'](42).encodeABI();
|
||||
|
||||
describe('when the sender is the admin', function () {
|
||||
const from = proxyAdminAddress;
|
||||
const value = 1e5;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.receipt = await this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from, value });
|
||||
this.receipt = await this.proxy.upgradeToAndCall(this.behavior.address, initializeData, {
|
||||
from: this.proxyAdminAddress,
|
||||
value,
|
||||
});
|
||||
});
|
||||
|
||||
it('upgrades to the requested implementation', async function () {
|
||||
@ -137,13 +126,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
});
|
||||
|
||||
it('calls the initializer function', async function () {
|
||||
const migratable = new InitializableMock(this.proxyAddress);
|
||||
const migratable = new InitializableMock(this.proxy.address);
|
||||
const x = await migratable.x();
|
||||
expect(x).to.be.bignumber.equal('42');
|
||||
});
|
||||
|
||||
it('sends given value to the proxy', async function () {
|
||||
const balance = await web3.eth.getBalance(this.proxyAddress);
|
||||
const balance = await web3.eth.getBalance(this.proxy.address);
|
||||
expect(balance.toString()).to.be.bignumber.equal(value.toString());
|
||||
});
|
||||
|
||||
@ -151,7 +140,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
// storage layout should look as follows:
|
||||
// - 0: Initializable storage ++ initializerRan ++ onlyInitializingRan
|
||||
// - 1: x
|
||||
const storedValue = await web3.eth.getStorageAt(this.proxyAddress, 1);
|
||||
const storedValue = await web3.eth.getStorageAt(this.proxy.address, 1);
|
||||
expect(parseInt(storedValue)).to.eq(42);
|
||||
});
|
||||
});
|
||||
@ -170,7 +159,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: proxyAdminAddress }),
|
||||
this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: this.proxyAdminAddress }),
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -178,7 +167,6 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
describe('with migrations', function () {
|
||||
describe('when the sender is the admin', function () {
|
||||
const from = proxyAdminAddress;
|
||||
const value = 1e5;
|
||||
|
||||
describe('when upgrading to V1', function () {
|
||||
@ -186,8 +174,11 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
beforeEach(async function () {
|
||||
this.behaviorV1 = await MigratableMockV1.new();
|
||||
this.balancePreviousV1 = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||
this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV1.address, v1MigrationData, { from, value });
|
||||
this.balancePreviousV1 = new BN(await web3.eth.getBalance(this.proxy.address));
|
||||
this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV1.address, v1MigrationData, {
|
||||
from: this.proxyAdminAddress,
|
||||
value,
|
||||
});
|
||||
});
|
||||
|
||||
it('upgrades to the requested version and emits an event', async function () {
|
||||
@ -197,12 +188,12 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
});
|
||||
|
||||
it("calls the 'initialize' function and sends given value to the proxy", async function () {
|
||||
const migratable = new MigratableMockV1(this.proxyAddress);
|
||||
const migratable = new MigratableMockV1(this.proxy.address);
|
||||
|
||||
const x = await migratable.x();
|
||||
expect(x).to.be.bignumber.equal('42');
|
||||
|
||||
const balance = await web3.eth.getBalance(this.proxyAddress);
|
||||
const balance = await web3.eth.getBalance(this.proxy.address);
|
||||
expect(new BN(balance)).to.be.bignumber.equal(this.balancePreviousV1.addn(value));
|
||||
});
|
||||
|
||||
@ -211,9 +202,9 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
beforeEach(async function () {
|
||||
this.behaviorV2 = await MigratableMockV2.new();
|
||||
this.balancePreviousV2 = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||
this.balancePreviousV2 = new BN(await web3.eth.getBalance(this.proxy.address));
|
||||
this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV2.address, v2MigrationData, {
|
||||
from,
|
||||
from: this.proxyAdminAddress,
|
||||
value,
|
||||
});
|
||||
});
|
||||
@ -225,7 +216,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
});
|
||||
|
||||
it("calls the 'migrate' function and sends given value to the proxy", async function () {
|
||||
const migratable = new MigratableMockV2(this.proxyAddress);
|
||||
const migratable = new MigratableMockV2(this.proxy.address);
|
||||
|
||||
const x = await migratable.x();
|
||||
expect(x).to.be.bignumber.equal('10');
|
||||
@ -233,7 +224,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
const y = await migratable.y();
|
||||
expect(y).to.be.bignumber.equal('42');
|
||||
|
||||
const balance = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||
const balance = new BN(await web3.eth.getBalance(this.proxy.address));
|
||||
expect(balance).to.be.bignumber.equal(this.balancePreviousV2.addn(value));
|
||||
});
|
||||
|
||||
@ -242,9 +233,9 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
beforeEach(async function () {
|
||||
this.behaviorV3 = await MigratableMockV3.new();
|
||||
this.balancePreviousV3 = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||
this.balancePreviousV3 = new BN(await web3.eth.getBalance(this.proxy.address));
|
||||
this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV3.address, v3MigrationData, {
|
||||
from,
|
||||
from: this.proxyAdminAddress,
|
||||
value,
|
||||
});
|
||||
});
|
||||
@ -256,7 +247,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
});
|
||||
|
||||
it("calls the 'migrate' function and sends given value to the proxy", async function () {
|
||||
const migratable = new MigratableMockV3(this.proxyAddress);
|
||||
const migratable = new MigratableMockV3(this.proxy.address);
|
||||
|
||||
const x = await migratable.x();
|
||||
expect(x).to.be.bignumber.equal('42');
|
||||
@ -264,7 +255,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
const y = await migratable.y();
|
||||
expect(y).to.be.bignumber.equal('10');
|
||||
|
||||
const balance = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||
const balance = new BN(await web3.eth.getBalance(this.proxy.address));
|
||||
expect(balance).to.be.bignumber.equal(this.balancePreviousV3.addn(value));
|
||||
});
|
||||
});
|
||||
@ -289,15 +280,18 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
const initializeData = Buffer.from('');
|
||||
this.clashingImplV0 = (await ClashingImplementation.new()).address;
|
||||
this.clashingImplV1 = (await ClashingImplementation.new()).address;
|
||||
this.proxy = await createProxy(this.clashingImplV0, proxyAdminAddress, initializeData, {
|
||||
from: proxyAdminOwner,
|
||||
});
|
||||
const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin(
|
||||
this.clashingImplV0,
|
||||
initializeData,
|
||||
);
|
||||
this.proxy = proxy;
|
||||
this.proxyAdminAddress = proxyAdminAddress;
|
||||
this.clashing = new ClashingImplementation(this.proxy.address);
|
||||
});
|
||||
|
||||
it('proxy admin cannot call delegated functions', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.clashing.delegatedFunction({ from: proxyAdminAddress }),
|
||||
this.clashing.delegatedFunction({ from: this.proxyAdminAddress }),
|
||||
'ProxyDeniedAdminAccess',
|
||||
[],
|
||||
);
|
||||
@ -305,26 +299,16 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
describe('when function names clash', function () {
|
||||
it('executes the proxy function if the sender is the admin', async function () {
|
||||
const receipt = await this.proxy.upgradeTo(this.clashingImplV1, { from: proxyAdminAddress, value: 0 });
|
||||
const receipt = await this.proxy.upgradeToAndCall(this.clashingImplV1, '0x', {
|
||||
from: this.proxyAdminAddress,
|
||||
});
|
||||
expectEvent(receipt, 'Upgraded', { implementation: this.clashingImplV1 });
|
||||
});
|
||||
|
||||
it('delegates the call to implementation when sender is not the admin', async function () {
|
||||
const receipt = await this.proxy.upgradeTo(this.clashingImplV1, { from: anotherAccount, value: 0 });
|
||||
expectEvent.notEmitted(receipt, 'Upgraded');
|
||||
expectEvent.inTransaction(receipt.tx, this.clashing, 'ClashingImplementationCall');
|
||||
});
|
||||
|
||||
it('requires 0 value calling upgradeTo by proxy admin', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.proxy.upgradeTo(this.clashingImplV1, { from: proxyAdminAddress, value: 1 }),
|
||||
'ProxyNonPayableFunction',
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it('allows calling with value if sender is not the admin', async function () {
|
||||
const receipt = await this.proxy.upgradeTo(this.clashingImplV1, { from: anotherAccount, value: 1 });
|
||||
const receipt = await this.proxy.upgradeToAndCall(this.clashingImplV1, '0x', {
|
||||
from: anotherAccount,
|
||||
});
|
||||
expectEvent.notEmitted(receipt, 'Upgraded');
|
||||
expectEvent.inTransaction(receipt.tx, this.clashing, 'ClashingImplementationCall');
|
||||
});
|
||||
@ -336,13 +320,16 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
it('should add new function', async () => {
|
||||
const instance1 = await Implementation1.new();
|
||||
const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin(
|
||||
instance1.address,
|
||||
initializeData,
|
||||
);
|
||||
|
||||
const proxyInstance1 = new Implementation1(proxy.address);
|
||||
await proxyInstance1.setValue(42);
|
||||
|
||||
const instance2 = await Implementation2.new();
|
||||
await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress });
|
||||
await proxy.upgradeToAndCall(instance2.address, '0x', { from: proxyAdminAddress });
|
||||
|
||||
const proxyInstance2 = new Implementation2(proxy.address);
|
||||
const res = await proxyInstance2.getValue();
|
||||
@ -351,7 +338,10 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
it('should remove function', async () => {
|
||||
const instance2 = await Implementation2.new();
|
||||
const proxy = await createProxy(instance2.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin(
|
||||
instance2.address,
|
||||
initializeData,
|
||||
);
|
||||
|
||||
const proxyInstance2 = new Implementation2(proxy.address);
|
||||
await proxyInstance2.setValue(42);
|
||||
@ -359,7 +349,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
expect(res.toString()).to.eq('42');
|
||||
|
||||
const instance1 = await Implementation1.new();
|
||||
await proxy.upgradeTo(instance1.address, { from: proxyAdminAddress });
|
||||
await proxy.upgradeToAndCall(instance1.address, '0x', { from: proxyAdminAddress });
|
||||
|
||||
const proxyInstance1 = new Implementation2(proxy.address);
|
||||
await expectRevert.unspecified(proxyInstance1.getValue());
|
||||
@ -367,13 +357,16 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
it('should change function signature', async () => {
|
||||
const instance1 = await Implementation1.new();
|
||||
const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin(
|
||||
instance1.address,
|
||||
initializeData,
|
||||
);
|
||||
|
||||
const proxyInstance1 = new Implementation1(proxy.address);
|
||||
await proxyInstance1.setValue(42);
|
||||
|
||||
const instance3 = await Implementation3.new();
|
||||
await proxy.upgradeTo(instance3.address, { from: proxyAdminAddress });
|
||||
await proxy.upgradeToAndCall(instance3.address, '0x', { from: proxyAdminAddress });
|
||||
const proxyInstance3 = new Implementation3(proxy.address);
|
||||
|
||||
const res = await proxyInstance3.getValue(8);
|
||||
@ -383,10 +376,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
it('should add fallback function', async () => {
|
||||
const initializeData = Buffer.from('');
|
||||
const instance1 = await Implementation1.new();
|
||||
const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin(
|
||||
instance1.address,
|
||||
initializeData,
|
||||
);
|
||||
|
||||
const instance4 = await Implementation4.new();
|
||||
await proxy.upgradeTo(instance4.address, { from: proxyAdminAddress });
|
||||
await proxy.upgradeToAndCall(instance4.address, '0x', { from: proxyAdminAddress });
|
||||
const proxyInstance4 = new Implementation4(proxy.address);
|
||||
|
||||
const data = '0x';
|
||||
@ -398,10 +394,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
|
||||
it('should remove fallback function', async () => {
|
||||
const instance4 = await Implementation4.new();
|
||||
const proxy = await createProxy(instance4.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin(
|
||||
instance4.address,
|
||||
initializeData,
|
||||
);
|
||||
|
||||
const instance2 = await Implementation2.new();
|
||||
await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress });
|
||||
await proxy.upgradeToAndCall(instance2.address, '0x', { from: proxyAdminAddress });
|
||||
|
||||
const data = '0x';
|
||||
await expectRevert.unspecified(web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data }));
|
||||
|
||||
@ -5,13 +5,20 @@ const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeablePro
|
||||
const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableProxy');
|
||||
|
||||
contract('TransparentUpgradeableProxy', function (accounts) {
|
||||
const [proxyAdminAddress, proxyAdminOwner] = accounts;
|
||||
const [owner, ...otherAccounts] = accounts;
|
||||
|
||||
const createProxy = async function (logic, admin, initData, opts) {
|
||||
const { address } = await TransparentUpgradeableProxy.new(logic, admin, initData, opts);
|
||||
return ITransparentUpgradeableProxy.at(address);
|
||||
// `undefined`, `null` and other false-ish opts will not be forwarded.
|
||||
const createProxy = async function (logic, initData, opts = undefined) {
|
||||
const { address, transactionHash } = await TransparentUpgradeableProxy.new(
|
||||
logic,
|
||||
owner,
|
||||
initData,
|
||||
...[opts].filter(Boolean),
|
||||
);
|
||||
const instance = await ITransparentUpgradeableProxy.at(address);
|
||||
return { ...instance, transactionHash };
|
||||
};
|
||||
|
||||
shouldBehaveLikeProxy(createProxy, proxyAdminAddress, proxyAdminOwner);
|
||||
shouldBehaveLikeTransparentUpgradeableProxy(createProxy, accounts);
|
||||
shouldBehaveLikeProxy(createProxy, otherAccounts);
|
||||
shouldBehaveLikeTransparentUpgradeableProxy(createProxy, owner, otherAccounts);
|
||||
});
|
||||
|
||||
@ -25,8 +25,12 @@ contract('UUPSUpgradeable', function () {
|
||||
this.instance = await UUPSUpgradeableMock.at(address);
|
||||
});
|
||||
|
||||
it('has an interface version', async function () {
|
||||
expect(await this.instance.UPGRADE_INTERFACE_VERSION()).to.equal('5.0.0');
|
||||
});
|
||||
|
||||
it('upgrade to upgradeable implementation', async function () {
|
||||
const { receipt } = await this.instance.upgradeTo(this.implUpgradeOk.address);
|
||||
const { receipt } = await this.instance.upgradeToAndCall(this.implUpgradeOk.address, '0x');
|
||||
expect(receipt.logs.filter(({ event }) => event === 'Upgraded').length).to.be.equal(1);
|
||||
expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeOk.address });
|
||||
expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.be.equal(this.implUpgradeOk.address);
|
||||
@ -48,7 +52,7 @@ contract('UUPSUpgradeable', function () {
|
||||
|
||||
it('calling upgradeTo on the implementation reverts', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.implInitial.upgradeTo(this.implUpgradeOk.address),
|
||||
this.implInitial.upgradeToAndCall(this.implUpgradeOk.address, '0x'),
|
||||
'UUPSUnauthorizedCallContext',
|
||||
[],
|
||||
);
|
||||
@ -72,7 +76,7 @@ contract('UUPSUpgradeable', function () {
|
||||
);
|
||||
|
||||
await expectRevertCustomError(
|
||||
instance.upgradeTo(this.implUpgradeUnsafe.address),
|
||||
instance.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x'),
|
||||
'UUPSUnauthorizedCallContext',
|
||||
[],
|
||||
);
|
||||
@ -93,14 +97,14 @@ contract('UUPSUpgradeable', function () {
|
||||
|
||||
it('rejects upgrading to an unsupported UUID', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.instance.upgradeTo(this.implUnsupportedUUID.address),
|
||||
this.instance.upgradeToAndCall(this.implUnsupportedUUID.address, '0x'),
|
||||
'UUPSUnsupportedProxiableUUID',
|
||||
[web3.utils.keccak256('invalid UUID')],
|
||||
);
|
||||
});
|
||||
|
||||
it('upgrade to and unsafe upgradeable implementation', async function () {
|
||||
const { receipt } = await this.instance.upgradeTo(this.implUpgradeUnsafe.address);
|
||||
const { receipt } = await this.instance.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x');
|
||||
expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeUnsafe.address });
|
||||
expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.be.equal(this.implUpgradeUnsafe.address);
|
||||
});
|
||||
@ -108,7 +112,7 @@ contract('UUPSUpgradeable', function () {
|
||||
// delegate to a non existing upgradeTo function causes a low level revert
|
||||
it('reject upgrade to non uups implementation', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.instance.upgradeTo(this.implUpgradeNonUUPS.address),
|
||||
this.instance.upgradeToAndCall(this.implUpgradeNonUUPS.address, '0x'),
|
||||
'ERC1967InvalidImplementation',
|
||||
[this.implUpgradeNonUUPS.address],
|
||||
);
|
||||
@ -118,8 +122,10 @@ contract('UUPSUpgradeable', function () {
|
||||
const { address } = await ERC1967Proxy.new(this.implInitial.address, '0x');
|
||||
const otherInstance = await UUPSUpgradeableMock.at(address);
|
||||
|
||||
await expectRevertCustomError(this.instance.upgradeTo(otherInstance.address), 'ERC1967InvalidImplementation', [
|
||||
otherInstance.address,
|
||||
]);
|
||||
await expectRevertCustomError(
|
||||
this.instance.upgradeToAndCall(otherInstance.address, '0x'),
|
||||
'ERC1967InvalidImplementation',
|
||||
[otherInstance.address],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user