From 456e667b7c4267befa568816b70fb60f459fd155 Mon Sep 17 00:00:00 2001 From: JordyDutch Date: Thu, 27 Mar 2025 09:50:57 +0100 Subject: [PATCH 01/14] chore: remove prettier dev dependencies --- package-lock.json | 33 --------------------------------- package.json | 7 +++---- 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0dc1f28..04b1b69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@lukso/lsp6-contracts": "^0.15.0", "@lukso/universalprofile-contracts": "^0.15.0", "@openzeppelin/contracts-v4.9.0": "npm:@openzeppelin/contracts@4.9.0", - "prettier-plugin-solidity": "^1.4.2", "solhint": "^5.0.5" } }, @@ -5040,38 +5039,6 @@ "node": ">= 0.6" } }, - "node_modules/prettier": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", - "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", - "dev": true, - "peer": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-solidity": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.4.2.tgz", - "integrity": "sha512-VVD/4XlDjSzyPWWCPW8JEleFa8JNKFYac5kNlMjVXemQyQZKfpekPMhFZSePuXB6L+RixlFvWe20iacGjFYrLw==", - "dev": true, - "dependencies": { - "@solidity-parser/parser": "^0.19.0", - "semver": "^7.6.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "prettier": ">=2.3.0" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", diff --git a/package.json b/package.json index 7fe9dc8..99f1d47 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,10 @@ "@lukso/lsp7-contracts": "~0.16.3" }, "devDependencies": { - "prettier-plugin-solidity": "^1.4.2", "@lukso/lsp1delegate-contracts": "^0.15.0", "@lukso/lsp6-contracts": "^0.15.0", "@lukso/universalprofile-contracts": "^0.15.0", - "solhint": "^5.0.5", - "@openzeppelin/contracts-v4.9.0": "npm:@openzeppelin/contracts@4.9.0" + "@openzeppelin/contracts-v4.9.0": "npm:@openzeppelin/contracts@4.9.0", + "solhint": "^5.0.5" } -} \ No newline at end of file +} From a0aa860525299171463d2733862931c82425195f Mon Sep 17 00:00:00 2001 From: JordyDutch Date: Thu, 27 Mar 2025 09:51:31 +0100 Subject: [PATCH 02/14] test: check initialization not possible after deployment --- README.md | 14 +- test/Deployment.t.sol | 34 ++++- test/ForkTest.t.sol | 296 ++++++++++++++++++++++++++++++++---------- 3 files changed, 263 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index c31fbc6..4ff00b1 100755 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ Repository for the Stakingverse contracts. This repository includes the followin - Stakingverse Vault (based on [Universal Page vault's implementation](https://github.com/Universal-Page/contracts/blob/main/src/pool/Vault.sol)) - Liquid Staking Token (sLYX) based on the LUKSO LSP7 standard (linked to the Vault contract below deployed on LUKSO Mainnet). -| Contract | Address on LUKSO Mainnet | -| :-------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | -| Staking Vault Proxy | [`0x9F49a95b0c3c9e2A6c77a16C177928294c0F6F04`](https://explorer.lukso.network/address/0x9F49a95b0c3c9e2A6c77a16C177928294c0F6F04?tab=contract) | -| Staking Vault `Vault.sol` Implementation (old) | [`0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4`](https://explorer.lukso.network/address/0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4?tab=contract) | -| Staking Vault `StakingverseVault.sol` Implementation (upgraded) | _To be deployed_ | -| SLYX Token Proxy | [`0x8a3982f0a7d154d11a5f43eec7f50e52ebbc8f7d`](https://explorer.lukso.network/address/0x8a3982f0a7d154d11a5f43eec7f50e52ebbc8f7d?tab=contract) | -| SLYX Token Implementation | _To be deployed_ | +| Contract | Address on LUKSO Mainnet | +| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------- | +| Staking Vault Proxy | [`0x9F49a95b0c3c9e2A6c77a16C177928294c0F6F04`](https://explorer.lukso.network/address/0x9F49a95b0c3c9e2A6c77a16C177928294c0F6F04?tab=contract) | +| Staking Vault `Vault.sol` Implementation
(commit [`33d1619` on Universal.Page repository](https://github.com/Universal-Page/contracts/tree/33d1619a19162444c870b8a5a4bf42eb4532818c)) | [`0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4`](https://explorer.lukso.network/address/0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4?tab=contract) | +| Staking Vault `StakingverseVault.sol` Implementation (upgraded) | _To be deployed_ | +| SLYX Token Proxy | [`0x8a3982f0a7d154d11a5f43eec7f50e52ebbc8f7d`](https://explorer.lukso.network/address/0x8a3982f0a7d154d11a5f43eec7f50e52ebbc8f7d?tab=contract) | +| SLYX Token Implementation | _To be deployed_ | - [Stakingverse Contracts](#stakingverse-contracts) - [Installation](#installation) diff --git a/test/Deployment.t.sol b/test/Deployment.t.sol index 9731c88..8d31a73 100644 --- a/test/Deployment.t.sol +++ b/test/Deployment.t.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.13; import {SLYXTokenBaseTest} from "./base/SLYXTokenBaseTest.t.sol"; -import {_INTERFACEID_LSP7} from "@lukso/lsp7-contracts/contracts/LSP7Constants.sol"; +import { + _INTERFACEID_LSP7 +} from "@lukso/lsp7-contracts/contracts/LSP7Constants.sol"; import {IVaultStakeRecipient} from "../src/IVaultStakeRecipient.sol"; // Constants @@ -22,13 +24,20 @@ contract Deployment is SLYXTokenBaseTest { function test_deploymentParametersOfSLYXToken() public view { // We need to re-encode in memory, as in the ERC725Y storage, strings are not stored as [string.length][string] - bytes memory encodedName = abi.encode(sLyxToken.getData(_LSP4_TOKEN_NAME_KEY)); + bytes memory encodedName = abi.encode( + sLyxToken.getData(_LSP4_TOKEN_NAME_KEY) + ); string memory name = abi.decode(encodedName, (string)); - bytes memory encodedSymbol = abi.encode(sLyxToken.getData(_LSP4_TOKEN_SYMBOL_KEY)); + bytes memory encodedSymbol = abi.encode( + sLyxToken.getData(_LSP4_TOKEN_SYMBOL_KEY) + ); string memory symbol = abi.decode(encodedSymbol, (string)); - uint256 tokenType = abi.decode(sLyxToken.getData(_LSP4_TOKEN_TYPE_KEY), (uint256)); + uint256 tokenType = abi.decode( + sLyxToken.getData(_LSP4_TOKEN_TYPE_KEY), + (uint256) + ); uint256 decimals = sLyxToken.decimals(); address owner = sLyxToken.owner(); @@ -43,6 +52,18 @@ contract Deployment is SLYXTokenBaseTest { assertEq(sLyxToken.totalSupply(), 0); } + // This test ensures two things. Calling `getExchangeRate()` initially: + // 1. will not revert + // 2. will return 1:1 ratio + function test_callingGetExhangeRateWhenNoSLYXTokensHaveBeenMintedInitially() + public + view + { + assertEq(sLyxToken.getExchangeRate(), 1 ether); + assertEq(sLyxToken.getNativeTokenValue(12345 ether), 12345 ether); + assertEq(sLyxToken.getSLYXTokenValue(12345 ether), 12345 ether); + } + function test_shouldHaveSetCorrectLinkedStakingVault() public view { assertEq(address(sLyxToken.stakingVault()), address(vault)); } @@ -57,7 +78,10 @@ contract Deployment is SLYXTokenBaseTest { } function test_supportsCorrectInterfaces() public view { - assertEq(sLyxToken.supportsInterface(type(IVaultStakeRecipient).interfaceId), true); + assertEq( + sLyxToken.supportsInterface(type(IVaultStakeRecipient).interfaceId), + true + ); assertEq(sLyxToken.supportsInterface(_INTERFACEID_LSP7), true); } } diff --git a/test/ForkTest.t.sol b/test/ForkTest.t.sol index 9947a02..0c22f4c 100644 --- a/test/ForkTest.t.sol +++ b/test/ForkTest.t.sol @@ -15,21 +15,30 @@ import { // Contracts to test import {SLYXToken} from "../../src/SLYXToken.sol"; -address payable constant VAULT_IMPLEMENTATION = payable(0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4); - -address payable constant VAULT_PROXY = payable(0x9F49a95b0c3c9e2A6c77a16C177928294c0F6F04); - -address payable constant SLYX_PROXY = payable(0x8A3982f0A7d154D11a5f43EEc7F50E52eBBc8F7D); - -// Existing addresses setup on mainnet -address constant VAULT_AND_SLYX_PROXY_ADMIN = 0xe460f0cB1227399B129715895157E8cB443b269E; -address constant VAULT_ADMIN = 0x8909ce174B12Be1311bA80797d2f3A8BEdD913Bf; +// Constants +import { + PROXY_ADMIN_MAINNET, + VAULT_PROXY_MAINNET, + SLYX_TOKEN_PROXY_MAINNET +} from "../script/MainnetConstants.sol"; + +// Contract addresses deployed on mainnet +address payable constant VAULT_IMPLEMENTATION = payable( + 0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4 +); +address payable constant VAULT_PROXY = payable(VAULT_PROXY_MAINNET); +address payable constant SLYX_PROXY = payable(SLYX_TOKEN_PROXY_MAINNET); + +// Config addresses setup on mainnet +address constant VAULT_AND_SLYX_PROXY_ADMIN = PROXY_ADMIN_MAINNET; +address constant VAULT_CONTRACT_OWNER_AND_OPERATOR = 0x8909ce174B12Be1311bA80797d2f3A8BEdD913Bf; address constant VAULT_ORACLE = 0x6a44823e20CD97250AfA3c73e45aBef4Ff79c51F; -bytes constant SLYX_PROXY_BYTECODE = - hex"60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f80fd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f80375f80365f845af43d5f803e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016108036027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f80856001600160a01b03168560405161056691906107b5565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107d0565b5f8085851115610676575f80fd5b83861115610682575f80fd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f80fd5b919050565b5f602082840312156106ba575f80fd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156106e8575f80fd5b6106f18361068f565b9150602083013567ffffffffffffffff8082111561070d575f80fd5b818501915085601f830112610720575f80fd5b813581811115610732576107326106c3565b604051601f8201601f19908116603f0116810190838211818310171561075a5761075a6106c3565b81604052828152886020848701011115610772575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f5b838110156107ad578181015183820152602001610795565b50505f910152565b5f82516107c6818460208701610793565b9190910192915050565b602081525f82518060208401526107ee816040850160208701610793565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122015b4231dda86f0402c82dbd8505d83543efd1acf89fcd12f9a9ddb52aab9449364736f6c63430008160033"; -bytes constant VAULT_PROXY_BYTECODE = - hex"60806040523661001357610011610017565b005b6100115b61001f610202565b6001600160a01b031633036101f85760606001600160e01b0319600035167fc9a6301a0000000000000000000000000000000000000000000000000000000081016100735761006c610235565b91506101f0565b7fb0e10d7a000000000000000000000000000000000000000000000000000000006001600160e01b03198216016100ac5761006c61028c565b7f70d7c690000000000000000000000000000000000000000000000000000000006001600160e01b03198216016100e55761006c6102d2565b7f07ae5bc0000000000000000000000000000000000000000000000000000000006001600160e01b031982160161011e5761006c610303565b7fa39f25e5000000000000000000000000000000000000000000000000000000006001600160e01b03198216016101575761006c610343565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b815160208301f35b610200610357565b565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b606061023f610367565b600061024e366004818461075b565b81019061025b91906107a1565b905061027881604051806020016040528060008152506000610372565b505060408051602081019091526000815290565b606060008061029e366004818461075b565b8101906102ab91906107eb565b915091506102bb82826001610372565b604051806020016040528060008152509250505090565b60606102dc610367565b60006102eb366004818461075b565b8101906102f891906107a1565b90506102788161039e565b606061030d610367565b6000610317610202565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b606061034d610367565b60006103176103f5565b6102006103626103f5565b610404565b341561020057600080fd5b61037b83610428565b6000825111806103885750805b15610399576103978383610468565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103c7610202565b604080516001600160a01b03928316815291841660208301520160405180910390a16103f281610494565b50565b60006103ff61056c565b905090565b3660008037600080366000845af43d6000803e808015610423573d6000f35b3d6000fd5b61043181610594565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b606061048d838360405180606001604052806027815260200161092160279139610638565b9392505050565b6001600160a01b0381166105105760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016101e7565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610226565b6001600160a01b0381163b6106115760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e74726163740000000000000000000000000000000000000060648201526084016101e7565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610533565b6060600080856001600160a01b03168560405161065591906108d1565b600060405180830381855af49150503d8060008114610690576040519150601f19603f3d011682016040523d82523d6000602084013e610695565b606091505b50915091506106a6868383876106b0565b9695505050505050565b6060831561071f578251600003610718576001600160a01b0385163b6107185760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101e7565b5081610729565b6107298383610731565b949350505050565b8151156107415781518083602001fd5b8060405162461bcd60e51b81526004016101e791906108ed565b6000808585111561076b57600080fd5b8386111561077857600080fd5b5050820193919092039150565b80356001600160a01b038116811461079c57600080fd5b919050565b6000602082840312156107b357600080fd5b61048d82610785565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080604083850312156107fe57600080fd5b61080783610785565b9150602083013567ffffffffffffffff8082111561082457600080fd5b818501915085601f83011261083857600080fd5b81358181111561084a5761084a6107bc565b604051601f8201601f19908116603f01168101908382118183101715610872576108726107bc565b8160405282815288602084870101111561088b57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156108c85781810151838201526020016108b0565b50506000910152565b600082516108e38184602087016108ad565b9190910192915050565b602081526000825180602084015261090c8160408501602087016108ad565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220280d55eaf8059181b8918c073f41b03e81059104244c761de8efef17dc73dad464736f6c63430008160033"; +// Upcoming SLYX Token Contract owner +address constant SLYX_CONTRACT_OWNER = 0x49d32954698344592407C2C1f76c431F0032167c; + +bytes constant SLYX_PROXY_BYTECODE = hex"60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f80fd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f80375f80365f845af43d5f803e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016108036027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f80856001600160a01b03168560405161056691906107b5565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107d0565b5f8085851115610676575f80fd5b83861115610682575f80fd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f80fd5b919050565b5f602082840312156106ba575f80fd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156106e8575f80fd5b6106f18361068f565b9150602083013567ffffffffffffffff8082111561070d575f80fd5b818501915085601f830112610720575f80fd5b813581811115610732576107326106c3565b604051601f8201601f19908116603f0116810190838211818310171561075a5761075a6106c3565b81604052828152886020848701011115610772575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f5b838110156107ad578181015183820152602001610795565b50505f910152565b5f82516107c6818460208701610793565b9190910192915050565b602081525f82518060208401526107ee816040850160208701610793565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122015b4231dda86f0402c82dbd8505d83543efd1acf89fcd12f9a9ddb52aab9449364736f6c63430008160033"; +bytes constant VAULT_PROXY_BYTECODE = hex"60806040523661001357610011610017565b005b6100115b61001f610202565b6001600160a01b031633036101f85760606001600160e01b0319600035167fc9a6301a0000000000000000000000000000000000000000000000000000000081016100735761006c610235565b91506101f0565b7fb0e10d7a000000000000000000000000000000000000000000000000000000006001600160e01b03198216016100ac5761006c61028c565b7f70d7c690000000000000000000000000000000000000000000000000000000006001600160e01b03198216016100e55761006c6102d2565b7f07ae5bc0000000000000000000000000000000000000000000000000000000006001600160e01b031982160161011e5761006c610303565b7fa39f25e5000000000000000000000000000000000000000000000000000000006001600160e01b03198216016101575761006c610343565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b815160208301f35b610200610357565b565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b606061023f610367565b600061024e366004818461075b565b81019061025b91906107a1565b905061027881604051806020016040528060008152506000610372565b505060408051602081019091526000815290565b606060008061029e366004818461075b565b8101906102ab91906107eb565b915091506102bb82826001610372565b604051806020016040528060008152509250505090565b60606102dc610367565b60006102eb366004818461075b565b8101906102f891906107a1565b90506102788161039e565b606061030d610367565b6000610317610202565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b606061034d610367565b60006103176103f5565b6102006103626103f5565b610404565b341561020057600080fd5b61037b83610428565b6000825111806103885750805b15610399576103978383610468565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103c7610202565b604080516001600160a01b03928316815291841660208301520160405180910390a16103f281610494565b50565b60006103ff61056c565b905090565b3660008037600080366000845af43d6000803e808015610423573d6000f35b3d6000fd5b61043181610594565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b606061048d838360405180606001604052806027815260200161092160279139610638565b9392505050565b6001600160a01b0381166105105760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016101e7565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610226565b6001600160a01b0381163b6106115760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e74726163740000000000000000000000000000000000000060648201526084016101e7565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610533565b6060600080856001600160a01b03168560405161065591906108d1565b600060405180830381855af49150503d8060008114610690576040519150601f19603f3d011682016040523d82523d6000602084013e610695565b606091505b50915091506106a6868383876106b0565b9695505050505050565b6060831561071f578251600003610718576001600160a01b0385163b6107185760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101e7565b5081610729565b6107298383610731565b949350505050565b8151156107415781518083602001fd5b8060405162461bcd60e51b81526004016101e791906108ed565b6000808585111561076b57600080fd5b8386111561077857600080fd5b5050820193919092039150565b80356001600160a01b038116811461079c57600080fd5b919050565b6000602082840312156107b357600080fd5b61048d82610785565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080604083850312156107fe57600080fd5b61080783610785565b9150602083013567ffffffffffffffff8082111561082457600080fd5b818501915085601f83011261083857600080fd5b81358181111561084a5761084a6107bc565b604051601f8201601f19908116603f01168101908382118183101715610872576108726107bc565b8160405282815288602084870101111561088b57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156108c85781810151838201526020016108b0565b50506000910152565b600082516108e38184602087016108ad565b9190910192915050565b602081526000825180602084015261090c8160408501602087016108ad565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220280d55eaf8059181b8918c073f41b03e81059104244c761de8efef17dc73dad464736f6c63430008160033"; contract ForkTest is Test { /// @dev There is a rounding error of 1 Wei accepted as loss. @@ -37,7 +46,6 @@ contract ForkTest is Test { uint256 internal constant VAULT_ROUNDING_ERROR_LOSS = 1 wei; address sLyxProxyAdmin; - address sLyxContractOwner; // existing vault contract address (proxy) StakingverseVault vault; @@ -50,10 +58,10 @@ contract ForkTest is Test { uint256 initialDepositLimit; function setUp() public { - sLyxProxyAdmin = VAULT_AND_SLYX_PROXY_ADMIN; - sLyxContractOwner = VAULT_ADMIN; - - console.log(unicode"๐Ÿ›ž Setup Fork testing. Block number: ", block.number); + console.log( + unicode"๐Ÿ›ž Setup Fork testing. Block number: ", + block.number + ); vault = StakingverseVault(VAULT_PROXY); @@ -66,8 +74,8 @@ contract ForkTest is Test { // Note that this value might change and is increased regularly. assertGt(initialDepositLimit, 1_476_000 ether); - assertEq(vault.owner(), VAULT_ADMIN); - assertEq(vault.operator(), VAULT_ADMIN); + assertEq(vault.owner(), VAULT_CONTRACT_OWNER_AND_OPERATOR); + assertEq(vault.operator(), VAULT_CONTRACT_OWNER_AND_OPERATOR); assertTrue(vault.isOracle(VAULT_ORACLE)); vm.startPrank(VAULT_AND_SLYX_PROXY_ADMIN); @@ -76,7 +84,7 @@ contract ForkTest is Test { vm.stopPrank(); // For simplicity, we set the deposit limit to very high value to not reach quickly the error `DepositLimitExceeded`.. - vm.prank(VAULT_ADMIN); + vm.prank(VAULT_CONTRACT_OWNER_AND_OPERATOR); vault.setDepositLimit(3_000_000 ether); // CHECK the bytecode of the Vault proxy is correct @@ -84,15 +92,24 @@ contract ForkTest is Test { // CHECK the bytecode of the implementation is not the same as the bytecode // of the new Vault we are upgrading to. - assertNotEq(address(newVaultImplementation).code, VAULT_IMPLEMENTATION.code); + assertNotEq( + address(newVaultImplementation).code, + VAULT_IMPLEMENTATION.code + ); // upgrade vault - bytes memory upgradeToCalldata = abi.encodeCall(IProxy.upgradeTo, address(newVaultImplementation)); + bytes memory upgradeToCalldata = abi.encodeCall( + IProxy.upgradeTo, + address(newVaultImplementation) + ); vm.prank(VAULT_AND_SLYX_PROXY_ADMIN); - (bool success,) = address(vault).call(upgradeToCalldata); + (bool success, ) = address(vault).call(upgradeToCalldata); assertTrue(success, "upgrade failed"); vm.prank(VAULT_AND_SLYX_PROXY_ADMIN); - assertEq(IProxy(VAULT_PROXY).implementation(), address(newVaultImplementation)); + assertEq( + IProxy(VAULT_PROXY).implementation(), + address(newVaultImplementation) + ); // allow the transaction to be validated vm.roll(block.number + 100); @@ -102,42 +119,91 @@ contract ForkTest is Test { // CHECK the bytecode of the Vault proxy is correct assertEq(address(sLyxToken).code, SLYX_PROXY_BYTECODE); + // CHECK SLYX Proxy admin is correctly set + vm.prank(VAULT_AND_SLYX_PROXY_ADMIN); + assertEq(IProxy(SLYX_PROXY).admin(), VAULT_AND_SLYX_PROXY_ADMIN); + // Deploy the SLYX Token contract implementation + // + upgrade the SLYX Proxy contract to the new implementation sLyxTokenImplementation = new SLYXToken(); - // upgrade SLYX Proxy to new implementation - bytes memory slyxInitializeCalldata = abi.encodeCall(SLYXToken.initialize, (sLyxContractOwner, vault)); - - vm.prank(VAULT_AND_SLYX_PROXY_ADMIN); - assertEq(IProxy(SLYX_PROXY).admin(), VAULT_AND_SLYX_PROXY_ADMIN); + bytes memory slyxInitializeCalldata = abi.encodeCall( + SLYXToken.initialize, + (SLYX_CONTRACT_OWNER, vault) + ); vm.startPrank(VAULT_AND_SLYX_PROXY_ADMIN); - IProxy(SLYX_PROXY).upgradeToAndCall(address(sLyxTokenImplementation), slyxInitializeCalldata); - assertEq(IProxy(SLYX_PROXY).implementation(), address(sLyxTokenImplementation)); + IProxy(SLYX_PROXY).upgradeToAndCall( + address(sLyxTokenImplementation), + slyxInitializeCalldata + ); + + // CHECK implementation address has been upgraded successfully + assertEq( + IProxy(SLYX_PROXY).implementation(), + address(sLyxTokenImplementation) + ); + + // CHECK admin remains the same after upgrade assertEq(IProxy(SLYX_PROXY).admin(), VAULT_AND_SLYX_PROXY_ADMIN); vm.stopPrank(); vm.roll(block.number + 1000); - console.log(unicode"๐Ÿงช Starting Fork testing. Block number: ", block.number); + console.log( + unicode"๐Ÿงช Starting Fork testing. Block number: ", + block.number + ); } function test_vaultStorageRemainSameAfterUpgrade() public view { assertGt(vault.depositLimit(), initialDepositLimit); // Note that this value might change - assertEq(vault.owner(), VAULT_ADMIN); - assertEq(vault.operator(), VAULT_ADMIN); + assertEq(vault.owner(), VAULT_CONTRACT_OWNER_AND_OPERATOR); + assertEq(vault.operator(), VAULT_CONTRACT_OWNER_AND_OPERATOR); assertTrue(vault.isOracle(VAULT_ORACLE)); } - function test_callingInitializeOnVaultAfterUpgradeNotPossible() public { + function test_cannotInitializeVaultProxyAfterUpgrade() public { + vm.expectRevert("Initializable: contract is already initialized"); + vault.initialize( + VAULT_CONTRACT_OWNER_AND_OPERATOR, + VAULT_CONTRACT_OWNER_AND_OPERATOR, + IDepositContract(0xCAfe00000000000000000000000000000000CAfe) + ); + } + + function test_cannotInitializeNewVaultImplementation() public { vm.expectRevert("Initializable: contract is already initialized"); - vault.initialize(VAULT_ADMIN, VAULT_ADMIN, IDepositContract(0xCAfe00000000000000000000000000000000CAfe)); + newVaultImplementation.initialize( + VAULT_CONTRACT_OWNER_AND_OPERATOR, + VAULT_CONTRACT_OWNER_AND_OPERATOR, + IDepositContract(0xCAfe00000000000000000000000000000000CAfe) + ); + } + + function test_cannotInitializeSLYXTokenProxyAfterUpgrade() public { + vm.expectRevert("Initializable: contract is already initialized"); + sLyxToken.initialize(address(111), vault); + } + + function test_cannotInitializeSLYXTokenImplementation() public { + vm.expectRevert("Initializable: contract is already initialized"); + sLyxTokenImplementation.initialize(address(111), vault); } function test_NoSLYXTokenMintedInitially() public view { assertEq(sLyxToken.totalSupply(), 0); } + function test_ExchangeRateForSLYXToLyxStartAt1To1SinceNoSLYXGotMinted() + public + view + { + assertEq(sLyxToken.getExchangeRate(), 1 ether); + assertEq(sLyxToken.getNativeTokenValue(12345 ether), 12345 ether); + assertEq(sLyxToken.getSLYXTokenValue(12345 ether), 12345 ether); + } + function test_NewUserStakeAndMintSLYXLater() public { address alice = makeAddr("alice"); uint256 depositAmount = 320 ether; @@ -162,9 +228,18 @@ contract ForkTest is Test { assertEq(vault.balanceOf(alice), 1 wei); // All test have rounding errors remaining - assertEq(vault.balanceOf(address(sLyxToken)), aliceStake - VAULT_ROUNDING_ERROR_LOSS); - assertEq(sLyxToken.totalSupply(), aliceShares - VAULT_ROUNDING_ERROR_LOSS); - assertEq(sLyxToken.balanceOf(alice), aliceShares - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + vault.balanceOf(address(sLyxToken)), + aliceStake - VAULT_ROUNDING_ERROR_LOSS + ); + assertEq( + sLyxToken.totalSupply(), + aliceShares - VAULT_ROUNDING_ERROR_LOSS + ); + assertEq( + sLyxToken.balanceOf(alice), + aliceShares - VAULT_ROUNDING_ERROR_LOSS + ); vm.stopPrank(); } @@ -183,7 +258,10 @@ contract ForkTest is Test { uint256 sLyxTokenShares = vault.sharesOf(address(sLyxToken)); assertEq(vault.balanceOf(alice), 0); - assertEq(vault.balanceOf(address(sLyxToken)), depositAmount - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + vault.balanceOf(address(sLyxToken)), + depositAmount - VAULT_ROUNDING_ERROR_LOSS + ); assertEq(sLyxToken.totalSupply(), sLyxTokenShares); assertEq(sLyxToken.balanceOf(alice), sLyxTokenShares); @@ -213,9 +291,18 @@ contract ForkTest is Test { vault.transferStake(address(sLyxToken), bobStake, ""); assertEq(vault.balanceOf(bob), 1); - assertEq(vault.balanceOf(address(sLyxToken)), bobStake - VAULT_ROUNDING_ERROR_LOSS); - assertEq(sLyxToken.totalSupply(), bobShares - VAULT_ROUNDING_ERROR_LOSS); - assertEq(sLyxToken.balanceOf(bob), bobShares - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + vault.balanceOf(address(sLyxToken)), + bobStake - VAULT_ROUNDING_ERROR_LOSS + ); + assertEq( + sLyxToken.totalSupply(), + bobShares - VAULT_ROUNDING_ERROR_LOSS + ); + assertEq( + sLyxToken.balanceOf(bob), + bobShares - VAULT_ROUNDING_ERROR_LOSS + ); vm.stopPrank(); } @@ -224,7 +311,7 @@ contract ForkTest is Test { address largeStaker = 0x01C2a6EB1568C1A3E0B8fE864056FaC8B4867f77; uint256 largeStakerStake = vault.balanceOf(largeStaker); uint256 largeStakerShares = vault.sharesOf(largeStaker); - console.log("largeStakerShares: ", largeStakerShares); + assertGt(vault.balanceOf(largeStaker), 6_400 ether); // Convert half of the stake to sLYX @@ -233,16 +320,28 @@ contract ForkTest is Test { vm.prank(largeStaker); vault.transferStake(address(sLyxToken), amountToConvertAsSLYX, ""); - assertEq(vault.balanceOf(largeStaker), largeStakerStake - amountToConvertAsSLYX); - assertEq(vault.balanceOf(address(sLyxToken)), amountToConvertAsSLYX - VAULT_ROUNDING_ERROR_LOSS); - assertEq(vault.sharesOf(largeStaker), largeStakerShares / 2 + VAULT_ROUNDING_ERROR_LOSS); + assertEq( + vault.balanceOf(largeStaker), + largeStakerStake - amountToConvertAsSLYX + VAULT_ROUNDING_ERROR_LOSS + ); + assertEq( + vault.balanceOf(address(sLyxToken)), + amountToConvertAsSLYX - VAULT_ROUNDING_ERROR_LOSS + ); + assertEq( + vault.sharesOf(largeStaker), + largeStakerShares / 2 + VAULT_ROUNDING_ERROR_LOSS + ); // broad check to ensure SLYX is minted as shares (shares MUST always be lower than LYX staked balances) assertLt(sLyxToken.totalSupply(), amountToConvertAsSLYX); assertLt(sLyxToken.balanceOf(largeStaker), amountToConvertAsSLYX); // Since user converted half of its stake to sLYX, its SLYX balance is half of its shares - assertEq(sLyxToken.balanceOf(largeStaker), vault.sharesOf(largeStaker) - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + sLyxToken.balanceOf(largeStaker), + vault.sharesOf(largeStaker) - VAULT_ROUNDING_ERROR_LOSS + ); } function test_ExistingStakeConvertAllItsStakeToSLYX() public { @@ -254,13 +353,27 @@ contract ForkTest is Test { vault.transferStake(address(sLyxToken), userStake, ""); assertEq(vault.balanceOf(existingUser), VAULT_ROUNDING_ERROR_LOSS); - assertEq(vault.balanceOf(address(sLyxToken)), userStake - VAULT_ROUNDING_ERROR_LOSS); - - assertEq(sLyxToken.totalSupply(), userShares - VAULT_ROUNDING_ERROR_LOSS); - assertEq(sLyxToken.balanceOf(existingUser), userShares - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + vault.balanceOf(address(sLyxToken)), + userStake - VAULT_ROUNDING_ERROR_LOSS + ); + + assertEq( + sLyxToken.totalSupply(), + userShares - VAULT_ROUNDING_ERROR_LOSS + ); + assertEq( + sLyxToken.balanceOf(existingUser), + userShares - VAULT_ROUNDING_ERROR_LOSS + ); } - function test_ExchangeRateForSLYXToLyxStartAbove1To1() public { + function test_ExchangeRateForSLYXToLyxStartIncreasesAfterMintingSLYX() + public + { + uint256 initialSLyxToLyxExchangeRate = sLyxToken.getExchangeRate(); + assertEq(initialSLyxToLyxExchangeRate, 1 ether); + address alice = makeAddr("alice"); uint256 depositAmount = 320 ether; @@ -276,9 +389,22 @@ contract ForkTest is Test { vault.transferStake(address(sLyxToken), aliceStake, ""); - assertEq(vault.balanceOf(address(sLyxToken)), aliceStake - VAULT_ROUNDING_ERROR_LOSS); - assertEq(sLyxToken.totalSupply(), aliceShares - VAULT_ROUNDING_ERROR_LOSS); - assertEq(sLyxToken.balanceOf(alice), aliceShares - VAULT_ROUNDING_ERROR_LOSS); + // CHECK the exchange rate increased + uint256 newSLyxToLyxExchangeRate = sLyxToken.getExchangeRate(); + assertGt(newSLyxToLyxExchangeRate, initialSLyxToLyxExchangeRate); + + assertEq( + vault.balanceOf(address(sLyxToken)), + aliceStake - VAULT_ROUNDING_ERROR_LOSS + ); + assertEq( + sLyxToken.totalSupply(), + aliceShares - VAULT_ROUNDING_ERROR_LOSS + ); + assertEq( + sLyxToken.balanceOf(alice), + aliceShares - VAULT_ROUNDING_ERROR_LOSS + ); // Check the exchange rate is not 1:1 from the start (since the Vault already contains rewards) assertGt(sLyxToken.getExchangeRate(), 1 ether); @@ -289,7 +415,10 @@ contract ForkTest is Test { assertEq(sLyxToken.totalSupply(), 0); assertEq(sLyxToken.balanceOf(alice), 0); assertEq(vault.balanceOf(address(sLyxToken)), 1); - assertEq(vault.balanceOf(alice), aliceStake - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + vault.balanceOf(alice), + aliceStake - VAULT_ROUNDING_ERROR_LOSS + ); vm.stopPrank(); } @@ -303,11 +432,17 @@ contract ForkTest is Test { vault.transferStake(address(sLyxToken), userStake, ""); assertEq(vault.balanceOf(existingUser), VAULT_ROUNDING_ERROR_LOSS); - assertEq(vault.balanceOf(address(sLyxToken)), userStake - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + vault.balanceOf(address(sLyxToken)), + userStake - VAULT_ROUNDING_ERROR_LOSS + ); uint256 userSLYXBalance = sLyxToken.balanceOf(existingUser); - assertEq(sLyxToken.totalSupply(), userShares - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + sLyxToken.totalSupply(), + userShares - VAULT_ROUNDING_ERROR_LOSS + ); assertEq(userSLYXBalance, userShares - VAULT_ROUNDING_ERROR_LOSS); // transfer some SLYX to another user @@ -319,7 +454,10 @@ contract ForkTest is Test { vm.prank(existingUser); sLyxToken.transfer(existingUser, alice, amountToTransfer, true, ""); - assertEq(sLyxToken.balanceOf(existingUser), userSLYXBalance - amountToTransfer); + assertEq( + sLyxToken.balanceOf(existingUser), + userSLYXBalance - amountToTransfer + ); assertEq(sLyxToken.balanceOf(alice), amountToTransfer); // Alice burns the SLYX to convert them to stake @@ -327,13 +465,17 @@ contract ForkTest is Test { sLyxToken.burn(alice, amountToTransfer, ""); assertEq(sLyxToken.balanceOf(alice), 0); - uint256 expectedLYXStake = sLyxToken.getNativeTokenValue(amountToTransfer); + uint256 expectedLYXStake = sLyxToken.getNativeTokenValue( + amountToTransfer + ); // not clear why there are 18 wei difference assertEq(vault.balanceOf(alice), expectedLYXStake - 18 wei); } - function test_UserReceivesSLYXAndBurnThemToConvertToStakeAfterAccumulatingRewards() public { + function test_UserReceivesSLYXAndBurnThemToConvertToStakeAfterAccumulatingRewards() + public + { address existingUser = 0xC99Fe517d665c23035B050e13188eb4d7075C91d; uint256 userStake = vault.balanceOf(existingUser); uint256 userShares = vault.sharesOf(existingUser); @@ -342,11 +484,17 @@ contract ForkTest is Test { vault.transferStake(address(sLyxToken), userStake, ""); assertEq(vault.balanceOf(existingUser), VAULT_ROUNDING_ERROR_LOSS); - assertEq(vault.balanceOf(address(sLyxToken)), userStake - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + vault.balanceOf(address(sLyxToken)), + userStake - VAULT_ROUNDING_ERROR_LOSS + ); uint256 userSLYXBalance = sLyxToken.balanceOf(existingUser); - assertEq(sLyxToken.totalSupply(), userShares - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + sLyxToken.totalSupply(), + userShares - VAULT_ROUNDING_ERROR_LOSS + ); assertEq(userSLYXBalance, userShares - VAULT_ROUNDING_ERROR_LOSS); // transfer some SLYX to another user @@ -358,11 +506,16 @@ contract ForkTest is Test { vm.prank(existingUser); sLyxToken.transfer(existingUser, alice, sLyxAmountToTransfer, true, ""); - assertEq(sLyxToken.balanceOf(existingUser), userSLYXBalance - sLyxAmountToTransfer); + assertEq( + sLyxToken.balanceOf(existingUser), + userSLYXBalance - sLyxAmountToTransfer + ); assertEq(sLyxToken.balanceOf(alice), sLyxAmountToTransfer); uint256 exchangeRateBefore = sLyxToken.getExchangeRate(); - uint256 sLyxToLYXValueBefore = sLyxToken.getNativeTokenValue(sLyxAmountToTransfer); + uint256 sLyxToLYXValueBefore = sLyxToken.getNativeTokenValue( + sLyxAmountToTransfer + ); vm.roll(block.number + 1000); @@ -377,7 +530,9 @@ contract ForkTest is Test { vm.roll(block.number + 1000); uint256 exchangeRateAfter = sLyxToken.getExchangeRate(); - uint256 sLyxToLYXValueAfter = sLyxToken.getNativeTokenValue(sLyxAmountToTransfer); + uint256 sLyxToLYXValueAfter = sLyxToken.getNativeTokenValue( + sLyxAmountToTransfer + ); // CHECK exchange rate increased assertGt(exchangeRateAfter, exchangeRateBefore); @@ -390,7 +545,10 @@ contract ForkTest is Test { // CHECK stake after burning increased assertEq(sLyxToken.balanceOf(alice), 0); - assertEq(vault.balanceOf(alice), sLyxToLYXValueAfter - VAULT_ROUNDING_ERROR_LOSS); + assertEq( + vault.balanceOf(alice), + sLyxToLYXValueAfter - VAULT_ROUNDING_ERROR_LOSS + ); vm.stopPrank(); } From f0dc68f3f7d8197c024fb0e3cbd9114cceac9a54 Mon Sep 17 00:00:00 2001 From: JordyDutch Date: Thu, 27 Mar 2025 09:52:05 +0100 Subject: [PATCH 03/14] forge install: openzeppelin-foundry-upgrades --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitmodules b/.gitmodules index d6fe983..519ac6f 100755 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/murky"] path = lib/murky url = https://github.com/dmfxyz/murky +[submodule "lib/openzeppelin-foundry-upgrades"] + path = lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades From e9d174470f6ae4d297bec44433a3a81be446fcbc Mon Sep 17 00:00:00 2001 From: JordyDutch Date: Thu, 27 Mar 2025 09:52:35 +0100 Subject: [PATCH 04/14] forge install: openzeppelin-contracts --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitmodules b/.gitmodules index 519ac6f..c5f66e7 100755 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/openzeppelin-foundry-upgrades"] path = lib/openzeppelin-foundry-upgrades url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts From d655cce3734c30caf2b4fa5598d7a32c599b7b43 Mon Sep 17 00:00:00 2001 From: JordyDutch Date: Thu, 27 Mar 2025 09:53:43 +0100 Subject: [PATCH 05/14] forge install: openzeppelin-contracts-upgradeable --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitmodules b/.gitmodules index c5f66e7..7fa96bc 100755 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable From 55418842f74f9ef8fdd70fa3e305345845ad41a9 Mon Sep 17 00:00:00 2001 From: JordyDutch Date: Thu, 27 Mar 2025 09:54:22 +0100 Subject: [PATCH 06/14] test: add upgrade tests using OZ upgrade foundry plugin --- .gitmodules | 3 + foundry.toml | 8 + package.json | 16 +- remappings.txt | 7 +- src/Vault.sol | 526 +++++++++++++++++++++++++++++++++++++++ test/OZUpgradeTest.t.sol | 191 ++++++++++++++ 6 files changed, 743 insertions(+), 8 deletions(-) create mode 100644 src/Vault.sol create mode 100644 test/OZUpgradeTest.t.sol diff --git a/.gitmodules b/.gitmodules index 7fa96bc..7ba188e 100755 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/contracts"] + path = lib/contracts + url = https://github.com/Universal-Page/contracts diff --git a/foundry.toml b/foundry.toml index 2dfd10f..b1cc515 100755 --- a/foundry.toml +++ b/foundry.toml @@ -8,9 +8,17 @@ gas_reports = ["SLYXToken", "StakingverseVault"] extra_output_files = ["metadata"] fs_permissions = [ { access = "read", path = "./scripts/" }, + { access = "read", path = "./build/" }, { access = "read-write", path = "./artifacts/" }, ] + +# For foundry upgrades +ffi = true +ast = true +build_info = true +extra_output = ["storageLayout"] + # solidity compiler solc_version = "0.8.22" diff --git a/package.json b/package.json index 99f1d47..5ae1b52 100644 --- a/package.json +++ b/package.json @@ -23,13 +23,15 @@ "build:sizes": "forge build --sizes", "build:storage": "forge build --extra-output storageLayout", "build:ir": "forge build --extra-output ir", - "test": "forge test --no-match-contract ForkTest", - "test:debug": "forge test -vvv --no-match-contract ForkTest", - "test:invariant": "forge test --match-test invariant", - "test:invariant:debug": "forge test --match-test invariant --no-match-contract ForkTest -vvv", - "test:coverage": "forge coverage --report summary --no-match-test ^invariant --no-match-contract ForkTest", - "test:gas": "forge test --no-match-contract ForkTest --gas-report", - "test:fork": "forge test --fork-url https://rpc.mainnet.lukso.network --match-contract ForkTest" + "test": "forge test --no-match-path test/OZUpgradeTest.t.sol", + "test:debug": "forge test -vvv --no-match-path test/OZUpgradeTest.t.sol", + "test:invariant": "forge test --match-test ^invariant", + "test:invariant:debug": "forge test --match-test ^invariant --no-match-contract MainnetForkUpgradeTest -vvv", + "test:coverage": "forge coverage --report summary --no-match-test ^invariant --no-match-path test/OZUpgradeTest.t.sol", + "test:gas": "forge test --no-match-path test/OZUpgradeTest.t.sol --gas-report", + "test:upgrade": "forge clean && forge test --match-contract LocalUpgradeTest", + "test:fork": "forge test --fork-url https://rpc.mainnet.lukso.network --match-contract ForkTest", + "test:upgrade:fork": "forge clean && forge test --fork-url https://rpc.mainnet.lukso.network --match-contract MainnetForkUpgradeTest" }, "dependencies": { "@lukso/lsp7-contracts": "~0.16.3" diff --git a/remappings.txt b/remappings.txt index 8c2fc4e..4b9d94c 100755 --- a/remappings.txt +++ b/remappings.txt @@ -1,8 +1,13 @@ @erc725/smart-contracts/=node_modules/@erc725/smart-contracts/ @erc725/smart-contracts-v8/=node_modules/@erc725/smart-contracts-v8/ @lukso/=node_modules/@lukso/ -@openzeppelin/=node_modules/@openzeppelin/ +@openzeppelin/contracts-v4.9.0/=node_modules/@openzeppelin/contracts-v4.9.0/ solidity-bytes-utils/=node_modules/solidity-bytes-utils/ + ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ murky/=lib/murky/src/ + +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +UniversalPage/contracts=lib/contracts/ \ No newline at end of file diff --git a/src/Vault.sol b/src/Vault.sol new file mode 100644 index 0000000..d22e2ca --- /dev/null +++ b/src/Vault.sol @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.22; + +import { + OwnableUnset +} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; +import { + ReentrancyGuardUpgradeable +} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import { + PausableUpgradeable +} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {IDepositContract, DEPOSIT_AMOUNT} from "../../src/IDepositContract.sol"; + +contract Vault is + OwnableUnset, + ReentrancyGuardUpgradeable, + PausableUpgradeable +{ + uint32 private constant _FEE_BASIS = 100_000; + uint32 private constant _MIN_FEE = 0; // 0% + uint32 private constant _MAX_FEE = 15_000; // 15% + uint256 private constant _MAX_VALIDATORS_SUPPORTED = 1_000_000; + uint256 private constant _MINIMUM_REQUIRED_SHARES = 1e3; + + error InvalidAmount(uint256 amount); + error WithdrawalFailed( + address account, + address beneficiary, + uint256 amount + ); + error ClaimFailed(address account, address beneficiary, uint256 amount); + error DepositLimitExceeded(uint256 totalValue, uint256 depositLimit); + error CallerNotOracle(address account); + error InsufficientBalance(uint256 availableAmount, uint256 requestedAmount); + error CallerNotFeeRecipient(address account); + error FeeClaimFailed(address account, address beneficiary, uint256 amount); + error InvalidAddress(address account); + error ValidatorAlreadyRegistered(bytes pubkey); + error CallerNotOperator(address account); + + event Deposited( + address indexed account, + address indexed beneficiary, + uint256 amount + ); + event Withdrawn( + address indexed account, + address indexed beneficiary, + uint256 amount + ); + event WithdrawalRequested( + address indexed account, + address indexed beneficiary, + uint256 amount + ); + event Claimed( + address indexed account, + address indexed beneficiary, + uint256 amount + ); + event DepositLimitChanged(uint256 previousLimit, uint256 newLimit); + event FeeChanged(uint32 previousFee, uint32 newFee); + event FeeRecipientChanged( + address previousFeeRecipient, + address newFeeRecipient + ); + event FeeClaimed( + address indexed account, + address indexed beneficiary, + uint256 amount + ); + event RewardsDistributed(uint256 balance, uint256 rewards, uint256 fee); + event OracleEnabled(address indexed oracle, bool enabled); + event Rebalanced( + uint256 previousTotalStaked, + uint256 previousTotalUnstaked, + uint256 totalStaked, + uint256 totalUnstaked + ); + + // limit of total deposits in wei. + // This limits the total number of validators that can be registered. + uint256 public depositLimit; + // total number of shares in the vault + uint256 public totalShares; + // total amount of active stake in wei on beacon chain + uint256 public totalStaked; + // total amount of inactive stake in wei on execution layer + uint256 public totalUnstaked; + // total amount of pending withdrawals in wei. + // This is the amount that is taken from staked balance and may not be immidiately available for withdrawal + uint256 public totalPendingWithdrawal; + // Total number of ever registered validators + uint256 public totalValidatorsRegistered; + // Vault fee in parts per 100,000 + uint32 public fee; + // Recipient of the vault fee + address public feeRecipient; + // Total amount of fees available for withdrawal + uint256 public totalFees; + // Whether only allowlisted accounts can deposit + bool public restricted; + IDepositContract private _depositContract; + mapping(address => uint256) private _shares; + mapping(address => bool) private _oracles; + mapping(address => uint256) private _pendingWithdrawals; + mapping(address => bool) private _allowlisted; + mapping(bytes => bool) private _registeredKeys; + // Total amount of pending withdrawals that can be claimed immidiately + uint256 public totalClaimable; + address public operator; + + modifier onlyOracle() { + _checkOracle(); + _; + } + + modifier onlyOperator() { + _checkOperator(); + _; + } + + constructor() { + _disableInitializers(); + } + + function initialize( + address owner_, + address operator_, + IDepositContract depositContract_ + ) external initializer { + if (address(depositContract_) == address(0)) { + revert InvalidAddress(address(depositContract_)); + } + __ReentrancyGuard_init(); + __Pausable_init(); + _setOwner(owner_); + _setOperator(operator_); + _depositContract = depositContract_; + } + + receive() external payable { + deposit(msg.sender); + } + + function _checkOperator() private view { + if (msg.sender != operator && msg.sender != owner()) { + revert CallerNotOperator(msg.sender); + } + } + + function setOperator(address newOperator) external onlyOperator { + _setOperator(newOperator); + } + + function _setOperator(address newOperator) private { + if (newOperator == address(0)) { + revert InvalidAddress(newOperator); + } + operator = newOperator; + } + + function pause() external onlyOwner { + _pause(); + } + + function unpause() external onlyOwner { + _unpause(); + } + + function setFee(uint32 newFee) external onlyOperator { + if (newFee > _FEE_BASIS) { + revert InvalidAmount(newFee); + } + if (newFee < _MIN_FEE || newFee > _MAX_FEE) { + revert InvalidAmount(newFee); + } + uint32 previousFee = fee; + fee = newFee; + emit FeeChanged(previousFee, newFee); + } + + function setFeeRecipient(address newFeeRecipient) external onlyOperator { + if (newFeeRecipient == address(0)) { + revert InvalidAddress(newFeeRecipient); + } + address previousFeeRecipient = feeRecipient; + feeRecipient = newFeeRecipient; + emit FeeRecipientChanged(previousFeeRecipient, newFeeRecipient); + } + + function setDepositLimit(uint256 newDepositLimit) external onlyOperator { + if ( + newDepositLimit < totalValidatorsRegistered * DEPOSIT_AMOUNT || + newDepositLimit > _MAX_VALIDATORS_SUPPORTED * DEPOSIT_AMOUNT + ) { + revert InvalidAmount(newDepositLimit); + } + uint256 previousDepositLimit = depositLimit; + depositLimit = newDepositLimit; + emit DepositLimitChanged(previousDepositLimit, newDepositLimit); + } + + function enableOracle(address oracle, bool enabled) external onlyOperator { + _oracles[oracle] = enabled; + emit OracleEnabled(oracle, enabled); + } + + function isOracle(address oracle) public view returns (bool) { + return _oracles[oracle]; + } + + function allowlist(address account, bool enabled) external onlyOperator { + _allowlisted[account] = enabled; + } + + function isAllowlisted(address account) public view returns (bool) { + return _allowlisted[account]; + } + + function setRestricted(bool enabled) external onlyOperator { + restricted = enabled; + } + + function _checkOracle() private view { + address oracle = msg.sender; + if (!isOracle(oracle)) { + revert CallerNotOracle(oracle); + } + } + + function sharesOf(address account) external view returns (uint256) { + return _shares[account]; + } + + function balanceOf(address account) public view returns (uint256) { + return _toBalance(_shares[account]); + } + + function pendingBalanceOf(address account) external view returns (uint256) { + return _pendingWithdrawals[account]; + } + + function claimableBalanceOf( + address account + ) external view returns (uint256) { + uint256 pendingWithdrawal = _pendingWithdrawals[account]; + return + pendingWithdrawal > totalClaimable + ? totalClaimable + : pendingWithdrawal; + } + + function claim( + uint256 amount, + address beneficiary + ) external nonReentrant whenNotPaused { + if (beneficiary == address(0)) { + revert InvalidAddress(beneficiary); + } + address account = msg.sender; + if (amount == 0) { + revert InvalidAmount(amount); + } + if (amount > _pendingWithdrawals[account]) { + revert InsufficientBalance(_pendingWithdrawals[account], amount); + } + if (amount > totalClaimable) { + revert InsufficientBalance(totalClaimable, amount); + } + _pendingWithdrawals[account] -= amount; + totalPendingWithdrawal -= amount; + totalClaimable -= amount; + (bool success, ) = beneficiary.call{value: amount}(""); + if (!success) { + revert ClaimFailed(account, beneficiary, amount); + } + emit Claimed(account, beneficiary, amount); + } + + function totalAssets() public view returns (uint256) { + return + totalStaked + + totalUnstaked + + totalClaimable - + totalPendingWithdrawal; + } + + function _toBalance(uint256 shares) private view returns (uint256) { + if (totalShares == 0) { + return 0; + } + // In some cases, totalShares may be slightly less than totalStaked + totalUnstaked due to rounding errors. + // The error is 1 wei considered insignificant and can be ignored. + return Math.mulDiv(shares, totalAssets(), totalShares); + } + + function _toShares(uint256 amount) private view returns (uint256) { + if (totalShares == 0) { + return amount; + } + return Math.mulDiv(amount, totalShares, totalAssets()); + } + + function deposit(address beneficiary) public payable whenNotPaused { + if (beneficiary == address(0)) { + revert InvalidAddress(beneficiary); + } + address account = msg.sender; + if (restricted && !isAllowlisted(account)) { + revert InvalidAddress(account); + } + uint256 amount = msg.value; + if (amount == 0) { + revert InvalidAmount(amount); + } + uint256 newTotalDeposits = Math.max( + totalValidatorsRegistered * DEPOSIT_AMOUNT, + totalStaked + totalUnstaked + ) + amount; + if (newTotalDeposits > depositLimit) { + revert DepositLimitExceeded(newTotalDeposits, depositLimit); + } + uint256 shares = _toShares(amount); + if (shares == 0) { + revert InvalidAmount(amount); + } + // burn minimum shares of first depositor to prevent share inflation and dust shares attacks. + if (totalShares == 0) { + if (shares < _MINIMUM_REQUIRED_SHARES) { + revert InvalidAmount(amount); + } + _shares[address(0)] = _MINIMUM_REQUIRED_SHARES; + totalShares += _MINIMUM_REQUIRED_SHARES; + shares -= _MINIMUM_REQUIRED_SHARES; + } + _shares[beneficiary] += shares; + totalShares += shares; + totalUnstaked += amount; + emit Deposited(account, beneficiary, amount); + } + + function withdraw( + uint256 amount, + address beneficiary + ) external nonReentrant whenNotPaused { + if (beneficiary == address(0)) { + revert InvalidAddress(beneficiary); + } + address account = msg.sender; + if (amount == 0) { + revert InvalidAmount(amount); + } + if (amount > balanceOf(account)) { + revert InsufficientBalance(balanceOf(account), amount); + } + uint256 shares = _toShares(amount); + if (shares == 0) { + revert InvalidAmount(amount); + } + if (shares > _shares[account]) { + revert InsufficientBalance(_shares[account], shares); + } + _shares[account] -= shares; + totalShares -= shares; + + uint256 immediateAmount = amount > totalUnstaked + ? totalUnstaked + : amount; + uint256 delayedAmount = amount - immediateAmount; + + totalUnstaked -= immediateAmount; + totalPendingWithdrawal += delayedAmount; + _pendingWithdrawals[beneficiary] += delayedAmount; + + if (immediateAmount > 0) { + (bool success, ) = beneficiary.call{value: immediateAmount}(""); + if (!success) { + revert WithdrawalFailed(account, beneficiary, immediateAmount); + } + emit Withdrawn(account, beneficiary, immediateAmount); + } + + if (delayedAmount > 0) { + emit WithdrawalRequested(account, beneficiary, delayedAmount); + } + } + + function claimFees( + uint256 amount, + address beneficiary + ) external nonReentrant whenNotPaused { + if (beneficiary == address(0)) { + revert InvalidAddress(beneficiary); + } + address account = msg.sender; + if (account != feeRecipient) { + revert CallerNotFeeRecipient(account); + } + if (amount == 0) { + revert InvalidAmount(amount); + } + if (amount > totalFees) { + revert InsufficientBalance(totalFees, amount); + } + totalFees -= amount; + (bool success, ) = beneficiary.call{value: amount}(""); + if (!success) { + revert FeeClaimFailed(account, beneficiary, amount); + } + emit FeeClaimed(account, beneficiary, amount); + } + + // Rebalance the vault by accounting balance of the vault. + // This is called periodically by the oracle. + // The balance of the vault is the sum of: + // - totalStaked + totalUnstaked: the total amount of stake on beacon chain and execution layer + // - totalPendingWithdrawal: the total amount of pending withdrawals + // - totalClaimable: the total amount of pending withdrawals that can be claimed immidiately + // - totalFees: the total amount of fees available for withdrawal + // + // Rebalancing is not accounting for potential validator penalties. It is assumed that the penalties + // shall not occur or shall be negligible. + function rebalance() external onlyOracle whenNotPaused { + uint256 balance = address(this).balance; + + // account for completed withdrawals + uint256 pendingWithdrawal = totalPendingWithdrawal - totalClaimable; + uint256 completedWithdrawal = Math.min( + (balance - totalFees - totalUnstaked - totalClaimable) / + DEPOSIT_AMOUNT, // actual completed withdrawals + pendingWithdrawal / + DEPOSIT_AMOUNT + // pending withdrawals + (pendingWithdrawal % DEPOSIT_AMOUNT == 0 ? 0 : 1) // partial withdrawals + ) * DEPOSIT_AMOUNT; + + // adjust staked balance for completed withdrawals + uint256 staked = totalStaked - completedWithdrawal; + + // take out any claimable balances from unstaked balance prior to calculating rewards. + uint256 unstaked = balance - + totalFees - + totalClaimable - + completedWithdrawal; + + // account for withdrawals to claim later + uint256 claimable = totalClaimable + completedWithdrawal; + + // account for partial completeted withdrawals + uint256 partialWithdrawal = 0; + if (claimable > totalPendingWithdrawal) { + partialWithdrawal = claimable - totalPendingWithdrawal; + unstaked += partialWithdrawal; + claimable = totalPendingWithdrawal; + } + + // at this point the difference represents the rewards. + // If the difference is positive, it means that the rewards are available for distribution. + // Fees are subsidized in attempt to prevent validator exits. + if (unstaked - partialWithdrawal > totalUnstaked) { + uint256 rewards = unstaked - partialWithdrawal - totalUnstaked; + uint256 feeAmount = Math.mulDiv(rewards, fee, _FEE_BASIS); + emit RewardsDistributed( + totalStaked + totalUnstaked, + rewards, + feeAmount + ); + totalFees += feeAmount; + unstaked -= feeAmount; + } + + emit Rebalanced(totalStaked, totalUnstaked, staked, unstaked); + totalClaimable = claimable; + totalUnstaked = unstaked; + totalStaked = staked; + } + + function isValidatorRegistered( + bytes calldata pubkey + ) external view returns (bool) { + return _registeredKeys[pubkey]; + } + + function registerValidator( + bytes calldata pubkey, + bytes calldata signature, + bytes32 depositDataRoot + ) public onlyOracle nonReentrant whenNotPaused { + if (totalUnstaked < DEPOSIT_AMOUNT) { + revert InsufficientBalance(totalUnstaked, DEPOSIT_AMOUNT); + } + if (_registeredKeys[pubkey]) { + revert ValidatorAlreadyRegistered(pubkey); + } + _registeredKeys[pubkey] = true; + totalValidatorsRegistered += 1; + totalStaked += DEPOSIT_AMOUNT; + totalUnstaked -= DEPOSIT_AMOUNT; + bytes memory withdrawalCredentials = abi.encodePacked( + hex"010000000000000000000000", + address(this) + ); + _depositContract.deposit{value: DEPOSIT_AMOUNT}( + pubkey, + withdrawalCredentials, + signature, + depositDataRoot + ); + } + + function registerValidators( + bytes[] calldata pubkeys, + bytes[] calldata signatures, + bytes32[] calldata depositDataRoots + ) external { + uint256 length = pubkeys.length; + if (length != signatures.length || length != depositDataRoots.length) { + revert InvalidAmount(length); + } + for (uint256 i = 0; i < length; i++) { + registerValidator(pubkeys[i], signatures[i], depositDataRoots[i]); + } + } +} diff --git a/test/OZUpgradeTest.t.sol b/test/OZUpgradeTest.t.sol new file mode 100644 index 0000000..97fc75c --- /dev/null +++ b/test/OZUpgradeTest.t.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import { + Upgrades, + Options +} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol"; +import {IDepositContract} from "../src/IDepositContract.sol"; + +// Contracts to test +import { + TransparentUpgradeableProxy, + ITransparentUpgradeableProxy as IProxy +} from "@openzeppelin/contracts-v4.9.0/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {Vault as VaultV1} from "../src/Vault.sol"; +import {StakingverseVault} from "../src/StakingverseVault.sol"; + +// Constants +import { + PROXY_ADMIN_MAINNET, + VAULT_PROXY_MAINNET +} from "../script/MainnetConstants.sol"; + +/// @dev Test to ensure that the upgrade is safe and the new implementation contract is valid, using OpenZeppelin Foundry upgrades library +/// This ensures that the new implementation contract is safe to upgrade to and that the storage layout is compatible with the proxy linked to the current implementation +contract MainnetForkUpgradeTest is Test { + address constant CURRENT_VAULT_IMPLEMENTATION = + 0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4; + + function test_shouldReturnCorrectProxyAdminAddress() public view { + // CHECK current proxy admin address before upgrading + assertEq( + Upgrades.getAdminAddress(VAULT_PROXY_MAINNET), + PROXY_ADMIN_MAINNET + ); + + // TODO: upgrade and check the proxy admin address has NOT changed + } + + function test_shouldReturnCorrectVaultImplementationAddress() public view { + // CHECK current implementation address before upgrading + assertEq( + Upgrades.getImplementationAddress(VAULT_PROXY_MAINNET), + CURRENT_VAULT_IMPLEMENTATION + ); + + // TODO: upgrade and check the vault implementation address has changed + } + + function test_validateUpgradeIsSafe() public { + /** + * @dev Validates a new implementation contract in comparison with a reference contract, but does not deploy it. + * + * Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. + * + * @param contractName Name of the contract to validate, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory + * @param opts Common options + */ + // function validateUpgrade(string memory contractName, Options memory opts) internal { + // Core.validateUpgrade(contractName, opts); + // } + Options memory opts; + opts.referenceContract = "Vault.sol"; + opts.unsafeAllow = "constructor"; + + Upgrades.validateUpgrade("StakingverseVault.sol", opts); + } + + function test_preparingUpgrade() public { + /** + * @dev Validates a new implementation contract in comparison with a reference contract, deploys the new implementation contract, + * and returns its address. + * + * Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. + * + * Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from your deployment environment. + * + * @param contractName Name of the contract to deploy, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory + * @param opts Common options + * @return Address of the new implementation contract + */ + // function prepareUpgrade(string memory contractName, Options memory opts) internal returns (address) { + // return Core.prepareUpgrade(contractName, opts); + // } + Options memory opts; + opts.referenceContract = "Vault.sol"; + opts.unsafeAllow = "constructor"; + + address newStakingverseVaultAddress = Upgrades.prepareUpgrade( + "StakingverseVault.sol", + opts + ); + console.log( + unicode"๐Ÿงช Test deploying new StakingverseVault implementation!" + ); + console.log( + unicode"๐Ÿš€ New StakingverseVault deployed at address: %s", + newStakingverseVaultAddress + ); + } + + function test_tryDeployingNewImplementationAndUpgrade() public { + vm.skip(true); + console.log(unicode"๐Ÿ‘€ Testing if upgrade is safe with OZ upgrade"); + + Options memory opts; + + // reference current Vault implementation to compare to for storage layout and upgrade checks + opts.referenceContract = "Vault.sol"; + + // SKIP this check as new implementation contains a constructor that includes a `_disableInitializer` + // (to prevent implementation contract from being initialized) + // but also an actual `initialize(...)` function that is called by the proxy. + // TODO: open an issue with OpenZeppelin Foundry to add a check for this + opts.unsafeAllow = "constructor"; + + Upgrades.upgradeProxy( + VAULT_PROXY_MAINNET, + "StakingverseVault.sol", + "", + opts, + PROXY_ADMIN_MAINNET + ); + } +} + +/// @dev Test with local anvil chain +contract LocalUpgradeTest is Test { + address constant CURRENT_VAULT_OWNER = + 0x983aBC616f2442bAB7a917E6bb8660Df8b01F3bF; + address constant CURRENT_VAULT_OPERATOR = + 0x983aBC616f2442bAB7a917E6bb8660Df8b01F3bF; + + address proxyAdmin; + + // current vault contract implementation to upgrade from + VaultV1 currentVaultImplementation = new VaultV1(); + + IProxy vaultProxy; + + function setUp() public { + proxyAdmin = address(11); + + // 1. Deploy the existing vault implementation + currentVaultImplementation = new VaultV1(); + + // 2. Deploy the proxy and initialize it + bytes memory initializeCalldata = abi.encodeCall( + currentVaultImplementation.initialize, + ( + CURRENT_VAULT_OWNER, + CURRENT_VAULT_OPERATOR, + IDepositContract(0xCAfe00000000000000000000000000000000CAfe) + ) + ); + + vaultProxy = IProxy( + address( + new TransparentUpgradeableProxy( + address(currentVaultImplementation), + proxyAdmin, + initializeCalldata + ) + ) + ); + } + + // 3. Deploy the new vault implementation contract + upgrade the proxy to this new implementation. + // The function `Upgrades.upgradeProxy` deploy the new implementation contract for us. + function test_tryDeployingNewImplementationAndUpgrade() public { + Options memory opts; + + // reference current Vault implementation to compare to for storage layout and upgrade checks + opts.referenceContract = "Vault.sol"; + + // SKIP this check as new implementation contains a constructor that includes a `_disableInitializer` + // (to prevent implementation contract from being initialized) + // but also an actual `initialize(...)` function that is called by the proxy. + opts.unsafeAllow = "constructor"; + + Upgrades.upgradeProxy( + VAULT_PROXY_MAINNET, + "StakingverseVault.sol", + "", + opts, + PROXY_ADMIN_MAINNET + ); + } +} From 9d7b40a0508c77c1fb83b01439c17f6cbe01d625 Mon Sep 17 00:00:00 2001 From: JordyDutch Date: Thu, 27 Mar 2025 09:55:17 +0100 Subject: [PATCH 07/14] chore: use forge for solidity formatter + prettify --- .prettierrc | 13 -- .vscode/settings.json | 3 + script/SLYXTokenScriptMainnet.s.sol | 49 +---- script/SLYXTokenScriptTestnet.s.sol | 49 +---- script/StakingverseVaultScriptMainnet.s.sol | 41 +--- script/StakingverseVaultScriptTestnet.s.sol | 41 +--- src/Vault.sol | 166 ++++------------ test/Deployment.t.sol | 27 +-- test/ForkTest.t.sol | 208 +++++--------------- test/OZUpgradeTest.t.sol | 71 ++----- test/base/SLYXTokenBaseTest.t.sol | 5 +- test/base/UniversalProfileTestHelpers.t.sol | 54 ++--- 12 files changed, 164 insertions(+), 563 deletions(-) delete mode 100644 .prettierrc create mode 100644 .vscode/settings.json diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 0b199ec..0000000 --- a/.prettierrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "plugins": ["prettier-plugin-solidity"], - "overrides": [ - { - "files": "*.sol", - "options": { - "tabWidth": 4, - "printWidth": 80, - "compiler": "0.8.17" - } - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b7e2454 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "solidity.formatter": "forge" +} \ No newline at end of file diff --git a/script/SLYXTokenScriptMainnet.s.sol b/script/SLYXTokenScriptMainnet.s.sol index 4b77077..ab0f7d0 100644 --- a/script/SLYXTokenScriptMainnet.s.sol +++ b/script/SLYXTokenScriptMainnet.s.sol @@ -13,10 +13,7 @@ import {SLYXToken} from "../src/SLYXToken.sol"; import {IVault} from "../src/IVault.sol"; -import { - PROXY_ADMIN_MAINNET, - SLYX_TOKEN_PROXY_MAINNET -} from "./MainnetConstants.sol"; +import {PROXY_ADMIN_MAINNET, SLYX_TOKEN_PROXY_MAINNET} from "./MainnetConstants.sol"; contract DeploySLYXTokenImplementation is Script { function run() external { @@ -24,12 +21,7 @@ contract DeploySLYXTokenImplementation is Script { SLYXToken slyxToken = new SLYXToken(); vm.stopBroadcast(); - console.log( - string.concat( - "SLYXToken implementation deployed at ", - Strings.toHexString(address(slyxToken)) - ) - ); + console.log(string.concat("SLYXToken implementation deployed at ", Strings.toHexString(address(slyxToken)))); } } @@ -37,9 +29,7 @@ contract DeploySLYXTokenProxy is Script { function run() external { // Proxy deployment parameters for the SLYXToken address proxyAdmin = vm.envAddress("SLYX_PROXY_ADMIN_ADDRESS"); - address sLyxTokenImplementation = vm.envAddress( - "SLYX_TOKEN_IMPLEMENTATION_ADDRESS" - ); + address sLyxTokenImplementation = vm.envAddress("SLYX_TOKEN_IMPLEMENTATION_ADDRESS"); // Parameters to initialize the SLYX Token contract address owner = vm.envAddress("SLYX_TOKEN_CONTRACT_OWNER_ADDRESS"); @@ -53,34 +43,19 @@ contract DeploySLYXTokenProxy is Script { // proxy admin proxyAdmin, // `initialize(address,IVault)` calldata - abi.encodeCall( - SLYXToken.initialize, - (owner, IVault(linkedVault)) - ) + abi.encodeCall(SLYXToken.initialize, (owner, IVault(linkedVault))) ) ); vm.stopBroadcast(); - console.log( - string.concat( - "SLYXToken proxy deployed at ", - Strings.toHexString(proxy) - ) - ); - console.log( - string.concat( - "Linked to implementation at address ", - Strings.toHexString(sLyxTokenImplementation) - ) - ); + console.log(string.concat("SLYXToken proxy deployed at ", Strings.toHexString(proxy))); + console.log(string.concat("Linked to implementation at address ", Strings.toHexString(sLyxTokenImplementation))); } } contract UpgradeSLYXToken is Script { function run() external { - address newSLYXTokenImplementation = vm.envAddress( - "NEW_SLYX_TOKEN_IMPLEMENTATION_ADDRESS" - ); + address newSLYXTokenImplementation = vm.envAddress("NEW_SLYX_TOKEN_IMPLEMENTATION_ADDRESS"); vm.startBroadcast(); IProxy(SLYX_TOKEN_PROXY_MAINNET).upgradeTo( @@ -91,8 +66,7 @@ contract UpgradeSLYXToken is Script { console.log( string.concat( - "SLYXToken proxy upgraded to implementation at ", - Strings.toHexString(newSLYXTokenImplementation) + "SLYXToken proxy upgraded to implementation at ", Strings.toHexString(newSLYXTokenImplementation) ) ); } @@ -106,11 +80,6 @@ contract ChangeAdmin is Script { IProxy(SLYX_TOKEN_PROXY_MAINNET).changeAdmin(newProxyAdmin); vm.stopBroadcast(); - console.log( - string.concat( - "SLYXToken proxy admin changed to ", - Strings.toHexString(newProxyAdmin) - ) - ); + console.log(string.concat("SLYXToken proxy admin changed to ", Strings.toHexString(newProxyAdmin))); } } diff --git a/script/SLYXTokenScriptTestnet.s.sol b/script/SLYXTokenScriptTestnet.s.sol index e51aa33..86fd3c9 100644 --- a/script/SLYXTokenScriptTestnet.s.sol +++ b/script/SLYXTokenScriptTestnet.s.sol @@ -13,10 +13,7 @@ import {SLYXToken} from "../src/SLYXToken.sol"; import {IVault} from "../src/IVault.sol"; -import { - PROXY_ADMIN_TESTNET, - SLYX_TOKEN_PROXY_TESTNET -} from "./TestnetConstants.sol"; +import {PROXY_ADMIN_TESTNET, SLYX_TOKEN_PROXY_TESTNET} from "./TestnetConstants.sol"; contract DeploySLYXTokenImplementation is Script { function run() external { @@ -24,12 +21,7 @@ contract DeploySLYXTokenImplementation is Script { vm.broadcast(deployerPrivateKey); SLYXToken slyxToken = new SLYXToken(); - console.log( - string.concat( - "SLYXToken implementation deployed at ", - Strings.toHexString(address(slyxToken)) - ) - ); + console.log(string.concat("SLYXToken implementation deployed at ", Strings.toHexString(address(slyxToken)))); } } @@ -39,9 +31,7 @@ contract DeploySLYXTokenProxy is Script { // Proxy deployment parameters for the SLYXToken address proxyAdmin = vm.envAddress("SLYX_PROXY_ADMIN_ADDRESS"); - address sLyxTokenImplementation = vm.envAddress( - "SLYX_TOKEN_IMPLEMENTATION_ADDRESS" - ); + address sLyxTokenImplementation = vm.envAddress("SLYX_TOKEN_IMPLEMENTATION_ADDRESS"); // Parameters to initialize the SLYX Token contract address owner = vm.envAddress("SLYX_TOKEN_CONTRACT_OWNER_ADDRESS"); @@ -55,25 +45,12 @@ contract DeploySLYXTokenProxy is Script { // proxy admin proxyAdmin, // `initialize(address,IVault)` calldata - abi.encodeCall( - SLYXToken.initialize, - (owner, IVault(linkedVault)) - ) + abi.encodeCall(SLYXToken.initialize, (owner, IVault(linkedVault))) ) ); - console.log( - string.concat( - "SLYXToken proxy deployed at ", - Strings.toHexString(proxy) - ) - ); - console.log( - string.concat( - "Linked to implementation at address ", - Strings.toHexString(sLyxTokenImplementation) - ) - ); + console.log(string.concat("SLYXToken proxy deployed at ", Strings.toHexString(proxy))); + console.log(string.concat("Linked to implementation at address ", Strings.toHexString(sLyxTokenImplementation))); } } @@ -81,9 +58,7 @@ contract UpgradeSLYXToken is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address newSLYXTokenImplementation = vm.envAddress( - "NEW_SLYX_TOKEN_IMPLEMENTATION_ADDRESS" - ); + address newSLYXTokenImplementation = vm.envAddress("NEW_SLYX_TOKEN_IMPLEMENTATION_ADDRESS"); vm.startBroadcast(deployerPrivateKey); IProxy(SLYX_TOKEN_PROXY_TESTNET).upgradeTo( @@ -94,8 +69,7 @@ contract UpgradeSLYXToken is Script { console.log( string.concat( - "SLYXToken proxy upgraded to implementation at ", - Strings.toHexString(newSLYXTokenImplementation) + "SLYXToken proxy upgraded to implementation at ", Strings.toHexString(newSLYXTokenImplementation) ) ); } @@ -119,11 +93,6 @@ contract ChangeAdmin is Script { vm.broadcast(signerPrivateKey); IProxy(SLYX_TOKEN_PROXY_TESTNET).changeAdmin(newProxyAdmin); - console.log( - string.concat( - "SLYXToken proxy admin changed to ", - Strings.toHexString(newProxyAdmin) - ) - ); + console.log(string.concat("SLYXToken proxy admin changed to ", Strings.toHexString(newProxyAdmin))); } } diff --git a/script/StakingverseVaultScriptMainnet.s.sol b/script/StakingverseVaultScriptMainnet.s.sol index 250c3e8..884635b 100644 --- a/script/StakingverseVaultScriptMainnet.s.sol +++ b/script/StakingverseVaultScriptMainnet.s.sol @@ -22,12 +22,7 @@ contract DeployVaultImplementation is Script { StakingverseVault vault = new StakingverseVault(); vm.stopBroadcast(); - console.log( - string.concat( - "StakingverseVault implementation deployed at ", - Strings.toHexString(address(vault)) - ) - ); + console.log(string.concat("StakingverseVault implementation deployed at ", Strings.toHexString(address(vault)))); } } @@ -35,9 +30,7 @@ contract DeployVaultProxy is Script { function run() external { // Proxy deployment parameters for the vault address admin = vm.envAddress("VAULT_PROXY_ADMIN_ADDRESS"); - address vaultImplementation = vm.envAddress( - "VAULT_IMPLEMENTATION_ADDRESS" - ); + address vaultImplementation = vm.envAddress("VAULT_IMPLEMENTATION_ADDRESS"); // Parameters to initialize the vault address owner = vm.envAddress("VAULT_OWNER_ADDRESS"); @@ -51,26 +44,13 @@ contract DeployVaultProxy is Script { // proxy admin admin, // `initialize(address,address,IDepositContract)` calldata - abi.encodeCall( - StakingverseVault.initialize, - (owner, operator, DepositContract) - ) + abi.encodeCall(StakingverseVault.initialize, (owner, operator, DepositContract)) ) ); vm.stopBroadcast(); - console.log( - string.concat( - "StakingverseVault proxy deployed at ", - Strings.toHexString(proxy) - ) - ); - console.log( - string.concat( - "Linked to implementation at address ", - Strings.toHexString(vaultImplementation) - ) - ); + console.log(string.concat("StakingverseVault proxy deployed at ", Strings.toHexString(proxy))); + console.log(string.concat("Linked to implementation at address ", Strings.toHexString(vaultImplementation))); } } @@ -82,20 +62,13 @@ contract ChangeAdmin is Script { IProxy(PROXY_ADMIN_MAINNET).changeAdmin(newProxyAdmin); vm.stopBroadcast(); - console.log( - string.concat( - "Vault proxy admin changed to ", - Strings.toHexString(newProxyAdmin) - ) - ); + console.log(string.concat("Vault proxy admin changed to ", Strings.toHexString(newProxyAdmin))); } } contract UpgradeVault is Script { function run() external { - address newVaultImplementationAddress = vm.envAddress( - "NEW_VAULT_IMPLEMENTATION_ADDRESS" - ); + address newVaultImplementationAddress = vm.envAddress("NEW_VAULT_IMPLEMENTATION_ADDRESS"); vm.startBroadcast(); IProxy(VAULT_PROXY_MAINNET).upgradeTo(newVaultImplementationAddress); diff --git a/script/StakingverseVaultScriptTestnet.s.sol b/script/StakingverseVaultScriptTestnet.s.sol index cd82995..547dfe2 100644 --- a/script/StakingverseVaultScriptTestnet.s.sol +++ b/script/StakingverseVaultScriptTestnet.s.sol @@ -23,12 +23,7 @@ contract DeployVaultImplementation is Script { vm.broadcast(deployerPrivateKey); StakingverseVault vault = new StakingverseVault(); - console.log( - string.concat( - "StakingverseVault implementation deployed at ", - Strings.toHexString(address(vault)) - ) - ); + console.log(string.concat("StakingverseVault implementation deployed at ", Strings.toHexString(address(vault)))); } } @@ -38,9 +33,7 @@ contract DeployVaultProxy is Script { // Proxy deployment parameters for the vault address proxyAdmin = vm.envAddress("VAULT_PROXY_ADMIN_ADDRESS"); - address vaultImplementation = vm.envAddress( - "VAULT_IMPLEMENTATION_ADDRESS" - ); + address vaultImplementation = vm.envAddress("VAULT_IMPLEMENTATION_ADDRESS"); // Parameters to initialize the vault address owner = vm.envAddress("VAULT_OWNER_ADDRESS"); @@ -54,25 +47,12 @@ contract DeployVaultProxy is Script { // proxy admin proxyAdmin, // `initialize(address,address,IDepositContract)` calldata - abi.encodeCall( - StakingverseVault.initialize, - (owner, operator, DepositContract) - ) + abi.encodeCall(StakingverseVault.initialize, (owner, operator, DepositContract)) ) ); - console.log( - string.concat( - "StakingverseVault proxy deployed at ", - Strings.toHexString(proxy) - ) - ); - console.log( - string.concat( - "Linked to implementation at address ", - Strings.toHexString(vaultImplementation) - ) - ); + console.log(string.concat("StakingverseVault proxy deployed at ", Strings.toHexString(proxy))); + console.log(string.concat("Linked to implementation at address ", Strings.toHexString(vaultImplementation))); } } @@ -94,12 +74,7 @@ contract ChangeAdmin is Script { vm.broadcast(signerAddress); IProxy(VAULT_PROXY_TESTNET).changeAdmin(newProxyAdmin); - console.log( - string.concat( - "Vault proxy admin changed to ", - Strings.toHexString(newProxyAdmin) - ) - ); + console.log(string.concat("Vault proxy admin changed to ", Strings.toHexString(newProxyAdmin))); } } @@ -116,9 +91,7 @@ contract UpgradeVault is Script { ) ); - address newVaultImplementationAddress = vm.envAddress( - "NEW_VAULT_IMPLEMENTATION_ADDRESS" - ); + address newVaultImplementationAddress = vm.envAddress("NEW_VAULT_IMPLEMENTATION_ADDRESS"); vm.broadcast(signerPrivateKey); IProxy(VAULT_PROXY_TESTNET).upgradeTo(newVaultImplementationAddress); diff --git a/src/Vault.sol b/src/Vault.sol index d22e2ca..28dafe4 100644 --- a/src/Vault.sol +++ b/src/Vault.sol @@ -1,23 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity =0.8.22; -import { - OwnableUnset -} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; -import { - ReentrancyGuardUpgradeable -} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import { - PausableUpgradeable -} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import {OwnableUnset} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; +import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {IDepositContract, DEPOSIT_AMOUNT} from "../../src/IDepositContract.sol"; -contract Vault is - OwnableUnset, - ReentrancyGuardUpgradeable, - PausableUpgradeable -{ +contract Vault is OwnableUnset, ReentrancyGuardUpgradeable, PausableUpgradeable { uint32 private constant _FEE_BASIS = 100_000; uint32 private constant _MIN_FEE = 0; // 0% uint32 private constant _MAX_FEE = 15_000; // 15% @@ -25,11 +15,7 @@ contract Vault is uint256 private constant _MINIMUM_REQUIRED_SHARES = 1e3; error InvalidAmount(uint256 amount); - error WithdrawalFailed( - address account, - address beneficiary, - uint256 amount - ); + error WithdrawalFailed(address account, address beneficiary, uint256 amount); error ClaimFailed(address account, address beneficiary, uint256 amount); error DepositLimitExceeded(uint256 totalValue, uint256 depositLimit); error CallerNotOracle(address account); @@ -40,44 +26,18 @@ contract Vault is error ValidatorAlreadyRegistered(bytes pubkey); error CallerNotOperator(address account); - event Deposited( - address indexed account, - address indexed beneficiary, - uint256 amount - ); - event Withdrawn( - address indexed account, - address indexed beneficiary, - uint256 amount - ); - event WithdrawalRequested( - address indexed account, - address indexed beneficiary, - uint256 amount - ); - event Claimed( - address indexed account, - address indexed beneficiary, - uint256 amount - ); + event Deposited(address indexed account, address indexed beneficiary, uint256 amount); + event Withdrawn(address indexed account, address indexed beneficiary, uint256 amount); + event WithdrawalRequested(address indexed account, address indexed beneficiary, uint256 amount); + event Claimed(address indexed account, address indexed beneficiary, uint256 amount); event DepositLimitChanged(uint256 previousLimit, uint256 newLimit); event FeeChanged(uint32 previousFee, uint32 newFee); - event FeeRecipientChanged( - address previousFeeRecipient, - address newFeeRecipient - ); - event FeeClaimed( - address indexed account, - address indexed beneficiary, - uint256 amount - ); + event FeeRecipientChanged(address previousFeeRecipient, address newFeeRecipient); + event FeeClaimed(address indexed account, address indexed beneficiary, uint256 amount); event RewardsDistributed(uint256 balance, uint256 rewards, uint256 fee); event OracleEnabled(address indexed oracle, bool enabled); event Rebalanced( - uint256 previousTotalStaked, - uint256 previousTotalUnstaked, - uint256 totalStaked, - uint256 totalUnstaked + uint256 previousTotalStaked, uint256 previousTotalUnstaked, uint256 totalStaked, uint256 totalUnstaked ); // limit of total deposits in wei. @@ -126,11 +86,7 @@ contract Vault is _disableInitializers(); } - function initialize( - address owner_, - address operator_, - IDepositContract depositContract_ - ) external initializer { + function initialize(address owner_, address operator_, IDepositContract depositContract_) external initializer { if (address(depositContract_) == address(0)) { revert InvalidAddress(address(depositContract_)); } @@ -193,8 +149,8 @@ contract Vault is function setDepositLimit(uint256 newDepositLimit) external onlyOperator { if ( - newDepositLimit < totalValidatorsRegistered * DEPOSIT_AMOUNT || - newDepositLimit > _MAX_VALIDATORS_SUPPORTED * DEPOSIT_AMOUNT + newDepositLimit < totalValidatorsRegistered * DEPOSIT_AMOUNT + || newDepositLimit > _MAX_VALIDATORS_SUPPORTED * DEPOSIT_AMOUNT ) { revert InvalidAmount(newDepositLimit); } @@ -243,20 +199,12 @@ contract Vault is return _pendingWithdrawals[account]; } - function claimableBalanceOf( - address account - ) external view returns (uint256) { + function claimableBalanceOf(address account) external view returns (uint256) { uint256 pendingWithdrawal = _pendingWithdrawals[account]; - return - pendingWithdrawal > totalClaimable - ? totalClaimable - : pendingWithdrawal; + return pendingWithdrawal > totalClaimable ? totalClaimable : pendingWithdrawal; } - function claim( - uint256 amount, - address beneficiary - ) external nonReentrant whenNotPaused { + function claim(uint256 amount, address beneficiary) external nonReentrant whenNotPaused { if (beneficiary == address(0)) { revert InvalidAddress(beneficiary); } @@ -273,7 +221,7 @@ contract Vault is _pendingWithdrawals[account] -= amount; totalPendingWithdrawal -= amount; totalClaimable -= amount; - (bool success, ) = beneficiary.call{value: amount}(""); + (bool success,) = beneficiary.call{value: amount}(""); if (!success) { revert ClaimFailed(account, beneficiary, amount); } @@ -281,11 +229,7 @@ contract Vault is } function totalAssets() public view returns (uint256) { - return - totalStaked + - totalUnstaked + - totalClaimable - - totalPendingWithdrawal; + return totalStaked + totalUnstaked + totalClaimable - totalPendingWithdrawal; } function _toBalance(uint256 shares) private view returns (uint256) { @@ -316,10 +260,8 @@ contract Vault is if (amount == 0) { revert InvalidAmount(amount); } - uint256 newTotalDeposits = Math.max( - totalValidatorsRegistered * DEPOSIT_AMOUNT, - totalStaked + totalUnstaked - ) + amount; + uint256 newTotalDeposits = + Math.max(totalValidatorsRegistered * DEPOSIT_AMOUNT, totalStaked + totalUnstaked) + amount; if (newTotalDeposits > depositLimit) { revert DepositLimitExceeded(newTotalDeposits, depositLimit); } @@ -342,10 +284,7 @@ contract Vault is emit Deposited(account, beneficiary, amount); } - function withdraw( - uint256 amount, - address beneficiary - ) external nonReentrant whenNotPaused { + function withdraw(uint256 amount, address beneficiary) external nonReentrant whenNotPaused { if (beneficiary == address(0)) { revert InvalidAddress(beneficiary); } @@ -366,9 +305,7 @@ contract Vault is _shares[account] -= shares; totalShares -= shares; - uint256 immediateAmount = amount > totalUnstaked - ? totalUnstaked - : amount; + uint256 immediateAmount = amount > totalUnstaked ? totalUnstaked : amount; uint256 delayedAmount = amount - immediateAmount; totalUnstaked -= immediateAmount; @@ -376,7 +313,7 @@ contract Vault is _pendingWithdrawals[beneficiary] += delayedAmount; if (immediateAmount > 0) { - (bool success, ) = beneficiary.call{value: immediateAmount}(""); + (bool success,) = beneficiary.call{value: immediateAmount}(""); if (!success) { revert WithdrawalFailed(account, beneficiary, immediateAmount); } @@ -388,10 +325,7 @@ contract Vault is } } - function claimFees( - uint256 amount, - address beneficiary - ) external nonReentrant whenNotPaused { + function claimFees(uint256 amount, address beneficiary) external nonReentrant whenNotPaused { if (beneficiary == address(0)) { revert InvalidAddress(beneficiary); } @@ -406,7 +340,7 @@ contract Vault is revert InsufficientBalance(totalFees, amount); } totalFees -= amount; - (bool success, ) = beneficiary.call{value: amount}(""); + (bool success,) = beneficiary.call{value: amount}(""); if (!success) { revert FeeClaimFailed(account, beneficiary, amount); } @@ -429,21 +363,16 @@ contract Vault is // account for completed withdrawals uint256 pendingWithdrawal = totalPendingWithdrawal - totalClaimable; uint256 completedWithdrawal = Math.min( - (balance - totalFees - totalUnstaked - totalClaimable) / - DEPOSIT_AMOUNT, // actual completed withdrawals - pendingWithdrawal / - DEPOSIT_AMOUNT + // pending withdrawals - (pendingWithdrawal % DEPOSIT_AMOUNT == 0 ? 0 : 1) // partial withdrawals + (balance - totalFees - totalUnstaked - totalClaimable) / DEPOSIT_AMOUNT, // actual completed withdrawals + pendingWithdrawal / DEPOSIT_AMOUNT // pending withdrawals + + (pendingWithdrawal % DEPOSIT_AMOUNT == 0 ? 0 : 1) // partial withdrawals ) * DEPOSIT_AMOUNT; // adjust staked balance for completed withdrawals uint256 staked = totalStaked - completedWithdrawal; // take out any claimable balances from unstaked balance prior to calculating rewards. - uint256 unstaked = balance - - totalFees - - totalClaimable - - completedWithdrawal; + uint256 unstaked = balance - totalFees - totalClaimable - completedWithdrawal; // account for withdrawals to claim later uint256 claimable = totalClaimable + completedWithdrawal; @@ -462,11 +391,7 @@ contract Vault is if (unstaked - partialWithdrawal > totalUnstaked) { uint256 rewards = unstaked - partialWithdrawal - totalUnstaked; uint256 feeAmount = Math.mulDiv(rewards, fee, _FEE_BASIS); - emit RewardsDistributed( - totalStaked + totalUnstaked, - rewards, - feeAmount - ); + emit RewardsDistributed(totalStaked + totalUnstaked, rewards, feeAmount); totalFees += feeAmount; unstaked -= feeAmount; } @@ -477,17 +402,16 @@ contract Vault is totalStaked = staked; } - function isValidatorRegistered( - bytes calldata pubkey - ) external view returns (bool) { + function isValidatorRegistered(bytes calldata pubkey) external view returns (bool) { return _registeredKeys[pubkey]; } - function registerValidator( - bytes calldata pubkey, - bytes calldata signature, - bytes32 depositDataRoot - ) public onlyOracle nonReentrant whenNotPaused { + function registerValidator(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) + public + onlyOracle + nonReentrant + whenNotPaused + { if (totalUnstaked < DEPOSIT_AMOUNT) { revert InsufficientBalance(totalUnstaked, DEPOSIT_AMOUNT); } @@ -498,16 +422,8 @@ contract Vault is totalValidatorsRegistered += 1; totalStaked += DEPOSIT_AMOUNT; totalUnstaked -= DEPOSIT_AMOUNT; - bytes memory withdrawalCredentials = abi.encodePacked( - hex"010000000000000000000000", - address(this) - ); - _depositContract.deposit{value: DEPOSIT_AMOUNT}( - pubkey, - withdrawalCredentials, - signature, - depositDataRoot - ); + bytes memory withdrawalCredentials = abi.encodePacked(hex"010000000000000000000000", address(this)); + _depositContract.deposit{value: DEPOSIT_AMOUNT}(pubkey, withdrawalCredentials, signature, depositDataRoot); } function registerValidators( diff --git a/test/Deployment.t.sol b/test/Deployment.t.sol index 8d31a73..9b06910 100644 --- a/test/Deployment.t.sol +++ b/test/Deployment.t.sol @@ -2,9 +2,7 @@ pragma solidity ^0.8.13; import {SLYXTokenBaseTest} from "./base/SLYXTokenBaseTest.t.sol"; -import { - _INTERFACEID_LSP7 -} from "@lukso/lsp7-contracts/contracts/LSP7Constants.sol"; +import {_INTERFACEID_LSP7} from "@lukso/lsp7-contracts/contracts/LSP7Constants.sol"; import {IVaultStakeRecipient} from "../src/IVaultStakeRecipient.sol"; // Constants @@ -24,20 +22,13 @@ contract Deployment is SLYXTokenBaseTest { function test_deploymentParametersOfSLYXToken() public view { // We need to re-encode in memory, as in the ERC725Y storage, strings are not stored as [string.length][string] - bytes memory encodedName = abi.encode( - sLyxToken.getData(_LSP4_TOKEN_NAME_KEY) - ); + bytes memory encodedName = abi.encode(sLyxToken.getData(_LSP4_TOKEN_NAME_KEY)); string memory name = abi.decode(encodedName, (string)); - bytes memory encodedSymbol = abi.encode( - sLyxToken.getData(_LSP4_TOKEN_SYMBOL_KEY) - ); + bytes memory encodedSymbol = abi.encode(sLyxToken.getData(_LSP4_TOKEN_SYMBOL_KEY)); string memory symbol = abi.decode(encodedSymbol, (string)); - uint256 tokenType = abi.decode( - sLyxToken.getData(_LSP4_TOKEN_TYPE_KEY), - (uint256) - ); + uint256 tokenType = abi.decode(sLyxToken.getData(_LSP4_TOKEN_TYPE_KEY), (uint256)); uint256 decimals = sLyxToken.decimals(); address owner = sLyxToken.owner(); @@ -55,10 +46,7 @@ contract Deployment is SLYXTokenBaseTest { // This test ensures two things. Calling `getExchangeRate()` initially: // 1. will not revert // 2. will return 1:1 ratio - function test_callingGetExhangeRateWhenNoSLYXTokensHaveBeenMintedInitially() - public - view - { + function test_callingGetExhangeRateWhenNoSLYXTokensHaveBeenMintedInitially() public view { assertEq(sLyxToken.getExchangeRate(), 1 ether); assertEq(sLyxToken.getNativeTokenValue(12345 ether), 12345 ether); assertEq(sLyxToken.getSLYXTokenValue(12345 ether), 12345 ether); @@ -78,10 +66,7 @@ contract Deployment is SLYXTokenBaseTest { } function test_supportsCorrectInterfaces() public view { - assertEq( - sLyxToken.supportsInterface(type(IVaultStakeRecipient).interfaceId), - true - ); + assertEq(sLyxToken.supportsInterface(type(IVaultStakeRecipient).interfaceId), true); assertEq(sLyxToken.supportsInterface(_INTERFACEID_LSP7), true); } } diff --git a/test/ForkTest.t.sol b/test/ForkTest.t.sol index 0c22f4c..984f8af 100644 --- a/test/ForkTest.t.sol +++ b/test/ForkTest.t.sol @@ -16,16 +16,10 @@ import { import {SLYXToken} from "../../src/SLYXToken.sol"; // Constants -import { - PROXY_ADMIN_MAINNET, - VAULT_PROXY_MAINNET, - SLYX_TOKEN_PROXY_MAINNET -} from "../script/MainnetConstants.sol"; +import {PROXY_ADMIN_MAINNET, VAULT_PROXY_MAINNET, SLYX_TOKEN_PROXY_MAINNET} from "../script/MainnetConstants.sol"; // Contract addresses deployed on mainnet -address payable constant VAULT_IMPLEMENTATION = payable( - 0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4 -); +address payable constant VAULT_IMPLEMENTATION = payable(0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4); address payable constant VAULT_PROXY = payable(VAULT_PROXY_MAINNET); address payable constant SLYX_PROXY = payable(SLYX_TOKEN_PROXY_MAINNET); @@ -37,8 +31,10 @@ address constant VAULT_ORACLE = 0x6a44823e20CD97250AfA3c73e45aBef4Ff79c51F; // Upcoming SLYX Token Contract owner address constant SLYX_CONTRACT_OWNER = 0x49d32954698344592407C2C1f76c431F0032167c; -bytes constant SLYX_PROXY_BYTECODE = hex"60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f80fd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f80375f80365f845af43d5f803e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016108036027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f80856001600160a01b03168560405161056691906107b5565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107d0565b5f8085851115610676575f80fd5b83861115610682575f80fd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f80fd5b919050565b5f602082840312156106ba575f80fd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156106e8575f80fd5b6106f18361068f565b9150602083013567ffffffffffffffff8082111561070d575f80fd5b818501915085601f830112610720575f80fd5b813581811115610732576107326106c3565b604051601f8201601f19908116603f0116810190838211818310171561075a5761075a6106c3565b81604052828152886020848701011115610772575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f5b838110156107ad578181015183820152602001610795565b50505f910152565b5f82516107c6818460208701610793565b9190910192915050565b602081525f82518060208401526107ee816040850160208701610793565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122015b4231dda86f0402c82dbd8505d83543efd1acf89fcd12f9a9ddb52aab9449364736f6c63430008160033"; -bytes constant VAULT_PROXY_BYTECODE = hex"60806040523661001357610011610017565b005b6100115b61001f610202565b6001600160a01b031633036101f85760606001600160e01b0319600035167fc9a6301a0000000000000000000000000000000000000000000000000000000081016100735761006c610235565b91506101f0565b7fb0e10d7a000000000000000000000000000000000000000000000000000000006001600160e01b03198216016100ac5761006c61028c565b7f70d7c690000000000000000000000000000000000000000000000000000000006001600160e01b03198216016100e55761006c6102d2565b7f07ae5bc0000000000000000000000000000000000000000000000000000000006001600160e01b031982160161011e5761006c610303565b7fa39f25e5000000000000000000000000000000000000000000000000000000006001600160e01b03198216016101575761006c610343565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b815160208301f35b610200610357565b565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b606061023f610367565b600061024e366004818461075b565b81019061025b91906107a1565b905061027881604051806020016040528060008152506000610372565b505060408051602081019091526000815290565b606060008061029e366004818461075b565b8101906102ab91906107eb565b915091506102bb82826001610372565b604051806020016040528060008152509250505090565b60606102dc610367565b60006102eb366004818461075b565b8101906102f891906107a1565b90506102788161039e565b606061030d610367565b6000610317610202565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b606061034d610367565b60006103176103f5565b6102006103626103f5565b610404565b341561020057600080fd5b61037b83610428565b6000825111806103885750805b15610399576103978383610468565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103c7610202565b604080516001600160a01b03928316815291841660208301520160405180910390a16103f281610494565b50565b60006103ff61056c565b905090565b3660008037600080366000845af43d6000803e808015610423573d6000f35b3d6000fd5b61043181610594565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b606061048d838360405180606001604052806027815260200161092160279139610638565b9392505050565b6001600160a01b0381166105105760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016101e7565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610226565b6001600160a01b0381163b6106115760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e74726163740000000000000000000000000000000000000060648201526084016101e7565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610533565b6060600080856001600160a01b03168560405161065591906108d1565b600060405180830381855af49150503d8060008114610690576040519150601f19603f3d011682016040523d82523d6000602084013e610695565b606091505b50915091506106a6868383876106b0565b9695505050505050565b6060831561071f578251600003610718576001600160a01b0385163b6107185760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101e7565b5081610729565b6107298383610731565b949350505050565b8151156107415781518083602001fd5b8060405162461bcd60e51b81526004016101e791906108ed565b6000808585111561076b57600080fd5b8386111561077857600080fd5b5050820193919092039150565b80356001600160a01b038116811461079c57600080fd5b919050565b6000602082840312156107b357600080fd5b61048d82610785565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080604083850312156107fe57600080fd5b61080783610785565b9150602083013567ffffffffffffffff8082111561082457600080fd5b818501915085601f83011261083857600080fd5b81358181111561084a5761084a6107bc565b604051601f8201601f19908116603f01168101908382118183101715610872576108726107bc565b8160405282815288602084870101111561088b57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156108c85781810151838201526020016108b0565b50506000910152565b600082516108e38184602087016108ad565b9190910192915050565b602081526000825180602084015261090c8160408501602087016108ad565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220280d55eaf8059181b8918c073f41b03e81059104244c761de8efef17dc73dad464736f6c63430008160033"; +bytes constant SLYX_PROXY_BYTECODE = + hex"60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f80fd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f80375f80365f845af43d5f803e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016108036027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f80856001600160a01b03168560405161056691906107b5565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107d0565b5f8085851115610676575f80fd5b83861115610682575f80fd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f80fd5b919050565b5f602082840312156106ba575f80fd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156106e8575f80fd5b6106f18361068f565b9150602083013567ffffffffffffffff8082111561070d575f80fd5b818501915085601f830112610720575f80fd5b813581811115610732576107326106c3565b604051601f8201601f19908116603f0116810190838211818310171561075a5761075a6106c3565b81604052828152886020848701011115610772575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f5b838110156107ad578181015183820152602001610795565b50505f910152565b5f82516107c6818460208701610793565b9190910192915050565b602081525f82518060208401526107ee816040850160208701610793565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122015b4231dda86f0402c82dbd8505d83543efd1acf89fcd12f9a9ddb52aab9449364736f6c63430008160033"; +bytes constant VAULT_PROXY_BYTECODE = + hex"60806040523661001357610011610017565b005b6100115b61001f610202565b6001600160a01b031633036101f85760606001600160e01b0319600035167fc9a6301a0000000000000000000000000000000000000000000000000000000081016100735761006c610235565b91506101f0565b7fb0e10d7a000000000000000000000000000000000000000000000000000000006001600160e01b03198216016100ac5761006c61028c565b7f70d7c690000000000000000000000000000000000000000000000000000000006001600160e01b03198216016100e55761006c6102d2565b7f07ae5bc0000000000000000000000000000000000000000000000000000000006001600160e01b031982160161011e5761006c610303565b7fa39f25e5000000000000000000000000000000000000000000000000000000006001600160e01b03198216016101575761006c610343565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b815160208301f35b610200610357565b565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b606061023f610367565b600061024e366004818461075b565b81019061025b91906107a1565b905061027881604051806020016040528060008152506000610372565b505060408051602081019091526000815290565b606060008061029e366004818461075b565b8101906102ab91906107eb565b915091506102bb82826001610372565b604051806020016040528060008152509250505090565b60606102dc610367565b60006102eb366004818461075b565b8101906102f891906107a1565b90506102788161039e565b606061030d610367565b6000610317610202565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b606061034d610367565b60006103176103f5565b6102006103626103f5565b610404565b341561020057600080fd5b61037b83610428565b6000825111806103885750805b15610399576103978383610468565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103c7610202565b604080516001600160a01b03928316815291841660208301520160405180910390a16103f281610494565b50565b60006103ff61056c565b905090565b3660008037600080366000845af43d6000803e808015610423573d6000f35b3d6000fd5b61043181610594565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b606061048d838360405180606001604052806027815260200161092160279139610638565b9392505050565b6001600160a01b0381166105105760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016101e7565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610226565b6001600160a01b0381163b6106115760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e74726163740000000000000000000000000000000000000060648201526084016101e7565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610533565b6060600080856001600160a01b03168560405161065591906108d1565b600060405180830381855af49150503d8060008114610690576040519150601f19603f3d011682016040523d82523d6000602084013e610695565b606091505b50915091506106a6868383876106b0565b9695505050505050565b6060831561071f578251600003610718576001600160a01b0385163b6107185760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101e7565b5081610729565b6107298383610731565b949350505050565b8151156107415781518083602001fd5b8060405162461bcd60e51b81526004016101e791906108ed565b6000808585111561076b57600080fd5b8386111561077857600080fd5b5050820193919092039150565b80356001600160a01b038116811461079c57600080fd5b919050565b6000602082840312156107b357600080fd5b61048d82610785565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080604083850312156107fe57600080fd5b61080783610785565b9150602083013567ffffffffffffffff8082111561082457600080fd5b818501915085601f83011261083857600080fd5b81358181111561084a5761084a6107bc565b604051601f8201601f19908116603f01168101908382118183101715610872576108726107bc565b8160405282815288602084870101111561088b57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156108c85781810151838201526020016108b0565b50506000910152565b600082516108e38184602087016108ad565b9190910192915050565b602081526000825180602084015261090c8160408501602087016108ad565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220280d55eaf8059181b8918c073f41b03e81059104244c761de8efef17dc73dad464736f6c63430008160033"; contract ForkTest is Test { /// @dev There is a rounding error of 1 Wei accepted as loss. @@ -58,10 +54,7 @@ contract ForkTest is Test { uint256 initialDepositLimit; function setUp() public { - console.log( - unicode"๐Ÿ›ž Setup Fork testing. Block number: ", - block.number - ); + console.log(unicode"๐Ÿ›ž Setup Fork testing. Block number: ", block.number); vault = StakingverseVault(VAULT_PROXY); @@ -92,24 +85,15 @@ contract ForkTest is Test { // CHECK the bytecode of the implementation is not the same as the bytecode // of the new Vault we are upgrading to. - assertNotEq( - address(newVaultImplementation).code, - VAULT_IMPLEMENTATION.code - ); + assertNotEq(address(newVaultImplementation).code, VAULT_IMPLEMENTATION.code); // upgrade vault - bytes memory upgradeToCalldata = abi.encodeCall( - IProxy.upgradeTo, - address(newVaultImplementation) - ); + bytes memory upgradeToCalldata = abi.encodeCall(IProxy.upgradeTo, address(newVaultImplementation)); vm.prank(VAULT_AND_SLYX_PROXY_ADMIN); - (bool success, ) = address(vault).call(upgradeToCalldata); + (bool success,) = address(vault).call(upgradeToCalldata); assertTrue(success, "upgrade failed"); vm.prank(VAULT_AND_SLYX_PROXY_ADMIN); - assertEq( - IProxy(VAULT_PROXY).implementation(), - address(newVaultImplementation) - ); + assertEq(IProxy(VAULT_PROXY).implementation(), address(newVaultImplementation)); // allow the transaction to be validated vm.roll(block.number + 100); @@ -127,22 +111,13 @@ contract ForkTest is Test { // + upgrade the SLYX Proxy contract to the new implementation sLyxTokenImplementation = new SLYXToken(); - bytes memory slyxInitializeCalldata = abi.encodeCall( - SLYXToken.initialize, - (SLYX_CONTRACT_OWNER, vault) - ); + bytes memory slyxInitializeCalldata = abi.encodeCall(SLYXToken.initialize, (SLYX_CONTRACT_OWNER, vault)); vm.startPrank(VAULT_AND_SLYX_PROXY_ADMIN); - IProxy(SLYX_PROXY).upgradeToAndCall( - address(sLyxTokenImplementation), - slyxInitializeCalldata - ); + IProxy(SLYX_PROXY).upgradeToAndCall(address(sLyxTokenImplementation), slyxInitializeCalldata); // CHECK implementation address has been upgraded successfully - assertEq( - IProxy(SLYX_PROXY).implementation(), - address(sLyxTokenImplementation) - ); + assertEq(IProxy(SLYX_PROXY).implementation(), address(sLyxTokenImplementation)); // CHECK admin remains the same after upgrade assertEq(IProxy(SLYX_PROXY).admin(), VAULT_AND_SLYX_PROXY_ADMIN); @@ -150,10 +125,7 @@ contract ForkTest is Test { vm.roll(block.number + 1000); - console.log( - unicode"๐Ÿงช Starting Fork testing. Block number: ", - block.number - ); + console.log(unicode"๐Ÿงช Starting Fork testing. Block number: ", block.number); } function test_vaultStorageRemainSameAfterUpgrade() public view { @@ -195,10 +167,7 @@ contract ForkTest is Test { assertEq(sLyxToken.totalSupply(), 0); } - function test_ExchangeRateForSLYXToLyxStartAt1To1SinceNoSLYXGotMinted() - public - view - { + function test_ExchangeRateForSLYXToLyxStartAt1To1SinceNoSLYXGotMinted() public view { assertEq(sLyxToken.getExchangeRate(), 1 ether); assertEq(sLyxToken.getNativeTokenValue(12345 ether), 12345 ether); assertEq(sLyxToken.getSLYXTokenValue(12345 ether), 12345 ether); @@ -228,18 +197,9 @@ contract ForkTest is Test { assertEq(vault.balanceOf(alice), 1 wei); // All test have rounding errors remaining - assertEq( - vault.balanceOf(address(sLyxToken)), - aliceStake - VAULT_ROUNDING_ERROR_LOSS - ); - assertEq( - sLyxToken.totalSupply(), - aliceShares - VAULT_ROUNDING_ERROR_LOSS - ); - assertEq( - sLyxToken.balanceOf(alice), - aliceShares - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(address(sLyxToken)), aliceStake - VAULT_ROUNDING_ERROR_LOSS); + assertEq(sLyxToken.totalSupply(), aliceShares - VAULT_ROUNDING_ERROR_LOSS); + assertEq(sLyxToken.balanceOf(alice), aliceShares - VAULT_ROUNDING_ERROR_LOSS); vm.stopPrank(); } @@ -258,10 +218,7 @@ contract ForkTest is Test { uint256 sLyxTokenShares = vault.sharesOf(address(sLyxToken)); assertEq(vault.balanceOf(alice), 0); - assertEq( - vault.balanceOf(address(sLyxToken)), - depositAmount - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(address(sLyxToken)), depositAmount - VAULT_ROUNDING_ERROR_LOSS); assertEq(sLyxToken.totalSupply(), sLyxTokenShares); assertEq(sLyxToken.balanceOf(alice), sLyxTokenShares); @@ -291,18 +248,9 @@ contract ForkTest is Test { vault.transferStake(address(sLyxToken), bobStake, ""); assertEq(vault.balanceOf(bob), 1); - assertEq( - vault.balanceOf(address(sLyxToken)), - bobStake - VAULT_ROUNDING_ERROR_LOSS - ); - assertEq( - sLyxToken.totalSupply(), - bobShares - VAULT_ROUNDING_ERROR_LOSS - ); - assertEq( - sLyxToken.balanceOf(bob), - bobShares - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(address(sLyxToken)), bobStake - VAULT_ROUNDING_ERROR_LOSS); + assertEq(sLyxToken.totalSupply(), bobShares - VAULT_ROUNDING_ERROR_LOSS); + assertEq(sLyxToken.balanceOf(bob), bobShares - VAULT_ROUNDING_ERROR_LOSS); vm.stopPrank(); } @@ -320,28 +268,16 @@ contract ForkTest is Test { vm.prank(largeStaker); vault.transferStake(address(sLyxToken), amountToConvertAsSLYX, ""); - assertEq( - vault.balanceOf(largeStaker), - largeStakerStake - amountToConvertAsSLYX + VAULT_ROUNDING_ERROR_LOSS - ); - assertEq( - vault.balanceOf(address(sLyxToken)), - amountToConvertAsSLYX - VAULT_ROUNDING_ERROR_LOSS - ); - assertEq( - vault.sharesOf(largeStaker), - largeStakerShares / 2 + VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(largeStaker), largeStakerStake - amountToConvertAsSLYX + VAULT_ROUNDING_ERROR_LOSS); + assertEq(vault.balanceOf(address(sLyxToken)), amountToConvertAsSLYX - VAULT_ROUNDING_ERROR_LOSS); + assertEq(vault.sharesOf(largeStaker), largeStakerShares / 2 + VAULT_ROUNDING_ERROR_LOSS); // broad check to ensure SLYX is minted as shares (shares MUST always be lower than LYX staked balances) assertLt(sLyxToken.totalSupply(), amountToConvertAsSLYX); assertLt(sLyxToken.balanceOf(largeStaker), amountToConvertAsSLYX); // Since user converted half of its stake to sLYX, its SLYX balance is half of its shares - assertEq( - sLyxToken.balanceOf(largeStaker), - vault.sharesOf(largeStaker) - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(sLyxToken.balanceOf(largeStaker), vault.sharesOf(largeStaker) - VAULT_ROUNDING_ERROR_LOSS); } function test_ExistingStakeConvertAllItsStakeToSLYX() public { @@ -353,24 +289,13 @@ contract ForkTest is Test { vault.transferStake(address(sLyxToken), userStake, ""); assertEq(vault.balanceOf(existingUser), VAULT_ROUNDING_ERROR_LOSS); - assertEq( - vault.balanceOf(address(sLyxToken)), - userStake - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(address(sLyxToken)), userStake - VAULT_ROUNDING_ERROR_LOSS); - assertEq( - sLyxToken.totalSupply(), - userShares - VAULT_ROUNDING_ERROR_LOSS - ); - assertEq( - sLyxToken.balanceOf(existingUser), - userShares - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(sLyxToken.totalSupply(), userShares - VAULT_ROUNDING_ERROR_LOSS); + assertEq(sLyxToken.balanceOf(existingUser), userShares - VAULT_ROUNDING_ERROR_LOSS); } - function test_ExchangeRateForSLYXToLyxStartIncreasesAfterMintingSLYX() - public - { + function test_ExchangeRateForSLYXToLyxStartIncreasesAfterMintingSLYX() public { uint256 initialSLyxToLyxExchangeRate = sLyxToken.getExchangeRate(); assertEq(initialSLyxToLyxExchangeRate, 1 ether); @@ -393,18 +318,9 @@ contract ForkTest is Test { uint256 newSLyxToLyxExchangeRate = sLyxToken.getExchangeRate(); assertGt(newSLyxToLyxExchangeRate, initialSLyxToLyxExchangeRate); - assertEq( - vault.balanceOf(address(sLyxToken)), - aliceStake - VAULT_ROUNDING_ERROR_LOSS - ); - assertEq( - sLyxToken.totalSupply(), - aliceShares - VAULT_ROUNDING_ERROR_LOSS - ); - assertEq( - sLyxToken.balanceOf(alice), - aliceShares - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(address(sLyxToken)), aliceStake - VAULT_ROUNDING_ERROR_LOSS); + assertEq(sLyxToken.totalSupply(), aliceShares - VAULT_ROUNDING_ERROR_LOSS); + assertEq(sLyxToken.balanceOf(alice), aliceShares - VAULT_ROUNDING_ERROR_LOSS); // Check the exchange rate is not 1:1 from the start (since the Vault already contains rewards) assertGt(sLyxToken.getExchangeRate(), 1 ether); @@ -415,10 +331,7 @@ contract ForkTest is Test { assertEq(sLyxToken.totalSupply(), 0); assertEq(sLyxToken.balanceOf(alice), 0); assertEq(vault.balanceOf(address(sLyxToken)), 1); - assertEq( - vault.balanceOf(alice), - aliceStake - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(alice), aliceStake - VAULT_ROUNDING_ERROR_LOSS); vm.stopPrank(); } @@ -432,17 +345,11 @@ contract ForkTest is Test { vault.transferStake(address(sLyxToken), userStake, ""); assertEq(vault.balanceOf(existingUser), VAULT_ROUNDING_ERROR_LOSS); - assertEq( - vault.balanceOf(address(sLyxToken)), - userStake - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(address(sLyxToken)), userStake - VAULT_ROUNDING_ERROR_LOSS); uint256 userSLYXBalance = sLyxToken.balanceOf(existingUser); - assertEq( - sLyxToken.totalSupply(), - userShares - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(sLyxToken.totalSupply(), userShares - VAULT_ROUNDING_ERROR_LOSS); assertEq(userSLYXBalance, userShares - VAULT_ROUNDING_ERROR_LOSS); // transfer some SLYX to another user @@ -454,10 +361,7 @@ contract ForkTest is Test { vm.prank(existingUser); sLyxToken.transfer(existingUser, alice, amountToTransfer, true, ""); - assertEq( - sLyxToken.balanceOf(existingUser), - userSLYXBalance - amountToTransfer - ); + assertEq(sLyxToken.balanceOf(existingUser), userSLYXBalance - amountToTransfer); assertEq(sLyxToken.balanceOf(alice), amountToTransfer); // Alice burns the SLYX to convert them to stake @@ -465,17 +369,13 @@ contract ForkTest is Test { sLyxToken.burn(alice, amountToTransfer, ""); assertEq(sLyxToken.balanceOf(alice), 0); - uint256 expectedLYXStake = sLyxToken.getNativeTokenValue( - amountToTransfer - ); + uint256 expectedLYXStake = sLyxToken.getNativeTokenValue(amountToTransfer); // not clear why there are 18 wei difference assertEq(vault.balanceOf(alice), expectedLYXStake - 18 wei); } - function test_UserReceivesSLYXAndBurnThemToConvertToStakeAfterAccumulatingRewards() - public - { + function test_UserReceivesSLYXAndBurnThemToConvertToStakeAfterAccumulatingRewards() public { address existingUser = 0xC99Fe517d665c23035B050e13188eb4d7075C91d; uint256 userStake = vault.balanceOf(existingUser); uint256 userShares = vault.sharesOf(existingUser); @@ -484,17 +384,11 @@ contract ForkTest is Test { vault.transferStake(address(sLyxToken), userStake, ""); assertEq(vault.balanceOf(existingUser), VAULT_ROUNDING_ERROR_LOSS); - assertEq( - vault.balanceOf(address(sLyxToken)), - userStake - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(address(sLyxToken)), userStake - VAULT_ROUNDING_ERROR_LOSS); uint256 userSLYXBalance = sLyxToken.balanceOf(existingUser); - assertEq( - sLyxToken.totalSupply(), - userShares - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(sLyxToken.totalSupply(), userShares - VAULT_ROUNDING_ERROR_LOSS); assertEq(userSLYXBalance, userShares - VAULT_ROUNDING_ERROR_LOSS); // transfer some SLYX to another user @@ -506,16 +400,11 @@ contract ForkTest is Test { vm.prank(existingUser); sLyxToken.transfer(existingUser, alice, sLyxAmountToTransfer, true, ""); - assertEq( - sLyxToken.balanceOf(existingUser), - userSLYXBalance - sLyxAmountToTransfer - ); + assertEq(sLyxToken.balanceOf(existingUser), userSLYXBalance - sLyxAmountToTransfer); assertEq(sLyxToken.balanceOf(alice), sLyxAmountToTransfer); uint256 exchangeRateBefore = sLyxToken.getExchangeRate(); - uint256 sLyxToLYXValueBefore = sLyxToken.getNativeTokenValue( - sLyxAmountToTransfer - ); + uint256 sLyxToLYXValueBefore = sLyxToken.getNativeTokenValue(sLyxAmountToTransfer); vm.roll(block.number + 1000); @@ -530,9 +419,7 @@ contract ForkTest is Test { vm.roll(block.number + 1000); uint256 exchangeRateAfter = sLyxToken.getExchangeRate(); - uint256 sLyxToLYXValueAfter = sLyxToken.getNativeTokenValue( - sLyxAmountToTransfer - ); + uint256 sLyxToLYXValueAfter = sLyxToken.getNativeTokenValue(sLyxAmountToTransfer); // CHECK exchange rate increased assertGt(exchangeRateAfter, exchangeRateBefore); @@ -545,10 +432,7 @@ contract ForkTest is Test { // CHECK stake after burning increased assertEq(sLyxToken.balanceOf(alice), 0); - assertEq( - vault.balanceOf(alice), - sLyxToLYXValueAfter - VAULT_ROUNDING_ERROR_LOSS - ); + assertEq(vault.balanceOf(alice), sLyxToLYXValueAfter - VAULT_ROUNDING_ERROR_LOSS); vm.stopPrank(); } diff --git a/test/OZUpgradeTest.t.sol b/test/OZUpgradeTest.t.sol index 97fc75c..f97d8e5 100644 --- a/test/OZUpgradeTest.t.sol +++ b/test/OZUpgradeTest.t.sol @@ -2,10 +2,7 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; -import { - Upgrades, - Options -} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol"; +import {Upgrades, Options} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol"; import {IDepositContract} from "../src/IDepositContract.sol"; // Contracts to test @@ -18,33 +15,23 @@ import {Vault as VaultV1} from "../src/Vault.sol"; import {StakingverseVault} from "../src/StakingverseVault.sol"; // Constants -import { - PROXY_ADMIN_MAINNET, - VAULT_PROXY_MAINNET -} from "../script/MainnetConstants.sol"; +import {PROXY_ADMIN_MAINNET, VAULT_PROXY_MAINNET} from "../script/MainnetConstants.sol"; /// @dev Test to ensure that the upgrade is safe and the new implementation contract is valid, using OpenZeppelin Foundry upgrades library /// This ensures that the new implementation contract is safe to upgrade to and that the storage layout is compatible with the proxy linked to the current implementation contract MainnetForkUpgradeTest is Test { - address constant CURRENT_VAULT_IMPLEMENTATION = - 0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4; + address constant CURRENT_VAULT_IMPLEMENTATION = 0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4; function test_shouldReturnCorrectProxyAdminAddress() public view { // CHECK current proxy admin address before upgrading - assertEq( - Upgrades.getAdminAddress(VAULT_PROXY_MAINNET), - PROXY_ADMIN_MAINNET - ); + assertEq(Upgrades.getAdminAddress(VAULT_PROXY_MAINNET), PROXY_ADMIN_MAINNET); // TODO: upgrade and check the proxy admin address has NOT changed } function test_shouldReturnCorrectVaultImplementationAddress() public view { // CHECK current implementation address before upgrading - assertEq( - Upgrades.getImplementationAddress(VAULT_PROXY_MAINNET), - CURRENT_VAULT_IMPLEMENTATION - ); + assertEq(Upgrades.getImplementationAddress(VAULT_PROXY_MAINNET), CURRENT_VAULT_IMPLEMENTATION); // TODO: upgrade and check the vault implementation address has changed } @@ -88,17 +75,9 @@ contract MainnetForkUpgradeTest is Test { opts.referenceContract = "Vault.sol"; opts.unsafeAllow = "constructor"; - address newStakingverseVaultAddress = Upgrades.prepareUpgrade( - "StakingverseVault.sol", - opts - ); - console.log( - unicode"๐Ÿงช Test deploying new StakingverseVault implementation!" - ); - console.log( - unicode"๐Ÿš€ New StakingverseVault deployed at address: %s", - newStakingverseVaultAddress - ); + address newStakingverseVaultAddress = Upgrades.prepareUpgrade("StakingverseVault.sol", opts); + console.log(unicode"๐Ÿงช Test deploying new StakingverseVault implementation!"); + console.log(unicode"๐Ÿš€ New StakingverseVault deployed at address: %s", newStakingverseVaultAddress); } function test_tryDeployingNewImplementationAndUpgrade() public { @@ -116,22 +95,14 @@ contract MainnetForkUpgradeTest is Test { // TODO: open an issue with OpenZeppelin Foundry to add a check for this opts.unsafeAllow = "constructor"; - Upgrades.upgradeProxy( - VAULT_PROXY_MAINNET, - "StakingverseVault.sol", - "", - opts, - PROXY_ADMIN_MAINNET - ); + Upgrades.upgradeProxy(VAULT_PROXY_MAINNET, "StakingverseVault.sol", "", opts, PROXY_ADMIN_MAINNET); } } /// @dev Test with local anvil chain contract LocalUpgradeTest is Test { - address constant CURRENT_VAULT_OWNER = - 0x983aBC616f2442bAB7a917E6bb8660Df8b01F3bF; - address constant CURRENT_VAULT_OPERATOR = - 0x983aBC616f2442bAB7a917E6bb8660Df8b01F3bF; + address constant CURRENT_VAULT_OWNER = 0x983aBC616f2442bAB7a917E6bb8660Df8b01F3bF; + address constant CURRENT_VAULT_OPERATOR = 0x983aBC616f2442bAB7a917E6bb8660Df8b01F3bF; address proxyAdmin; @@ -149,20 +120,12 @@ contract LocalUpgradeTest is Test { // 2. Deploy the proxy and initialize it bytes memory initializeCalldata = abi.encodeCall( currentVaultImplementation.initialize, - ( - CURRENT_VAULT_OWNER, - CURRENT_VAULT_OPERATOR, - IDepositContract(0xCAfe00000000000000000000000000000000CAfe) - ) + (CURRENT_VAULT_OWNER, CURRENT_VAULT_OPERATOR, IDepositContract(0xCAfe00000000000000000000000000000000CAfe)) ); vaultProxy = IProxy( address( - new TransparentUpgradeableProxy( - address(currentVaultImplementation), - proxyAdmin, - initializeCalldata - ) + new TransparentUpgradeableProxy(address(currentVaultImplementation), proxyAdmin, initializeCalldata) ) ); } @@ -180,12 +143,6 @@ contract LocalUpgradeTest is Test { // but also an actual `initialize(...)` function that is called by the proxy. opts.unsafeAllow = "constructor"; - Upgrades.upgradeProxy( - VAULT_PROXY_MAINNET, - "StakingverseVault.sol", - "", - opts, - PROXY_ADMIN_MAINNET - ); + Upgrades.upgradeProxy(VAULT_PROXY_MAINNET, "StakingverseVault.sol", "", opts, PROXY_ADMIN_MAINNET); } } diff --git a/test/base/SLYXTokenBaseTest.t.sol b/test/base/SLYXTokenBaseTest.t.sol index 9a2a7b7..4bd4e44 100644 --- a/test/base/SLYXTokenBaseTest.t.sol +++ b/test/base/SLYXTokenBaseTest.t.sol @@ -98,20 +98,23 @@ abstract contract SLYXTokenBaseTest is Test /*, FoundryRandom */ { // but we cannot mock these addresses in Foundry tests. // The cheatcode vm.etch(...) set the bytecode, but does not initialize the state variables of the vault proxy, // (logic contract, admin, etc...) so any call to the proxy will fail. - // Therefore, we need to deploy the new Vault contracts in the test suite. vaultImplementation = new StakingverseVault(); bytes memory initializeCalldata = abi.encodeCall(StakingverseVault.initialize, (vaultOwner, vaultOperator, depositContract)); + // Therefore, we need to deploy the new Vault contracts in the test suite... vault = StakingverseVault( payable(new TransparentUpgradeableProxy(address(vaultImplementation), proxyAdmin, initializeCalldata)) ); + // ...and mock the `implementation()` function to force returning the address of the implementation this proxy is linked to. vm.mockCall( address(vault), abi.encodeWithSelector(IProxy.implementation.selector), abi.encode(vaultImplementation) ); + // configure the vault + vm.startPrank(vaultOperator); vault.enableOracle(vaultOracle, true); vault.setFee(10_000); diff --git a/test/base/UniversalProfileTestHelpers.t.sol b/test/base/UniversalProfileTestHelpers.t.sol index 1bfb799..f966ec6 100644 --- a/test/base/UniversalProfileTestHelpers.t.sol +++ b/test/base/UniversalProfileTestHelpers.t.sol @@ -6,7 +6,8 @@ import {Test} from "forge-std/Test.sol"; // modules import {UniversalProfile} from "@lukso/universalprofile-contracts/contracts/UniversalProfile.sol"; import {LSP6KeyManager} from "@lukso/lsp6-contracts/contracts/LSP6KeyManager.sol"; -import {LSP1UniversalReceiverDelegateUP as LSP1Delegate} from "@lukso/lsp1delegate-contracts/contracts/LSP1UniversalReceiverDelegateUP.sol"; +import {LSP1UniversalReceiverDelegateUP as LSP1Delegate} from + "@lukso/lsp1delegate-contracts/contracts/LSP1UniversalReceiverDelegateUP.sol"; // libraries import {LSP2Utils} from "@lukso/lsp2-contracts/contracts/LSP2Utils.sol"; @@ -14,7 +15,12 @@ import {LSP6Utils} from "@lukso/lsp6-contracts/contracts/LSP6Utils.sol"; // constants import {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY} from "@lukso/lsp1-contracts/contracts/LSP1Constants.sol"; -import {_LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, _PERMISSION_REENTRANCY, _PERMISSION_SUPER_SETDATA, ALL_REGULAR_PERMISSIONS} from "@lukso/lsp6-contracts/contracts/LSP6Constants.sol"; +import { + _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, + _PERMISSION_REENTRANCY, + _PERMISSION_SUPER_SETDATA, + ALL_REGULAR_PERMISSIONS +} from "@lukso/lsp6-contracts/contracts/LSP6Constants.sol"; contract UniversalProfileTestHelpers is Test { LSP1Delegate lsp1DelegateImplementation; @@ -23,62 +29,38 @@ contract UniversalProfileTestHelpers is Test { lsp1DelegateImplementation = new LSP1Delegate(); } - function _setUpUniversalProfileLikeBrowserExtension( - address mainController - ) internal returns (UniversalProfile) { - UniversalProfile universalProfile = new UniversalProfile( - mainController - ); + function _setUpUniversalProfileLikeBrowserExtension(address mainController) internal returns (UniversalProfile) { + UniversalProfile universalProfile = new UniversalProfile(mainController); - LSP6KeyManager keyManager = new LSP6KeyManager( - address(universalProfile) - ); + LSP6KeyManager keyManager = new LSP6KeyManager(address(universalProfile)); _setupMainControllerPermissions(universalProfile, mainController); _setUPLSP1DelegateWithPermissions(universalProfile, mainController); - _transferOwnershipToKeyManager( - universalProfile, - mainController, - keyManager - ); + _transferOwnershipToKeyManager(universalProfile, mainController, keyManager); return universalProfile; } - function _setUPLSP1DelegateWithPermissions( - UniversalProfile universalProfile, - address mainController - ) internal { + function _setUPLSP1DelegateWithPermissions(UniversalProfile universalProfile, address mainController) internal { vm.prank(mainController); - universalProfile.setData( - _LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY, - abi.encodePacked(lsp1DelegateImplementation) - ); + universalProfile.setData(_LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY, abi.encodePacked(lsp1DelegateImplementation)); // give SUPER_SETDATA permission to universalReceiverDelegate bytes32 dataKeyURD = LSP2Utils.generateMappingWithGroupingKey( - _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, - bytes20(abi.encodePacked(lsp1DelegateImplementation)) + _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, bytes20(abi.encodePacked(lsp1DelegateImplementation)) ); // use Bitwise OR to set each permission bit individually // (just for simplicity here and avoid creating a `bytes32[] memory` array). // However, it is recommended to use the LSP6Utils.combinePermissions(...) function. vm.prank(mainController); - universalProfile.setData( - dataKeyURD, - abi.encodePacked(_PERMISSION_REENTRANCY | _PERMISSION_SUPER_SETDATA) - ); + universalProfile.setData(dataKeyURD, abi.encodePacked(_PERMISSION_REENTRANCY | _PERMISSION_SUPER_SETDATA)); } - function _setupMainControllerPermissions( - UniversalProfile universalProfile, - address mainController - ) internal { + function _setupMainControllerPermissions(UniversalProfile universalProfile, address mainController) internal { bytes32 dataKey = LSP2Utils.generateMappingWithGroupingKey( - _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, - bytes20(mainController) + _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, bytes20(mainController) ); bytes memory dataValue = abi.encodePacked(ALL_REGULAR_PERMISSIONS); From e02b24165af6ac7f4086ad10cbc86f117118b481 Mon Sep 17 00:00:00 2001 From: JordyDutch Date: Thu, 27 Mar 2025 09:55:56 +0100 Subject: [PATCH 08/14] test: fix test for OZ upgrade using `ProxyAdmin` contract --- package.json | 10 ++-- test/OZUpgradeTest.t.sol | 95 ++++--------------------------- test/fork/ForkOZUpgradeTest.t.sol | 91 +++++++++++++++++++++++++++++ test/{ => fork}/ForkTest.t.sol | 6 +- 4 files changed, 111 insertions(+), 91 deletions(-) create mode 100644 test/fork/ForkOZUpgradeTest.t.sol rename test/{ => fork}/ForkTest.t.sol (99%) diff --git a/package.json b/package.json index 5ae1b52..d3b6403 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,12 @@ "build:sizes": "forge build --sizes", "build:storage": "forge build --extra-output storageLayout", "build:ir": "forge build --extra-output ir", - "test": "forge test --no-match-path test/OZUpgradeTest.t.sol", - "test:debug": "forge test -vvv --no-match-path test/OZUpgradeTest.t.sol", + "test": "forge test --no-match-contract ^Fork", + "test:debug": "forge test -vvv --no-match-contract ^Fork", "test:invariant": "forge test --match-test ^invariant", - "test:invariant:debug": "forge test --match-test ^invariant --no-match-contract MainnetForkUpgradeTest -vvv", - "test:coverage": "forge coverage --report summary --no-match-test ^invariant --no-match-path test/OZUpgradeTest.t.sol", - "test:gas": "forge test --no-match-path test/OZUpgradeTest.t.sol --gas-report", + "test:invariant:debug": "forge test --match-test ^invariant --no-match-contract ^Fork -vvv", + "test:coverage": "forge coverage --report summary --no-match-test ^invariant --no-match-contract ^Fork", + "test:gas": "forge test --no-match-contract ^Fork --gas-report", "test:upgrade": "forge clean && forge test --match-contract LocalUpgradeTest", "test:fork": "forge test --fork-url https://rpc.mainnet.lukso.network --match-contract ForkTest", "test:upgrade:fork": "forge clean && forge test --fork-url https://rpc.mainnet.lukso.network --match-contract MainnetForkUpgradeTest" diff --git a/test/OZUpgradeTest.t.sol b/test/OZUpgradeTest.t.sol index f97d8e5..3a99805 100644 --- a/test/OZUpgradeTest.t.sol +++ b/test/OZUpgradeTest.t.sol @@ -10,6 +10,7 @@ import { TransparentUpgradeableProxy, ITransparentUpgradeableProxy as IProxy } from "@openzeppelin/contracts-v4.9.0/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {Vault as VaultV1} from "../src/Vault.sol"; import {StakingverseVault} from "../src/StakingverseVault.sol"; @@ -19,93 +20,16 @@ import {PROXY_ADMIN_MAINNET, VAULT_PROXY_MAINNET} from "../script/MainnetConstan /// @dev Test to ensure that the upgrade is safe and the new implementation contract is valid, using OpenZeppelin Foundry upgrades library /// This ensures that the new implementation contract is safe to upgrade to and that the storage layout is compatible with the proxy linked to the current implementation -contract MainnetForkUpgradeTest is Test { - address constant CURRENT_VAULT_IMPLEMENTATION = 0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4; - - function test_shouldReturnCorrectProxyAdminAddress() public view { - // CHECK current proxy admin address before upgrading - assertEq(Upgrades.getAdminAddress(VAULT_PROXY_MAINNET), PROXY_ADMIN_MAINNET); - - // TODO: upgrade and check the proxy admin address has NOT changed - } - - function test_shouldReturnCorrectVaultImplementationAddress() public view { - // CHECK current implementation address before upgrading - assertEq(Upgrades.getImplementationAddress(VAULT_PROXY_MAINNET), CURRENT_VAULT_IMPLEMENTATION); - - // TODO: upgrade and check the vault implementation address has changed - } - - function test_validateUpgradeIsSafe() public { - /** - * @dev Validates a new implementation contract in comparison with a reference contract, but does not deploy it. - * - * Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. - * - * @param contractName Name of the contract to validate, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory - * @param opts Common options - */ - // function validateUpgrade(string memory contractName, Options memory opts) internal { - // Core.validateUpgrade(contractName, opts); - // } - Options memory opts; - opts.referenceContract = "Vault.sol"; - opts.unsafeAllow = "constructor"; - - Upgrades.validateUpgrade("StakingverseVault.sol", opts); - } - - function test_preparingUpgrade() public { - /** - * @dev Validates a new implementation contract in comparison with a reference contract, deploys the new implementation contract, - * and returns its address. - * - * Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. - * - * Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from your deployment environment. - * - * @param contractName Name of the contract to deploy, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory - * @param opts Common options - * @return Address of the new implementation contract - */ - // function prepareUpgrade(string memory contractName, Options memory opts) internal returns (address) { - // return Core.prepareUpgrade(contractName, opts); - // } - Options memory opts; - opts.referenceContract = "Vault.sol"; - opts.unsafeAllow = "constructor"; - - address newStakingverseVaultAddress = Upgrades.prepareUpgrade("StakingverseVault.sol", opts); - console.log(unicode"๐Ÿงช Test deploying new StakingverseVault implementation!"); - console.log(unicode"๐Ÿš€ New StakingverseVault deployed at address: %s", newStakingverseVaultAddress); - } - - function test_tryDeployingNewImplementationAndUpgrade() public { - vm.skip(true); - console.log(unicode"๐Ÿ‘€ Testing if upgrade is safe with OZ upgrade"); - - Options memory opts; - - // reference current Vault implementation to compare to for storage layout and upgrade checks - opts.referenceContract = "Vault.sol"; - - // SKIP this check as new implementation contains a constructor that includes a `_disableInitializer` - // (to prevent implementation contract from being initialized) - // but also an actual `initialize(...)` function that is called by the proxy. - // TODO: open an issue with OpenZeppelin Foundry to add a check for this - opts.unsafeAllow = "constructor"; - - Upgrades.upgradeProxy(VAULT_PROXY_MAINNET, "StakingverseVault.sol", "", opts, PROXY_ADMIN_MAINNET); - } -} - -/// @dev Test with local anvil chain +/// Tested with local anvil chain contract LocalUpgradeTest is Test { address constant CURRENT_VAULT_OWNER = 0x983aBC616f2442bAB7a917E6bb8660Df8b01F3bF; address constant CURRENT_VAULT_OPERATOR = 0x983aBC616f2442bAB7a917E6bb8660Df8b01F3bF; address proxyAdmin; + // Necessary as OZ upgrade plugin uses a contract to manage the proxy and perform upgrade through operations through it. + address proxyAdminContract; + // current vault contract implementation to upgrade from VaultV1 currentVaultImplementation = new VaultV1(); @@ -114,6 +38,9 @@ contract LocalUpgradeTest is Test { function setUp() public { proxyAdmin = address(11); + vm.prank(proxyAdmin); + proxyAdminContract = address(new ProxyAdmin()); + // 1. Deploy the existing vault implementation currentVaultImplementation = new VaultV1(); @@ -125,7 +52,9 @@ contract LocalUpgradeTest is Test { vaultProxy = IProxy( address( - new TransparentUpgradeableProxy(address(currentVaultImplementation), proxyAdmin, initializeCalldata) + new TransparentUpgradeableProxy( + address(currentVaultImplementation), proxyAdminContract, initializeCalldata + ) ) ); } @@ -143,6 +72,6 @@ contract LocalUpgradeTest is Test { // but also an actual `initialize(...)` function that is called by the proxy. opts.unsafeAllow = "constructor"; - Upgrades.upgradeProxy(VAULT_PROXY_MAINNET, "StakingverseVault.sol", "", opts, PROXY_ADMIN_MAINNET); + Upgrades.upgradeProxy(address(vaultProxy), "StakingverseVault.sol", "", opts, proxyAdmin); } } diff --git a/test/fork/ForkOZUpgradeTest.t.sol b/test/fork/ForkOZUpgradeTest.t.sol new file mode 100644 index 0000000..eb94a15 --- /dev/null +++ b/test/fork/ForkOZUpgradeTest.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Upgrades, Options} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol"; + +// Contracts to test +import { + TransparentUpgradeableProxy, + ITransparentUpgradeableProxy as IProxy +} from "@openzeppelin/contracts-v4.9.0/proxy/transparent/TransparentUpgradeableProxy.sol"; + +// Constants +import {PROXY_ADMIN_MAINNET, VAULT_PROXY_MAINNET} from "../../script/MainnetConstants.sol"; + +/// @dev Test to ensure that the upgrade is safe and the new implementation contract is valid, using OpenZeppelin Foundry upgrades library +/// This ensures that the new implementation contract is safe to upgrade to and that the storage layout is compatible with the proxy linked to the current implementation +contract MainnetForkUpgradeTest is Test { + address constant CURRENT_VAULT_IMPLEMENTATION = 0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4; + + function test_shouldReturnCorrectProxyAdminAddress() public view { + // CHECK current proxy admin address before upgrading + assertEq(Upgrades.getAdminAddress(VAULT_PROXY_MAINNET), PROXY_ADMIN_MAINNET); + } + + function test_shouldReturnCorrectVaultImplementationAddress() public view { + // CHECK current implementation address before upgrading + assertEq(Upgrades.getImplementationAddress(VAULT_PROXY_MAINNET), CURRENT_VAULT_IMPLEMENTATION); + } + + function test_validateUpgradeIsSafe() public { + /** + * @dev Validates a new implementation contract in comparison with a reference contract, but does not deploy it. + * + * Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. + * + * @param contractName Name of the contract to validate, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory + * @param opts Common options + */ + // function validateUpgrade(string memory contractName, Options memory opts) internal { + // Core.validateUpgrade(contractName, opts); + // } + Options memory opts; + opts.referenceContract = "Vault.sol"; + opts.unsafeAllow = "constructor"; + + Upgrades.validateUpgrade("StakingverseVault.sol", opts); + } + + function test_preparingUpgrade() public { + /** + * @dev Validates a new implementation contract in comparison with a reference contract, deploys the new implementation contract, + * and returns its address. + * + * Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. + * + * Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from your deployment environment. + * + * @param contractName Name of the contract to deploy, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory + * @param opts Common options + * @return Address of the new implementation contract + */ + // function prepareUpgrade(string memory contractName, Options memory opts) internal returns (address) { + // return Core.prepareUpgrade(contractName, opts); + // } + Options memory opts; + opts.referenceContract = "Vault.sol"; + opts.unsafeAllow = "constructor"; + + address newStakingverseVaultAddress = Upgrades.prepareUpgrade("StakingverseVault.sol", opts); + console.log(unicode"๐Ÿงช Test deploying new StakingverseVault implementation!"); + console.log(unicode"๐Ÿš€ New StakingverseVault deployed at address: %s", newStakingverseVaultAddress); + } + + function test_tryDeployingNewImplementationAndUpgrade() public { + vm.skip(true); + console.log(unicode"๐Ÿ‘€ Testing if upgrade is safe with OZ upgrade"); + + Options memory opts; + + // reference current Vault implementation to compare to for storage layout and upgrade checks + opts.referenceContract = "Vault.sol"; + + // SKIP this check as new implementation contains a constructor that includes a `_disableInitializer` + // (to prevent implementation contract from being initialized) + // but also an actual `initialize(...)` function that is called by the proxy. + opts.unsafeAllow = "constructor"; + + Upgrades.upgradeProxy(VAULT_PROXY_MAINNET, "StakingverseVault.sol", "", opts, PROXY_ADMIN_MAINNET); + } +} diff --git a/test/ForkTest.t.sol b/test/fork/ForkTest.t.sol similarity index 99% rename from test/ForkTest.t.sol rename to test/fork/ForkTest.t.sol index 984f8af..d8078bc 100644 --- a/test/ForkTest.t.sol +++ b/test/fork/ForkTest.t.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; // Testing + Setups -import {StakingverseVault} from "../src/StakingverseVault.sol"; -import {IDepositContract} from "../src/IDepositContract.sol"; +import {StakingverseVault} from "../../src/StakingverseVault.sol"; +import {IDepositContract} from "../../src/IDepositContract.sol"; import { TransparentUpgradeableProxy, ITransparentUpgradeableProxy as IProxy @@ -16,7 +16,7 @@ import { import {SLYXToken} from "../../src/SLYXToken.sol"; // Constants -import {PROXY_ADMIN_MAINNET, VAULT_PROXY_MAINNET, SLYX_TOKEN_PROXY_MAINNET} from "../script/MainnetConstants.sol"; +import {PROXY_ADMIN_MAINNET, VAULT_PROXY_MAINNET, SLYX_TOKEN_PROXY_MAINNET} from "../../script/MainnetConstants.sol"; // Contract addresses deployed on mainnet address payable constant VAULT_IMPLEMENTATION = payable(0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4); From ef0e1dba7419187b84294d63b843b9cfd0e157f6 Mon Sep 17 00:00:00 2001 From: jordydutch Date: Mon, 31 Mar 2025 18:41:28 +0200 Subject: [PATCH 09/14] build: add missing submodules --- .gitmodules | 6 ------ lib/contracts | 1 + lib/openzeppelin-contracts | 1 + lib/openzeppelin-foundry-upgrades | 1 + 4 files changed, 3 insertions(+), 6 deletions(-) create mode 160000 lib/contracts create mode 160000 lib/openzeppelin-contracts create mode 160000 lib/openzeppelin-foundry-upgrades diff --git a/.gitmodules b/.gitmodules index 7ba188e..f1e7ba3 100755 --- a/.gitmodules +++ b/.gitmodules @@ -1,18 +1,12 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "lib/murky"] - path = lib/murky - url = https://github.com/dmfxyz/murky [submodule "lib/openzeppelin-foundry-upgrades"] path = lib/openzeppelin-foundry-upgrades url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "lib/openzeppelin-contracts-upgradeable"] - path = lib/openzeppelin-contracts-upgradeable - url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable [submodule "lib/contracts"] path = lib/contracts url = https://github.com/Universal-Page/contracts diff --git a/lib/contracts b/lib/contracts new file mode 160000 index 0000000..91893d7 --- /dev/null +++ b/lib/contracts @@ -0,0 +1 @@ +Subproject commit 91893d701ef041a8a4f9d83b69d5b04da4dc9789 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..1873ecb --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 1873ecb38e0833fa3552f58e639eeeb134b82135 diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000..326d96b --- /dev/null +++ b/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit 326d96b5d9b5fa87b8cdb734f02a8f73324dad3e From 9c9e7a4a01e520e443a02a3659f901553eea6804 Mon Sep 17 00:00:00 2001 From: jordydutch Date: Mon, 31 Mar 2025 18:54:33 +0200 Subject: [PATCH 10/14] forge install: openzeppelin-contracts-upgradeable v4.9.6 --- .gitmodules | 3 +++ lib/openzeppelin-contracts-upgradeable | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index f1e7ba3..04fad90 100755 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/contracts"] path = lib/contracts url = https://github.com/Universal-Page/contracts +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..2d081f2 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 2d081f24cac1a867f6f73d512f2022e1fa987854 From 9cb98b11418f4ae190d2b00e725360be9a0eadb5 Mon Sep 17 00:00:00 2001 From: jordydutch Date: Mon, 31 Mar 2025 19:03:13 +0200 Subject: [PATCH 11/14] build: remove incorrect openzeppelin submodule version --- .gitmodules | 3 --- lib/openzeppelin-contracts | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index 04fad90..faea61a 100755 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "lib/openzeppelin-foundry-upgrades"] path = lib/openzeppelin-foundry-upgrades url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades -[submodule "lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "lib/contracts"] path = lib/contracts url = https://github.com/Universal-Page/contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts deleted file mode 160000 index 1873ecb..0000000 --- a/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1873ecb38e0833fa3552f58e639eeeb134b82135 From 55e9e6724c668e449b6247ad9746fc768392d720 Mon Sep 17 00:00:00 2001 From: jordydutch Date: Mon, 31 Mar 2025 19:05:34 +0200 Subject: [PATCH 12/14] forge install: openzeppelin-contracts v4.9.6 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index faea61a..4257d04 100755 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..dc44c9f --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dc44c9f1a4c3b10af99492eed84f83ed244203f6 From f34710385058cf153eb9da17d97e34a8b054c7a0 Mon Sep 17 00:00:00 2001 From: jordydutch Date: Mon, 31 Mar 2025 19:19:02 +0200 Subject: [PATCH 13/14] chore: update readme --- README.md | 6 +++--- foundry.toml | 1 + test/fork/ForkOZUpgradeTest.t.sol | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4ff00b1..2311299 100755 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ Repository for the Stakingverse contracts. This repository includes the followin ## Installation ```bash -# Install LUKSO and OpenZeppelin contracts dependencies +# Install LUKSO LSP7 dependencies npm install -# Install forge contracts testing library -forge install https://github.com/foundry-rs/forge-std --no-commit --no-git +# Install git submodule dependencies OZ contracts + forge library +git submodule update --init --recursive ``` ## Build diff --git a/foundry.toml b/foundry.toml index b1cc515..816ad05 100755 --- a/foundry.toml +++ b/foundry.toml @@ -8,6 +8,7 @@ gas_reports = ["SLYXToken", "StakingverseVault"] extra_output_files = ["metadata"] fs_permissions = [ { access = "read", path = "./scripts/" }, + { access = "read", path = "./test/" }, { access = "read", path = "./build/" }, { access = "read-write", path = "./artifacts/" }, ] diff --git a/test/fork/ForkOZUpgradeTest.t.sol b/test/fork/ForkOZUpgradeTest.t.sol index eb94a15..c09b16f 100644 --- a/test/fork/ForkOZUpgradeTest.t.sol +++ b/test/fork/ForkOZUpgradeTest.t.sol @@ -15,7 +15,7 @@ import {PROXY_ADMIN_MAINNET, VAULT_PROXY_MAINNET} from "../../script/MainnetCons /// @dev Test to ensure that the upgrade is safe and the new implementation contract is valid, using OpenZeppelin Foundry upgrades library /// This ensures that the new implementation contract is safe to upgrade to and that the storage layout is compatible with the proxy linked to the current implementation -contract MainnetForkUpgradeTest is Test { +contract ForkMainnetUpgradeTest is Test { address constant CURRENT_VAULT_IMPLEMENTATION = 0x2Cb02ef26aDDAB15686ed634d70699ab64F195f4; function test_shouldReturnCorrectProxyAdminAddress() public view { From da0a227717e51484571d00933aa34f6a79ac3dff Mon Sep 17 00:00:00 2001 From: jordydutch Date: Mon, 31 Mar 2025 19:38:22 +0200 Subject: [PATCH 14/14] chore: fix ci tests --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d3b6403..29c644b 100644 --- a/package.json +++ b/package.json @@ -23,15 +23,15 @@ "build:sizes": "forge build --sizes", "build:storage": "forge build --extra-output storageLayout", "build:ir": "forge build --extra-output ir", - "test": "forge test --no-match-contract ^Fork", - "test:debug": "forge test -vvv --no-match-contract ^Fork", + "test": "forge test --no-match-contract '^Fork|LocalUpgradeTest'", + "test:debug": "forge test -vvv --no-match-contract '^Fork|LocalUpgradeTest'", "test:invariant": "forge test --match-test ^invariant", "test:invariant:debug": "forge test --match-test ^invariant --no-match-contract ^Fork -vvv", - "test:coverage": "forge coverage --report summary --no-match-test ^invariant --no-match-contract ^Fork", - "test:gas": "forge test --no-match-contract ^Fork --gas-report", - "test:upgrade": "forge clean && forge test --match-contract LocalUpgradeTest", + "test:coverage": "forge coverage --report summary --no-match-test ^invariant --no-match-contract '^Fork|LocalUpgradeTest'", + "test:gas": "forge test --no-match-contract '^Fork|LocalUpgradeTest' --gas-report", + "test:upgrade": "FOUNDRY_OUT=build forge clean && forge test --match-contract LocalUpgradeTest", "test:fork": "forge test --fork-url https://rpc.mainnet.lukso.network --match-contract ForkTest", - "test:upgrade:fork": "forge clean && forge test --fork-url https://rpc.mainnet.lukso.network --match-contract MainnetForkUpgradeTest" + "test:upgrade:fork": "FOUNDRY_OUT=build forge clean && forge test --fork-url https://rpc.mainnet.lukso.network --match-contract ForkMainnetUpgradeTest" }, "dependencies": { "@lukso/lsp7-contracts": "~0.16.3"