Reentrancy Exploit in Yield Accumulation Mechanism of SNst Contract
Summary
The drip function in the SNst is vulnerable to reentrancy attacks due to external calls to vat.suck and nstJoin.exit before updating critical state variables. This could allow an attacker to manipulate the state and drain funds.
JuggerNaut63
High
Reentrancy Exploit in Yield Accumulation Mechanism of SNst Contract
Summary
The
drip
function in theSNst
is vulnerable to reentrancy attacks due to external calls tovat.suck
andnstJoin.exit
before updating critical state variables. This could allow an attacker to manipulate the state and drain funds.Vulnerability Detail
drip
is called.nChi
anddiff
.vat.suck
. https://github.com/sherlock-audit/2024-06-makerdao-endgame/blob/main/sdai/src/SNst.sol#L221vat.suck
ornstJoin.exit
triggers a callback todrip
, the function could be reentered before the state variableschi
andrho
are updated. https://github.com/sherlock-audit/2024-06-makerdao-endgame/blob/main/sdai/src/SNst.sol#L221-L222chi
andrho
, leading to incorrect calculations and potential double spending ofdiff
.Impact
Code Snippet
https://github.com/sherlock-audit/2024-06-makerdao-endgame/blob/main/sdai/src/SNst.sol#L214-L229
Tool used
Manual Review
Recommendation
Use OpenZeppelin's
ReentrancyGuard
to prevent reentrancy attacks.import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SNst is UUPSUpgradeable {
contract SNst is UUPSUpgradeable, ReentrancyGuard {
function drip() public returns (uint256 nChi) {
function drip() public nonReentrant returns (uint256 nChi) { (uint256 chi, uint256 rho) = (chi, rho); uint256 diff; if (block.timestamp > rho_) { nChi = rpow(nsr, block.timestamp - rho) chi / RAY; uint256 totalSupply = totalSupply; diff = totalSupply_ nChi / RAY - totalSupply * chi / RAY; vat.suck(address(vow), address(this), diff * RAY); nstJoin.exit(address(this), diff); chi = uint192(nChi); // safe as nChi is limited to maxUint256/RAY (which is < maxUint192) rho = uint64(block.timestamp); } else { nChi = chi_; } emit Drip(nChi, diff); } }
Ensure all state changes occur before any external calls.
chi = uint192(nChi);
rho = uint64(block.timestamp);
chi = uint192(nChi); // safe as nChi is limited to maxUint256/RAY (which is < maxUint192)
rho = uint64(block.timestamp); } else { nChi = chi_; } emit Drip(nChi, diff); }