|
| 1 | +const { BN, expectRevert } = require('@openzeppelin/test-helpers'); |
| 2 | +const ethereumjsUtil = require('ethereumjs-util'); |
| 3 | +const { keccak256 } = ethereumjsUtil; |
| 4 | + |
| 5 | +const { expect } = require('chai'); |
| 6 | + |
| 7 | +const UpgradeableBeacon = artifacts.require('UpgradeableBeacon'); |
| 8 | +const BeaconProxy = artifacts.require('BeaconProxy'); |
| 9 | +const DummyImplementation = artifacts.require('DummyImplementation'); |
| 10 | +const DummyImplementationV2 = artifacts.require('DummyImplementationV2'); |
| 11 | +const BadBeaconNoImpl = artifacts.require('BadBeaconNoImpl'); |
| 12 | +const BadBeaconNotContract = artifacts.require('BadBeaconNotContract'); |
| 13 | + |
| 14 | +function toChecksumAddress (address) { |
| 15 | + return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0')); |
| 16 | +} |
| 17 | + |
| 18 | +const BEACON_LABEL = 'eip1967.proxy.beacon'; |
| 19 | +const BEACON_SLOT = '0x' + new BN(keccak256(Buffer.from(BEACON_LABEL))).subn(1).toString(16); |
| 20 | + |
| 21 | +contract('BeaconProxy', function (accounts) { |
| 22 | + const [anotherAccount] = accounts; |
| 23 | + |
| 24 | + describe('bad beacon is not accepted', async function () { |
| 25 | + it('non-contract beacon', async function () { |
| 26 | + await expectRevert( |
| 27 | + BeaconProxy.new(anotherAccount, '0x'), |
| 28 | + 'BeaconProxy: beacon is not a contract', |
| 29 | + ); |
| 30 | + }); |
| 31 | + |
| 32 | + it('non-compliant beacon', async function () { |
| 33 | + const beacon = await BadBeaconNoImpl.new(); |
| 34 | + await expectRevert.unspecified( |
| 35 | + BeaconProxy.new(beacon.address, '0x'), |
| 36 | + ); |
| 37 | + }); |
| 38 | + |
| 39 | + it('non-contract implementation', async function () { |
| 40 | + const beacon = await BadBeaconNotContract.new(); |
| 41 | + await expectRevert( |
| 42 | + BeaconProxy.new(beacon.address, '0x'), |
| 43 | + 'BeaconProxy: beacon implementation is not a contract', |
| 44 | + ); |
| 45 | + }); |
| 46 | + }); |
| 47 | + |
| 48 | + before('deploy implementation', async function () { |
| 49 | + this.implementationV0 = await DummyImplementation.new(); |
| 50 | + this.implementationV1 = await DummyImplementationV2.new(); |
| 51 | + }); |
| 52 | + |
| 53 | + describe('initialization', function () { |
| 54 | + before(function () { |
| 55 | + this.assertInitialized = async ({ value, balance }) => { |
| 56 | + const beaconAddress = toChecksumAddress(await web3.eth.getStorageAt(this.proxy.address, BEACON_SLOT)); |
| 57 | + expect(beaconAddress).to.equal(this.beacon.address); |
| 58 | + |
| 59 | + const dummy = new DummyImplementation(this.proxy.address); |
| 60 | + expect(await dummy.value()).to.bignumber.eq(value); |
| 61 | + |
| 62 | + expect(await web3.eth.getBalance(this.proxy.address)).to.bignumber.eq(balance); |
| 63 | + }; |
| 64 | + }); |
| 65 | + |
| 66 | + beforeEach('deploy beacon', async function () { |
| 67 | + this.beacon = await UpgradeableBeacon.new(this.implementationV0.address); |
| 68 | + }); |
| 69 | + |
| 70 | + it('no initialization', async function () { |
| 71 | + const data = Buffer.from(''); |
| 72 | + const balance = '10'; |
| 73 | + this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance }); |
| 74 | + await this.assertInitialized({ value: '0', balance }); |
| 75 | + }); |
| 76 | + |
| 77 | + it('non-payable initialization', async function () { |
| 78 | + const value = '55'; |
| 79 | + const data = this.implementationV0.contract.methods |
| 80 | + .initializeNonPayableWithValue(value) |
| 81 | + .encodeABI(); |
| 82 | + this.proxy = await BeaconProxy.new(this.beacon.address, data); |
| 83 | + await this.assertInitialized({ value, balance: '0' }); |
| 84 | + }); |
| 85 | + |
| 86 | + it('payable initialization', async function () { |
| 87 | + const value = '55'; |
| 88 | + const data = this.implementationV0.contract.methods |
| 89 | + .initializePayableWithValue(value) |
| 90 | + .encodeABI(); |
| 91 | + const balance = '100'; |
| 92 | + this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance }); |
| 93 | + await this.assertInitialized({ value, balance }); |
| 94 | + }); |
| 95 | + |
| 96 | + it('reverting initialization', async function () { |
| 97 | + const data = this.implementationV0.contract.methods.reverts().encodeABI(); |
| 98 | + await expectRevert( |
| 99 | + BeaconProxy.new(this.beacon.address, data), |
| 100 | + 'DummyImplementation reverted', |
| 101 | + ); |
| 102 | + }); |
| 103 | + }); |
| 104 | + |
| 105 | + it('upgrade a proxy by upgrading its beacon', async function () { |
| 106 | + const beacon = await UpgradeableBeacon.new(this.implementationV0.address); |
| 107 | + |
| 108 | + const value = '10'; |
| 109 | + const data = this.implementationV0.contract.methods |
| 110 | + .initializeNonPayableWithValue(value) |
| 111 | + .encodeABI(); |
| 112 | + const proxy = await BeaconProxy.new(beacon.address, data); |
| 113 | + |
| 114 | + const dummy = new DummyImplementation(proxy.address); |
| 115 | + |
| 116 | + // test initial values |
| 117 | + expect(await dummy.value()).to.bignumber.eq(value); |
| 118 | + |
| 119 | + // test initial version |
| 120 | + expect(await dummy.version()).to.eq('V1'); |
| 121 | + |
| 122 | + // upgrade beacon |
| 123 | + await beacon.upgradeTo(this.implementationV1.address); |
| 124 | + |
| 125 | + // test upgraded version |
| 126 | + expect(await dummy.version()).to.eq('V2'); |
| 127 | + }); |
| 128 | + |
| 129 | + it('upgrade 2 proxies by upgrading shared beacon', async function () { |
| 130 | + const value1 = '10'; |
| 131 | + const value2 = '42'; |
| 132 | + |
| 133 | + const beacon = await UpgradeableBeacon.new(this.implementationV0.address); |
| 134 | + |
| 135 | + const proxy1InitializeData = this.implementationV0.contract.methods |
| 136 | + .initializeNonPayableWithValue(value1) |
| 137 | + .encodeABI(); |
| 138 | + const proxy1 = await BeaconProxy.new(beacon.address, proxy1InitializeData); |
| 139 | + |
| 140 | + const proxy2InitializeData = this.implementationV0.contract.methods |
| 141 | + .initializeNonPayableWithValue(value2) |
| 142 | + .encodeABI(); |
| 143 | + const proxy2 = await BeaconProxy.new(beacon.address, proxy2InitializeData); |
| 144 | + |
| 145 | + const dummy1 = new DummyImplementation(proxy1.address); |
| 146 | + const dummy2 = new DummyImplementation(proxy2.address); |
| 147 | + |
| 148 | + // test initial values |
| 149 | + expect(await dummy1.value()).to.bignumber.eq(value1); |
| 150 | + expect(await dummy2.value()).to.bignumber.eq(value2); |
| 151 | + |
| 152 | + // test initial version |
| 153 | + expect(await dummy1.version()).to.eq('V1'); |
| 154 | + expect(await dummy2.version()).to.eq('V1'); |
| 155 | + |
| 156 | + // upgrade beacon |
| 157 | + await beacon.upgradeTo(this.implementationV1.address); |
| 158 | + |
| 159 | + // test upgraded version |
| 160 | + expect(await dummy1.version()).to.eq('V2'); |
| 161 | + expect(await dummy2.version()).to.eq('V2'); |
| 162 | + }); |
| 163 | +}); |
0 commit comments