The use of fixed decimal values such as 1e18 or 1e8 in Solidity contracts can lead to inaccuracies, bugs, and vulnerabilities, particularly when interacting with tokens having different decimal configurations. Not all ERC20 tokens follow the standard 18 decimal places, and assumptions about decimal places can lead to miscalculations. Always retrieve and use the decimals() function from the token contract itself when performing calculations involving token amounts.
function _earned(
StakeInfo memory _stakeInfo,
address _account,
uint256 _index
) internal view returns (uint256) {
uint256 vestingRate = _getVestingRate(_stakeInfo);
if (vestingRate == 0) {
return 0;
}
uint256 _perTokenReward;
if (vestingRate == 1e18) {
_perTokenReward = _rewardPerToken();
} else {
_perTokenReward = _rewardPerToken() * vestingRate / 1e18;
}
return
(_stakeInfo.amount * (_perTokenReward - userRewardPerTokenPaid[_account][_index])) / 1e18 +
claimableRewards[_account][_index];
}
modifier updateReward(address _account, uint256 _index) {
{
rewardData.rewardPerTokenStored = uint216(_rewardPerToken());
rewardData.lastUpdateTime = uint40(_lastTimeRewardApplicable(rewardData.periodFinish));
if (_account != address(0)) {
StakeInfo memory _stakeInfo = _stakeInfos[_account][_index];
uint256 vestingRate = _getVestingRate(_stakeInfo);
claimableRewards[_account][_index] = _earned(_stakeInfo, _account, _index);
userRewardPerTokenPaid[_account][_index] = vestingRate * uint256(rewardData.rewardPerTokenStored) / 1e18;
}
}
_;
}
interface IERC20 {
function decimals() external view returns (uint8);
}
contract TempleGoldStaking {
struct StakeInfo {
uint256 amount;
uint256 stakeTime;
uint256 fullyVestedAt;
}
struct RewardData {
uint256 rewardPerTokenStored;
uint256 lastUpdateTime;
uint256 periodFinish;
uint256 rewardRate;
}
mapping(address => mapping(uint256 => uint256)) public userRewardPerTokenPaid;
mapping(address => mapping(uint256 => uint256)) public claimableRewards;
mapping(address => mapping(uint256 => StakeInfo)) private _stakeInfos;
uint256 public totalSupply;
uint256 public vestingPeriod;
RewardData public rewardData;
IERC20 public token;
constructor(address _token) {
token = IERC20(_token);
}
function _earned(
StakeInfo memory _stakeInfo,
address _account,
uint256 _index
) internal view returns (uint256) {
uint8 tokenDecimals = token.decimals();
uint256 vestingRate = _getVestingRate(_stakeInfo);
if (vestingRate == 0) {
return 0;
}
uint256 _perTokenReward;
if (vestingRate == 10 ** tokenDecimals) {
_perTokenReward = _rewardPerToken();
} else {
_perTokenReward = _rewardPerToken() * vestingRate / (10 ** tokenDecimals);
}
return
(_stakeInfo.amount * (_perTokenReward - userRewardPerTokenPaid[_account][_index])) / (10 ** tokenDecimals) +
claimableRewards[_account][_index];
}
function _getVestingRate(StakeInfo memory _stakeInfo) internal view returns (uint256 vestingRate) {
uint8 tokenDecimals = token.decimals();
if (_stakeInfo.stakeTime == 0) {
return 0;
}
if (block.timestamp > _stakeInfo.fullyVestedAt) {
vestingRate = 10 ** tokenDecimals;
} else {
vestingRate = (block.timestamp - _stakeInfo.stakeTime) * (10 ** tokenDecimals) / vestingPeriod;
}
}
function _rewardPerToken() internal view returns (uint256) {
uint8 tokenDecimals = token.decimals();
if (totalSupply == 0) {
return rewardData.rewardPerTokenStored;
}
return
rewardData.rewardPerTokenStored +
(((_lastTimeRewardApplicable(rewardData.periodFinish) -
rewardData.lastUpdateTime) *
rewardData.rewardRate * (10 ** tokenDecimals))
/ totalSupply);
}
modifier updateReward(address _account, uint256 _index) {
{
uint8 tokenDecimals = token.decimals();
rewardData.rewardPerTokenStored = uint216(_rewardPerToken());
rewardData.lastUpdateTime = uint40(_lastTimeRewardApplicable(rewardData.periodFinish));
if (_account != address(0)) {
StakeInfo memory _stakeInfo = _stakeInfos[_account][_index];
uint256 vestingRate = _getVestingRate(_stakeInfo);
claimableRewards[_account][_index] = _earned(_stakeInfo, _account, _index);
userRewardPerTokenPaid[_account][_index] = vestingRate * uint256(rewardData.rewardPerTokenStored) / (10 ** tokenDecimals);
}
}
_;
}
function _lastTimeRewardApplicable(uint256 periodFinish) internal view returns (uint256) {
return block.timestamp < periodFinish ? block.timestamp : periodFinish;
}
}