Basic Lilac Marmot
High
function stake(address _user, uint256 _amount) external override {
require(stakingToken != address(0), "I");
if (stakeUserRestriction != address(0)) {
require(_user == stakeUserRestriction, "U");
}
_mint(_user, _amount);
IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
emit Stake(_msgSender(), _user, _amount);
}
function unstake(uint256 _amount) external override {
_burn(_msgSender(), _amount);
IERC20(stakingToken).safeTransfer(_msgSender(), _amount);
emit Unstake(_msgSender(), _amount);
}
The stake function cannot be directly called when stakeUserRestriction is set, but it can be called through the indexUtils contract. After calling the stake function, the unstake function can be invoked, triggering both the _burn function and the _update function.
function _update(address _from, address _to, uint256 _value) internal override {
super._update(_from, _to, _value);
if (_from != address(0)) {
TokenRewards(POOL_REWARDS).setShares(_from, _value, true);
}
if (_to != address(0) && _to != address(0xdead)) {
TokenRewards(POOL_REWARDS).setShares(_to, _value, false);
}
}
When the _update function is called, it invokes setShares from POOL_REWARDS, adding the value of shares to the from address.
function setShares(address _wallet, uint256 _amount, bool _sharesRemoving) external override {
require(_msgSender() == trackingToken, "UNAUTHORIZED");
_setShares(_wallet, _amount, _sharesRemoving);
}
function _setShares(address _wallet, uint256 _amount, bool _sharesRemoving) internal {
_processFeesIfApplicable();
if (_sharesRemoving) {
_removeShares(_wallet, _amount);
emit RemoveShares(_wallet, _amount);
} else {
_addShares(_wallet, _amount);
emit AddShares(_wallet, _amount);
}
}
The setShares function adds the specified amount of shares to the _wallet address's share storage.
function _distributeReward(address _wallet) internal {
if (shares[_wallet] == 0) {
return;
}
for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
address _token = _allRewardsTokens[_i];
if (REWARDS_WHITELISTER.paused(_token)) {
continue;
}
uint256 _amount = getUnpaid(_token, _wallet);
rewards[_token][_wallet].realized += _amount;
rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
if (_amount > 0) {
rewardsDistributed[_token] += _amount;
IERC20(_token).safeTransfer(_wallet, _amount);
emit DistributeReward(_wallet, _token, _amount);
}
}
}
The claimReward function calls _distributeReward in the TokenRewards contract, allowing tokens to be stolen in this process.
The system is designed to give rewards when the unstake function is called, but since unstake can be called at any time, this can be exploited to withdraw all rewards.
No response
No response
The attack sequence is as follows:
- The attacker calls the stake and unstake functions to trigger the setShares function in POOL_REWARDS.
- Once step 1 is completed, the attacker can withdraw tokens through claimRewards in the TokenRewards contract.
- The above steps are repeated to steal all the tokens.
can steal all rewards
No response
No response