diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 13dec7c..b43d762 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,20 +14,12 @@ "JuanBlanco.solidity" ] } + }, + "containerEnv": { + "PRIVATE_KEY": "${localEnv:PRIVATE_KEY}", + "PUBLIC_KEY": "${localEnv:PUBLIC_KEY}", + "RPC_URL": "${localEnv:RPC_URL}", + "HOLESKY_URL": "${localEnv:HOLESKY_URL}", + "ETHERSCAN_API_KEY": "${localEnv:ETHERSCAN_API_KEY}" } - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Uncomment the next line to run commands after the container is created. - // "postCreateCommand": "cat /etc/os-release", - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "devcontainer" -} +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 38b6608..c5129ca 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ out/ !/broadcast /broadcast/*/31337/ /broadcast/**/dry-run/ +broadcast/ # Docs docs/ diff --git a/.solhint.json b/.solhint.json index 744b9da..07f0b62 100644 --- a/.solhint.json +++ b/.solhint.json @@ -3,6 +3,12 @@ "plugins": [], "rules": { "avoid-suicide": "error", - "avoid-sha3": "warn" + "avoid-sha3": "warn", + "func-visibility": [ + "warn", + { + "ignoreConstructors": true + } + ] } -} +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 701e16a..e287e6a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -114,6 +114,93 @@ "kind": "test", "isDefault": false } - } + }, + { + "label": "deployproxyadmin", + "type": "shell", + "command": "forge script ./script/HSETH.s.sol:DeployHSETH --sig 'deployAdmin()' --broadcast --slow --rpc-url ${HOLESKY_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "HSETH_ADMIN": "0x2E1F5C7f87096fb7FfFbB6654Fc3b2CE303aEff5", + } + }, + "dependsOn": "test", + "group": { + "kind": "test", + "isDefault": false + } + }, + { + "label": "deployhseth", + "type": "shell", + "command": "forge script ./script/HSETH.s.sol:DeployHSETH --sig 'proxyDeploy()' --broadcast --slow --rpc-url ${HOLESKY_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "HSETH_ADMIN": "0x2E1F5C7f87096fb7FfFbB6654Fc3b2CE303aEff5", + "PROXY_ADMIN": "0x6904603c27392310D19E389105CA792FB935C43C", + } + }, + "dependsOn": "test", + "group": { + "kind": "test", + "isDefault": false + } + }, + { + "label": "upgradehseth", + "type": "shell", + "command": "forge script ./script/HSETH.s.sol:DeployHSETH --sig 'proxyUpgrade()' --broadcast --slow --rpc-url ${HOLESKY_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "PROXY_ADMIN": "0x6904603c27392310D19E389105CA792FB935C43C", + "PROXY_ADDRESS": "0x217EBabCf15EC6deaCF11f737d79275e95C97EFE", + } + }, + "dependsOn": "test", + "group": { + "kind": "test", + "isDefault": false + } + }, + { + "label": "deploystakingmanager", + "type": "shell", + "command": "forge script ./script/StaderHavenStakingManager.s.sol:DeployStakingManager --sig 'proxyDeploy()' --broadcast --slow --rpc-url ${HOLESKY_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "HSETH_ADMIN": "0x2E1F5C7f87096fb7FfFbB6654Fc3b2CE303aEff5", + "PROXY_ADMIN": "0x6904603c27392310D19E389105CA792FB935C43C", + "HSETH": "0x217EBabCf15EC6deaCF11f737d79275e95C97EFE", + "TREASURY": "0x2E1F5C7f87096fb7FfFbB6654Fc3b2CE303aEff5", + "STADER_CONFIG": "0x50FD3384783EE49011E7b57d7A3430a762b3f3F2" + } + }, + "dependsOn": "test", + "group": { + "kind": "test", + "isDefault": false + } + }, + { + "label": "upgradestakingmanager", + "type": "shell", + "command": "forge script ./script/StaderHavenStakingManager.s.sol:DeployStakingManager --sig 'proxyUpgrade()' --broadcast --slow --rpc-url ${HOLESKY_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "PROXY_ADMIN": "0x6904603c27392310D19E389105CA792FB935C43C", + "PROXY_ADDRESS": "0xDBAaD20ffd67dfaeBdE40b842cB78eAa18F1BB74", + } + }, + "dependsOn": "test", + "group": { + "kind": "test", + "isDefault": false + } + }, ] } \ No newline at end of file diff --git a/README.md b/README.md index 9265b45..460b994 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,47 @@ +## HsETH + +### Haven1 ETHx Wrapper designed by Stader Labs + +hsETH is an ERC-20 token developed by Stader Labs, specifically designed for the Staders restaking ecosystem. It allows users to deposit ETH tokens into the contract and mint liquid hsETH, providing unique opportunities for decentralized exchange liquidity pools. This facilitates easy entry and exit from positions representing restaked assets. + +### Deployment + +| Contract | Address | Network | +| ------------------------- | ------------------------------------------ | ------- | +| ProxyAdmin | 0x6904603c27392310D19E389105CA792FB935C43C | Holesky | +| HsETH | 0x217EBabCf15EC6deaCF11f737d79275e95C97EFE | Holesky | +| StaderHavenStakingManager | 0xDBAaD20ffd67dfaeBdE40b842cB78eAa18F1BB74 | Holesky | +| ProxyAdmin | 0x12eA3B1265d5D41a3b582410241537A751FC52ff | Sepolia | +| HsETH | 0x063d4c8CFeF375C2Fc1710934504e2b7aB85fd15 | Sepolia | + +#### Deployment Process + +##### hsETH + +1. Deploy ProxyAdmin or choose an existing Admin + ```bash + $ HSETH_ADMIN=0x2E1F5C7f87096fb7FfFbB6654Fc3b2CE303aEff5 forge script ./script/HSETH.s.sol:DeployHSETH --sig 'deployAdmin()' --broadcast --slow --rpc-url ${RPC_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify + ``` + Make note of deployment details, contract address and owner. +2. Deploy HsETH contract with ProxyAdmin as owner + ```bash + $ HSETH_ADMIN=0x2E1F5C7f87096fb7FfFbB6654Fc3b2CE303aEff5 PROXY_ADMIN=0x6904603c27392310D19E389105CA792FB935C43C forge script ./script/HSETH.s.sol:DeployHSETH --sig 'proxyDeploy()' --broadcast --slow --rpc-url ${RPC_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify + ``` +3. Upgrade HsETH Contract as required + ```bash + $ PROXY_ADMIN=0x6904603c27392310D19E389105CA792FB935C43C PROXY_ADDRESS=0x217EBabCf15EC6deaCF11f737d79275e95C97EFE forge script ./script/HSETH.s.sol:DeployHSETH --sig 'proxyUpgrade()' --broadcast --slow --rpc-url ${RPC_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify + ``` + +##### StaderHavenStakingManager + +1. Deploy StaderHavenStakingManager + ```bash + $ HSETH_ADMIN=0x2E1F5C7f87096fb7FfFbB6654Fc3b2CE303aEff5 PROXY_ADMIN=0x6904603c27392310D19E389105CA792FB935C43C HSETH=0x217EBabCf15EC6deaCF11f737d79275e95C97EFE TREASURY=0x2E1F5C7f87096fb7FfFbB6654Fc3b2CE303aEff5 STADER_CONFIG=0x50FD3384783EE49011E7b57d7A3430a762b3f3F2 forge script ./script/StaderHavenStakingManager.s.sol:DeployStakingManager --sig 'proxyDeploy()' --broadcast --slow --rpc-url ${HOLESKY_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify + ``` +2. Upgrade StaderHavenStakingManager as needed + ```bash + $ PROXY_ADMIN=0x6904603c27392310D19E389105CA792FB935C43C PROXY_ADDRESS=0xDBAaD20ffd67dfaeBdE40b842cB78eAa18F1BB74 forge script ./script/StaderHavenStakingManager.s.sol:DeployStakingManager --sig 'proxyUpgrade()' --broadcast --slow --rpc-url ${HOLESKY_URL} --private-key ${PRIVATE_KEY} --etherscan-api-key ${ETHERSCAN_API_KEY} --verify + ``` ## Foundry **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** diff --git a/src/HSETH.sol b/contracts/HSETH.sol similarity index 100% rename from src/HSETH.sol rename to contracts/HSETH.sol diff --git a/src/StaderHavenStakingManager.sol b/contracts/StaderHavenStakingManager.sol similarity index 100% rename from src/StaderHavenStakingManager.sol rename to contracts/StaderHavenStakingManager.sol diff --git a/src/interfaces/IStaderConfig.sol b/contracts/interfaces/IStaderConfig.sol similarity index 100% rename from src/interfaces/IStaderConfig.sol rename to contracts/interfaces/IStaderConfig.sol diff --git a/src/interfaces/IStaderHavenStakingManager.sol b/contracts/interfaces/IStaderHavenStakingManager.sol similarity index 100% rename from src/interfaces/IStaderHavenStakingManager.sol rename to contracts/interfaces/IStaderHavenStakingManager.sol diff --git a/src/interfaces/IStaderStakePoolManager.sol b/contracts/interfaces/IStaderStakePoolManager.sol similarity index 100% rename from src/interfaces/IStaderStakePoolManager.sol rename to contracts/interfaces/IStaderStakePoolManager.sol diff --git a/src/interfaces/IStaderUserWithdrawManager.sol b/contracts/interfaces/IStaderUserWithdrawManager.sol similarity index 100% rename from src/interfaces/IStaderUserWithdrawManager.sol rename to contracts/interfaces/IStaderUserWithdrawManager.sol diff --git a/foundry.toml b/foundry.toml index ad41e2b..c2ffb61 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -src = "src" +src = "contracts" out = "out" libs = ["lib"] diff --git a/package.json b/package.json index 36c368b..0feac13 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "lint": "solhint 'contracts/**/*.sol'" + "lint": "solhint 'contracts/**/*.sol' 'script/**/*.sol'" }, "repository": { "type": "git", diff --git a/script/HSETH.s.sol b/script/HSETH.s.sol new file mode 100644 index 0000000..983c6f3 --- /dev/null +++ b/script/HSETH.s.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +// solhint-disable no-console +pragma solidity 0.8.16; + +import { Script, console } from "forge-std/Script.sol"; +import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import { + ITransparentUpgradeableProxy, + TransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import { HSETH } from "../contracts/HSETH.sol"; + +contract DeployHSETH is Script { + event ProxyAdminCreated(address admin); + event HsETHProxy(address proxy); + event HsETHUpgrade(address proxy, address implementation); + + function deployAdmin() public { + address admin = vm.envAddress("HSETH_ADMIN"); + vm.startBroadcast(); + ProxyAdmin proxyAdmin = new ProxyAdmin(); + console.log("ProxyAdmin: ", address(proxyAdmin)); + proxyAdmin.transferOwnership(admin); + emit ProxyAdminCreated(address(proxyAdmin)); + vm.stopBroadcast(); + } + + function proxyDeploy() public { + address admin = vm.envAddress("HSETH_ADMIN"); + address proxyAdmin = vm.envAddress("PROXY_ADMIN"); + vm.startBroadcast(); + HSETH implementation = new HSETH(); + bytes memory initializationCalldata = abi.encodeWithSelector(implementation.initialize.selector, admin); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(address(implementation), proxyAdmin, initializationCalldata); + console.log("hsETH Transparent Proxy: ", address(proxy)); + emit HsETHProxy(address(proxy)); + vm.stopBroadcast(); + } + + function proxyUpgrade() public { + address proxyAdmin = vm.envAddress("PROXY_ADMIN"); + address proxyAddress = vm.envAddress("PROXY_ADDRESS"); + vm.startBroadcast(); + HSETH implementation = new HSETH(); + ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(proxyAddress); + ProxyAdmin(proxyAdmin).upgrade(proxy, address(implementation)); + console.log("hsETH Transparent Proxy Upgraded: ", address(proxyAddress), " to ", address(implementation)); + emit HsETHUpgrade(address(proxyAddress), address(implementation)); + vm.stopBroadcast(); + } +} diff --git a/script/StaderHavenStakingManager.s.sol b/script/StaderHavenStakingManager.s.sol new file mode 100644 index 0000000..9a9cf5e --- /dev/null +++ b/script/StaderHavenStakingManager.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: UNLICENSED +// solhint-disable no-console +pragma solidity 0.8.16; + +import { Script, console } from "forge-std/Script.sol"; +import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import { + ITransparentUpgradeableProxy, + TransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import { StaderHavenStakingManager } from "../contracts/StaderHavenStakingManager.sol"; +import { HSETH } from "../contracts/HSETH.sol"; + +contract DeployStakingManager is Script { + event StakingManagerProxy(address proxy); + event StakingManagerUpgrade(address proxy, address implementation); + + function proxyDeploy() public { + address admin = vm.envAddress("HSETH_ADMIN"); + address proxyAdmin = vm.envAddress("PROXY_ADMIN"); + address hsETH = vm.envAddress("HSETH"); + address treasury = vm.envAddress("TREASURY"); + address staderConfig = vm.envAddress("STADER_CONFIG"); + vm.startBroadcast(); + StaderHavenStakingManager implementation = new StaderHavenStakingManager(); + bytes memory initializationCalldata = abi.encodeWithSelector(implementation.initialize.selector, admin, hsETH, treasury, staderConfig); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(address(implementation), proxyAdmin, initializationCalldata); + console.log("StakingManager Transparent Proxy: ", address(proxy)); + StaderHavenStakingManager staderHavenStakingManager = StaderHavenStakingManager(address(proxy)); + staderHavenStakingManager.grantRole(staderHavenStakingManager.MANAGER(), admin); + HSETH hsETHToken = HSETH(hsETH); + hsETHToken.grantRole(hsETHToken.MINTER_ROLE(), address(staderHavenStakingManager)); + hsETHToken.grantRole(hsETHToken.BURNER_ROLE(), address(staderHavenStakingManager)); + emit StakingManagerProxy(address(proxy)); + vm.stopBroadcast(); + } + + function proxyUpgrade() public { + address proxyAdmin = vm.envAddress("PROXY_ADMIN"); + address proxyAddress = vm.envAddress("PROXY_ADDRESS"); + vm.startBroadcast(); + StaderHavenStakingManager implementation = new StaderHavenStakingManager(); + ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(proxyAddress); + ProxyAdmin(proxyAdmin).upgrade(proxy, address(implementation)); + console.log("StakingManager Transparent Proxy Upgraded: ", address(proxyAddress), " to ", address(implementation)); + emit StakingManagerUpgrade(address(proxyAddress), address(implementation)); + vm.stopBroadcast(); + } +} diff --git a/test/HSETH.t.sol b/test/HSETH.t.sol index 6530477..6fabb40 100644 --- a/test/HSETH.t.sol +++ b/test/HSETH.t.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.16; -import "../src/HSETH.sol"; +import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "forge-std/Test.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { Test } from "forge-std/Test.sol"; + +import { HSETH } from "../contracts/HSETH.sol"; contract HSETHTest is Test { address admin; diff --git a/test/StaderHavenStakingManager.t.sol b/test/StaderHavenStakingManager.t.sol index 1995dee..2b49701 100644 --- a/test/StaderHavenStakingManager.t.sol +++ b/test/StaderHavenStakingManager.t.sol @@ -3,29 +3,34 @@ pragma solidity 0.8.16; // solhint-disable -import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + ITransparentUpgradeableProxy, + TransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import { HSETH } from "../src/HSETH.sol"; -import { StaderHavenStakingManager } from "../src/StaderHavenStakingManager.sol"; -import { IStaderHavenStakingManager } from "../src/interfaces/IStaderHavenStakingManager.sol"; -import { IStaderStakePoolManager } from "../src/interfaces/IStaderStakePoolManager.sol"; +import { Test, console } from "forge-std/Test.sol"; + +import { HSETH } from "../contracts/HSETH.sol"; +import { StaderHavenStakingManager } from "../contracts/StaderHavenStakingManager.sol"; +import { IStaderHavenStakingManager } from "../contracts/interfaces/IStaderHavenStakingManager.sol"; +import { IStaderStakePoolManager } from "../contracts/interfaces/IStaderStakePoolManager.sol"; import { ETHxMock } from "./mocks/ETHxMock.sol"; import { StaderConfigMock } from "./mocks/StaderConfigMock.sol"; import { StaderStakePoolManagerMock } from "./mocks/StaderStakePoolManagerMock.sol"; import { StaderUserWithdrawManagerMock } from "./mocks/StaderUserWithdrawManagerMock.sol"; -import { Test, console } from "forge-std/Test.sol"; - contract StaderHavenStakingManagerTest is Test { uint256 public constant DECIMAL = 1e18; + event Upgraded(address indexed implementation); event WithdrawnProtocolFees(address treasury, uint256 protocolFeesAmount); address private admin; address private manager; address private treasury; + ProxyAdmin private proxyAdmin; HSETH private hsETH; address private ethX; StaderConfigMock private staderConfig; @@ -46,19 +51,24 @@ contract StaderHavenStakingManagerTest is Test { staderConfig.updateUserWithdrawManager(userWithdrawManager); staderConfig.updateStaderStakePoolManager(staderStakePoolManager); - ProxyAdmin proxyAdmin = new ProxyAdmin(); + proxyAdmin = new ProxyAdmin(); + proxyAdmin.transferOwnership(admin); address hsETHImpl = address(new HSETH()); address staderHavenStakingManagerImpl = address(new StaderHavenStakingManager()); - TransparentUpgradeableProxy hsETHProxy = new TransparentUpgradeableProxy(hsETHImpl, address(proxyAdmin), ""); + bytes memory hsETHInitData = abi.encodeWithSelector(HSETH.initialize.selector, admin); + TransparentUpgradeableProxy hsETHProxy = + new TransparentUpgradeableProxy(hsETHImpl, address(proxyAdmin), hsETHInitData); hsETH = HSETH(address(hsETHProxy)); - hsETH.initialize(admin); - TransparentUpgradeableProxy staderHavenStakingManagerProxy = - new TransparentUpgradeableProxy(staderHavenStakingManagerImpl, address(proxyAdmin), ""); + bytes memory staderHavenStakingManagerInitData = abi.encodeWithSelector( + StaderHavenStakingManager.initialize.selector, admin, address(hsETH), treasury, address(staderConfig) + ); + TransparentUpgradeableProxy staderHavenStakingManagerProxy = new TransparentUpgradeableProxy( + staderHavenStakingManagerImpl, address(proxyAdmin), staderHavenStakingManagerInitData + ); staderHavenStakingManager = StaderHavenStakingManager(address(staderHavenStakingManagerProxy)); - staderHavenStakingManager.initialize(admin, address(hsETH), treasury, address(staderConfig)); vm.startPrank(admin); staderHavenStakingManager.grantRole(staderHavenStakingManager.MANAGER(), manager); hsETH.grantRole(hsETH.MINTER_ROLE(), address(staderHavenStakingManager)); @@ -439,4 +449,13 @@ contract StaderHavenStakingManagerTest is Test { staderHavenStakingManager.updateStaderConfig(staderConfigAddr); assertEq(address(staderHavenStakingManager.staderConfig()), staderConfigAddr); } + + function testProxyUpgradeWorkflow() public { + address newImpl = address(new StaderHavenStakingManager()); + ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(address(staderHavenStakingManager)); + vm.prank(admin); + vm.expectEmit(); + emit Upgraded(newImpl); + proxyAdmin.upgrade(proxy, newImpl); + } }