Make TransparentUpgradeableProxy admin immutable (#4354)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: Francisco <fg@frang.io>
This commit is contained in:
@ -6,11 +6,11 @@ const ProxyAdmin = artifacts.require('ProxyAdmin');
|
||||
const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeableProxy');
|
||||
const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableProxy');
|
||||
|
||||
const { getAddressInSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967');
|
||||
const { getAddressInSlot, ImplementationSlot } = require('../../helpers/erc1967');
|
||||
const { expectRevertCustomError } = require('../../helpers/customError');
|
||||
|
||||
contract('ProxyAdmin', function (accounts) {
|
||||
const [proxyAdminOwner, newAdmin, anotherAccount] = accounts;
|
||||
const [proxyAdminOwner, anotherAccount] = accounts;
|
||||
|
||||
before('set implementations', async function () {
|
||||
this.implementationV1 = await ImplV1.new();
|
||||
@ -32,23 +32,6 @@ contract('ProxyAdmin', function (accounts) {
|
||||
expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner);
|
||||
});
|
||||
|
||||
describe('#changeProxyAdmin', function () {
|
||||
it('fails to change proxy admin if its not the proxy owner', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: anotherAccount }),
|
||||
'OwnableUnauthorizedAccount',
|
||||
[anotherAccount],
|
||||
);
|
||||
});
|
||||
|
||||
it('changes proxy admin', async function () {
|
||||
await this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: proxyAdminOwner });
|
||||
|
||||
const newProxyAdmin = await getAddressInSlot(this.proxy, AdminSlot);
|
||||
expect(newProxyAdmin).to.be.equal(newAdmin);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#upgrade', function () {
|
||||
context('with unauthorized account', function () {
|
||||
it('fails to upgrade', async function () {
|
||||
|
||||
@ -47,6 +47,32 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
});
|
||||
});
|
||||
|
||||
describe('proxy admin', function () {
|
||||
it('emits AdminChanged event during construction', async function () {
|
||||
expectEvent.inConstruction(this.proxy, 'AdminChanged', {
|
||||
previousAdmin: ZERO_ADDRESS,
|
||||
newAdmin: proxyAdminAddress,
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the admin in the storage', async function () {
|
||||
expect(await getAddressInSlot(this.proxy, AdminSlot)).to.be.equal(proxyAdminAddress);
|
||||
});
|
||||
|
||||
it('can overwrite the admin by the implementation', async function () {
|
||||
const dummy = new DummyImplementation(this.proxyAddress);
|
||||
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;
|
||||
@ -258,51 +284,14 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeAdmin', function () {
|
||||
describe('when the new proposed admin is not the zero address', function () {
|
||||
const newAdmin = anotherAccount;
|
||||
|
||||
describe('when the sender is the admin', function () {
|
||||
beforeEach('transferring', async function () {
|
||||
this.receipt = await this.proxy.changeAdmin(newAdmin, { from: proxyAdminAddress });
|
||||
});
|
||||
|
||||
it('assigns new proxy admin', async function () {
|
||||
const newProxyAdmin = await getAddressInSlot(this.proxy, AdminSlot);
|
||||
expect(newProxyAdmin).to.be.equal(anotherAccount);
|
||||
});
|
||||
|
||||
it('emits an event', function () {
|
||||
expectEvent(this.receipt, 'AdminChanged', {
|
||||
previousAdmin: proxyAdminAddress,
|
||||
newAdmin: newAdmin,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender is not the admin', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(this.proxy.changeAdmin(newAdmin, { from: anotherAccount }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the new proposed admin is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.proxy.changeAdmin(ZERO_ADDRESS, { from: proxyAdminAddress }),
|
||||
'ERC1967InvalidAdmin',
|
||||
[ZERO_ADDRESS],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transparent proxy', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
const initializeData = Buffer.from('');
|
||||
this.impl = await ClashingImplementation.new();
|
||||
this.proxy = await createProxy(this.impl.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
this.clashingImplV0 = (await ClashingImplementation.new()).address;
|
||||
this.clashingImplV1 = (await ClashingImplementation.new()).address;
|
||||
this.proxy = await createProxy(this.clashingImplV0, proxyAdminAddress, initializeData, {
|
||||
from: proxyAdminOwner,
|
||||
});
|
||||
this.clashing = new ClashingImplementation(this.proxy.address);
|
||||
});
|
||||
|
||||
@ -315,24 +304,28 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx
|
||||
});
|
||||
|
||||
describe('when function names clash', function () {
|
||||
it('when sender is proxy admin should run the proxy function', async function () {
|
||||
const receipt = await this.proxy.changeAdmin(anotherAccount, { from: proxyAdminAddress, value: 0 });
|
||||
expectEvent(receipt, 'AdminChanged');
|
||||
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 });
|
||||
expectEvent(receipt, 'Upgraded', { implementation: this.clashingImplV1 });
|
||||
});
|
||||
|
||||
it('when sender is other should delegate to implementation', async function () {
|
||||
const receipt = await this.proxy.changeAdmin(anotherAccount, { from: anotherAccount, value: 0 });
|
||||
expectEvent.notEmitted(receipt, 'AdminChanged');
|
||||
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('when sender is proxy admin value should not be accepted', async function () {
|
||||
await expectRevert.unspecified(this.proxy.changeAdmin(anotherAccount, { from: proxyAdminAddress, value: 1 }));
|
||||
it('requires 0 value calling upgradeTo by proxy admin', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.proxy.upgradeTo(this.clashingImplV1, { from: proxyAdminAddress, value: 1 }),
|
||||
'ProxyNonPayableFunction',
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it('when sender is other value should be accepted', async function () {
|
||||
const receipt = await this.proxy.changeAdmin(anotherAccount, { from: anotherAccount, value: 1 });
|
||||
expectEvent.notEmitted(receipt, 'AdminChanged');
|
||||
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 });
|
||||
expectEvent.notEmitted(receipt, 'Upgraded');
|
||||
expectEvent.inTransaction(receipt.tx, this.clashing, 'ClashingImplementationCall');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user