Skip to content
Draft
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
129 changes: 129 additions & 0 deletions packages/contracts/.openzeppelin/unknown-8453.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@
"address": "0x48EA70BCe76FE2F0c79B29Bf852a1DCF957982aa",
"txHash": "0x99bcdd5255ef14b7f295a8517d4e5072f15c8eb8059613fe2990c073902cf638",
"kind": "transparent"
},
{
"address": "0x588BBC083fe6AB67C18c93BC77974e25eDd23349",
"txHash": "0x115a9296400b9500a13e5ef6d34e44c0f4cd558da459f2bf6c1fdf4fa18bd0c0",
"kind": "transparent"
}
],
"impls": {
Expand Down Expand Up @@ -7918,6 +7923,130 @@
"types": {},
"namespaces": {}
}
},
"85789a4c98323c8244fc8f8fb860862727e647f4c35b41e29c7c3cdefed493cc": {
"address": "0xE3BA6B6039E1313A00157f41F017df1d661B76Ec",
"txHash": "0x30536ffe36056efda7c410d616d73f670c0739e00d67946311e0c4a9bd5110e1",
"layout": {
"solcVersion": "0.8.11",
"storage": [],
"types": {},
"namespaces": {}
}
},
"d545cc9d4c2303c27321d4e7a36d80a477c2503c4403883068bc655dd2c2bd9b": {
"address": "0x58b8c376cbd6d57df0a5d495079F57b90fc82865",
"txHash": "0xc290ff2a8df48f4b22e9b4f64730dafdd3a619f431cf65bba9710252289d3017",
"layout": {
"solcVersion": "0.8.11",
"storage": [
{
"label": "_initialized",
"offset": 0,
"slot": "0",
"type": "t_uint8",
"contract": "Initializable",
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62",
"retypedFrom": "bool"
},
{
"label": "_initializing",
"offset": 1,
"slot": "0",
"type": "t_bool",
"contract": "Initializable",
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67"
}
],
"types": {
"t_bool": {
"label": "bool",
"numberOfBytes": "1"
},
"t_uint8": {
"label": "uint8",
"numberOfBytes": "1"
}
},
"namespaces": {}
}
},
"353a621709f9faf1b74aba1fbf9643f15262895f5ab1e638ade3e9ed4bfcd400": {
"address": "0x81E108071CDcFF9ec8b9Cf443bF6c5E93a3c3313",
"txHash": "0xa0066876c78e109da18e7e228f74a03d0ced19c7e9dc3a0391e0dd72cca684fd",
"layout": {
"solcVersion": "0.8.11",
"storage": [
{
"label": "_initialized",
"offset": 0,
"slot": "0",
"type": "t_uint8",
"contract": "Initializable",
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62",
"retypedFrom": "bool"
},
{
"label": "_initializing",
"offset": 1,
"slot": "0",
"type": "t_bool",
"contract": "Initializable",
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67"
},
{
"label": "__gap",
"offset": 0,
"slot": "1",
"type": "t_array(t_uint256)50_storage",
"contract": "ContextUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
},
{
"label": "_owner",
"offset": 0,
"slot": "51",
"type": "t_address",
"contract": "OwnableUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22"
},
{
"label": "__gap",
"offset": 0,
"slot": "52",
"type": "t_array(t_uint256)49_storage",
"contract": "OwnableUpgradeable",
"src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94"
}
],
"types": {
"t_address": {
"label": "address",
"numberOfBytes": "20"
},
"t_array(t_uint256)49_storage": {
"label": "uint256[49]",
"numberOfBytes": "1568"
},
"t_array(t_uint256)50_storage": {
"label": "uint256[50]",
"numberOfBytes": "1600"
},
"t_bool": {
"label": "bool",
"numberOfBytes": "1"
},
"t_uint256": {
"label": "uint256",
"numberOfBytes": "32"
},
"t_uint8": {
"label": "uint8",
"numberOfBytes": "1"
}
},
"namespaces": {}
}
}
}
}
159 changes: 159 additions & 0 deletions packages/contracts/contracts/auxiliary/reward_redeemer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

/*


The ThirdWeb staking contracts expose 1 claim per 1 staked pool, but users have many pools and a lot of tokens staked, so it's just a lot of buttons for them to click
In order for a user who has ie 10 staked pool tokens, so 10 reward tokens to claim, to only have 1 claim all tx, we'd need an actual solidity function for it. The wallet must be msg.sender for the tx
The solidity function for claim all rewards would take an array of staking contracts and loop those, calling the claimRewards write function on each. It can also take in the amount the user claims for each.
Auto-compound would take both a staking contract and a teller pool input. The function would call both claimRewards from the staking contract + call approve + deposit on the teller pool. It can take in both the amount to claim for each, and amount to deposit.
To combine the two, there could be another function for claimAllAndAutoCompound which does a loop of auto-compound for an array of [staking contracts + teller pools]

*/

import "../interfaces/auxiliary/IStaking20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

interface ILenderCommitmentGroupPool {
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
}

/*
This contract needs to be set as a trusted forwarder as per EIP2771
during init
*/
contract RewardRedeemer is Initializable, OwnableUpgradeable {

using AddressUpgradeable for address;

/**
* @notice Initializes the RewardRedeemer contract
*/
function initialize() public initializer {
__Ownable_init();
}

// Events
event RewardsClaimed(address indexed user, address[] stakingContracts);
event RewardsCompounded(address indexed user, address[] stakingContracts, address[] tellerPools, uint256[] depositedAmounts);

struct AutoCompoundInputRow {
address stakingContract;
address tellerPool;
address rewardToken;
}

/**
* @notice Claims rewards from multiple staking contracts in a single transaction
* @param stakingContractsArray Array of staking contract addresses to claim rewards from
*/
function claim_multi(
address[] calldata stakingContractsArray
) external {

address stakerAddress = msg.sender;

// Loop through each staking contract and call claimRewards
for (uint256 i = 0; i < stakingContractsArray.length; i++) {
address stakingContract = stakingContractsArray[i];

// Call claimRewards on the staking contract using EIP-2771 forwarding
// The staking contract must trust this contract as a forwarder
_forwardCall(
stakingContract,
abi.encodeWithSelector(
IStaking20.claimRewards.selector
),
stakerAddress
);
}

emit RewardsClaimed(stakerAddress, stakingContractsArray);
}

/**
* @notice Claims rewards and automatically compounds them by depositing into Teller pools
* @param inputRows Array of compound instructions containing staking contract, pool, and reward token info
*/
function compound_multi(
AutoCompoundInputRow[] calldata inputRows
) external {

address stakerAddress = msg.sender;

address[] memory stakingContracts = new address[](inputRows.length);
address[] memory tellerPools = new address[](inputRows.length);
uint256[] memory depositedAmounts = new uint256[](inputRows.length);

// Loop through each compound instruction
for (uint256 i = 0; i < inputRows.length; i++) {
AutoCompoundInputRow calldata row = inputRows[i];

stakingContracts[i] = row.stakingContract;
tellerPools[i] = row.tellerPool;

IERC20Upgradeable rewardToken = IERC20Upgradeable(row.rewardToken);

// Step 1: Check balance before claiming
uint256 balanceBefore = rewardToken.balanceOf(stakerAddress);

// Step 2: Claim rewards from the staking contract
_forwardCall(
row.stakingContract,
abi.encodeWithSelector(
IStaking20.claimRewards.selector
),
stakerAddress
);


/*
// Step 3: Check balance after claiming to determine amount received
uint256 balanceAfter = rewardToken.balanceOf(stakerAddress);
uint256 claimedAmount = balanceAfter - balanceBefore;
depositedAmounts[i] = claimedAmount;

// Only proceed if we actually claimed some rewards
if (claimedAmount > 0) {
// Step 4: Transfer reward tokens from user to this contract
rewardToken.transferFrom(stakerAddress, address(this), claimedAmount);

// Step 5: Approve the teller pool to spend the reward tokens
rewardToken.approve(row.tellerPool, claimedAmount);

// Step 6: Deposit the reward tokens into the teller pool
ILenderCommitmentGroupPool(row.tellerPool).deposit(
claimedAmount,
stakerAddress
);
}

*/


}

emit RewardsCompounded(stakerAddress, stakingContracts, tellerPools, depositedAmounts);
}

/**
* @dev Internal function to forward calls using EIP-2771 meta-transaction format
* @param target The contract address to call
* @param data The encoded function call data
* @param msgSender The address that should be treated as the msg.sender
*/
function _forwardCall(
address target,
bytes memory data,
address msgSender
) internal returns (bytes memory) {
// Append the msgSender address to the calldata for EIP-2771 compatibility
return target.functionCall(
abi.encodePacked(data, msgSender)
);
}
}
Loading