ccipReceive
validationSince ccipReceive
is the receiver of the message, it must be well validated to exclude malicious messages.
The _ccipReceive
function in WrappedTokenBridge.sol
only checks the router when looking at the caller.
According to CCIP Best Practices (https://docs.chain.link/ccip/best-practices), it is necessary to check the source chain and sender for security reasons. (Sender depends on case).
function _ccipReceive(Client.Any2EVMMessage memory _message) internal override {
https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/ccip/WrappedTokenBridge.sol#L234
May execute messages that the protocol does not expect.
Manual
Reinforce verification
boostAmount
may not be received depending on the set value of the protocol.The maxBoost
and maxLockingDuration
used to calculate boostAmount
can be set to any value at any time by the owner.
Depending on the user's input values, boostAmount
may be set to zero, in which case the user loses his/her reward.
maxBoost
and maxLockingDuration
have no upper or lower limits. Therefore, depending on the value of _amount
and _lockingDuration
, there are cases where the calculation result is zero.
This is especially a problem when a strange setting value is set even temporarily due to a misunderstanding or mistake by the management.
function getBoostAmount(uint256 _amount, uint64 _lockingDuration) external view returns (uint256) {
if (_lockingDuration > maxLockingDuration) revert MaxLockingDurationExceeded();
return (_amount * uint256(maxBoost) * uint256(_lockingDuration)) / uint256(maxLockingDuration);
}
https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/LinearBoostController.sol#L36-L39
User does not receive boostAmount
even though he/she specified a lock period. It is inconsistent with the documentation and also the user loses the reward.
Manual
Validate realistic upper and lower limits when setting maxBoost
and maxLockingDuration
.
UpdateLock
process could be processed as InitiateUnlock
.In the secondary chain, the user's action enters queue and is finalized at execute time.
However, there is a possibility that the action in queue and the action in execute may differ.
As a result, the user may perform an unintended operation.
When executing, the process is classified according to the difference between the previous amount
and the boostAmount
.
Looking at the conditional branch, if baseAmountDiff >= 0
and boostAmountDiff < 0
, it is considered InitiateUnlock
.
int256 baseAmountDiff = int256(updateLockState.amount) - int256(curLockState.amount);
int256 boostAmountDiff = int256(updateLockState.boostAmount) - int256(curLockState.boostAmount);
if (baseAmountDiff < 0) {
emit Withdraw(_owner, lockId, uint256(-1 * baseAmountDiff));
if (updateLockState.amount == 0) {
delete locks[lockId];
delete lockOwners[lockId];
balances[_owner] -= 1;
if (tokenApprovals[lockId] != address(0)) delete tokenApprovals[lockId];
emit Transfer(_owner, address(0), lockId);
} else {
locks[lockId].amount = updateLockState.amount;
}
sdlToken.safeTransfer(_owner, uint256(-1 * baseAmountDiff));
} else if (boostAmountDiff < 0) {
locks[lockId].expiry = updateLockState.expiry;
locks[lockId].boostAmount = 0;
emit InitiateUnlock(_owner, lockId, updateLockState.expiry);
https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/SDLPoolSecondary.sol#L465-L483
However, looking at the boostAmount
formula, if maxBoost
becomes smaller or maxLockingDuration
becomes larger, boostAmount
may become negative even if _amount
and _lockingDuration
increase.
return (_amount * uint256(maxBoost) * uint256(_lockingDuration)) / uint256(maxLockingDuration);
https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/LinearBoostController.sol#L38
In other words, an update process that adds _amount
or _lockingDuration
may be treated as InitiateUnlock
.
However, as long as the protocol and user use common-sense configuration values, they will not fall prey to this phenomenon.
Therefore, the actual risk from this phenomenon by itself is small.
An action is performed that is different from the user's intention. However, it is unlikely to happen naturally.
Manual
Instead of classifying actions by numerical increase or decrease, only actions that correspond to the actions put in the queue can be executed.
SDLPool
has no storage gapSDLPool
is inherited by upgradeable contracts. (SDLPoolPrimary
and SDLPoolSecondary
).
Therefore, the contract implementation may change in the future.
However, since the storage gap is not implemented, it is not possible to increase the number of variables.
This limits the scope of future implementations.
The SDLpool
is inherited by the following contracts
__SDLPoolBase_init(_name, _symbol, _sdlToken, _boostController);
https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/SDLPoolPrimary.sol#L37
__SDLPoolBase_init(_name, _symbol, _sdlToken, _boostController);
https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/SDLPoolSecondary.sol#L73
Below is an example of OwnableUpgradeable.sol
. This storage gap will ensure future scalability.
uint256[49] private __gap;
https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/a40cb0bda838c2ef3dfc252c179f5c37c32e80c4/contracts/access/OwnableUpgradeable.sol#L94
Narrow future expansion possibilities
Manual
Add a storage gap at the end of the variable.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.