Skip to content

Commit

Permalink
TRUST M-8: Reward distribution in RewardableERC20 (#994)
Browse files Browse the repository at this point in the history
Co-authored-by: Taylor Brent <[email protected]>
  • Loading branch information
julianmrodri and tbrent authored Nov 1, 2023
1 parent 7af3d31 commit 64684ff
Show file tree
Hide file tree
Showing 2 changed files with 229 additions and 37 deletions.
26 changes: 16 additions & 10 deletions contracts/plugins/assets/erc20/RewardableERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../../../interfaces/IRewardable.sol";

uint256 constant SHARE_DECIMAL_OFFSET = 9; // to prevent reward rounding issues

/**
* @title RewardableERC20
* @notice An abstract class that can be extended to create rewardable wrapper.
Expand All @@ -19,11 +21,11 @@ import "../../../interfaces/IRewardable.sol";
abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard {
using SafeERC20 for IERC20;

uint256 public immutable one; // {qShare/share}
uint256 public immutable one; // 1e9 * {qShare/share}
IERC20 public immutable rewardToken;

uint256 public rewardsPerShare; // {qRewards/share}
mapping(address => uint256) public lastRewardsPerShare; // {qRewards/share}
uint256 public rewardsPerShare; // 1e9 * {qRewards/share}
mapping(address => uint256) public lastRewardsPerShare; // 1e9 * {qRewards/share}
mapping(address => uint256) public accumulatedRewards; // {qRewards}
mapping(address => uint256) public claimedRewards; // {qRewards}

Expand All @@ -35,7 +37,8 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard {
/// @dev Extending class must ensure ERC20 constructor is called
constructor(IERC20 _rewardToken, uint8 _decimals) {
rewardToken = _rewardToken;
one = 10**_decimals; // set via pass-in to prevent inheritance issues
// set via pass-in to prevent inheritance issues
one = 10**(_decimals + SHARE_DECIMAL_OFFSET);
}

function claimRewards() external nonReentrant {
Expand All @@ -47,7 +50,7 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard {
function _syncAccount(address account) internal {
if (account == address(0)) return;

// {qRewards/share}
// 1e9 * {qRewards/share}
uint256 accountRewardsPerShare = lastRewardsPerShare[account];

// {qShare}
Expand All @@ -56,13 +59,13 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard {
// {qRewards}
uint256 _accumulatedRewards = accumulatedRewards[account];

// {qRewards/share}
// 1e9 * {qRewards/share}
uint256 _rewardsPerShare = rewardsPerShare;
if (accountRewardsPerShare < _rewardsPerShare) {
// {qRewards/share}
// 1e9 * {qRewards/share}
uint256 delta = _rewardsPerShare - accountRewardsPerShare;

// {qRewards} = {qRewards/share} * {qShare}
// {qRewards} = (1e9 * {qRewards/share}) * {qShare} / (1e9 * {qShare/share})
_accumulatedRewards += (delta * shares) / one;
}
lastRewardsPerShare[account] = _rewardsPerShare;
Expand All @@ -81,12 +84,15 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard {
uint256 _previousBalance = lastRewardBalance;

if (balanceAfterClaimingRewards > _previousBalance) {
uint256 delta = balanceAfterClaimingRewards - _previousBalance;
uint256 delta = balanceAfterClaimingRewards - _previousBalance; // {qRewards}

// 1e9 * {qRewards/share} = {qRewards} * (1e9 * {qShare/share}) / {qShare}
uint256 deltaPerShare = (delta * one) / _totalSupply;

// {qRewards} = {qRewards} + (1e9*(qRewards/share)) * {qShare} / (1e9*{qShare/share})
balanceAfterClaimingRewards = _previousBalance + (deltaPerShare * _totalSupply) / one;

// {qRewards/share} += {qRewards} * {qShare/share} / {qShare}
// 1e9 * {qRewards/share} += {qRewards} * (1e9*{qShare/share}) / {qShare}
_rewardsPerShare += deltaPerShare;
}

Expand Down
Loading

0 comments on commit 64684ff

Please sign in to comment.