Skip to content

Commit dd0f9b3

Browse files
zmalatraxjaybuidl
authored andcommitted
feat(proxy): add test on mock implementation
1 parent e5fc1d7 commit dd0f9b3

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//SPDX-License-Identifier: MIT
2+
// Adapted from <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/proxy/UUPSUpgradeableMock.sol>
3+
4+
pragma solidity 0.8.18;
5+
6+
import "../UUPSProxiable.sol";
7+
8+
contract NonUpgradeableMock {
9+
uint256 public _counter;
10+
11+
function counter() external view returns (uint256) {
12+
return _counter;
13+
}
14+
15+
function increment() external {
16+
_counter++;
17+
}
18+
19+
function version() external pure virtual returns (string memory) {
20+
return "NonUpgradeableMock 0.0.0";
21+
}
22+
}
23+
24+
contract UUPSUpgradeableMock is UUPSProxiable, NonUpgradeableMock {
25+
bool private initialized;
26+
address public governor;
27+
28+
uint256[50] __gap;
29+
30+
constructor() {
31+
initialized = true;
32+
}
33+
34+
function initialize(address _governor) external {
35+
require(!initialized, "Contract instance has already been initialized");
36+
governor = _governor;
37+
initialized = true;
38+
}
39+
40+
function _authorizeUpgrade(address) internal view override {
41+
require(governor == msg.sender, "No privilege to upgrade");
42+
}
43+
44+
function version() external pure virtual override returns (string memory) {
45+
return "UUPSUpgradeableMock 1.0.0";
46+
}
47+
}
48+
49+
contract UUPSUpgradeableMockV2 is UUPSUpgradeableMock {
50+
function version() external pure override returns (string memory) {
51+
return "UUPSUpgradeableMock 2.0.0";
52+
}
53+
}
54+
55+
contract UUPSUnsupportedProxiableUUID is UUPSUpgradeableMock {
56+
function proxiableUUID() external pure override returns (bytes32) {
57+
return keccak256("invalid UUID");
58+
}
59+
60+
function version() external pure override returns (string memory) {
61+
return "UUPSUnsupportedProxiableUUID 1.0.0";
62+
}
63+
}

contracts/test/proxy/index.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { expect } from "chai";
2+
import { ethers, deployments, getNamedAccounts } from "hardhat";
3+
const hre = require("hardhat");
4+
5+
let deployer;
6+
let user1;
7+
8+
let uupsProxyDeployment;
9+
let uupsProxy;
10+
let uupsImplementationInit;
11+
12+
describe("Mock Implementation Proxy", async () => {
13+
beforeEach("Setup Contracts", async () => {
14+
[deployer, user1] = await ethers.getSigners();
15+
// uupsProxyDeployment = await deployments.get("UUPSUpgradeableMock");
16+
17+
// Deploy Proxy with hardhat-deploy
18+
uupsProxyDeployment = await deployments.deploy("UUPSUpgradeableMock", {
19+
from: deployer.address,
20+
proxy: {
21+
proxyContract: "UUPSProxy",
22+
execute: {
23+
init: {
24+
methodName: "initialize",
25+
args: [deployer.address],
26+
},
27+
onUpgrade: {
28+
methodName: "counter",
29+
args: [],
30+
},
31+
},
32+
proxyArgs: ["{implementation}", "{data}"],
33+
},
34+
log: true,
35+
args: [],
36+
});
37+
uupsProxy = await ethers.getContractAt("UUPSUpgradeableMock", uupsProxyDeployment.address);
38+
uupsImplementationInit = await ethers.getContractAt("UUPSUpgradeableMock", uupsProxyDeployment.implementation);
39+
// console.log(uupsProxyDeployment.implementation);
40+
});
41+
42+
describe("Initialization", async () => {
43+
it("Governor cannot re-initialize the proxy", async () => {
44+
await expect(uupsProxy.connect(deployer).initialize(deployer.address)).to.be.revertedWith(
45+
"Contract instance has already been initialized"
46+
);
47+
});
48+
it("User cannot re-initialize the proxy", async () => {
49+
await expect(uupsProxy.connect(user1).initialize(user1.address)).to.be.revertedWith(
50+
"Contract instance has already been initialized"
51+
);
52+
});
53+
it("Implementation cannot be directly upgraded", async () => {
54+
await expect(uupsImplementationInit.initialize(user1.address)).to.be.revertedWith(
55+
"Contract instance has already been initialized"
56+
);
57+
});
58+
// it("Unauthorized user cannot upgrade", async () => {});
59+
});
60+
describe("Upgrade", async () => {
61+
describe("Security", async () => {
62+
it("Should revert if implementation has a broken UUID", async () => {
63+
const UUPSUnsupportedProxiableUUIDFactory = await ethers.getContractFactory("UUPSUnsupportedProxiableUUID");
64+
const uupsUnsupportedUUID = await UUPSUnsupportedProxiableUUIDFactory.deploy();
65+
await expect(
66+
uupsProxy.connect(deployer).upgradeToAndCall(uupsUnsupportedUUID.address, "0x")
67+
).to.be.revertedWithCustomError(uupsProxy, "UUPSUnsupportedProxiableUUID");
68+
});
69+
it("Should revert on upgrades to non UUPS-compliant implementation", async () => {
70+
const NonUpgradeableMockFactory = await ethers.getContractFactory("NonUpgradeableMock");
71+
const nonUpgradeableMock = await NonUpgradeableMockFactory.deploy();
72+
await expect(uupsProxy.upgradeToAndCall(nonUpgradeableMock.address, "0x"))
73+
.to.be.revertedWithCustomError(uupsProxy, "InvalidImplementation")
74+
.withArgs(nonUpgradeableMock.address);
75+
});
76+
77+
// If the `governor` storage slot is not initialized in the constructor, trying to directly upgrade the implementation as `governor === address(0)`
78+
// it("Should revert if upgrade is performed directly through the implementation", async () => {
79+
// const UUPSUpgradeableMockV2Factory = await ethers.getContractFactory("UUPSUpgradeableMockV2");
80+
// const newImplementation = await UUPSUpgradeableMockV2Factory.connect(deployer).deploy();
81+
82+
// await expect(uupsImplementationInit.connect(deployer).upgradeToAndCall(newImplementation.address, "0x")).to.be.revertedWith(
83+
// "Must be called through delegatecall"
84+
// );
85+
// })
86+
});
87+
88+
describe("Governance", async () => {
89+
it("Only the governor (deployer here) can perform upgrades", async () => {
90+
// Unauthorized user try to upgrade the implementation
91+
const UUPSUpgradeableMockV2Factory = await ethers.getContractFactory("UUPSUpgradeableMockV2");
92+
const newUserImplementation = await UUPSUpgradeableMockV2Factory.connect(user1).deploy();
93+
94+
await expect(uupsProxy.connect(user1).upgradeToAndCall(newUserImplementation.address, "0x")).to.be.revertedWith(
95+
"No privilege to upgrade"
96+
);
97+
98+
// Governor updates the implementation
99+
const newGovernorImplementation = await UUPSUpgradeableMockV2Factory.connect(deployer).deploy();
100+
console.log("Version: ", await uupsProxy.version());
101+
102+
await expect(uupsProxy.connect(deployer).upgradeToAndCall(newGovernorImplementation.address, "0x"))
103+
.to.emit(uupsProxy, "Upgraded")
104+
.withArgs(newGovernorImplementation.address);
105+
106+
console.log("Version: ", await uupsProxy.version());
107+
});
108+
});
109+
});
110+
111+
describe("After Test", async () => {
112+
it("Reset implementation to deployment's implementation address", async () => {
113+
await uupsProxy.upgradeToAndCall(uupsProxyDeployment.implementation, "0x");
114+
console.log("Version: ", await uupsProxy.version());
115+
});
116+
});
117+
});

0 commit comments

Comments
 (0)