mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 19:26:33 +00:00
Fix .gitignore: stop tracking ignored files
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const DummyImplementation = artifacts.require('DummyImplementation');
|
||||
|
||||
module.exports = function shouldBehaveLikeClone(createClone) {
|
||||
before('deploy implementation', async function () {
|
||||
this.implementation = web3.utils.toChecksumAddress((await DummyImplementation.new()).address);
|
||||
});
|
||||
|
||||
const assertProxyInitialization = function ({ value, balance }) {
|
||||
it('initializes the proxy', async function () {
|
||||
const dummy = new DummyImplementation(this.proxy);
|
||||
expect(await dummy.value()).to.be.bignumber.equal(value.toString());
|
||||
});
|
||||
|
||||
it('has expected balance', async function () {
|
||||
expect(await web3.eth.getBalance(this.proxy)).to.be.bignumber.equal(balance.toString());
|
||||
});
|
||||
};
|
||||
|
||||
describe('initialization without parameters', function () {
|
||||
describe('non payable', function () {
|
||||
const expectedInitializedValue = 10;
|
||||
const initializeData = new DummyImplementation('').contract.methods['initializeNonPayable()']().encodeABI();
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (await createClone(this.implementation, initializeData)).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: 0,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sending some balance', function () {
|
||||
const value = 10e5;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(createClone(this.implementation, initializeData, { value }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('payable', function () {
|
||||
const expectedInitializedValue = 100;
|
||||
const initializeData = new DummyImplementation('').contract.methods['initializePayable()']().encodeABI();
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (await createClone(this.implementation, initializeData)).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: 0,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sending some balance', function () {
|
||||
const value = 10e5;
|
||||
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (await createClone(this.implementation, initializeData, { value })).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: value,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initialization with parameters', function () {
|
||||
describe('non payable', function () {
|
||||
const expectedInitializedValue = 10;
|
||||
const initializeData = new DummyImplementation('').contract.methods
|
||||
.initializeNonPayableWithValue(expectedInitializedValue)
|
||||
.encodeABI();
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (await createClone(this.implementation, initializeData)).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: 0,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sending some balance', function () {
|
||||
const value = 10e5;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(createClone(this.implementation, initializeData, { value }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('payable', function () {
|
||||
const expectedInitializedValue = 42;
|
||||
const initializeData = new DummyImplementation('').contract.methods
|
||||
.initializePayableWithValue(expectedInitializedValue)
|
||||
.encodeABI();
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (await createClone(this.implementation, initializeData)).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: 0,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sending some balance', function () {
|
||||
const value = 10e5;
|
||||
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (await createClone(this.implementation, initializeData, { value })).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: value,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { computeCreate2Address } = require('../helpers/create2');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const shouldBehaveLikeClone = require('./Clones.behaviour');
|
||||
|
||||
const Clones = artifacts.require('$Clones');
|
||||
|
||||
contract('Clones', function (accounts) {
|
||||
const [deployer] = accounts;
|
||||
|
||||
describe('clone', function () {
|
||||
shouldBehaveLikeClone(async (implementation, initData, opts = {}) => {
|
||||
const factory = await Clones.new();
|
||||
const receipt = await factory.$clone(implementation);
|
||||
const address = receipt.logs.find(({ event }) => event === 'return$clone').args.instance;
|
||||
await web3.eth.sendTransaction({ from: deployer, to: address, value: opts.value, data: initData });
|
||||
return { address };
|
||||
});
|
||||
});
|
||||
|
||||
describe('cloneDeterministic', function () {
|
||||
shouldBehaveLikeClone(async (implementation, initData, opts = {}) => {
|
||||
const salt = web3.utils.randomHex(32);
|
||||
const factory = await Clones.new();
|
||||
const receipt = await factory.$cloneDeterministic(implementation, salt);
|
||||
const address = receipt.logs.find(({ event }) => event === 'return$cloneDeterministic').args.instance;
|
||||
await web3.eth.sendTransaction({ from: deployer, to: address, value: opts.value, data: initData });
|
||||
return { address };
|
||||
});
|
||||
|
||||
it('address already used', async function () {
|
||||
const implementation = web3.utils.randomHex(20);
|
||||
const salt = web3.utils.randomHex(32);
|
||||
const factory = await Clones.new();
|
||||
// deploy once
|
||||
expectEvent(await factory.$cloneDeterministic(implementation, salt), 'return$cloneDeterministic');
|
||||
// deploy twice
|
||||
await expectRevert(factory.$cloneDeterministic(implementation, salt), 'ERC1167: create2 failed');
|
||||
});
|
||||
|
||||
it('address prediction', async function () {
|
||||
const implementation = web3.utils.randomHex(20);
|
||||
const salt = web3.utils.randomHex(32);
|
||||
const factory = await Clones.new();
|
||||
const predicted = await factory.$predictDeterministicAddress(implementation, salt);
|
||||
|
||||
const creationCode = [
|
||||
'0x3d602d80600a3d3981f3363d3d373d3d3d363d73',
|
||||
implementation.replace(/0x/, '').toLowerCase(),
|
||||
'5af43d82803e903d91602b57fd5bf3',
|
||||
].join('');
|
||||
|
||||
expect(computeCreate2Address(salt, creationCode, factory.address)).to.be.equal(predicted);
|
||||
|
||||
expectEvent(await factory.$cloneDeterministic(implementation, salt), 'return$cloneDeterministic', {
|
||||
instance: predicted,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
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);
|
||||
};
|
||||
|
||||
shouldBehaveLikeProxy(createProxy, undefined, proxyAdminOwner);
|
||||
});
|
||||
@@ -0,0 +1,225 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { getSlot, ImplementationSlot } = require('../helpers/erc1967');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const DummyImplementation = artifacts.require('DummyImplementation');
|
||||
|
||||
module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, proxyCreator) {
|
||||
it('cannot be initialized with a non-contract address', async function () {
|
||||
const nonContractAddress = proxyCreator;
|
||||
const initializeData = Buffer.from('');
|
||||
await expectRevert.unspecified(
|
||||
createProxy(nonContractAddress, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
before('deploy implementation', async function () {
|
||||
this.implementation = web3.utils.toChecksumAddress((await DummyImplementation.new()).address);
|
||||
});
|
||||
|
||||
const assertProxyInitialization = function ({ value, balance }) {
|
||||
it('sets the implementation address', async function () {
|
||||
const implementationSlot = await getSlot(this.proxy, ImplementationSlot);
|
||||
const implementationAddress = web3.utils.toChecksumAddress(implementationSlot.substr(-40));
|
||||
expect(implementationAddress).to.be.equal(this.implementation);
|
||||
});
|
||||
|
||||
it('initializes the proxy', async function () {
|
||||
const dummy = new DummyImplementation(this.proxy);
|
||||
expect(await dummy.value()).to.be.bignumber.equal(value.toString());
|
||||
});
|
||||
|
||||
it('has expected balance', async function () {
|
||||
expect(await web3.eth.getBalance(this.proxy)).to.be.bignumber.equal(balance.toString());
|
||||
});
|
||||
};
|
||||
|
||||
describe('without initialization', function () {
|
||||
const initializeData = Buffer.from('');
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({ value: 0, balance: 0 });
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
assertProxyInitialization({ value: 0, balance: value });
|
||||
});
|
||||
});
|
||||
|
||||
describe('initialization without parameters', function () {
|
||||
describe('non payable', function () {
|
||||
const expectedInitializedValue = 10;
|
||||
const initializeData = new DummyImplementation('').contract.methods['initializeNonPayable()']().encodeABI();
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: 0,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sending some balance', function () {
|
||||
const value = 10e5;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('payable', function () {
|
||||
const expectedInitializedValue = 100;
|
||||
const initializeData = new DummyImplementation('').contract.methods['initializePayable()']().encodeABI();
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: 0,
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: value,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initialization with parameters', function () {
|
||||
describe('non payable', function () {
|
||||
const expectedInitializedValue = 10;
|
||||
const initializeData = new DummyImplementation('').contract.methods
|
||||
.initializeNonPayableWithValue(expectedInitializedValue)
|
||||
.encodeABI();
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: 0,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sending some balance', function () {
|
||||
const value = 10e5;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('payable', function () {
|
||||
const expectedInitializedValue = 42;
|
||||
const initializeData = new DummyImplementation('').contract.methods
|
||||
.initializePayableWithValue(expectedInitializedValue)
|
||||
.encodeABI();
|
||||
|
||||
describe('when not sending balance', function () {
|
||||
beforeEach('creating proxy', async function () {
|
||||
this.proxy = (
|
||||
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||
from: proxyCreator,
|
||||
})
|
||||
).address;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: 0,
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
assertProxyInitialization({
|
||||
value: expectedInitializedValue,
|
||||
balance: value,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reverting initialization', function () {
|
||||
const initializeData = new DummyImplementation('').contract.methods.reverts().encodeABI();
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert(
|
||||
createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator }),
|
||||
'DummyImplementation reverted',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { getSlot, BeaconSlot } = require('../../helpers/erc1967');
|
||||
|
||||
const { expect } = require('chai');
|
||||
|
||||
const UpgradeableBeacon = artifacts.require('UpgradeableBeacon');
|
||||
const BeaconProxy = artifacts.require('BeaconProxy');
|
||||
const DummyImplementation = artifacts.require('DummyImplementation');
|
||||
const DummyImplementationV2 = artifacts.require('DummyImplementationV2');
|
||||
const BadBeaconNoImpl = artifacts.require('BadBeaconNoImpl');
|
||||
const BadBeaconNotContract = artifacts.require('BadBeaconNotContract');
|
||||
|
||||
contract('BeaconProxy', function (accounts) {
|
||||
const [anotherAccount] = accounts;
|
||||
|
||||
describe('bad beacon is not accepted', async function () {
|
||||
it('non-contract beacon', async function () {
|
||||
await expectRevert(BeaconProxy.new(anotherAccount, '0x'), 'ERC1967: new beacon is not a contract');
|
||||
});
|
||||
|
||||
it('non-compliant beacon', async function () {
|
||||
const beacon = await BadBeaconNoImpl.new();
|
||||
await expectRevert.unspecified(BeaconProxy.new(beacon.address, '0x'));
|
||||
});
|
||||
|
||||
it('non-contract implementation', async function () {
|
||||
const beacon = await BadBeaconNotContract.new();
|
||||
await expectRevert(BeaconProxy.new(beacon.address, '0x'), 'ERC1967: beacon implementation is not a contract');
|
||||
});
|
||||
});
|
||||
|
||||
before('deploy implementation', async function () {
|
||||
this.implementationV0 = await DummyImplementation.new();
|
||||
this.implementationV1 = await DummyImplementationV2.new();
|
||||
});
|
||||
|
||||
describe('initialization', function () {
|
||||
before(function () {
|
||||
this.assertInitialized = async ({ value, balance }) => {
|
||||
const beaconSlot = await getSlot(this.proxy, BeaconSlot);
|
||||
const beaconAddress = web3.utils.toChecksumAddress(beaconSlot.substr(-40));
|
||||
expect(beaconAddress).to.equal(this.beacon.address);
|
||||
|
||||
const dummy = new DummyImplementation(this.proxy.address);
|
||||
expect(await dummy.value()).to.bignumber.eq(value);
|
||||
|
||||
expect(await web3.eth.getBalance(this.proxy.address)).to.bignumber.eq(balance);
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach('deploy beacon', async function () {
|
||||
this.beacon = await UpgradeableBeacon.new(this.implementationV0.address);
|
||||
});
|
||||
|
||||
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 });
|
||||
});
|
||||
|
||||
it('non-payable initialization', async function () {
|
||||
const value = '55';
|
||||
const data = this.implementationV0.contract.methods.initializeNonPayableWithValue(value).encodeABI();
|
||||
this.proxy = await BeaconProxy.new(this.beacon.address, data);
|
||||
await this.assertInitialized({ value, balance: '0' });
|
||||
});
|
||||
|
||||
it('payable initialization', async function () {
|
||||
const value = '55';
|
||||
const data = this.implementationV0.contract.methods.initializePayableWithValue(value).encodeABI();
|
||||
const balance = '100';
|
||||
this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance });
|
||||
await this.assertInitialized({ value, balance });
|
||||
});
|
||||
|
||||
it('reverting initialization', async function () {
|
||||
const data = this.implementationV0.contract.methods.reverts().encodeABI();
|
||||
await expectRevert(BeaconProxy.new(this.beacon.address, data), 'DummyImplementation reverted');
|
||||
});
|
||||
});
|
||||
|
||||
it('upgrade a proxy by upgrading its beacon', async function () {
|
||||
const beacon = await UpgradeableBeacon.new(this.implementationV0.address);
|
||||
|
||||
const value = '10';
|
||||
const data = this.implementationV0.contract.methods.initializeNonPayableWithValue(value).encodeABI();
|
||||
const proxy = await BeaconProxy.new(beacon.address, data);
|
||||
|
||||
const dummy = new DummyImplementation(proxy.address);
|
||||
|
||||
// test initial values
|
||||
expect(await dummy.value()).to.bignumber.eq(value);
|
||||
|
||||
// test initial version
|
||||
expect(await dummy.version()).to.eq('V1');
|
||||
|
||||
// upgrade beacon
|
||||
await beacon.upgradeTo(this.implementationV1.address);
|
||||
|
||||
// test upgraded version
|
||||
expect(await dummy.version()).to.eq('V2');
|
||||
});
|
||||
|
||||
it('upgrade 2 proxies by upgrading shared beacon', async function () {
|
||||
const value1 = '10';
|
||||
const value2 = '42';
|
||||
|
||||
const beacon = await UpgradeableBeacon.new(this.implementationV0.address);
|
||||
|
||||
const proxy1InitializeData = this.implementationV0.contract.methods
|
||||
.initializeNonPayableWithValue(value1)
|
||||
.encodeABI();
|
||||
const proxy1 = await BeaconProxy.new(beacon.address, proxy1InitializeData);
|
||||
|
||||
const proxy2InitializeData = this.implementationV0.contract.methods
|
||||
.initializeNonPayableWithValue(value2)
|
||||
.encodeABI();
|
||||
const proxy2 = await BeaconProxy.new(beacon.address, proxy2InitializeData);
|
||||
|
||||
const dummy1 = new DummyImplementation(proxy1.address);
|
||||
const dummy2 = new DummyImplementation(proxy2.address);
|
||||
|
||||
// test initial values
|
||||
expect(await dummy1.value()).to.bignumber.eq(value1);
|
||||
expect(await dummy2.value()).to.bignumber.eq(value2);
|
||||
|
||||
// test initial version
|
||||
expect(await dummy1.version()).to.eq('V1');
|
||||
expect(await dummy2.version()).to.eq('V1');
|
||||
|
||||
// upgrade beacon
|
||||
await beacon.upgradeTo(this.implementationV1.address);
|
||||
|
||||
// test upgraded version
|
||||
expect(await dummy1.version()).to.eq('V2');
|
||||
expect(await dummy2.version()).to.eq('V2');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const UpgradeableBeacon = artifacts.require('UpgradeableBeacon');
|
||||
const Implementation1 = artifacts.require('Implementation1');
|
||||
const Implementation2 = artifacts.require('Implementation2');
|
||||
|
||||
contract('UpgradeableBeacon', function (accounts) {
|
||||
const [owner, other] = accounts;
|
||||
|
||||
it('cannot be created with non-contract implementation', async function () {
|
||||
await expectRevert(UpgradeableBeacon.new(accounts[0]), 'UpgradeableBeacon: implementation is not a contract');
|
||||
});
|
||||
|
||||
context('once deployed', async function () {
|
||||
beforeEach('deploying beacon', async function () {
|
||||
this.v1 = await Implementation1.new();
|
||||
this.beacon = await UpgradeableBeacon.new(this.v1.address, { from: owner });
|
||||
});
|
||||
|
||||
it('returns implementation', async function () {
|
||||
expect(await this.beacon.implementation()).to.equal(this.v1.address);
|
||||
});
|
||||
|
||||
it('can be upgraded by the owner', async function () {
|
||||
const v2 = await Implementation2.new();
|
||||
const receipt = await this.beacon.upgradeTo(v2.address, { from: owner });
|
||||
expectEvent(receipt, 'Upgraded', { implementation: v2.address });
|
||||
expect(await this.beacon.implementation()).to.equal(v2.address);
|
||||
});
|
||||
|
||||
it('cannot be upgraded to a non-contract', async function () {
|
||||
await expectRevert(
|
||||
this.beacon.upgradeTo(other, { from: owner }),
|
||||
'UpgradeableBeacon: implementation is not a contract',
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot be upgraded by other account', async function () {
|
||||
const v2 = await Implementation2.new();
|
||||
await expectRevert(this.beacon.upgradeTo(v2.address, { from: other }), 'Ownable: caller is not the owner');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
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');
|
||||
|
||||
contract('ProxyAdmin', function (accounts) {
|
||||
const [proxyAdminOwner, newAdmin, anotherAccount] = accounts;
|
||||
|
||||
before('set implementations', async function () {
|
||||
this.implementationV1 = await ImplV1.new();
|
||||
this.implementationV2 = await ImplV2.new();
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
const initializeData = Buffer.from('');
|
||||
this.proxyAdmin = await ProxyAdmin.new({ from: proxyAdminOwner });
|
||||
const proxy = await TransparentUpgradeableProxy.new(
|
||||
this.implementationV1.address,
|
||||
this.proxyAdmin.address,
|
||||
initializeData,
|
||||
{ from: proxyAdminOwner },
|
||||
);
|
||||
this.proxy = await ITransparentUpgradeableProxy.at(proxy.address);
|
||||
});
|
||||
|
||||
it('has an owner', async function () {
|
||||
expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner);
|
||||
});
|
||||
|
||||
describe('#getProxyAdmin', function () {
|
||||
it('returns proxyAdmin as admin of the proxy', async function () {
|
||||
const admin = await this.proxyAdmin.getProxyAdmin(this.proxy.address);
|
||||
expect(admin).to.be.equal(this.proxyAdmin.address);
|
||||
});
|
||||
|
||||
it('call to invalid proxy', async function () {
|
||||
await expectRevert.unspecified(this.proxyAdmin.getProxyAdmin(this.implementationV1.address));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#changeProxyAdmin', function () {
|
||||
it('fails to change proxy admin if its not the proxy owner', async function () {
|
||||
await expectRevert(
|
||||
this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: anotherAccount }),
|
||||
'caller is not the owner',
|
||||
);
|
||||
});
|
||||
|
||||
it('changes proxy admin', async function () {
|
||||
await this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: proxyAdminOwner });
|
||||
expect(await this.proxy.admin.call({ from: newAdmin })).to.eq(newAdmin);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getProxyImplementation', function () {
|
||||
it('returns proxy implementation address', async function () {
|
||||
const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address);
|
||||
expect(implementationAddress).to.be.equal(this.implementationV1.address);
|
||||
});
|
||||
|
||||
it('call to invalid proxy', async function () {
|
||||
await expectRevert.unspecified(this.proxyAdmin.getProxyImplementation(this.implementationV1.address));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#upgrade', function () {
|
||||
context('with unauthorized account', function () {
|
||||
it('fails to upgrade', async function () {
|
||||
await expectRevert(
|
||||
this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: anotherAccount }),
|
||||
'caller is not the owner',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('with authorized account', function () {
|
||||
it('upgrades implementation', async function () {
|
||||
await this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: proxyAdminOwner });
|
||||
const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address);
|
||||
expect(implementationAddress).to.be.equal(this.implementationV2.address);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#upgradeAndCall', function () {
|
||||
context('with unauthorized account', function () {
|
||||
it('fails to upgrade', async function () {
|
||||
const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI();
|
||||
await expectRevert(
|
||||
this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, {
|
||||
from: anotherAccount,
|
||||
}),
|
||||
'caller is not the owner',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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 this.proxyAdmin.getProxyImplementation(this.proxy.address);
|
||||
expect(implementationAddress).to.be.equal(this.implementationV2.address);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+433
@@ -0,0 +1,433 @@
|
||||
const { BN, expectRevert, expectEvent, constants } = require('@openzeppelin/test-helpers');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
const { getSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967');
|
||||
|
||||
const { expect } = require('chai');
|
||||
const { web3 } = require('hardhat');
|
||||
|
||||
const Implementation1 = artifacts.require('Implementation1');
|
||||
const Implementation2 = artifacts.require('Implementation2');
|
||||
const Implementation3 = artifacts.require('Implementation3');
|
||||
const Implementation4 = artifacts.require('Implementation4');
|
||||
const MigratableMockV1 = artifacts.require('MigratableMockV1');
|
||||
const MigratableMockV2 = artifacts.require('MigratableMockV2');
|
||||
const MigratableMockV3 = artifacts.require('MigratableMockV3');
|
||||
const InitializableMock = artifacts.require('InitializableMock');
|
||||
const DummyImplementation = artifacts.require('DummyImplementation');
|
||||
const ClashingImplementation = artifacts.require('ClashingImplementation');
|
||||
|
||||
module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProxy, accounts) {
|
||||
const [proxyAdminAddress, proxyAdminOwner, anotherAccount] = accounts;
|
||||
|
||||
before(async function () {
|
||||
this.implementationV0 = (await DummyImplementation.new()).address;
|
||||
this.implementationV1 = (await DummyImplementation.new()).address;
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
const initializeData = Buffer.from('');
|
||||
this.proxy = await createProxy(this.implementationV0, proxyAdminAddress, initializeData, {
|
||||
from: proxyAdminOwner,
|
||||
});
|
||||
this.proxyAddress = this.proxy.address;
|
||||
});
|
||||
|
||||
describe('implementation', function () {
|
||||
it('returns the current implementation address', async function () {
|
||||
const implementation = await this.proxy.implementation({ from: proxyAdminAddress });
|
||||
|
||||
expect(implementation).to.be.equal(this.implementationV0);
|
||||
});
|
||||
|
||||
it('delegates to the implementation', async function () {
|
||||
const dummy = new DummyImplementation(this.proxyAddress);
|
||||
const value = await dummy.get();
|
||||
|
||||
expect(value).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
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 implementation = await this.proxy.implementation({ from: proxyAdminAddress });
|
||||
expect(implementation).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 expectRevert(
|
||||
this.proxy.upgradeTo(ZERO_ADDRESS, { from }),
|
||||
'ERC1967: new implementation is not a contract',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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 }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('upgradeToAndCall', function () {
|
||||
describe('without migrations', function () {
|
||||
beforeEach(async function () {
|
||||
this.behavior = await InitializableMock.new();
|
||||
});
|
||||
|
||||
describe('when the call does not fail', function () {
|
||||
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 });
|
||||
});
|
||||
|
||||
it('upgrades to the requested implementation', async function () {
|
||||
const implementation = await this.proxy.implementation({ from: proxyAdminAddress });
|
||||
expect(implementation).to.be.equal(this.behavior.address);
|
||||
});
|
||||
|
||||
it('emits an event', function () {
|
||||
expectEvent(this.receipt, 'Upgraded', { implementation: this.behavior.address });
|
||||
});
|
||||
|
||||
it('calls the initializer function', async function () {
|
||||
const migratable = new InitializableMock(this.proxyAddress);
|
||||
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);
|
||||
expect(balance.toString()).to.be.bignumber.equal(value.toString());
|
||||
});
|
||||
|
||||
it('uses the storage of the proxy', async function () {
|
||||
// storage layout should look as follows:
|
||||
// - 0: Initializable storage ++ initializerRan ++ onlyInitializingRan
|
||||
// - 1: x
|
||||
const storedValue = await web3.eth.getStorageAt(this.proxyAddress, 1);
|
||||
expect(parseInt(storedValue)).to.eq(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender is not the admin', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: anotherAccount }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the call does fail', function () {
|
||||
const initializeData = new InitializableMock('').contract.methods.fail().encodeABI();
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevert.unspecified(
|
||||
this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: proxyAdminAddress }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with migrations', function () {
|
||||
describe('when the sender is the admin', function () {
|
||||
const from = proxyAdminAddress;
|
||||
const value = 1e5;
|
||||
|
||||
describe('when upgrading to V1', function () {
|
||||
const v1MigrationData = new MigratableMockV1('').contract.methods.initialize(42).encodeABI();
|
||||
|
||||
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 });
|
||||
});
|
||||
|
||||
it('upgrades to the requested version and emits an event', async function () {
|
||||
const implementation = await this.proxy.implementation({ from: proxyAdminAddress });
|
||||
expect(implementation).to.be.equal(this.behaviorV1.address);
|
||||
expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV1.address });
|
||||
});
|
||||
|
||||
it("calls the 'initialize' function and sends given value to the proxy", async function () {
|
||||
const migratable = new MigratableMockV1(this.proxyAddress);
|
||||
|
||||
const x = await migratable.x();
|
||||
expect(x).to.be.bignumber.equal('42');
|
||||
|
||||
const balance = await web3.eth.getBalance(this.proxyAddress);
|
||||
expect(new BN(balance)).to.be.bignumber.equal(this.balancePreviousV1.addn(value));
|
||||
});
|
||||
|
||||
describe('when upgrading to V2', function () {
|
||||
const v2MigrationData = new MigratableMockV2('').contract.methods.migrate(10, 42).encodeABI();
|
||||
|
||||
beforeEach(async function () {
|
||||
this.behaviorV2 = await MigratableMockV2.new();
|
||||
this.balancePreviousV2 = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||
this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV2.address, v2MigrationData, {
|
||||
from,
|
||||
value,
|
||||
});
|
||||
});
|
||||
|
||||
it('upgrades to the requested version and emits an event', async function () {
|
||||
const implementation = await this.proxy.implementation({ from: proxyAdminAddress });
|
||||
expect(implementation).to.be.equal(this.behaviorV2.address);
|
||||
expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV2.address });
|
||||
});
|
||||
|
||||
it("calls the 'migrate' function and sends given value to the proxy", async function () {
|
||||
const migratable = new MigratableMockV2(this.proxyAddress);
|
||||
|
||||
const x = await migratable.x();
|
||||
expect(x).to.be.bignumber.equal('10');
|
||||
|
||||
const y = await migratable.y();
|
||||
expect(y).to.be.bignumber.equal('42');
|
||||
|
||||
const balance = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||
expect(balance).to.be.bignumber.equal(this.balancePreviousV2.addn(value));
|
||||
});
|
||||
|
||||
describe('when upgrading to V3', function () {
|
||||
const v3MigrationData = new MigratableMockV3('').contract.methods['migrate()']().encodeABI();
|
||||
|
||||
beforeEach(async function () {
|
||||
this.behaviorV3 = await MigratableMockV3.new();
|
||||
this.balancePreviousV3 = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||
this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV3.address, v3MigrationData, {
|
||||
from,
|
||||
value,
|
||||
});
|
||||
});
|
||||
|
||||
it('upgrades to the requested version and emits an event', async function () {
|
||||
const implementation = await this.proxy.implementation({ from: proxyAdminAddress });
|
||||
expect(implementation).to.be.equal(this.behaviorV3.address);
|
||||
expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV3.address });
|
||||
});
|
||||
|
||||
it("calls the 'migrate' function and sends given value to the proxy", async function () {
|
||||
const migratable = new MigratableMockV3(this.proxyAddress);
|
||||
|
||||
const x = await migratable.x();
|
||||
expect(x).to.be.bignumber.equal('42');
|
||||
|
||||
const y = await migratable.y();
|
||||
expect(y).to.be.bignumber.equal('10');
|
||||
|
||||
const balance = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||
expect(balance).to.be.bignumber.equal(this.balancePreviousV3.addn(value));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender is not the admin', function () {
|
||||
const from = anotherAccount;
|
||||
|
||||
it('reverts', async function () {
|
||||
const behaviorV1 = await MigratableMockV1.new();
|
||||
const v1MigrationData = new MigratableMockV1('').contract.methods.initialize(42).encodeABI();
|
||||
await expectRevert.unspecified(this.proxy.upgradeToAndCall(behaviorV1.address, v1MigrationData, { from }));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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 this.proxy.admin({ from: newAdmin });
|
||||
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 expectRevert(
|
||||
this.proxy.changeAdmin(ZERO_ADDRESS, { from: proxyAdminAddress }),
|
||||
'ERC1967: new admin is the zero address',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('storage', function () {
|
||||
it('should store the implementation address in specified location', async function () {
|
||||
const implementationSlot = await getSlot(this.proxy, ImplementationSlot);
|
||||
const implementationAddress = web3.utils.toChecksumAddress(implementationSlot.substr(-40));
|
||||
expect(implementationAddress).to.be.equal(this.implementationV0);
|
||||
});
|
||||
|
||||
it('should store the admin proxy in specified location', async function () {
|
||||
const proxyAdminSlot = await getSlot(this.proxy, AdminSlot);
|
||||
const proxyAdminAddress = web3.utils.toChecksumAddress(proxyAdminSlot.substr(-40));
|
||||
expect(proxyAdminAddress).to.be.equal(proxyAdminAddress);
|
||||
});
|
||||
});
|
||||
|
||||
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.clashing = new ClashingImplementation(this.proxy.address);
|
||||
});
|
||||
|
||||
it('proxy admin cannot call delegated functions', async function () {
|
||||
await expectRevert(
|
||||
this.clashing.delegatedFunction({ from: proxyAdminAddress }),
|
||||
'TransparentUpgradeableProxy: admin cannot fallback to proxy target',
|
||||
);
|
||||
});
|
||||
|
||||
describe('when function names clash', function () {
|
||||
it('when sender is proxy admin should run the proxy function', async function () {
|
||||
const value = await this.proxy.admin({ from: proxyAdminAddress, value: 0 });
|
||||
expect(value).to.be.equal(proxyAdminAddress);
|
||||
});
|
||||
|
||||
it('when sender is other should delegate to implementation', async function () {
|
||||
const value = await this.proxy.admin({ from: anotherAccount, value: 0 });
|
||||
expect(value).to.be.equal('0x0000000000000000000000000000000011111142');
|
||||
});
|
||||
|
||||
it('when sender is proxy admin value should not be accepted', async function () {
|
||||
await expectRevert.unspecified(this.proxy.admin({ from: proxyAdminAddress, value: 1 }));
|
||||
});
|
||||
|
||||
it('when sender is other value should be accepted', async function () {
|
||||
const value = await this.proxy.admin({ from: anotherAccount, value: 1 });
|
||||
expect(value).to.be.equal('0x0000000000000000000000000000000011111142');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('regression', () => {
|
||||
const initializeData = Buffer.from('');
|
||||
|
||||
it('should add new function', async () => {
|
||||
const instance1 = await Implementation1.new();
|
||||
const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
|
||||
const proxyInstance1 = new Implementation1(proxy.address);
|
||||
await proxyInstance1.setValue(42);
|
||||
|
||||
const instance2 = await Implementation2.new();
|
||||
await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress });
|
||||
|
||||
const proxyInstance2 = new Implementation2(proxy.address);
|
||||
const res = await proxyInstance2.getValue();
|
||||
expect(res.toString()).to.eq('42');
|
||||
});
|
||||
|
||||
it('should remove function', async () => {
|
||||
const instance2 = await Implementation2.new();
|
||||
const proxy = await createProxy(instance2.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
|
||||
const proxyInstance2 = new Implementation2(proxy.address);
|
||||
await proxyInstance2.setValue(42);
|
||||
const res = await proxyInstance2.getValue();
|
||||
expect(res.toString()).to.eq('42');
|
||||
|
||||
const instance1 = await Implementation1.new();
|
||||
await proxy.upgradeTo(instance1.address, { from: proxyAdminAddress });
|
||||
|
||||
const proxyInstance1 = new Implementation2(proxy.address);
|
||||
await expectRevert.unspecified(proxyInstance1.getValue());
|
||||
});
|
||||
|
||||
it('should change function signature', async () => {
|
||||
const instance1 = await Implementation1.new();
|
||||
const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
|
||||
const proxyInstance1 = new Implementation1(proxy.address);
|
||||
await proxyInstance1.setValue(42);
|
||||
|
||||
const instance3 = await Implementation3.new();
|
||||
await proxy.upgradeTo(instance3.address, { from: proxyAdminAddress });
|
||||
const proxyInstance3 = new Implementation3(proxy.address);
|
||||
|
||||
const res = await proxyInstance3.getValue(8);
|
||||
expect(res.toString()).to.eq('50');
|
||||
});
|
||||
|
||||
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 instance4 = await Implementation4.new();
|
||||
await proxy.upgradeTo(instance4.address, { from: proxyAdminAddress });
|
||||
const proxyInstance4 = new Implementation4(proxy.address);
|
||||
|
||||
const data = '0x';
|
||||
await web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data });
|
||||
|
||||
const res = await proxyInstance4.getValue();
|
||||
expect(res.toString()).to.eq('1');
|
||||
});
|
||||
|
||||
it('should remove fallback function', async () => {
|
||||
const instance4 = await Implementation4.new();
|
||||
const proxy = await createProxy(instance4.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||
|
||||
const instance2 = await Implementation2.new();
|
||||
await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress });
|
||||
|
||||
const data = '0x';
|
||||
await expectRevert.unspecified(web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data }));
|
||||
|
||||
const proxyInstance2 = new Implementation2(proxy.address);
|
||||
const res = await proxyInstance2.getValue();
|
||||
expect(res.toString()).to.eq('0');
|
||||
});
|
||||
});
|
||||
};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
const shouldBehaveLikeProxy = require('../Proxy.behaviour');
|
||||
const shouldBehaveLikeTransparentUpgradeableProxy = require('./TransparentUpgradeableProxy.behaviour');
|
||||
|
||||
const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeableProxy');
|
||||
const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableProxy');
|
||||
|
||||
contract('TransparentUpgradeableProxy', function (accounts) {
|
||||
const [proxyAdminAddress, proxyAdminOwner] = accounts;
|
||||
|
||||
const createProxy = async function (logic, admin, initData, opts) {
|
||||
const { address } = await TransparentUpgradeableProxy.new(logic, admin, initData, opts);
|
||||
return ITransparentUpgradeableProxy.at(address);
|
||||
};
|
||||
|
||||
shouldBehaveLikeProxy(createProxy, proxyAdminAddress, proxyAdminOwner);
|
||||
shouldBehaveLikeTransparentUpgradeableProxy(createProxy, accounts);
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const InitializableMock = artifacts.require('InitializableMock');
|
||||
const ConstructorInitializableMock = artifacts.require('ConstructorInitializableMock');
|
||||
const ChildConstructorInitializableMock = artifacts.require('ChildConstructorInitializableMock');
|
||||
const ReinitializerMock = artifacts.require('ReinitializerMock');
|
||||
const SampleChild = artifacts.require('SampleChild');
|
||||
const DisableBad1 = artifacts.require('DisableBad1');
|
||||
const DisableBad2 = artifacts.require('DisableBad2');
|
||||
const DisableOk = artifacts.require('DisableOk');
|
||||
|
||||
contract('Initializable', function () {
|
||||
describe('basic testing without inheritance', function () {
|
||||
beforeEach('deploying', async function () {
|
||||
this.contract = await InitializableMock.new();
|
||||
});
|
||||
|
||||
describe('before initialize', function () {
|
||||
it('initializer has not run', async function () {
|
||||
expect(await this.contract.initializerRan()).to.equal(false);
|
||||
});
|
||||
|
||||
it('_initializing returns false before initialization', async function () {
|
||||
expect(await this.contract.isInitializing()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('after initialize', function () {
|
||||
beforeEach('initializing', async function () {
|
||||
await this.contract.initialize();
|
||||
});
|
||||
|
||||
it('initializer has run', async function () {
|
||||
expect(await this.contract.initializerRan()).to.equal(true);
|
||||
});
|
||||
|
||||
it('_initializing returns false after initialization', async function () {
|
||||
expect(await this.contract.isInitializing()).to.equal(false);
|
||||
});
|
||||
|
||||
it('initializer does not run again', async function () {
|
||||
await expectRevert(this.contract.initialize(), 'Initializable: contract is already initialized');
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested under an initializer', function () {
|
||||
it('initializer modifier reverts', async function () {
|
||||
await expectRevert(this.contract.initializerNested(), 'Initializable: contract is already initialized');
|
||||
});
|
||||
|
||||
it('onlyInitializing modifier succeeds', async function () {
|
||||
await this.contract.onlyInitializingNested();
|
||||
expect(await this.contract.onlyInitializingRan()).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot call onlyInitializable function outside the scope of an initializable function', async function () {
|
||||
await expectRevert(this.contract.initializeOnlyInitializing(), 'Initializable: contract is not initializing');
|
||||
});
|
||||
});
|
||||
|
||||
it('nested initializer can run during construction', async function () {
|
||||
const contract2 = await ConstructorInitializableMock.new();
|
||||
expect(await contract2.initializerRan()).to.equal(true);
|
||||
expect(await contract2.onlyInitializingRan()).to.equal(true);
|
||||
});
|
||||
|
||||
it('multiple constructor levels can be initializers', async function () {
|
||||
const contract2 = await ChildConstructorInitializableMock.new();
|
||||
expect(await contract2.initializerRan()).to.equal(true);
|
||||
expect(await contract2.childInitializerRan()).to.equal(true);
|
||||
expect(await contract2.onlyInitializingRan()).to.equal(true);
|
||||
});
|
||||
|
||||
describe('reinitialization', function () {
|
||||
beforeEach('deploying', async function () {
|
||||
this.contract = await ReinitializerMock.new();
|
||||
});
|
||||
|
||||
it('can reinitialize', async function () {
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('0');
|
||||
await this.contract.initialize();
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('1');
|
||||
await this.contract.reinitialize(2);
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('2');
|
||||
await this.contract.reinitialize(3);
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('3');
|
||||
});
|
||||
|
||||
it('can jump multiple steps', async function () {
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('0');
|
||||
await this.contract.initialize();
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('1');
|
||||
await this.contract.reinitialize(128);
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('2');
|
||||
});
|
||||
|
||||
it('cannot nest reinitializers', async function () {
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('0');
|
||||
await expectRevert(this.contract.nestedReinitialize(2, 2), 'Initializable: contract is already initialized');
|
||||
await expectRevert(this.contract.nestedReinitialize(2, 3), 'Initializable: contract is already initialized');
|
||||
await expectRevert(this.contract.nestedReinitialize(3, 2), 'Initializable: contract is already initialized');
|
||||
});
|
||||
|
||||
it('can chain reinitializers', async function () {
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('0');
|
||||
await this.contract.chainReinitialize(2, 3);
|
||||
expect(await this.contract.counter()).to.be.bignumber.equal('2');
|
||||
});
|
||||
|
||||
it('_getInitializedVersion returns right version', async function () {
|
||||
await this.contract.initialize();
|
||||
expect(await this.contract.getInitializedVersion()).to.be.bignumber.equal('1');
|
||||
await this.contract.reinitialize(12);
|
||||
expect(await this.contract.getInitializedVersion()).to.be.bignumber.equal('12');
|
||||
});
|
||||
|
||||
describe('contract locking', function () {
|
||||
it('prevents initialization', async function () {
|
||||
await this.contract.disableInitializers();
|
||||
await expectRevert(this.contract.initialize(), 'Initializable: contract is already initialized');
|
||||
});
|
||||
|
||||
it('prevents re-initialization', async function () {
|
||||
await this.contract.disableInitializers();
|
||||
await expectRevert(this.contract.reinitialize(255), 'Initializable: contract is already initialized');
|
||||
});
|
||||
|
||||
it('can lock contract after initialization', async function () {
|
||||
await this.contract.initialize();
|
||||
await this.contract.disableInitializers();
|
||||
await expectRevert(this.contract.reinitialize(255), 'Initializable: contract is already initialized');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('events', function () {
|
||||
it('constructor initialization emits event', async function () {
|
||||
const contract = await ConstructorInitializableMock.new();
|
||||
|
||||
await expectEvent.inTransaction(contract.transactionHash, contract, 'Initialized', { version: '1' });
|
||||
});
|
||||
|
||||
it('initialization emits event', async function () {
|
||||
const contract = await ReinitializerMock.new();
|
||||
|
||||
const { receipt } = await contract.initialize();
|
||||
expect(receipt.logs.filter(({ event }) => event === 'Initialized').length).to.be.equal(1);
|
||||
expectEvent(receipt, 'Initialized', { version: '1' });
|
||||
});
|
||||
|
||||
it('reinitialization emits event', async function () {
|
||||
const contract = await ReinitializerMock.new();
|
||||
|
||||
const { receipt } = await contract.reinitialize(128);
|
||||
expect(receipt.logs.filter(({ event }) => event === 'Initialized').length).to.be.equal(1);
|
||||
expectEvent(receipt, 'Initialized', { version: '128' });
|
||||
});
|
||||
|
||||
it('chained reinitialization emits multiple events', async function () {
|
||||
const contract = await ReinitializerMock.new();
|
||||
|
||||
const { receipt } = await contract.chainReinitialize(2, 3);
|
||||
expect(receipt.logs.filter(({ event }) => event === 'Initialized').length).to.be.equal(2);
|
||||
expectEvent(receipt, 'Initialized', { version: '2' });
|
||||
expectEvent(receipt, 'Initialized', { version: '3' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('complex testing with inheritance', function () {
|
||||
const mother = '12';
|
||||
const gramps = '56';
|
||||
const father = '34';
|
||||
const child = '78';
|
||||
|
||||
beforeEach('deploying', async function () {
|
||||
this.contract = await SampleChild.new();
|
||||
});
|
||||
|
||||
beforeEach('initializing', async function () {
|
||||
await this.contract.initialize(mother, gramps, father, child);
|
||||
});
|
||||
|
||||
it('initializes human', async function () {
|
||||
expect(await this.contract.isHuman()).to.be.equal(true);
|
||||
});
|
||||
|
||||
it('initializes mother', async function () {
|
||||
expect(await this.contract.mother()).to.be.bignumber.equal(mother);
|
||||
});
|
||||
|
||||
it('initializes gramps', async function () {
|
||||
expect(await this.contract.gramps()).to.be.bignumber.equal(gramps);
|
||||
});
|
||||
|
||||
it('initializes father', async function () {
|
||||
expect(await this.contract.father()).to.be.bignumber.equal(father);
|
||||
});
|
||||
|
||||
it('initializes child', async function () {
|
||||
expect(await this.contract.child()).to.be.bignumber.equal(child);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabling initialization', function () {
|
||||
it('old and new patterns in bad sequence', async function () {
|
||||
await expectRevert(DisableBad1.new(), 'Initializable: contract is already initialized');
|
||||
await expectRevert(DisableBad2.new(), 'Initializable: contract is initializing');
|
||||
});
|
||||
|
||||
it('old and new patterns in good sequence', async function () {
|
||||
const ok = await DisableOk.new();
|
||||
await expectEvent.inConstruction(ok, 'Initialized', { version: '1' });
|
||||
await expectEvent.inConstruction(ok, 'Initialized', { version: '255' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { web3 } = require('@openzeppelin/test-helpers/src/setup');
|
||||
const { getSlot, ImplementationSlot } = require('../../helpers/erc1967');
|
||||
|
||||
const ERC1967Proxy = artifacts.require('ERC1967Proxy');
|
||||
const UUPSUpgradeableMock = artifacts.require('UUPSUpgradeableMock');
|
||||
const UUPSUpgradeableUnsafeMock = artifacts.require('UUPSUpgradeableUnsafeMock');
|
||||
const UUPSUpgradeableLegacyMock = artifacts.require('UUPSUpgradeableLegacyMock');
|
||||
const NonUpgradeableMock = artifacts.require('NonUpgradeableMock');
|
||||
|
||||
contract('UUPSUpgradeable', function () {
|
||||
before(async function () {
|
||||
this.implInitial = await UUPSUpgradeableMock.new();
|
||||
this.implUpgradeOk = await UUPSUpgradeableMock.new();
|
||||
this.implUpgradeUnsafe = await UUPSUpgradeableUnsafeMock.new();
|
||||
this.implUpgradeNonUUPS = await NonUpgradeableMock.new();
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
const { address } = await ERC1967Proxy.new(this.implInitial.address, '0x');
|
||||
this.instance = await UUPSUpgradeableMock.at(address);
|
||||
});
|
||||
|
||||
it('upgrade to upgradeable implementation', async function () {
|
||||
const { receipt } = await this.instance.upgradeTo(this.implUpgradeOk.address);
|
||||
expect(receipt.logs.filter(({ event }) => event === 'Upgraded').length).to.be.equal(1);
|
||||
expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeOk.address });
|
||||
});
|
||||
|
||||
it('upgrade to upgradeable implementation with call', async function () {
|
||||
expect(await this.instance.current()).to.be.bignumber.equal('0');
|
||||
|
||||
const { receipt } = await this.instance.upgradeToAndCall(
|
||||
this.implUpgradeOk.address,
|
||||
this.implUpgradeOk.contract.methods.increment().encodeABI(),
|
||||
);
|
||||
expect(receipt.logs.filter(({ event }) => event === 'Upgraded').length).to.be.equal(1);
|
||||
expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeOk.address });
|
||||
|
||||
expect(await this.instance.current()).to.be.bignumber.equal('1');
|
||||
});
|
||||
|
||||
it('upgrade to and unsafe upgradeable implementation', async function () {
|
||||
const { receipt } = await this.instance.upgradeTo(this.implUpgradeUnsafe.address);
|
||||
expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeUnsafe.address });
|
||||
});
|
||||
|
||||
// delegate to a non existing upgradeTo function causes a low level revert
|
||||
it('reject upgrade to non uups implementation', async function () {
|
||||
await expectRevert(
|
||||
this.instance.upgradeTo(this.implUpgradeNonUUPS.address),
|
||||
'ERC1967Upgrade: new implementation is not UUPS',
|
||||
);
|
||||
});
|
||||
|
||||
it('reject proxy address as implementation', async function () {
|
||||
const { address } = await ERC1967Proxy.new(this.implInitial.address, '0x');
|
||||
const otherInstance = await UUPSUpgradeableMock.at(address);
|
||||
|
||||
await expectRevert(
|
||||
this.instance.upgradeTo(otherInstance.address),
|
||||
'ERC1967Upgrade: new implementation is not UUPS',
|
||||
);
|
||||
});
|
||||
|
||||
it('can upgrade from legacy implementations', async function () {
|
||||
const legacyImpl = await UUPSUpgradeableLegacyMock.new();
|
||||
const legacyInstance = await ERC1967Proxy.new(legacyImpl.address, '0x').then(({ address }) =>
|
||||
UUPSUpgradeableLegacyMock.at(address),
|
||||
);
|
||||
|
||||
const receipt = await legacyInstance.upgradeTo(this.implInitial.address);
|
||||
|
||||
const UpgradedEvents = receipt.logs.filter(
|
||||
({ address, event }) => address === legacyInstance.address && event === 'Upgraded',
|
||||
);
|
||||
expect(UpgradedEvents.length).to.be.equal(1);
|
||||
|
||||
expectEvent(receipt, 'Upgraded', { implementation: this.implInitial.address });
|
||||
|
||||
const implementationSlot = await getSlot(legacyInstance, ImplementationSlot);
|
||||
const implementationAddress = web3.utils.toChecksumAddress(implementationSlot.substr(-40));
|
||||
expect(implementationAddress).to.be.equal(this.implInitial.address);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user