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/ fee share #96

Merged
merged 17 commits into from
Oct 16, 2023
Merged
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
27 changes: 12 additions & 15 deletions docs/audit/LidoSplit.md → docs/audit/ObolLidoSplit.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ Source Units in Scope: **`1`** (**100%**)

| Type | File | Logic Contracts | Interfaces | Lines | nLines | nSLOC | Comment Lines | Complex. Score | Capabilities |
| ---- | ------ | --------------- | ---------- | ----- | ------ | ----- | ------------- | -------------- | ------------ |
| 📝🔍 | src/lido/LidoSplit.sol | 1 | 1 | 72 | 64 | 29 | 31 | 34 | **** |
| 📝🔍 | **Totals** | **1** | **1** | **72** | **64** | **29** | **31** | **34** | **** |
| 📝 | src/lido/ObolLidoSplit.sol | 1 | **** | 117 | 117 | 53 | 46 | 59 | **** |
| 📝 | **Totals** | **1** | **** | **117** | **117** | **53** | **46** | **59** | **** |

<sub>
Legend: <a onclick="toggleVisibility('table-legend', this)">[➕]</a>
Expand Down Expand Up @@ -131,38 +131,38 @@ The analysis finished with **`0`** errors and **`0`** duplicate files.

#### <span id=t-inline-documentation>Inline Documentation</span>

- **Comment-to-Source Ratio:** On average there are`1` code lines per comment (lower=better).
- **Comment-to-Source Ratio:** On average there are`1.15` code lines per comment (lower=better).
- **ToDo's:** `0`

#### <span id=t-components>Components</span>

| 📝Contracts | 📚Libraries | 🔍Interfaces | 🎨Abstract |
| ------------- | ----------- | ------------ | ---------- |
| 1 | 0 | 1 | 0 |
| 1 | 0 | 0 | 0 |

#### <span id=t-exposed-functions>Exposed Functions</span>

This section lists functions that are explicitly declared public or payable. Please note that getter methods for public stateVars are not included.

| 🌐Public | 💰Payable |
| ---------- | --------- |
| 5 | 0 |
| 3 | 0 |

| External | Internal | Private | Pure | View |
| ---------- | -------- | ------- | ---- | ---- |
| 2 | 3 | 0 | 3 | 0 |
| 2 | 3 | 0 | 1 | 0 |

#### <span id=t-statevariables>StateVariables</span>

| Total | 🌐Public |
| ---------- | --------- |
| 3 | 0 |
| 7 | 4 |

#### <span id=t-capabilities>Capabilities</span>

| Solidity Versions observed | 🧪 Experimental Features | 💰 Can Receive Funds | 🖥 Uses Assembly | 💣 Has Destroyable Contracts |
| -------------------------- | ------------------------ | -------------------- | ---------------- | ---------------------------- |
| `=0.8.17` | | **** | **** | **** |
| `0.8.19` | | **** | **** | **** |

| 📤 Transfers ETH | ⚡ Low-Level Calls | 👥 DelegateCall | 🧮 Uses Hash Functions | 🔖 ECRecover | 🌀 New/Create/Create2 |
| ---------------- | ----------------- | --------------- | ---------------------- | ------------ | --------------------- |
Expand All @@ -179,6 +179,7 @@ This section lists functions that are explicitly declared public or payable. Ple
| solady/utils/Clone.sol | 1 |
| solmate/tokens/ERC20.sol | 1 |
| solmate/utils/SafeTransferLib.sol | 1 |
| src/interfaces/IwstETH.sol | 1 |

#### <span id=t-totals>Totals</span>

Expand Down Expand Up @@ -237,7 +238,7 @@ This section lists functions that are explicitly declared public or payable. Ple

| File Name | SHA-1 Hash |
|-------------|--------------|
| src/lido/LidoSplit.sol | a6d06d355c3e9abd9b6674b54a0b9b9960d3da33 |
| src/lido/ObolLidoSplit.sol | e60ac5c37593dd7b11dc04af62baa7b122e98ed5 |


Contracts Description Table
Expand All @@ -247,15 +248,11 @@ This section lists functions that are explicitly declared public or payable. Ple
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
| **IwSTETH** | Interface | |||
| └ | wrap | External ❗️ | 🛑 |NO❗️ |
||||||
| **LidoSplit** | Implementation | Clone |||
| **ObolLidoSplit** | Implementation | Clone |||
| └ | <Constructor> | Public ❗️ | 🛑 |NO❗️ |
| └ | splitWallet | Public ❗️ | |NO❗️ |
| └ | stETHAddress | Public ❗️ | |NO❗️ |
| └ | wstETHAddress | Public ❗️ | |NO❗️ |
| └ | distribute | External ❗️ | 🛑 |NO❗️ |
| └ | rescueFunds | External ❗️ | 🛑 |NO❗️ |


Legend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ Source Units in Scope: **`1`** (**100%**)

| Type | File | Logic Contracts | Interfaces | Lines | nLines | nSLOC | Comment Lines | Complex. Score | Capabilities |
| ---- | ------ | --------------- | ---------- | ----- | ------ | ----- | ------------- | -------------- | ------------ |
| 📝 | src/lido/LidoSplitFactory.sol | 1 | **** | 73 | 73 | 31 | 25 | 24 | **<abbr title='create/create2'>🌀</abbr>** |
| 📝 | **Totals** | **1** | **** | **73** | **73** | **31** | **25** | **24** | **<abbr title='create/create2'>🌀</abbr>** |
| 📝 | src/lido/ObolLidoSplitFactory.sol | 1 | **** | 54 | 54 | 18 | 24 | 22 | **<abbr title='create/create2'>🌀</abbr>** |
| 📝 | **Totals** | **1** | **** | **54** | **54** | **18** | **24** | **22** | **<abbr title='create/create2'>🌀</abbr>** |

<sub>
Legend: <a onclick="toggleVisibility('table-legend', this)">[➕]</a>
Expand Down Expand Up @@ -131,7 +131,7 @@ The analysis finished with **`0`** errors and **`0`** duplicate files.

#### <span id=t-inline-documentation>Inline Documentation</span>

- **Comment-to-Source Ratio:** On average there are`1.24` code lines per comment (lower=better).
- **Comment-to-Source Ratio:** On average there are`0.75` code lines per comment (lower=better).
- **ToDo's:** `0`

#### <span id=t-components>Components</span>
Expand All @@ -156,17 +156,17 @@ This section lists functions that are explicitly declared public or payable. Ple

| Total | 🌐Public |
| ---------- | --------- |
| 3 | 3 |
| 1 | 1 |

#### <span id=t-capabilities>Capabilities</span>

| Solidity Versions observed | 🧪 Experimental Features | 💰 Can Receive Funds | 🖥 Uses Assembly | 💣 Has Destroyable Contracts |
| -------------------------- | ------------------------ | -------------------- | ---------------- | ---------------------------- |
| `=0.8.17` | | **** | **** | **** |
| `0.8.19` | | **** | **** | **** |

| 📤 Transfers ETH | ⚡ Low-Level Calls | 👥 DelegateCall | 🧮 Uses Hash Functions | 🔖 ECRecover | 🌀 New/Create/Create2 |
| ---------------- | ----------------- | --------------- | ---------------------- | ------------ | --------------------- |
| **** | **** | **** | **** | **** | `yes`<br>→ `NewContract:LidoSplit` |
| **** | **** | **** | **** | **** | `yes`<br>→ `NewContract:ObolLidoSplit` |

| ♻️ TryCatch | Σ Unchecked |
| ---------- | ----------- |
Expand Down Expand Up @@ -236,7 +236,7 @@ This section lists functions that are explicitly declared public or payable. Ple

| File Name | SHA-1 Hash |
|-------------|--------------|
| src/lido/LidoSplitFactory.sol | fbe7fc44155c90479b3d1c3f46886b2e67f0d5c0 |
| src/lido/ObolLidoSplitFactory.sol | 39e631fd6416d7ab96b78b1b26855fda259dff64 |


Contracts Description Table
Expand All @@ -246,7 +246,7 @@ This section lists functions that are explicitly declared public or payable. Ple
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
| **LidoSplitFactory** | Implementation | |||
| **ObolLidoSplitFactory** | Implementation | |||
| └ | <Constructor> | Public ❗️ | 🛑 |NO❗️ |
| └ | createSplit | External ❗️ | 🛑 |NO❗️ |

Expand Down
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ remappings = [
'solady/=lib/solady/src/',
]
solc_version = '0.8.19'
gas_reports = ["*"]


[rpc_endpoints]
Expand All @@ -27,4 +28,4 @@ tab_width = 2
wrap_comments = true

[fuzz]
runs = 1000
runs = 100
2 changes: 1 addition & 1 deletion src/controllers/ImmutableSplitController.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.19;

import {ISplitMain} from "../interfaces/ISplitMain.sol";
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/ImmutableSplitControllerFactory.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.19;

import {ISplitMain} from "../interfaces/ISplitMain.sol";
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/IwstETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IwstETH {
function wrap(uint256 amount) external returns (uint256);
function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256);
}
62 changes: 46 additions & 16 deletions src/lido/LidoSplit.sol → src/lido/ObolLidoSplit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,26 @@ pragma solidity 0.8.19;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {Clone} from "solady/utils/Clone.sol";
import {IwstETH} from "src/interfaces/IwstETH.sol";

interface IwSTETH {
function wrap(uint256 amount) external returns (uint256);
}

/// @title LidoSplit
/// @title ObolLidoSplit
/// @author Obol
/// @notice A wrapper for 0xsplits/split-contracts SplitWallet that transforms
/// stETH token to wstETH token because stETH is a rebasing token
/// @dev Wraps stETH to wstETH and transfers to defined SplitWallet address
contract LidoSplit is Clone {

contract ObolLidoSplit is Clone {
error Invalid_Address();

error Invalid_FeeShare(uint256 fee);
error Invalid_FeeRecipient();

/// -----------------------------------------------------------------------
/// libraries
/// -----------------------------------------------------------------------
using SafeTransferLib for ERC20;
using SafeTransferLib for address;

address internal constant ETH_ADDRESS = address(0);
uint256 internal constant PERCENTAGE_SCALE = 1e5;

/// -----------------------------------------------------------------------
/// storage - cwia offsets
Expand All @@ -34,20 +33,35 @@ contract LidoSplit is Clone {
// 0; first item
uint256 internal constant SPLIT_WALLET_ADDRESS_OFFSET = 0;


/// -----------------------------------------------------------------------
/// storage
/// -----------------------------------------------------------------------

/// @notice stETH token
ERC20 public immutable stETH;

/// @notice wstETH token
ERC20 public immutable wstETH;

constructor(ERC20 _stETH, ERC20 _wstETH) {
/// @notice fee address
address public immutable feeRecipient;

/// @notice fee share
uint256 public immutable feeShare;
Comment on lines +46 to +50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is an intentional decision, but of course these being immutably set on the implementation means they can't ever be updated. If you wanted the ability to change while still setting immutably on the implementation, you'd need to immutably set an address for another contract that holds and returns the fee info, and allows updates.

Copy link
Collaborator Author

@samparsky samparsky Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we don't want the fee to change. Changing the fee would require a sign-off from Lido and a lot of discussions and most likely require new deployments.


/// @notice Constructor
/// @param _feeRecipient address to receive fee
/// @param _feeShare fee share scaled by PERCENTAGE_SCALE
/// @param _stETH stETH address
/// @param _wstETH wstETH address
constructor(address _feeRecipient, uint256 _feeShare, ERC20 _stETH, ERC20 _wstETH) {
samparsky marked this conversation as resolved.
Show resolved Hide resolved
if (_feeShare >= PERCENTAGE_SCALE) revert Invalid_FeeShare(_feeShare);
if (_feeShare > 0 && _feeRecipient == address(0)) revert Invalid_FeeRecipient();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! This would brick the contract because of a revert in the transfer.


feeRecipient = _feeRecipient;
stETH = _stETH;
wstETH = _wstETH;
feeShare = _feeShare;
}

/// Address of split wallet to send funds to to
Expand All @@ -65,17 +79,33 @@ contract LidoSplit is Clone {
// approve the wstETH
stETH.approve(address(wstETH), balance);
// wrap into wseth
amount = IwSTETH(address(wstETH)).wrap(balance);
// transfer to split wallet
ERC20(wstETH).safeTransfer(splitWallet(), amount);
// we ignore the return value
IwstETH(address(wstETH)).wrap(balance);
// we use balanceOf here in case some wstETH is stuck in the
// contract we would be able to rescue it
amount = ERC20(wstETH).balanceOf(address(this));

if (feeShare > 0) {
uint256 fee = (amount * feeShare) / PERCENTAGE_SCALE;
// transfer to split wallet
// update amount to reflect fee charged
ERC20(wstETH).safeTransfer(splitWallet(), amount -= fee);
// transfer to fee address
ERC20(wstETH).safeTransfer(feeRecipient, fee);
} else {
// transfer to split wallet
ERC20(wstETH).safeTransfer(splitWallet(), amount);
}
}

/// @notice Rescue stuck ETH and tokens
/// Uses token == address(0) to represent ETH
/// @return balance Amount of ETH or tokens rescued
function rescueFunds(address token) external returns (uint256 balance) {
if (token == address(stETH)) revert Invalid_Address();

// we check wstETH here so rescueFunds can't be used
// to bypass fee
if (token == address(stETH) || token == address(wstETH)) revert Invalid_Address();
samparsky marked this conversation as resolved.
Show resolved Hide resolved

if (token == ETH_ADDRESS) {
balance = address(this).balance;
if (balance > 0) splitWallet().safeTransferETH(balance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ pragma solidity 0.8.19;

import {LibClone} from "solady/utils/LibClone.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import "./LidoSplit.sol";
import "./ObolLidoSplit.sol";

/// @title LidoSplitFactory
/// @title ObolLidoSplitFactory
/// @author Obol
/// @notice A factory contract for cheaply deploying LidoSplit.
/// @notice A factory contract for cheaply deploying ObolLidoSplit.
/// @dev The address returned should be used to as reward address for Lido
contract LidoSplitFactory {
contract ObolLidoSplitFactory {
/// -----------------------------------------------------------------------
/// errors
/// -----------------------------------------------------------------------
Expand All @@ -27,17 +27,17 @@ contract LidoSplitFactory {
/// -----------------------------------------------------------------------

/// Emitted after lido split
event CreateLidoSplit(address split);
event CreateObolLidoSplit(address split);

/// -----------------------------------------------------------------------
/// storage
/// -----------------------------------------------------------------------

/// @dev lido split implementation
LidoSplit public immutable lidoSplitImpl;
ObolLidoSplit public immutable lidoSplitImpl;

constructor(ERC20 _stETH, ERC20 _wstETH) {
lidoSplitImpl = new LidoSplit(_stETH, _wstETH);
constructor(address _feeRecipient, uint256 _feeShare, ERC20 _stETH, ERC20 _wstETH) {
lidoSplitImpl = new ObolLidoSplit(_feeRecipient, _feeShare, _stETH, _wstETH);
}

/// Creates a wrapper for splitWallet that transforms stETH token into
Expand All @@ -49,6 +49,6 @@ contract LidoSplitFactory {

lidoSplit = address(lidoSplitImpl).clone(abi.encodePacked(splitWallet));

emit CreateLidoSplit(lidoSplit);
emit CreateObolLidoSplit(lidoSplit);
}
}
2 changes: 1 addition & 1 deletion src/test/controllers/IMSC.t.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
Expand Down
2 changes: 1 addition & 1 deletion src/test/controllers/IMSCFactory.t.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
Expand Down
39 changes: 0 additions & 39 deletions src/test/lido/LIdoSplitFactory.t.sol

This file was deleted.

Loading
Loading