Skip to content

Commit

Permalink
chore: base split implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
cosminobol committed Feb 28, 2024
1 parent 7616ef6 commit a4f0d6e
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 103 deletions.
114 changes: 114 additions & 0 deletions src/base/BaseSplit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: GPL-3.0-or-later
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";

abstract contract BaseSplit is Clone {
error Invalid_Address();
error Invalid_FeeShare();

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

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

/// @notice fee share
uint256 public immutable feeShare;

/// @notice fee address
address public immutable feeRecipient;

// withdrawal (adress, 20 bytes)
// 0; first item
uint256 internal constant WITHDRAWAL_ADDRESS_OFFSET = 0;
// 20 = withdrawalAddress_offset (0) + withdrawalAddress_size (address, 20 bytes)
uint256 internal constant TOKEN_ADDRESS_OFFSET = 20;

constructor(address _feeRecipient, uint256 _feeShare) {
if (_feeShare == 0 || _feeShare > PERCENTAGE_SCALE) revert Invalid_FeeShare();

feeShare = _feeShare;
feeRecipient = _feeRecipient;
}

/// -----------------------------------------------------------------------
/// View
/// -----------------------------------------------------------------------

/// Address to send funds to to
/// @dev equivalent to address public immutable withdrawalAddress
function withdrawalAddress() public pure returns (address) {
return _getArgAddress(WITHDRAWAL_ADDRESS_OFFSET);
}

/// Token addresss
/// @dev equivalent to address public immutable token
function token() public pure virtual returns (address) {
return _getArgAddress(TOKEN_ADDRESS_OFFSET);
}

/// -----------------------------------------------------------------------
/// Public
/// -----------------------------------------------------------------------

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

if (tokenAddress == ETH_ADDRESS) {
balance = address(this).balance;
if (balance > 0) withdrawalAddress().safeTransferETH(balance);
} else {
balance = ERC20(tokenAddress).balanceOf(address(this));
if (balance > 0) ERC20(tokenAddress).safeTransfer(withdrawalAddress(), balance);
}
}

/// @notice distribute funds to withdrawal address
function distribute() external virtual {
uint256 amount = 0;
address tokenAddress = token();

if (tokenAddress == ETH_ADDRESS) {
amount = address(this).balance;
} else {
amount = ERC20(tokenAddress).balanceOf(address(this));
}

if (feeShare > 0) {
//TODO check if feeShares > 0
uint256 fee = (amount * feeShare) / PERCENTAGE_SCALE;
_transfer(tokenAddress, feeRecipient, fee);
_transfer(tokenAddress, withdrawalAddress(), amount -= fee);
} else {
_transfer(tokenAddress, withdrawalAddress(), amount);
}

}

/// -----------------------------------------------------------------------
/// Internal
/// -----------------------------------------------------------------------

function _beforeRescueFunds(address tokenAddress) internal virtual {}

function _transfer(
address tokenAddress,
address receiver,
uint256 amount
) internal {
if (tokenAddress == ETH_ADDRESS) {
receiver.safeTransferETH(amount);
} else {
ERC20(tokenAddress).safeTransfer(receiver, amount);
}
}
}
19 changes: 19 additions & 0 deletions src/base/BaseSplitFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.19;


abstract contract BaseSplitFactory {
/// -----------------------------------------------------------------------
/// errors
/// -----------------------------------------------------------------------
/// @dev Invalid address
error Invalid_Address();

/// -----------------------------------------------------------------------
/// events
/// -----------------------------------------------------------------------
/// Emitted on createCollector
event CreateSplit(address token, address withdrawalAddress);

function createCollector(address token, address withdrawalAddress) external virtual returns (address collector);
}
89 changes: 4 additions & 85 deletions src/collector/ObolCollector.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,97 +4,16 @@ 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 {BaseSplit} from "../base/BaseSplit.sol";

/// @title ObolCollector
/// @author Obol
/// @notice An contract used to receive and distribute rewards minus fees
contract ObolCollector is Clone {

error Invalid_Address();
error Invalid_FeeShare();

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

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

/// @notice fee share
uint256 public immutable feeShare;

/// @notice fee address
address public immutable feeRecipient;

// withdrawal (adress, 20 bytes)
// 0; first item
uint256 internal constant WITHDRAWAL_ADDRESS_OFFSET = 0;
// 20 = withdrawalAddress_offset (0) + withdrawalAddress_size (address, 20 bytes)
uint256 internal constant TOKEN_ADDRESS_OFFSET = 20;

constructor(address _feeRecipient, uint256 _feeShare) {
if (_feeShare == 0 || _feeShare > PERCENTAGE_SCALE) revert Invalid_FeeShare();
contract ObolCollector is BaseSplit {
constructor(address _feeRecipient, uint256 _feeShare) BaseSplit(_feeRecipient, _feeShare) {}

feeShare = _feeShare;
feeRecipient = _feeRecipient;
}

/// @notice distribute funds to withdrawal address
function distribute() external virtual {
uint256 amount = 0;
address tokenAddress = token();

if (tokenAddress == ETH_ADDRESS) {
amount = address(this).balance;
} else {
amount = ERC20(tokenAddress).balanceOf(address(this));
}

uint256 fee = (amount * feeShare) / PERCENTAGE_SCALE;
_transfer(tokenAddress, feeRecipient, fee);
_transfer(tokenAddress, withdrawalAddress(), amount -= fee);
}

/// Address to send funds to to
/// @dev equivalent to address public immutable withdrawalAddress
function withdrawalAddress() public pure returns (address) {
return _getArgAddress(WITHDRAWAL_ADDRESS_OFFSET);
}

/// Token addresss
/// @dev equivalent to address public immutable token
function token() public pure returns (address) {
return _getArgAddress(TOKEN_ADDRESS_OFFSET);
}

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

if (tokenAddress == ETH_ADDRESS) {
balance = address(this).balance;
if (balance > 0) withdrawalAddress().safeTransferETH(balance);
} else {
balance = ERC20(tokenAddress).balanceOf(address(this));
if (balance > 0) ERC20(tokenAddress).safeTransfer(withdrawalAddress(), balance);
}
}

function _transfer(
address tokenAddress,
address receiver,
uint256 amount
) internal {
if (tokenAddress == ETH_ADDRESS) {
receiver.safeTransferETH(amount);
} else {
ERC20(tokenAddress).safeTransfer(receiver, amount);
}
}
}
22 changes: 4 additions & 18 deletions src/collector/ObolCollectorFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,18 @@
pragma solidity 0.8.19;
import {LibClone} from "solady/utils/LibClone.sol";
import {ObolCollector} from "./ObolCollector.sol";
import {BaseSplitFactory} from "../base/BaseSplitFactory.sol";

/// @title ObolCollector
/// @author Obol
/// @notice A factory contract for cheaply deploying ObolCollector.
/// @dev The address returned should be used to as reward address collecting rewards
contract ObolCollectorFactory {
/// -----------------------------------------------------------------------
/// errors
/// -----------------------------------------------------------------------

/// @dev Invalid address
error Invalid_Address();

contract ObolCollectorFactory is BaseSplitFactory{
/// -----------------------------------------------------------------------
/// libraries
/// -----------------------------------------------------------------------
using LibClone for address;

/// -----------------------------------------------------------------------
/// events
/// -----------------------------------------------------------------------

/// Emitted on createCollector
event CreateCollector(address token, address withdrawalAddress);


/// -----------------------------------------------------------------------
/// storage
/// -----------------------------------------------------------------------
Expand All @@ -43,13 +29,13 @@ contract ObolCollectorFactory {
/// @dev address(0) is used to represent ETH
/// @param token collector token address
/// @param withdrawalAddress withdrawalAddress to receive tokens
function createCollector(address token, address withdrawalAddress) external returns (address collector) {
function createCollector(address token, address withdrawalAddress) external override returns (address collector) {
if (withdrawalAddress == address(0)) revert Invalid_Address();

collector = address(collectorImpl).clone(
abi.encodePacked(withdrawalAddress, token)
);

emit CreateCollector(token, withdrawalAddress);
emit CreateSplit(token, withdrawalAddress);
}
}

0 comments on commit a4f0d6e

Please sign in to comment.