Skip to content

Commit

Permalink
feat: introduce VaultFactory
Browse files Browse the repository at this point in the history
This commit introduces a first version of a `VaultFactory` that later
will be extended to be capable of instantiating reward vaults and
possible keep track of vault instances per owner.

As a first step, this implementation comes with a `createVault()`
function which takes care of creating vaults.

Because `VaultFactory` also knows about `StakeManager` it can derive the
manager's address and stake token from it when creating vaults, allowing
the API to be without arguments.

Partially addresses #37
  • Loading branch information
0x-r4bbit committed Oct 20, 2023
1 parent 296cd0a commit d6244ab
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 8 deletions.
64 changes: 64 additions & 0 deletions contracts/VaultFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { StakeManager } from "./StakeManager.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 Ownable2Step {
error VaultFactory__InvalidStakeManagerAddress();

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

/// @dev Address of the `StakeManager` contract instance.
StakeManager public stakeManager;

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

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

/// @notice Creates an instance of a `StakeVault` contract.
/// @dev Anyone can call this function.
/// @dev Emits a {VaultCreated} event.
function createVault() external returns (StakeVault) {
StakeVault vault = new StakeVault(msg.sender, stakeManager.stakedToken(), stakeManager);
emit VaultCreated(address(vault), msg.sender);
return vault;
}
}
6 changes: 4 additions & 2 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ pragma solidity >=0.8.19 <=0.9.0;
import { BaseScript } from "./Base.s.sol";
import { DeploymentConfig } from "./DeploymentConfig.s.sol";
import { StakeManager } from "../contracts/StakeManager.sol";
import { VaultFactory } from "../contracts/VaultFactory.sol";

contract Deploy is BaseScript {
function run() public returns (StakeManager, DeploymentConfig) {
function run() public returns (VaultFactory, StakeManager, DeploymentConfig) {
DeploymentConfig deploymentConfig = new DeploymentConfig(broadcaster);
(, address token) = deploymentConfig.activeNetworkConfig();

vm.startBroadcast(broadcaster);
StakeManager stakeManager = new StakeManager(token, address(0));
VaultFactory vaultFactory = new VaultFactory(address(stakeManager));
vm.stopBroadcast();

return (stakeManager, deploymentConfig);
return (vaultFactory, stakeManager, deploymentConfig);
}
}
6 changes: 4 additions & 2 deletions test/StakeManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@ import { Deploy } from "../script/Deploy.s.sol";
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
import { StakeManager } from "../contracts/StakeManager.sol";
import { StakeVault } from "../contracts/StakeVault.sol";
import { VaultFactory } from "../contracts/VaultFactory.sol";

contract StakeManagerTest is Test {
DeploymentConfig internal deploymentConfig;
StakeManager internal stakeManager;
VaultFactory internal vaultFactory;

address internal stakeToken;
address internal deployer;
address internal testUser = makeAddr("testUser");

function setUp() public virtual {
Deploy deployment = new Deploy();
(stakeManager, deploymentConfig) = deployment.run();
(vaultFactory, stakeManager, deploymentConfig) = deployment.run();
(deployer, stakeToken) = deploymentConfig.activeNetworkConfig();
}

Expand All @@ -36,7 +38,7 @@ contract StakeManagerTest is Test {

function _createTestVault(address owner) internal returns (StakeVault vault) {
vm.prank(owner);
vault = new StakeVault(owner, ERC20(stakeToken), stakeManager);
vault = vaultFactory.createVault();

vm.prank(deployer);
stakeManager.setVault(address(vault).codehash);
Expand Down
9 changes: 5 additions & 4 deletions test/StakeVault.t.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

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

import { Test } from "forge-std/Test.sol";
import { Deploy } from "../script/Deploy.s.sol";
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
import { StakeManager } from "../contracts/StakeManager.sol";
import { StakeVault } from "../contracts/StakeVault.sol";
import { VaultFactory } from "../contracts/VaultFactory.sol";

contract StakeVaultTest is Test {
StakeManager internal stakeManager;

DeploymentConfig internal deploymentConfig;

VaultFactory internal vaultFactory;

StakeVault internal stakeVault;

address internal deployer;
Expand All @@ -24,11 +25,11 @@ contract StakeVaultTest is Test {

function setUp() public virtual {
Deploy deployment = new Deploy();
(stakeManager, deploymentConfig) = deployment.run();
(vaultFactory, stakeManager, deploymentConfig) = deployment.run();
(deployer, stakeToken) = deploymentConfig.activeNetworkConfig();

vm.prank(testUser);
stakeVault = new StakeVault(testUser, ERC20(stakeToken), stakeManager);
stakeVault = vaultFactory.createVault();
}
}

Expand Down
74 changes: 74 additions & 0 deletions test/VaultFactory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

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

import { Test } from "forge-std/Test.sol";
import { Deploy } from "../script/Deploy.s.sol";
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";

import { StakeManager } from "../contracts/StakeManager.sol";
import { StakeVault } from "../contracts/StakeVault.sol";
import { VaultFactory } from "../contracts/VaultFactory.sol";

contract VaultFactoryTest is Test {
DeploymentConfig internal deploymentConfig;

StakeManager internal stakeManager;

VaultFactory internal vaultFactory;

address internal deployer;

address internal stakedToken;

address internal testUser = makeAddr("testUser");

function setUp() public virtual {
Deploy deployment = new Deploy();
(vaultFactory, stakeManager, deploymentConfig) = deployment.run();
(deployer, stakedToken) = deploymentConfig.activeNetworkConfig();
}

function testDeployment() public {
assertEq(address(vaultFactory.stakeManager()), address(stakeManager));
}
}

contract SetStakeManagerTest is VaultFactoryTest {
function setUp() public override {
VaultFactoryTest.setUp();
}

function test_RevertWhen_InvalidStakeManagerAddress() public {
vm.startPrank(deployer);
vm.expectRevert(VaultFactory.VaultFactory__InvalidStakeManagerAddress.selector);
vaultFactory.setStakeManager(address(0));

vm.expectRevert(VaultFactory.VaultFactory__InvalidStakeManagerAddress.selector);
vaultFactory.setStakeManager(address(stakeManager));
}

function test_SetStakeManager() public {
vm.prank(deployer);
vaultFactory.setStakeManager(address(this));
assertEq(address(vaultFactory.stakeManager()), address(this));
}
}

contract CreateVaultTest is VaultFactoryTest {
event VaultCreated(address indexed vault, address indexed owner);

function setUp() public override {
VaultFactoryTest.setUp();
}

function test_createVault() public {
vm.prank(testUser);
vm.expectEmit(false, false, false, false);
emit VaultCreated(makeAddr("some address"), testUser);
StakeVault vault = vaultFactory.createVault();
assertEq(vault.owner(), testUser);
assertEq(address(vault.stakedToken()), address(stakedToken));
}
}

0 comments on commit d6244ab

Please sign in to comment.