Skip to content

Commit

Permalink
feat: stakewise tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cosminobol committed Feb 7, 2024
1 parent 76b99ae commit 265ed75
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 2 deletions.
36 changes: 34 additions & 2 deletions src/stakewise/ObolStakewiseSplit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,39 @@ contract ObolStakewiseSplit is Clone {
return _getArgAddress(SPLIT_WALLET_ADDRESS_OFFSET);
}

function distribute() external returns (uint256 amount) {}
/// Transfers the vault token balance to splitWallet for distribution
/// @return amount Amount of vault token transferred to splitWallet
function distribute() external returns (uint256 amount) {
// get current balance
amount = vaultToken.balanceOf(address(this));

function rescueFunds(address token) external returns (uint256 balance) {}
if (feeShare > 0) {
uint256 fee = (amount * feeShare) / PERCENTAGE_SCALE;
// transfer to split wallet
// update amount to reflect fee charged
vaultToken.safeTransfer(splitWallet(), amount -= fee);
// transfer to fee address
vaultToken.safeTransfer(feeRecipient, fee);
} else {
// transfer to split wallet
vaultToken.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) {
// we check wstETH here so rescueFunds can't be used
// to bypass fee
if (token == address(vaultToken)) revert Invalid_Address();

if (token == ETH_ADDRESS) {
balance = address(this).balance;
if (balance > 0) splitWallet().safeTransferETH(balance);
} else {
balance = ERC20(token).balanceOf(address(this));
if (balance > 0) ERC20(token).safeTransfer(splitWallet(), balance);
}
}
}
142 changes: 142 additions & 0 deletions src/test/stakewise/ObolStakewiseSplit.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {MockERC20} from "src/test/utils/mocks/MockERC20.sol";
import {ObolStakewiseSplitFactory, ObolStakewiseSplit} from "src/stakewise/ObolStakewiseSplitFactory.sol";

contract ObolStakewiseSplitTest is Test {
uint256 internal constant PERCENTAGE_SCALE = 1e5;

ObolStakewiseSplitFactory internal stakewiseSplitFactory;
ObolStakewiseSplitFactory internal stakewiseSplitFactoryWithFee;

ObolStakewiseSplit internal stakewiseSplit;
ObolStakewiseSplit internal stakewiseSplitWithFee;

address demoSplit;

address feeRecipient;
uint256 feeShare;

MockERC20 vaultToken;
MockERC20 canRescueToken;
uint256 tokenAmount;

function setUp() public {
uint256 mainnetBlock = 19_167_592;
vm.createSelectFork(getChain("mainnet").rpcUrl, mainnetBlock);

feeShare = 1e4; //10%
demoSplit = makeAddr("StakewiseDemoSplit");
feeRecipient = makeAddr("StakewiseFeeRecipient");
vaultToken = new MockERC20("Test", "TST", uint8(18));
canRescueToken = new MockERC20("Test2", "TST2", uint8(18));

stakewiseSplitFactory = new ObolStakewiseSplitFactory(address(0), 0, ERC20(vaultToken));

stakewiseSplitFactoryWithFee = new ObolStakewiseSplitFactory(feeRecipient, feeShare, ERC20(vaultToken));

stakewiseSplit = ObolStakewiseSplit(stakewiseSplitFactory.createSplit(demoSplit));
stakewiseSplitWithFee = ObolStakewiseSplit(stakewiseSplitFactoryWithFee.createSplit(demoSplit));

tokenAmount = 1000 * 1e18;
}

function test_stakewise_CannotCreateInvalidFeeRecipient() public {
vm.expectRevert(ObolStakewiseSplit.Invalid_FeeRecipient.selector);
new ObolStakewiseSplit(address(0), 1, ERC20(vaultToken));
}

function test_stakewise_cannotCreateInvalidFeeShare() public {
vm.expectRevert(abi.encodeWithSelector(ObolStakewiseSplit.Invalid_FeeShare.selector, PERCENTAGE_SCALE + 1));
new ObolStakewiseSplit(address(1), PERCENTAGE_SCALE + 1, ERC20(vaultToken));

vm.expectRevert(abi.encodeWithSelector(ObolStakewiseSplit.Invalid_FeeShare.selector, PERCENTAGE_SCALE));
new ObolStakewiseSplit(address(1), PERCENTAGE_SCALE, ERC20(vaultToken));
}

function test_stakewise_cloneArgsAreCorrect() public {
assertEq(stakewiseSplit.splitWallet(), demoSplit, "invalid address");
assertEq(address(stakewiseSplit.vaultToken()), address(vaultToken), "invalid vault token address");
assertEq(stakewiseSplit.feeRecipient(), address(0), "invalid fee recipient");
assertEq(stakewiseSplit.feeShare(), 0, "invalid fee amount");

assertEq(stakewiseSplitWithFee.splitWallet(), demoSplit, "invalid address");
assertEq(address(stakewiseSplitWithFee.vaultToken()), address(vaultToken), "invalid vault token address");
assertEq(stakewiseSplitWithFee.feeRecipient(), feeRecipient, "invalid fee recipient /2");
assertEq(stakewiseSplitWithFee.feeShare(), feeShare, "invalid fee share /2");
}

function test_stakewise_canRescueFunds() public {
// rescue ETH
uint256 amountOfEther = 1 ether;
deal(address(stakewiseSplit), amountOfEther);

uint256 balance = stakewiseSplit.rescueFunds(address(0));
assertEq(balance, amountOfEther, "balance not rescued");
assertEq(address(stakewiseSplit).balance, 0, "balance is not zero");
assertEq(address(stakewiseSplit.splitWallet()).balance, amountOfEther, "rescue not successful");

// rescue tokens
deal(address(canRescueToken), address(stakewiseSplit), tokenAmount);
uint256 tokenBalance = stakewiseSplit.rescueFunds(address(canRescueToken));
assertEq(tokenBalance, tokenAmount, "token - balance not rescued");
assertEq(canRescueToken.balanceOf(address(stakewiseSplit)), 0, "token - balance is not zero");
assertEq(canRescueToken.balanceOf(stakewiseSplit.splitWallet()), tokenAmount, "token - rescue not successful");
}

function test_stakewise_cannotVaultTokens() public {
deal(address(vaultToken), address(stakewiseSplit), tokenAmount);
vm.expectRevert(ObolStakewiseSplit.Invalid_Address.selector);
stakewiseSplit.rescueFunds(address(vaultToken));
}

function test_stakewise_canDistributeWithFee() public {
deal(address(vaultToken), address(stakewiseSplitWithFee), tokenAmount);
uint256 prevBalance = vaultToken.balanceOf(demoSplit);

uint256 amount = stakewiseSplitWithFee.distribute();
assertTrue(amount > 0, "invalid amount");

uint256 afterBalance = vaultToken.balanceOf(demoSplit);
assertGe(afterBalance, prevBalance, "after balance not greater");

uint256 expectedFee = (tokenAmount * feeShare) / PERCENTAGE_SCALE;
assertEq(ERC20(vaultToken).balanceOf(feeRecipient), expectedFee, "invalid fee transferred");
assertEq(vaultToken.balanceOf(demoSplit), tokenAmount - expectedFee, "invalid amount");
}

function test_stakewise_fuzzCanDistributeWithFee(
address anotherSplit,
uint256 amountToDistribute,
address fuzzFeeRecipient,
uint256 fuzzFeeShare
) public {
vm.assume(anotherSplit != address(0));
vm.assume(fuzzFeeRecipient != anotherSplit);
vm.assume(fuzzFeeShare > 0 && fuzzFeeShare < PERCENTAGE_SCALE);
vm.assume(fuzzFeeRecipient != address(0));
vm.assume(amountToDistribute > 1 ether);
vm.assume(amountToDistribute < 10 ether);

ObolStakewiseSplitFactory fuzzFactorySplitWithFee =
new ObolStakewiseSplitFactory(fuzzFeeRecipient, fuzzFeeShare, ERC20(vaultToken));
ObolStakewiseSplit fuzzSplitWithFee = ObolStakewiseSplit(fuzzFactorySplitWithFee.createSplit(anotherSplit));

deal(address(vaultToken), address(fuzzSplitWithFee), amountToDistribute);

uint256 prevBalance = vaultToken.balanceOf(anotherSplit);

uint256 amount = fuzzSplitWithFee.distribute();
assertTrue(amount > 0, "invalid amount");

uint256 afterBalance = vaultToken.balanceOf(anotherSplit);
assertGe(afterBalance, prevBalance, "after balance not greater");

uint256 expectedFee = (amountToDistribute * fuzzFeeShare) / PERCENTAGE_SCALE;
assertEq(vaultToken.balanceOf(fuzzFeeRecipient), expectedFee, "invalid fee transferred");
assertEq(vaultToken.balanceOf(anotherSplit), amountToDistribute - expectedFee, "invalid amount");
}
}
55 changes: 55 additions & 0 deletions src/test/stakewise/ObolStakewiseSplitFactory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {MockERC20} from "src/test/utils/mocks/MockERC20.sol";
import {ObolStakewiseSplitFactory} from "src/stakewise/ObolStakewiseSplitFactory.sol";

contract ObolStakewiseSplitFactoryTest is Test {
ObolStakewiseSplitFactory internal stakewiseSplitFactory;
ObolStakewiseSplitFactory internal stakewiseSplitFactoryWithFee;

MockERC20 internal vaultToken;

address feeRecipient;
uint256 feeShare;
address demoSplit;

event CreateObolStakewiseSplit(address split);

function setUp() public {
uint256 mainnetBlock = 19_167_592;
vm.createSelectFork(getChain("mainnet").rpcUrl, mainnetBlock);

feeShare = 1e4; //10%
demoSplit = makeAddr("StakewiseDemoSplit");
feeRecipient = makeAddr("StakewiseFeeRecipient");

vaultToken = new MockERC20("Test", "TST", uint8(18));

stakewiseSplitFactory = new ObolStakewiseSplitFactory(address(0), 0, ERC20(vaultToken));

stakewiseSplitFactoryWithFee = new ObolStakewiseSplitFactory(feeRecipient, feeShare, ERC20(vaultToken));
}

function test_stakewise_canCreateSplit() public {
vm.expectEmit(true, true, true, false, address(stakewiseSplitFactory));
emit CreateObolStakewiseSplit(address(0x1));

stakewiseSplitFactory.createSplit(demoSplit);

vm.expectEmit(true, true, true, false, address(stakewiseSplitFactoryWithFee));
emit CreateObolStakewiseSplit(address(0x1));

stakewiseSplitFactoryWithFee.createSplit(demoSplit);
}

function test_stakewise_cannotCreateSplitInvalidAddress() public {
vm.expectRevert(ObolStakewiseSplitFactory.Invalid_Wallet.selector);
stakewiseSplitFactory.createSplit(address(0));

vm.expectRevert(ObolStakewiseSplitFactory.Invalid_Wallet.selector);
stakewiseSplitFactoryWithFee.createSplit(address(0));
}
}

0 comments on commit 265ed75

Please sign in to comment.