stake.link

stake.link
DeFiHardhatBridge
27,500 USDC
View results
Submission Details
Severity: high
Invalid

Lack of Balance Check Leads To reSDL Loss During Transfers from Primary Chain to Secondary Chain

Vulnerability Details

When transferring a reSDLtoken from Primary Chain to Secondary Chain using RESDLTokenBridge::transferRESDL() this is the callpath:

--------------------------------------------------Primary Chain-------------------------------------------------------------
USER calls RESDLTokenBridge::transferRESDL()
└── RESDLTokenBridge::transferRESDL()
└── SDLPoolCCIPControllerPrimary::handleOutgoingRESDL()
├── SDLPoolPrimary::handleOutgoingRESDL()
└── 💰 underlying SDL tokens are transferred from the Pool to the Controller 💰
└── SDLPoolCCIPControllerPrimary::ccipSend()
└── Router(Primary)
└── OnRamp(Primary)
--------------------------------------------------Primary Chain-------------------------------------------------------------
└── CCIP OFF-CHAIN
--------------------------------------------------Secondary Chain-----------------------------------------------------------
└── OnRamp(Secondary)
└── Router(Secondary)
└── SDLPoolCCIPControllerSecondary::ccipReceive()
└── RESDLTokenBridge::ccipReceive()
├── SDLPoolCCIPControllerSecondary::handleIncomingRESDL()
└── 💰 underlying SDL tokens are transferred from the Controller to the Pool 💰
└── SDLPoolSecondary::handleIncomingRESDL()
--------------------------------------------------Secondary Chain-----------------------------------------------------------

But it never checks if SDLPoolCCIPControllerSecondary has enough SDL tokens to transfer to the Pool on the Secondary Chain.

Therefore, if a user tries to transfer a reSDLtoken from Primary Chain to Secondary Chain and the Secondary Chain doesn't have enough SDL tokens, the transfer will succeed on the Primary Chain but fail on the Secondary Chain and the user will lose his reSDLtoken since it will be deleted in the Primary Chain in SDLPoolPrimary::handleOutgoingRESDL()

SDLPoolPrimary.sol#L186-L188

function handleOutgoingRESDL(
address _sender,
uint256 _lockId,
address _sdlReceiver
)
external
onlyCCIPController
onlyLockOwner(_lockId, _sender)
updateRewards(_sender)
updateRewards(ccipController)
returns (Lock memory)
{
Lock memory lock = locks[_lockId];
👉 delete locks[_lockId].amount;
👉 delete lockOwners[_lockId];
👉 balances[_sender] -= 1;
uint256 totalAmount = lock.amount + lock.boostAmount;
effectiveBalances[_sender] -= totalAmount;
effectiveBalances[ccipController] += totalAmount;
sdlToken.safeTransfer(_sdlReceiver, lock.amount);
emit OutgoingRESDL(_sender, _lockId);
return lock;
}

Scenario

  • Alice deposits 500 SDL tokens in the Pool on the Primary Chain

  • Alice receives 1 reSDLtoken with lock.amount = 500 SDL tokens

  • Alice wants to transfer her reSDLtoken to the Secondary Chain and calls RESDLTokenBridge::transferRESDL()

  • The tx succeeds on the Primary Chain and the reSDL token is deleted when SDLPoolPrimary::handleOutgoingRESDL() is called

  • SDLPoolCCIPControllerSecondary has 200 SDL tokens

  • CCIP forwards the message to the Secondary Chain and when SDLPoolCCIPControllerSecondary::handleIncomingRESDL() is called it will fail because the Controller on the Secondary Chain doesn't have enough SDL tokens to transfer to the SDLPoolSecondary and the reSDLtoken will be lost

Recommendations

Check if the Controller on the Secondary Chain has enough SDL tokens to transfer to the Pool on the Secondary Chain before deleting the reSDLtoken on the Primary Chain.

Updates

Lead Judging Commences

0kage Lead Judge almost 2 years ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.