Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: introduce proxy clones #115

Open
wants to merge 2 commits into
base: refactor/transparent-proxy
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 43 additions & 29 deletions .gas-report

Large diffs are not rendered by default.

121 changes: 61 additions & 60 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,75 +1,76 @@
EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 92757)
EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 300544)
EmergencyExitTest:test_EmergencyExitBasic() (gas: 387340)
EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 667427)
EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 395139)
EmergencyExitTest:test_EmergencyExitWithLock() (gas: 394708)
EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 380241)
EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 306012)
EmergencyExitTest:test_EmergencyExitBasic() (gas: 392806)
EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 678359)
EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 400605)
EmergencyExitTest:test_EmergencyExitWithLock() (gas: 400174)
EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 385707)
EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39471)
IntegrationTest:testStakeFoo() (gas: 1218594)
LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 6214173)
LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 297675)
LeaveTest:test_TrustNewStakeManager() (gas: 6269901)
LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1000, μ: 344783, ~: 344801)
LockTest:test_LockFailsWithNoStake() (gas: 102637)
LockTest:test_LockFailsWithZero() (gas: 315022)
LockTest:test_LockWithoutPriorLock() (gas: 393335)
MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1752531)
IntegrationTest:testStakeFoo() (gas: 1232258)
LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 6218572)
LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 303138)
LeaveTest:test_TrustNewStakeManager() (gas: 6286362)
LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1000, μ: 350272, ~: 350297)
LockTest:test_LockFailsWithNoStake() (gas: 105377)
LockTest:test_LockFailsWithZero() (gas: 320506)
LockTest:test_LockWithoutPriorLock() (gas: 398817)
MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1757478)
MathTest:test_CalcAbsoluteMaxTotalMP() (gas: 4996)
MathTest:test_CalcAccrueMP() (gas: 7990)
MathTest:test_CalcBonusMP() (gas: 18676)
MathTest:test_CalcInitialMP() (gas: 5352)
MathTest:test_CalcAccrueMP() (gas: 8013)
MathTest:test_CalcBonusMP() (gas: 18644)
MathTest:test_CalcInitialMP() (gas: 5375)
MathTest:test_CalcMaxAccruedMP() (gas: 4642)
MathTest:test_CalcMaxTotalMP() (gas: 19449)
MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 731369)
MathTest:test_CalcMaxTotalMP() (gas: 19411)
MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 739601)
NFTMetadataGeneratorSVGTest:testGenerateMetadata() (gas: 85934)
NFTMetadataGeneratorSVGTest:testSetImageStrings() (gas: 58332)
NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35804)
NFTMetadataGeneratorURLTest:testGenerateMetadata() (gas: 102512)
NFTMetadataGeneratorURLTest:testSetBaseURL() (gas: 49555)
NFTMetadataGeneratorURLTest:testSetBaseURLRevert() (gas: 35979)
RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 490632)
RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 493376)
RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160880)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39384)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39407)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39442)
RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 620722)
StakeTest:test_StakeMultipleAccounts() (gas: 502561)
StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 508596)
StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 847390)
StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 517705)
StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 539649)
StakeTest:test_StakeOneAccount() (gas: 279841)
StakeTest:test_StakeOneAccountAndRewards() (gas: 285896)
StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 510467)
StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 500009)
StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 300111)
StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 300696)
StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 300763)
StakingTokenTest:testStakeToken() (gas: 10422)
UnstakeTest:test_StakeMultipleAccounts() (gas: 502560)
UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 508640)
UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 847367)
UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 517704)
UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 539693)
UnstakeTest:test_StakeOneAccount() (gas: 279841)
UnstakeTest:test_StakeOneAccountAndRewards() (gas: 285874)
UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 510511)
UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 500008)
UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 300111)
UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 300718)
UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 300762)
UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 546541)
UnstakeTest:test_UnstakeMultipleAccounts() (gas: 707663)
UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 803659)
UnstakeTest:test_UnstakeOneAccount() (gas: 481480)
UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 505028)
UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 410671)
UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 530083)
UpgradeTest:test_RevertWhenNotOwner() (gas: 2897740)
UpgradeTest:test_UpgradeStakeManager() (gas: 6114750)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39407)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39385)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39420)
RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 623466)
StakeTest:test_StakeMultipleAccounts() (gas: 508049)
StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 514084)
StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 852890)
StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 523193)
StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 545137)
StakeTest:test_StakeOneAccount() (gas: 282585)
StakeTest:test_StakeOneAccountAndRewards() (gas: 288640)
StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 513211)
StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 502753)
StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 302900)
StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 303440)
StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 303507)
StakingTokenTest:testStakeToken() (gas: 13140)
TrustedCodehashAccessTest:test_RevertWhenProxyCloneCodehashNotTrusted() (gas: 1896237)
UnstakeTest:test_StakeMultipleAccounts() (gas: 508026)
UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 514106)
UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 852867)
UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 523170)
UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 545159)
UnstakeTest:test_StakeOneAccount() (gas: 282585)
UnstakeTest:test_StakeOneAccountAndRewards() (gas: 288662)
UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 513233)
UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 502797)
UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 302900)
UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 303462)
UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 303551)
UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 551979)
UnstakeTest:test_UnstakeMultipleAccounts() (gas: 718583)
UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 816209)
UnstakeTest:test_UnstakeOneAccount() (gas: 488546)
UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 510466)
UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 416131)
UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 535543)
UpgradeTest:test_RevertWhenNotOwner() (gas: 2897728)
UpgradeTest:test_UpgradeStakeManager() (gas: 6117494)
VaultRegistrationTest:test_VaultRegistration() (gas: 62154)
WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 313397)
WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 318812)
XPNFTTokenTest:testApproveNotAllowed() (gas: 10500)
XPNFTTokenTest:testGetApproved() (gas: 10523)
XPNFTTokenTest:testIsApprovedForAll() (gas: 10698)
Expand Down
25 changes: 16 additions & 9 deletions script/DeployRewardsStreamerMP.s.sol
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";

import { BaseScript } from "./Base.s.sol";
import { DeploymentConfig } from "./DeploymentConfig.s.sol";

import { TransparentProxy } from "../src/TransparentProxy.sol";
import { IStakeManagerProxy } from "../src/interfaces/IStakeManagerProxy.sol";
import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol";
import { StakeVault } from "../src/StakeVault.sol";
import { VaultFactory } from "../src/VaultFactory.sol";

contract DeployRewardsStreamerMPScript is BaseScript {
function run() public returns (RewardsStreamerMP, DeploymentConfig) {
function run() public returns (RewardsStreamerMP, VaultFactory, DeploymentConfig) {
DeploymentConfig deploymentConfig = new DeploymentConfig(broadcaster);
(address deployer, address stakingToken) = deploymentConfig.activeNetworkConfig();

Expand All @@ -21,16 +25,19 @@ contract DeployRewardsStreamerMPScript is BaseScript {
address impl = address(new RewardsStreamerMP());
// Create upgradeable proxy
address proxy = address(new TransparentProxy(impl, initializeData));
vm.stopBroadcast();

RewardsStreamerMP stakeManager = RewardsStreamerMP(proxy);
StakeVault tempVault = new StakeVault(address(this), IStakeManagerProxy(proxy));
bytes32 vaultCodeHash = address(tempVault).codehash;
// Create vault implementation for proxy clones
address vaultImplementation = address(new StakeVault(IERC20(stakingToken)));
address proxyClone = Clones.clone(vaultImplementation);

// Whitelist vault implementation codehash
RewardsStreamerMP(proxy).setTrustedCodehash(proxyClone.codehash, true);

// Create vault factory
VaultFactory vaultFactory = new VaultFactory(deployer, proxy, vaultImplementation);

vm.startBroadcast(deployer);
stakeManager.setTrustedCodehash(vaultCodeHash, true);
vm.stopBroadcast();

return (stakeManager, deploymentConfig);
return (RewardsStreamerMP(proxy), vaultFactory, deploymentConfig);
}
}
24 changes: 17 additions & 7 deletions src/StakeVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

pragma solidity ^0.8.26;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
3esmit marked this conversation as resolved.
Show resolved Hide resolved
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IStakeManagerProxy } from "./interfaces/IStakeManagerProxy.sol";
import { IStakeVault } from "./interfaces/IStakeVault.sol";
Expand All @@ -12,8 +13,10 @@ import { IStakeVault } from "./interfaces/IStakeVault.sol";
* @author Ricardo Guilherme Schmidt <[email protected]>
* @notice A contract to secure user stakes and manage staking with IStakeManager.
* @dev This contract is owned by the user and allows staking, unstaking, and withdrawing tokens.
* @dev The only reason this is `OwnableUpgradeable` is because we use proxy clones
* to create stake vault instances. Hence, we need to use `Initializeable` to set the owner.
*/
contract StakeVault is IStakeVault, Ownable {
contract StakeVault is IStakeVault, Initializable, OwnableUpgradeable {
error StakeVault__NotEnoughAvailableBalance();
error StakeVault__InvalidDestinationAddress();
error StakeVault__UpdateNotAvailable();
Expand Down Expand Up @@ -55,13 +58,20 @@ contract StakeVault is IStakeVault, Ownable {

/**
* @notice Initializes the contract with the owner, staked token, and stake manager.
*/
constructor(IERC20 token) {
STAKING_TOKEN = token;
_disableInitializers();
}

/**
* @param _owner The address of the owner.
* @param _stakeManager The address of the StakeManager contract.
*/
constructor(address _owner, IStakeManagerProxy _stakeManager) Ownable(_owner) {
STAKING_TOKEN = _stakeManager.STAKING_TOKEN();
stakeManager = _stakeManager;
stakeManagerImplementationAddress = _stakeManager.implementation();
function initialize(address _owner, address _stakeManager) public initializer {
__Ownable_init(_owner);
stakeManager = IStakeManagerProxy(_stakeManager);
stakeManagerImplementationAddress = stakeManager.implementation();
}

/**
Expand All @@ -82,7 +92,7 @@ contract StakeVault is IStakeVault, Ownable {
/**
* @notice Returns the address of the current owner.
*/
function owner() public view override(Ownable, IStakeVault) returns (address) {
function owner() public view override(OwnableUpgradeable, IStakeVault) returns (address) {
return super.owner();
}

Expand Down
76 changes: 76 additions & 0 deletions src/VaultFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.26;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
// import { TransparentClones } from "./TransparentClones.sol";
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
import { StakeVault } from "./StakeVault.sol";

/**
* @title VaultFactory
* @author 0x-r4bbit
*
* This contract is reponsible for creating staking vaults for users.
* A user of the staking protocol is able to create multiple vaults to facilitate
* different strategies. For example, a user may want to create a vault for
* a long-term lock period, while also creating a vault that has no lock period
* at all.
*
* @notice This contract is used by users to create staking vaults.
* @dev This contract will be deployed by Status, making Status the owner of the contract.
* @dev A contract address for a `StakeManager` has to be provided to create this contract.
* @dev Reverts with {VaultFactory__InvalidStakeManagerAddress} if the provided
* `StakeManager` address is zero.
* @dev The `StakeManager` contract address can be changed by the owner.
*/
contract VaultFactory is Ownable {
error VaultFactory__InvalidStakeManagerAddress();

event VaultCreated(address indexed vault, address indexed owner);
event StakeManagerAddressChanged(address indexed newStakeManagerAddress);
event VaultImplementationChanged(address indexed newVaultImplementation);

/// @dev Address of the `StakeManager` contract instance.
address public stakeManager;
/// @dev Address of the `StakeVault` implementation contract.
address public vaultImplementation;

/// @param _stakeManager Address of the `StakeManager` contract instance.
constructor(address _owner, address _stakeManager, address _vaultImplementation) Ownable(_owner) {
if (_stakeManager == address(0)) {
revert VaultFactory__InvalidStakeManagerAddress();
}
stakeManager = _stakeManager;
vaultImplementation = _vaultImplementation;
}

/// @notice Sets the `StakeManager` contract address.
/// @dev Only the owner can call this function.
/// @dev Emits a {StakeManagerAddressChanged} event.
/// @param _stakeManager Address of the `StakeManager` contract instance.
function setStakeManager(address _stakeManager) external onlyOwner {
stakeManager = _stakeManager;
emit StakeManagerAddressChanged(_stakeManager);
}

/// @notice Sets the `StakeVault` implementation contract address.
/// @dev Only the owner can call this function.
/// @dev Emits a {VaultImplementationChanged} event.
/// @param _vaultImplementation Address of the `StakeVault` implementation contract.
/// @dev This function is used to change the implementation of the `StakeVault` contract.
function setVaultImplementation(address _vaultImplementation) external onlyOwner {
vaultImplementation = _vaultImplementation;
emit VaultImplementationChanged(_vaultImplementation);
}

/// @notice Creates an instance of a `StakeVault` contract.
/// @dev Anyone can call this function.
/// @dev Emits a {VaultCreated} event.
function createVault() external returns (StakeVault clone) {
clone = StakeVault(Clones.clone(vaultImplementation));
clone.initialize(msg.sender, stakeManager);
clone.register();
emit VaultCreated(address(clone), msg.sender);
}
}
2 changes: 0 additions & 2 deletions src/interfaces/IStakeConstants.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import { ITrustedCodehashAccess } from "./ITrustedCodehashAccess.sol";

/**
* @title IStakeConstants
* @author Ricardo Guilherme Schmidt <[email protected]>
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/IStakeManagerProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ pragma solidity ^0.8.26;
import { IStakeManager } from "./IStakeManager.sol";
import { ITransparentProxy } from "./ITransparentProxy.sol";

// solhint-disable-next-line
interface IStakeManagerProxy is IStakeManager, ITransparentProxy { }
2 changes: 2 additions & 0 deletions src/math/StakeMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ abstract contract StakeMath is MultiplierPointMath {
{
uint256 newBalance = _balance + _increasedAmount;
_newLockEnd = Math.max(_currentLockEndTime, _processTime) + _increasedLockSeconds;
// solhint-disable-next-line
uint256 dt_lock = _newLockEnd - _processTime;
if (dt_lock != 0 && (dt_lock < MIN_LOCKUP_PERIOD || dt_lock > MAX_LOCKUP_PERIOD)) {
revert StakeMath__InvalidLockingPeriod();
Expand Down Expand Up @@ -99,6 +100,7 @@ abstract contract StakeMath is MultiplierPointMath {
}

_newLockEnd = Math.max(_currentLockEndTime, _processTime) + _increasedLockSeconds;
// solhint-disable-next-line
uint256 dt_lock = _newLockEnd - _processTime;
if (dt_lock != 0 && (dt_lock < MIN_LOCKUP_PERIOD || dt_lock > MAX_LOCKUP_PERIOD)) {
revert StakeMath__InvalidLockingPeriod();
Expand Down
Loading
Loading