Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgradeability #1

Merged
merged 20 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 35 additions & 44 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -1,81 +1,72 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.25;

// TODO: adapt deploy script to deploy the KSXVault contract
/*
import {BaseGoerliParameters} from
"script/utils/parameters/BaseGoerliParameters.sol";
import {BaseParameters} from "script/utils/parameters/BaseParameters.sol";
// proxy
import {ERC1967Proxy as Proxy} from
"lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";

// contracts
import {KSXVault} from "src/KSXVault.sol";

// parameters
import {OptimismGoerliParameters} from
"script/utils/parameters/OptimismGoerliParameters.sol";
import {OptimismParameters} from
"script/utils/parameters/OptimismParameters.sol";

// forge utils
import {Script} from "lib/forge-std/src/Script.sol";
import {Counter} from "src/Counter.sol";

/// @title Kwenta deployment script
/// @title Kwenta KSX deployment script
/// @author Flocqst ([email protected])
contract Setup is Script {
function deploySystem() public returns (address) {
Counter counter = new Counter();
return address(counter);
}
}

/// @dev steps to deploy and verify on Base:
/// (1) load the variables in the .env file via `source .env`
/// (2) run `forge script script/Deploy.s.sol:DeployBase --rpc-url $BASE_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv`
contract DeployBase is Setup, BaseParameters {
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);

Setup.deploySystem();
function deploySystem(
address token,
address pDAO
) public returns (KSXVault ksxVault) {
ksxVault = new KSXVault({
_token: token,
_pDAO: pDAO
});

vm.stopBroadcast();
}
}
// deploy ERC1967 proxy and set implementation to ksxVault
Proxy proxy = new Proxy(address(ksxVault), "");

/// @dev steps to deploy and verify on Base Goerli:
/// (1) load the variables in the .env file via `source .env`
/// (2) run `forge script script/Deploy.s.sol:DeployBaseGoerli --rpc-url $BASE_GOERLI_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv`
contract DeployBaseGoerli is Setup, BaseGoerliParameters {
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);

Setup.deploySystem();

vm.stopBroadcast();
// "wrap" proxy in IKSXVault interface
ksxVault = KSXVault(address(proxy));
}
}

/// @dev steps to deploy and verify on Optimism:
/// (1) load the variables in the .env file via `source .env`
/// (2) run `forge script script/Deploy.s.sol:DeployOptimism --rpc-url $OPTIMISM_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv`
contract DeployOptimism is Setup, OptimismParameters {
/// (2) run `forge script script/Deploy.s.sol:DeployOptimism_Synthetix --rpc-url $OPTIMISM_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv`
contract DeployOptimism_Synthetix is Setup, OptimismParameters {
Flocqst marked this conversation as resolved.
Show resolved Hide resolved
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);

Setup.deploySystem();
Setup.deploySystem({
token: KWENTA,
pDAO: PDAO
});

vm.stopBroadcast();
}
}

/// @dev steps to deploy and verify on Optimism Goerli:
/// (1) load the variables in the .env file via `source .env`
/// (2) run `forge script script/Deploy.s.sol:DeployOptimismGoerli --rpc-url $OPTIMISM_GOERLI_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv`

contract DeployOptimismGoerli is Setup, OptimismGoerliParameters {
/// (2) run `forge script script/Deploy.s.sol:DeployOptimismGoerli_Synthetix --rpc-url $OPTIMISM_GOERLI_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv`
contract DeployOptimismGoerli_Synthetix is Setup, OptimismGoerliParameters {
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);

Setup.deploySystem();
Setup.deploySystem({
token: KWENTA,
pDAO: PDAO
});

vm.stopBroadcast();
}
}
*/
4 changes: 0 additions & 4 deletions script/utils/parameters/BaseGoerliParameters.sol

This file was deleted.

4 changes: 0 additions & 4 deletions script/utils/parameters/BaseParameters.sol

This file was deleted.

10 changes: 9 additions & 1 deletion script/utils/parameters/OptimismGoerliParameters.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.25;

contract OptimismGoerliParameters {}
contract OptimismGoerliParameters {
/// @dev this is an EOA used on testnet only
address public constant PDAO = 0x1b4fCFE451A15218aEeC811B508B4aa3f2A35904;

// https://developers.circle.com/stablecoins/docs/usdc-on-test-networks#usdc-on-op-goerli
address public constant USDC = 0xe05606174bac4A6364B31bd0eCA4bf4dD368f8C6;

address public constant KWENTA = 0x920Cf626a271321C151D027030D5d08aF699456b;
}
10 changes: 9 additions & 1 deletion script/utils/parameters/OptimismParameters.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.25;

contract OptimismParameters {}
contract OptimismParameters {
address public constant PDAO = 0xe826d43961a87fBE71C91d9B73F7ef9b16721C07;

// https://optimistic.etherscan.io/token/0x0b2c639c533813f4aa9d7837caf62653d097ff85
address public constant USDC = 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85;

// https://optimistic.etherscan.io/token/0x920cf626a271321c151d027030d5d08af699456b
address public constant KWENTA = 0x920Cf626a271321C151D027030D5d08aF699456b;
}
49 changes: 45 additions & 4 deletions src/KSXVault.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,57 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {IKSXVault} from "src/interfaces/IKSXVault.sol";
import {ERC4626} from
"@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/// @title Kwenta Example Contract
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

/// @title KSXVault Contract
/// @notice KSX ERC4626 Vault
/// @author Flocqst ([email protected])
contract KSXVault is ERC4626 {
constructor(address _token)
contract KSXVault is IKSXVault, ERC4626, UUPSUpgradeable {
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/

/// @notice Kwenta owned/operated multisig address that
/// can authorize upgrades
/// @dev if this address is the zero address, then the
/// KSX vault will no longer be upgradeable
/// @dev making immutable because the pDAO address
/// will *never* change
address internal immutable pDAO;

/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/

/// @notice Constructs the KSXVault contract
/// @param _token Kwenta token address
/// @param _pDAO Kwenta owned/operated multisig address
/// that can authorize upgrades
constructor(address _token, address _pDAO)
ERC4626(IERC20(_token))
ERC20("KSX Vault", "KSX")
{}
{
/// @dev pDAO address can be the zero address to
/// make the KSX vault non-upgradeable
pDAO = _pDAO;
}

/*//////////////////////////////////////////////////////////////
UPGRADE MANAGEMENT
//////////////////////////////////////////////////////////////*/

/// @inheritdoc UUPSUpgradeable
function _authorizeUpgrade(address /* _newImplementation */ )
jcmonte marked this conversation as resolved.
Show resolved Hide resolved
internal
view
override
{
if (pDAO == address(0)) revert NonUpgradeable();
if (msg.sender != pDAO) revert OnlyPDAO();
}
}
23 changes: 23 additions & 0 deletions src/interfaces/IKSXVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";

/// @title Kwenta KSXVault Interface
/// @author Flocqst ([email protected])
interface IKSXVault {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/

/// @notice thrown when attempting to update
/// the KSXVault when caller is not the Kwenta pDAO
error OnlyPDAO();

/// @notice thrown when attempting to upgrade
/// the KSXVault when the KSXVault is not upgradeable
/// @dev the KSXVault is not upgradeable when
/// the pDAO has been set to the zero address
error NonUpgradeable();
}
80 changes: 80 additions & 0 deletions test/Upgrade.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {Bootstrap, KSXVault} from "test/utils/Bootstrap.sol";
import {IKSXVault} from "src/interfaces/IKSXVault.sol";
import {MockVaultUpgrade} from "test/utils/mocks/MockVaultUpgrade.sol";

contract UpgradeTest is Bootstrap {
function setUp() public {
// vm.rollFork(BASE_BLOCK_NUMBER);
Flocqst marked this conversation as resolved.
Show resolved Hide resolved
initializeOptimism();
}
}

contract MockUpgrade is UpgradeTest {
MockVaultUpgrade mockVaultUpgrade;

function deployMockVault() internal {
mockVaultUpgrade = new MockVaultUpgrade(
address(TOKEN),
address(pDAO)
);
}

function test_upgrade() public {
string memory message = "hi";

bool success;
bytes memory response;

(success,) = address(ksxVault).call(
abi.encodeWithSelector(MockVaultUpgrade.echo.selector, message)
);
assert(!success);

deployMockVault();

vm.prank(pDAO);

ksxVault.upgradeToAndCall(address(mockVaultUpgrade), "");

(success, response) = address(ksxVault).call(
abi.encodeWithSelector(mockVaultUpgrade.echo.selector, message)
);
assert(success);
assertEq(abi.decode(response, (string)), message);
}

function test_upgrade_only_pDAO() public {
deployMockVault();

vm.prank(BAD_ACTOR);

vm.expectRevert(abi.encodeWithSelector(IKSXVault.OnlyPDAO.selector));

ksxVault.upgradeToAndCall(address(mockVaultUpgrade), "");
}
}

contract UpgradeVault is UpgradeTest {}

contract RemoveUpgradability is UpgradeTest {
function test_removeUpgradability() public {

MockVaultUpgrade mockVaultUpgrade = new MockVaultUpgrade(
address(TOKEN),
address(0) // set pDAO to zero address to effectively remove upgradability
);

vm.prank(pDAO);

ksxVault.upgradeToAndCall(address(mockVaultUpgrade), "");

vm.prank(pDAO);

vm.expectRevert(abi.encodeWithSelector(IKSXVault.NonUpgradeable.selector));

ksxVault.upgradeToAndCall(address(mockVaultUpgrade), "");
}
}
Loading
Loading