From 2ef86817549fe32965a41e4ef8bc1360a090901e Mon Sep 17 00:00:00 2001 From: Schlag <89420541+Schlagonia@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:02:52 -0700 Subject: [PATCH] fix: pack struct (#81) * fix: pack struct further * fix: format --- src/TokenizedStrategy.sol | 33 ++++++++++++--------- src/test/mocks/MockStorage.sol | 53 ++++++++++++++++++++++++++++++++++ src/test/utils/Setup.sol | 4 +-- 3 files changed, 75 insertions(+), 15 deletions(-) create mode 100644 src/test/mocks/MockStorage.sol diff --git a/src/TokenizedStrategy.sol b/src/TokenizedStrategy.sol index 2182806..eea4156 100644 --- a/src/TokenizedStrategy.sol +++ b/src/TokenizedStrategy.sol @@ -213,9 +213,10 @@ contract TokenizedStrategy { // These are the corresponding ERC20 variables needed for the // strategies token that is issued and burned on each deposit or withdraw. uint8 decimals; // The amount of decimals that `asset` and strategy use. + uint88 INITIAL_CHAIN_ID; // The initial chain id when the strategy was created. + string name; // The name of the token for the strategy. uint256 totalSupply; // The total amount of shares currently issued. - uint256 INITIAL_CHAIN_ID; // The initial chain id when the strategy was created. bytes32 INITIAL_DOMAIN_SEPARATOR; // The domain separator used for permits on the initial chain. mapping(address => uint256) nonces; // Mapping of nonces used for permit functions. mapping(address => uint256) balances; // Mapping to track current balances for each account that holds shares. @@ -228,22 +229,27 @@ contract TokenizedStrategy { // Variables for profit reporting and locking. - // We use uint128 for time stamps which is 1,025 years in the future. + // We use uint96 for timestamps to fit in the same slot as an address. + // We will surely all be dead by the time the slot overflows. uint256 profitUnlockingRate; // The rate at which locked profit is unlocking. - uint128 fullProfitUnlockDate; // The timestamp at which all locked shares will unlock. - uint128 lastReport; // The last time a {report} was called. + uint96 fullProfitUnlockDate; // The timestamp at which all locked shares will unlock. + address keeper; // Address given permission to call {report} and {tend}. uint32 profitMaxUnlockTime; // The amount of seconds that the reported profit unlocks over. uint16 performanceFee; // The percent in basis points of profit that is charged as a fee. address performanceFeeRecipient; // The address to pay the `performanceFee` to. + uint96 lastReport; // The last time a {report} was called. // Access management variables. address management; // Main address that can set all configurable variables. - address keeper; // Address given permission to call {report} and {tend}. address pendingManagement; // Address that is pending to take over `management`. address emergencyAdmin; // Address to act in emergencies as well as `management`. + + + // Strategy status checks. bool entered; // Bool to prevent reentrancy. - bool shutdown; // Bool that can be used to stop deposits into the strategy. + bool shutdown; // Bool that can be used to stop deposits into the strategy. + } /*////////////////////////////////////////////////////////////// @@ -454,7 +460,8 @@ contract TokenizedStrategy { // Set decimals based off the `asset`. S.decimals = ERC20(_asset).decimals(); // Set initial chain id for permit replay protection - S.INITIAL_CHAIN_ID = block.chainid; + require(block.chainid < type(uint88).max, "invalid chain id"); + S.INITIAL_CHAIN_ID = uint88(block.chainid); // Set the initial domain separator for permit functions S.INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(S); @@ -469,7 +476,7 @@ contract TokenizedStrategy { // Default to a 10% performance fee. S.performanceFee = 1_000; // Set last report to this block. - S.lastReport = uint128(block.timestamp); + S.lastReport = uint96(block.timestamp); // Set the default management address. Can't be 0. require(_management != address(0), "ZERO ADDRESS"); @@ -1191,7 +1198,7 @@ contract TokenizedStrategy { uint256 totalLockedShares = S.balances[address(this)]; if (totalLockedShares != 0) { uint256 previouslyLockedTime; - uint128 _fullProfitUnlockDate = S.fullProfitUnlockDate; + uint96 _fullProfitUnlockDate = S.fullProfitUnlockDate; // Check if we need to account for shares still unlocking. if (_fullProfitUnlockDate > block.timestamp) { unchecked { @@ -1215,7 +1222,7 @@ contract TokenizedStrategy { newProfitLockingPeriod; // Calculate how long until the full amount of shares is unlocked. - S.fullProfitUnlockDate = uint128( + S.fullProfitUnlockDate = uint96( block.timestamp + newProfitLockingPeriod ); } else { @@ -1226,7 +1233,7 @@ contract TokenizedStrategy { // Update the new total assets value. S.totalAssets = newTotalAssets; - S.lastReport = uint128(block.timestamp); + S.lastReport = uint96(block.timestamp); // Emit event with info emit Reported( @@ -1252,7 +1259,7 @@ contract TokenizedStrategy { // update variables (done here to keep _unlockedShares() as a view function) if (S.fullProfitUnlockDate > block.timestamp) { - S.lastReport = uint128(block.timestamp); + S.lastReport = uint96(block.timestamp); } _burn(S, address(this), unlocked); @@ -1278,7 +1285,7 @@ contract TokenizedStrategy { function _unlockedShares( StrategyData storage S ) internal view returns (uint256 unlocked) { - uint128 _fullProfitUnlockDate = S.fullProfitUnlockDate; + uint96 _fullProfitUnlockDate = S.fullProfitUnlockDate; if (_fullProfitUnlockDate > block.timestamp) { unchecked { unlocked = diff --git a/src/test/mocks/MockStorage.sol b/src/test/mocks/MockStorage.sol new file mode 100644 index 0000000..18324e0 --- /dev/null +++ b/src/test/mocks/MockStorage.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.18; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// use `make inspect contract=MockStorage` to easy view the structs storage layout. +// prettier-ignore +contract MockStorage { + // The ERC20 compliant underlying asset that will be + // used by the Strategy + ERC20 asset; + + + // These are the corresponding ERC20 variables needed for the + // strategies token that is issued and burned on each deposit or withdraw. + uint8 decimals; // The amount of decimals that `asset` and strategy use. + uint88 INITIAL_CHAIN_ID; // The initial chain id when the strategy was created. + + string name; // The name of the token for the strategy. + uint256 totalSupply; // The total amount of shares currently issued. + bytes32 INITIAL_DOMAIN_SEPARATOR; // The domain separator used for permits on the initial chain. + mapping(address => uint256) nonces; // Mapping of nonces used for permit functions. + mapping(address => uint256) balances; // Mapping to track current balances for each account that holds shares. + mapping(address => mapping(address => uint256)) allowances; // Mapping to track the allowances for the strategies shares. + + + // Assets data to track total the strategy holds. + // We manually track `totalAssets` to prevent PPS manipulation through airdrops. + uint256 totalAssets; + + + // Variables for profit reporting and locking. + // We use uint96 for time stamps to fit in the same slot as an address. + // We will surely all be dead by the time the slot overflows. + uint256 profitUnlockingRate; // The rate at which locked profit is unlocking. + uint96 fullProfitUnlockDate; // The timestamp at which all locked shares will unlock. + address keeper; // Address given permission to call {report} and {tend}. + uint32 profitMaxUnlockTime; // The amount of seconds that the reported profit unlocks over. + uint16 performanceFee; // The percent in basis points of profit that is charged as a fee. + address performanceFeeRecipient; // The address to pay the `performanceFee` to. + uint96 lastReport; // The last time a {report} was called. + + + // Access management variables. + address management; // Main address that can set all configurable variables. + address pendingManagement; // Address that is pending to take over `management`. + address emergencyAdmin; // Address to act in emergencies as well as `management`. + + + // Strategy status checks. + bool entered; // Bool to prevent reentrancy. + bool shutdown; // Bool that can be used to stop deposits into the strategy. +} diff --git a/src/test/utils/Setup.sol b/src/test/utils/Setup.sol index 8bb32cb..e385c8f 100644 --- a/src/test/utils/Setup.sol +++ b/src/test/utils/Setup.sol @@ -279,8 +279,8 @@ contract Setup is ExtendedTest, IEvents { TokenizedStrategy.StrategyData storage S = _strategyStorage(); assembly { - // Perf fee is stored in the 12th slot of the Struct. - slot := add(S.slot, 11) + // Perf fee is stored in the 10th slot of the Struct. + slot := add(S.slot, 10) } // Performance fee is packed in a slot with other variables so we need