Summary
Possible Reentrancy vulnerabilities (no theft of ethers). User funds or contract state may be compromised due to state being updated after external calls.
Vulnerability Details
File: contracts/beanstalk/silo/SiloFacet/Silo.sol
Reentrancy (no theft of ethers) in Silo._claimPlenty(address) (contracts/beanstalk/silo/SiloFacet/Silo.sol#154-164):
External calls:
- sopToken.safeTransfer(account,plenty) (contracts/beanstalk/silo/SiloFacet/Silo.sol#160)
State variables written after the call(s):
- delete s.a[account].sop.plenty (contracts/beanstalk/silo/SiloFacet/Silo.sol#161)
L#160, L#161,
154: function _claimPlenty(address account) internal {
155:
156: uint256 plenty = s.a[account].sop.plenty;
157: IWell well = IWell(s.sopWell);
158: IERC20[] memory tokens = well.tokens();
159: IERC20 sopToken = tokens[0] != C.bean() ? tokens[0] : tokens[1];
160: sopToken.safeTransfer(account, plenty);
161: delete s.a[account].sop.plenty;
162:
163: emit ClaimPlenty(account, address(sopToken), plenty);
164: }
GitHub : 154-164
File: contracts/beanstalk/sun/SeasonFacet/Sun.sol
Reentrancy (no theft of ethers) in Sun.stepSun(int256,uint256) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#63-76):
External calls:
- newHarvestable = rewardBeans(uint256(deltaB)) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#66)
- C.bean().mint(address(this),newSupply) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#86)
State variables written after the call(s):
- setSoilAbovePeg(newHarvestable,caseId) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#67)
- s.f.soil = amount.toUint128() (contracts/beanstalk/sun/SeasonFacet/Sun.sol#228)
- s.season.abovePeg = true (contracts/beanstalk/sun/SeasonFacet/Sun.sol#68)
L#66, L#86, L#67, L#228, L#68,
63: function stepSun(int256 deltaB, uint256 caseId) internal {
64:
65: if (deltaB > 0) {
66: uint256 newHarvestable = rewardBeans(uint256(deltaB));
67: setSoilAbovePeg(newHarvestable, caseId);
68: s.season.abovePeg = true;
69: }
70:
71:
72: else {
73: setSoil(uint256(-deltaB));
74: s.season.abovePeg = false;
75: }
76: }
86: C.bean().mint(address(this), newSupply);
228: s.f.soil = amount.toUint128();
GitHub : 63-76
File: contracts/beanstalk/sun/SeasonFacet/Weather.sol
Reentrancy (no theft of ethers) in Weather.sop() (contracts/beanstalk/sun/SeasonFacet/Weather.sol#181-213):
External calls:
- C.bean().mint(address(this),newHarvestable.add(sopBeans)) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#196)
- C.bean().mint(address(this),sopBeans) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#198)
- C.bean().approve(sopWell,sopBeans) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#202)
- amountOut = IWell(sopWell).swapFrom(C.bean(),sopToken,sopBeans,0,address(this),type()(uint256).max) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#203-210)
State variables written after the call(s):
- rewardSop(amountOut) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#211)
- s.sops[s.season.rainStart] = s.sops[s.season.lastSop].add(amount.mul(C.SOP_PRECISION).div(s.r.roots)) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#219-221)
- s.season.lastSop = s.season.rainStart (contracts/beanstalk/sun/SeasonFacet/Weather.sol#222)
- s.season.lastSopSeason = s.season.current (contracts/beanstalk/sun/SeasonFacet/Weather.sol#223)
L#196, L#198, L#202, L#203-210, L#211, L#219-221, L#222, L#223,
196: C.bean().mint(address(this), newHarvestable.add(sopBeans));
198: C.bean().mint(address(this), sopBeans);
202: C.bean().approve(sopWell, sopBeans);
203: uint256 amountOut = IWell(sopWell).swapFrom(
204: C.bean(),
205: sopToken,
206: sopBeans,
207: 0,
208: address(this),
209: type(uint256).max
210: );
211: rewardSop(amountOut);
219: s.sops[s.season.rainStart] = s.sops[s.season.lastSop].add(
220: amount.mul(C.SOP_PRECISION).div(s.r.roots)
221: );
222: s.season.lastSop = s.season.rainStart;
223: s.season.lastSopSeason = s.season.current;
GitHub : 181-213
File: contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol
Reentrancy (no theft of ethers) in SeasonFacet.gm(address,LibTransfer.To) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#44-57):
External calls:
- caseId = calcCaseIdandUpdate(deltaB) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#51)
- C.bean().mint(address(this),newHarvestable.add(sopBeans)) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#196)
- C.bean().mint(address(this),sopBeans) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#198)
- C.bean().approve(sopWell,sopBeans) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#202)
- amountOut = IWell(sopWell).swapFrom(C.bean(),sopToken,sopBeans,0,address(this),type()(uint256).max) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#203-210)
- stepSun(deltaB,caseId) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#54)
- C.bean().mint(address(this),newSupply) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#86)
State variables written after the call(s):
- stepSun(deltaB,caseId) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#54)
- s.earnedBeans = s.earnedBeans.add(amount.toUint128()) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#178)
- s.f.soil = amount.toUint128() (contracts/beanstalk/sun/SeasonFacet/Sun.sol#228)
- s.s.stalk = s.s.stalk.add(amount.mul(C.STALK_PER_BEAN)) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#186)
- s.siloBalances[C.BEAN].deposited = s.siloBalances[C.BEAN].deposited.add(amount.toUint128()) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#192-195)
- s.season.abovePeg = true (contracts/beanstalk/sun/SeasonFacet/Sun.sol#68)
- s.siloBalances[C.BEAN].depositedBdv = s.siloBalances[C.BEAN].depositedBdv.add(uint128(amount)) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#198-201)
- s.f.harvestable = s.f.harvestable.add(newHarvestable) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#164)
- s.season.abovePeg = false (contracts/beanstalk/sun/SeasonFacet/Sun.sol#74)
- s.bpf = uint128(firstEndBpf) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#132)
- s.fertilizedIndex = s.fertilizedIndex.add(newFertilized) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#133)
- s.bpf = uint128(newTotalBpf) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#146)
- s.fertilizedIndex = s.fertilizedIndex.add(newFertilized) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#148)
L#51, L#54,
44: function gm(address account, LibTransfer.To mode) public payable returns (uint256) {
45: uint256 initialGasLeft = gasleft();
46:
47: require(!s.paused, "Season: Paused.");
48: require(seasonTime() > s.season.current, "Season: Still current Season.");
49: uint32 season = stepSeason();
50: int256 deltaB = stepOracle();
51: uint256 caseId = calcCaseIdandUpdate(deltaB);
52: LibGerminate.endTotalGermination(season, LibWhitelistedTokens.getWhitelistedTokens());
53: LibGauge.stepGauge();
54: stepSun(deltaB, caseId);
55:
56: return incentivize(account, initialGasLeft, mode);
57: }
GitHub : 44-57
Impact
Possible loss of user funds or wrong contract state
Tools Used
Manual review
Recommendations
Code should follow the best-practice of check-effects-interaction, where state variables are updated before any external calls are made. Doing so prevents a large class of reentrancy bugs.