feat: introduce `VaultFactory` (#38)

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
This commit is contained in:
r4bbit 2023-11-07 09:49:22 +01:00 committed by GitHub
parent b5e513ce49
commit f259286e98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 14 deletions

View File

@ -1,18 +1,24 @@
CreateVaultTest:testDeployment() (gas: 9774)
CreateVaultTest:test_createVault() (gas: 650992)
ExecuteAccountTest:testDeployment() (gas: 26400)
ExecuteAccountTest:test_RevertWhen_InvalidLimitEpoch() (gas: 982104)
ExecuteAccountTest:test_RevertWhen_InvalidLimitEpoch() (gas: 991602)
LeaveTest:testDeployment() (gas: 26172)
LeaveTest:test_RevertWhen_NoPendingMigration() (gas: 670554)
LeaveTest:test_RevertWhen_NoPendingMigration() (gas: 678051)
LeaveTest:test_RevertWhen_SenderIsNotVault() (gas: 10562)
LockTest:testDeployment() (gas: 26400)
LockTest:test_RevertWhen_DecreasingLockTime() (gas: 985034)
LockTest:test_RevertWhen_DecreasingLockTime() (gas: 994528)
LockTest:test_RevertWhen_SenderIsNotVault() (gas: 10607)
MigrateTest:testDeployment() (gas: 26172)
MigrateTest:test_RevertWhen_NoPendingMigration() (gas: 670393)
MigrateTest:test_RevertWhen_NoPendingMigration() (gas: 677890)
MigrateTest:test_RevertWhen_SenderIsNotVault() (gas: 10629)
SetStakeManagerTest:testDeployment() (gas: 9774)
SetStakeManagerTest:test_RevertWhen_InvalidStakeManagerAddress() (gas: 20481)
SetStakeManagerTest:test_SetStakeManager() (gas: 19869)
StakeManagerTest:testDeployment() (gas: 26172)
StakeTest:testDeployment() (gas: 26172)
StakeTest:test_RevertWhen_SenderIsNotVault() (gas: 10638)
StakedTokenTest:testStakeToken() (gas: 7638)
UnstakeTest:testDeployment() (gas: 26355)
UnstakeTest:test_RevertWhen_FundsLocked() (gas: 981497)
UnstakeTest:test_RevertWhen_SenderIsNotVault() (gas: 10609)
UnstakeTest:test_RevertWhen_FundsLocked() (gas: 990991)
UnstakeTest:test_RevertWhen_SenderIsNotVault() (gas: 10609)
VaultFactoryTest:testDeployment() (gas: 9774)

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -8,10 +8,12 @@ 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;
@ -19,7 +21,7 @@ contract StakeManagerTest is Test {
function setUp() public virtual {
Deploy deployment = new Deploy();
(stakeManager, deploymentConfig) = deployment.run();
(vaultFactory, stakeManager, deploymentConfig) = deployment.run();
(deployer, stakeToken) = deploymentConfig.activeNetworkConfig();
}
@ -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);

View File

@ -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;
@ -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();
}
}

74
test/VaultFactory.t.sol Normal file
View File

@ -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));
}
}