diff --git a/test/misc/ProxyAdminTest.t.sol b/test/misc/ProxyAdminTest.t.sol new file mode 100644 index 0000000..23a4ac2 --- /dev/null +++ b/test/misc/ProxyAdminTest.t.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import 'test/base/BaseTest.t.sol'; +import {ProxyAdmin} from 'contracts/misc/access/ProxyAdmin.sol'; + +contract MockContract { + function testMockContract() public { + // Prevents being counted in Foundry Coverage + } + + function initialize(address initializationAddress) public pure {} +} + +contract ProxyAdminTest is BaseTest { + using stdJson for string; + + error Unauthorized(); + + ProxyAdmin proxyAdminContract; + address proxyAdminContractOwner = makeAddr('PROXY_ADMIN_CONTRACT_OWNER'); + address controllerContract = makeAddr('CONTROLLER_CONTRACT'); + bytes32 PROXY_IMPLEMENTATION_STORAGE_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); + + function setUp() public override { + super.setUp(); + + if (fork) { + proxyAdminContract = ProxyAdmin( + json.readAddress(string(abi.encodePacked('.', forkEnv, '.ProxyAdminContract'))) + ); + } else { + proxyAdminContract = new ProxyAdmin(address(hub), address(hubImpl), proxyAdminContractOwner); + } + + vm.prank(proxyAdmin); + hubAsProxy.changeAdmin(address(proxyAdminContract)); + + vm.prank(proxyAdminContractOwner); + proxyAdminContract.setControllerContract(controllerContract); + } + + // Negatives + + function testCannot_rollbackLastUpgrade_ifNotOwner(address otherAddress) public { + vm.assume(otherAddress != proxyAdminContractOwner); + vm.expectRevert('Ownable: caller is not the owner'); + vm.prank(otherAddress); + proxyAdminContract.rollbackLastUpgrade(); + } + + function testCannot_changeLensHubProxyAdmin_ifNotOwner(address newAdmin, address otherAddress) public { + vm.assume(otherAddress != proxyAdminContractOwner); + vm.assume(newAdmin != address(0)); + vm.expectRevert('Ownable: caller is not the owner'); + vm.prank(otherAddress); + proxyAdminContract.proxy_changeAdmin(newAdmin); + } + + function testCannot_upgrade_ifNotOwnerOrControllerContract(address otherAddress) public { + address hubImplV2 = address(new MockContract()); + + vm.assume(otherAddress != proxyAdminContractOwner); + vm.assume(otherAddress != controllerContract); + + vm.expectRevert(Unauthorized.selector); + vm.prank(otherAddress); + proxyAdminContract.proxy_upgrade(hubImplV2); + } + + function testCannot_upgradeAndCall_ifNotOwnerOrControllerContract(address otherAddress) public { + address hubImplV2 = address(new MockContract()); + + vm.assume(otherAddress != proxyAdminContractOwner); + vm.assume(otherAddress != controllerContract); + + vm.expectRevert(Unauthorized.selector); + vm.prank(otherAddress); + proxyAdminContract.proxy_upgradeAndCall( + hubImplV2, + abi.encodeWithSelector(MockContract.initialize.selector, address(0xdeadbeef)) + ); + } + + // Scenarios + + function testContructor() public { + assertEq(address(proxyAdminContract.LENS_HUB_PROXY()), address(hub)); + assertEq(proxyAdminContract.previousImplementation(), address(hubImpl)); + assertEq(proxyAdminContract.owner(), proxyAdminContractOwner); + assertEq(proxyAdminContract.controllerContract(), controllerContract); + } + + function testCurrentImplementation() public { + assertEq(proxyAdminContract.currentImplementation(), address(hubImpl)); + } + + function testRollbackLastUpgrade() public { + address hubImplV2 = address(new MockContract()); + + address prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertEq(prevImpl, proxyAdminContract.previousImplementation()); + + vm.prank(proxyAdminContractOwner); + proxyAdminContract.proxy_upgrade(hubImplV2); + + address newImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertEq(newImpl, hubImplV2); + + vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeTo, (prevImpl))); + + vm.prank(proxyAdminContractOwner); + proxyAdminContract.rollbackLastUpgrade(); + + address rolledBackImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertEq(rolledBackImpl, prevImpl); + } + + function testChangeLensHubProxyAdmin(address newAdmin) public { + vm.assume(newAdmin != address(0)); + address currentProxyAdmin = address(uint160(uint256(vm.load(hubProxyAddr, ADMIN_SLOT)))); + vm.assume(newAdmin != currentProxyAdmin); + + vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.changeAdmin, (newAdmin))); + vm.prank(proxyAdminContractOwner); + proxyAdminContract.proxy_changeAdmin(newAdmin); + + address changedProxyAdmin = address(uint160(uint256(vm.load(hubProxyAddr, ADMIN_SLOT)))); + assertEq(changedProxyAdmin, newAdmin); + } + + function testUpgrade_ifCalledByOwner() public { + address hubImplV2 = address(new MockContract()); + + address prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertTrue(prevImpl != hubImplV2); + + vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeTo, (hubImplV2))); + vm.prank(proxyAdminContractOwner); + proxyAdminContract.proxy_upgrade(hubImplV2); + + address upgradedImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertEq(upgradedImpl, hubImplV2); + + assertEq(proxyAdminContract.previousImplementation(), prevImpl); + assertEq(proxyAdminContract.controllerContract(), address(0)); // Controller is cleared after upgrade + } + + function testUpgrade_ifCalledByControllerContract() public { + address hubImplV2 = address(new MockContract()); + + address prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertTrue(prevImpl != hubImplV2); + + vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeTo, (hubImplV2))); + vm.prank(controllerContract); + proxyAdminContract.proxy_upgrade(hubImplV2); + + address upgradedImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertEq(upgradedImpl, hubImplV2); + + assertEq(proxyAdminContract.previousImplementation(), prevImpl); + assertEq(proxyAdminContract.controllerContract(), address(0)); // Controller is cleared after upgrade + } + + function testUpgradeAndCall_ifCalledByOwner() public { + address hubImplV2 = address(new MockContract()); + + address prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertTrue(prevImpl != hubImplV2); + + bytes memory data = abi.encodeCall(MockContract.initialize, (address(0xdeadbeef))); + + vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeToAndCall, (hubImplV2, data))); + vm.expectCall(address(hubImplV2), data); + vm.prank(proxyAdminContractOwner); + proxyAdminContract.proxy_upgradeAndCall(hubImplV2, data); + + address upgradedImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertEq(upgradedImpl, hubImplV2); + + assertEq(proxyAdminContract.previousImplementation(), prevImpl); + assertEq(proxyAdminContract.controllerContract(), address(0)); // Controller is cleared after upgrade + } + + function testUpgradeAndCall_ifCalledByControllerContract() public { + address hubImplV2 = address(new MockContract()); + + address prevImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertTrue(prevImpl != hubImplV2); + + bytes memory data = abi.encodeCall(MockContract.initialize, (address(0xdeadbeef))); + + vm.expectCall(address(hubAsProxy), abi.encodeCall(hubAsProxy.upgradeToAndCall, (hubImplV2, data))); + vm.expectCall(address(hubImplV2), data); + vm.prank(controllerContract); + proxyAdminContract.proxy_upgradeAndCall(hubImplV2, data); + + address upgradedImpl = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT)))); + assertEq(upgradedImpl, hubImplV2); + + assertEq(proxyAdminContract.previousImplementation(), prevImpl); + assertEq(proxyAdminContract.controllerContract(), address(0)); // Controller is cleared after upgrade + } +}