Skip to content

Commit

Permalink
Another deletion for review on updated master
Browse files Browse the repository at this point in the history
  • Loading branch information
sin3point14 committed Nov 24, 2023
1 parent a7bbdd6 commit 1a5d72e
Show file tree
Hide file tree
Showing 2 changed files with 0 additions and 338 deletions.
134 changes: 0 additions & 134 deletions src/FeeRewardsManager.sol
Original file line number Diff line number Diff line change
@@ -1,134 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/access/Ownable.sol";

contract RewardsCollector is Ownable {
event CollectedReward(
address withdrawalCredential,
uint256 withdrawalFee,
address owner,
uint256 ownerFee
);

// 1 - fee % will go to the user in this address.
address public withdrawalCredential;

// Fee's numerator.
uint32 public feeNumerator;

// Fee denominator, if `feeNumerator = 500`,
// the tax is 500/10000 = 5/100 = 5%.
uint32 public constant FEE_DENOMINATOR = 10000;

// Allow receiving MEV and other rewards.
receive() external payable {}

function collectRewards() public payable {
uint256 ownerAmount = (address(this).balance * feeNumerator) /
FEE_DENOMINATOR;
uint256 returnedAmount = address(this).balance - ownerAmount;
require(
ownerAmount != 0 || returnedAmount != 0,
"Nothing to distribute"
);
address owner = owner();
emit CollectedReward(
withdrawalCredential,
returnedAmount,
owner,
ownerAmount
);
// This can be used to call this contract again (reentrancy)
// but since all funds from this contract are used for the owner
payable(owner).transfer(ownerAmount);
(bool sent, ) = payable(withdrawalCredential).call{
value: returnedAmount
}("");
require(sent, "Failed to send Ether back to withdrawal credential");
}

constructor(address _withdrawalCredential, uint32 _feeNumerator) {
withdrawalCredential = _withdrawalCredential;
feeNumerator = _feeNumerator;
}

function changeFee(uint32 _newFeeNumerator) public onlyOwner {
feeNumerator = _newFeeNumerator;
}
}

contract FeeRewardsManager is Ownable {
uint32 public defaultFeeNumerator;

constructor(uint32 _defaultFeeNumerator) {
defaultFeeNumerator = _defaultFeeNumerator;
}

event ContractDeployed(address contractAddress, uint32 feeNumerator);

function changeDefaultFee(uint32 _newFeeNumerator) public onlyOwner {
defaultFeeNumerator = _newFeeNumerator;
}

function createFeeContract(
address _withdrawalCredential
) public returns (address payable) {
bytes32 withdrawalCredentialBytes = bytes32(
uint256(uint160(_withdrawalCredential))
);
address addr = address(
// Uses CREATE2 opcode.
new RewardsCollector{salt: withdrawalCredentialBytes}(
_withdrawalCredential,
defaultFeeNumerator
)
);
emit ContractDeployed(addr, defaultFeeNumerator);
return payable(addr);
}

function predictFeeContractAddress(
address _withdrawalCredential
) public view returns (address) {
bytes memory bytecode = type(RewardsCollector).creationCode;
bytecode = abi.encodePacked(
bytecode,
abi.encode(_withdrawalCredential, defaultFeeNumerator)
);
bytes32 withdrawalCredentialBytes = bytes32(
uint256(uint160(_withdrawalCredential))
);
bytes32 hash = keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
withdrawalCredentialBytes,
keccak256(bytecode)
)
);
return address(uint160(uint(hash)));
}

function changeFee(
address payable _feeContract,
uint32 _newFee
) public onlyOwner {
RewardsCollector(_feeContract).changeFee(_newFee);
}

function batchCollectRewards(
address payable[] calldata feeAddresses
) public {
for (uint32 i = 0; i < feeAddresses.length; ++i) {
RewardsCollector(feeAddresses[i]).collectRewards();
}
}

receive() external payable {}

// Withdraws Eth from the manager contract.
function getEth(address addr) external onlyOwner {
payable(addr).transfer(address(this).balance);
}
}
204 changes: 0 additions & 204 deletions test/FeeRewardsManager.t.sol
Original file line number Diff line number Diff line change
@@ -1,204 +0,0 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/FeeRewardsManager.sol";

contract ReentrantAttack {
fallback() external payable {
RewardsCollector(payable(msg.sender)).collectRewards();
}
}

contract ChangeOwnerContract {
fallback() external payable {
RewardsCollector(payable(msg.sender)).transferOwnership(address(0x200));
}
}

contract WithdrawalContract {
fallback() external payable {}
}

contract FeeRewardsTest is Test {
FeeRewardsManager public feeRewardsManager;

function setUp() public {
feeRewardsManager = new FeeRewardsManager(2800);
}

function testHappyPath() public {
address withdrawalCredential = address(100);
vm.deal(address(0), 10 ether);
address derivedAddr = feeRewardsManager.predictFeeContractAddress(
withdrawalCredential
);

vm.deal(derivedAddr, 10 ether);

address payable addr = feeRewardsManager.createFeeContract(
withdrawalCredential
);

//derived address matches the function to get one.
assertEq(derivedAddr, addr);

// derived address has the parent's as owner
assertEq(
address(feeRewardsManager),
RewardsCollector(payable(addr)).owner()
);

uint256 amountInContract = address(addr).balance;

// We've got 10 ether in the contract.
assertEq(amountInContract, 10 ether);

RewardsCollector(payable(addr)).collectRewards();

// User receives 72%.
assertEq(withdrawalCredential.balance, 7.2 ether);

// We receive 28%.
assertEq(address(feeRewardsManager).balance, 2.8 ether);

feeRewardsManager.getEth(address(101));

// We've got the ether.
assertEq(address(feeRewardsManager).balance, 0 ether);
assertEq(address(101).balance, 2.8 ether);
}

function createWithdrawalSimulateRewards(
address withdrawalCredential,
uint reward
) public returns (RewardsCollector) {
address payable addr = feeRewardsManager.createFeeContract(
withdrawalCredential
);
addr.transfer(reward);
return RewardsCollector(payable(addr));
}

function testGetMultipleRewards() public {
address payable[] memory addrs = new address payable[](100);
for (uint256 i = 0; i < 100; ++i) {
addrs[i] = payable(
address(
createWithdrawalSimulateRewards(
address(uint160(i + 100)),
10 ether
)
)
);
}
feeRewardsManager.batchCollectRewards(addrs);
assertEq(address(feeRewardsManager).balance, 280 ether);
}

function testChangeDefaultFee() public {
feeRewardsManager.changeDefaultFee(100);
assertEq(feeRewardsManager.defaultFeeNumerator(), 100);

address addr = address(
createWithdrawalSimulateRewards(address(100), 10 ether)
);
RewardsCollector(payable(addr)).collectRewards();
assertEq(address(100).balance, 9.9 ether);
// We receive 1%.
assertEq(address(feeRewardsManager).balance, 0.1 ether);
}

function testChangeFee() public {
address addr = address(
createWithdrawalSimulateRewards(address(100), 10 ether)
);
feeRewardsManager.changeFee(payable(addr), 10000);
RewardsCollector(payable(addr)).collectRewards();
assertEq(address(100).balance, 0 ether);
// We receive 100%.
assertEq(address(feeRewardsManager).balance, 10 ether);
}

function testZeroRewards() public {
address addr = address(
createWithdrawalSimulateRewards(address(100), 0)
);
vm.expectRevert("Nothing to distribute");
RewardsCollector(payable(addr)).collectRewards();
}

function testFuzzyHappyPathNoContracts(
uint128 rewards,
address owner
) public {
// Some smart contracts will revert when called, avoid them.
vm.assume(owner.code.length == 0);
// Avoid some precompiles.
vm.assume(owner > address(0x100));
vm.assume(rewards > 10000);
vm.assume(rewards < 1e30);
vm.deal(address(this), rewards);
RewardsCollector collector = createWithdrawalSimulateRewards(
owner,
rewards
);
uint256 chorusAmount = (address(collector).balance *
uint256(collector.feeNumerator())) / collector.FEE_DENOMINATOR();
uint256 withdrawalCredentialsAmount = address(collector).balance -
chorusAmount;
uint256 chorusBalanceBefore = address(feeRewardsManager).balance;
uint256 withdrawalBalanceBefore = owner.balance;
collector.collectRewards();
assertEq(
address(owner).balance,
withdrawalBalanceBefore + withdrawalCredentialsAmount
);
assertEq(
address(feeRewardsManager).balance,
chorusBalanceBefore + chorusAmount
);
}

// Test calling `collectRewards` from a contract that calls `collectRewards`
// again, this will revert as the Ether is divided just to Chorus and the
// withdrawal credential.
function testReentrantAttack() public {
ReentrantAttack withdrawalCredentialContract = new ReentrantAttack();
address addr = address(
createWithdrawalSimulateRewards(
address(withdrawalCredentialContract),
10 ether
)
);
vm.expectRevert("Failed to send Ether back to withdrawal credential");
RewardsCollector(payable(addr)).collectRewards();
}

function testSendToContractWithdrawalCredential() public {
WithdrawalContract withdrawalCredentialContract = new WithdrawalContract();
address addr = address(
createWithdrawalSimulateRewards(
address(withdrawalCredentialContract),
10 ether
)
);
RewardsCollector(payable(addr)).collectRewards();
assertEq(address(feeRewardsManager).balance, 2.8 ether);
assertEq(address(withdrawalCredentialContract).balance, 7.2 ether);
}

function testChangeOwnerWithContract() public {
WithdrawalContract withdrawalCredentialContract = new WithdrawalContract();
address addr = address(
createWithdrawalSimulateRewards(
address(withdrawalCredentialContract),
10 ether
)
);
RewardsCollector(payable(addr)).collectRewards();
assertEq(address(feeRewardsManager).balance, 2.8 ether);
assertEq(address(withdrawalCredentialContract).balance, 7.2 ether);
}
}

0 comments on commit 1a5d72e

Please sign in to comment.