Summary
Possible Reentrancy vulnerabilities leading to out-of-order Events, making difficult for off chain tools to monitor the state of protocol.
Vulnerability Details
File: contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol
Reentrancy (events) 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)
- incentivize(account,initialGasLeft,mode) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#56)
- returndata = address(token).functionCall(data,SafeERC20: low-level call failed) (node_modules/@openzeppelin/contracts/token/ERC20/SafeERC20.sol#69)
- token.mint(recipient,amount) (contracts/libraries/Token/LibTransfer.sol#110)
- token.mint(address(this),amount) (contracts/libraries/Token/LibTransfer.sol#112)
- (success,returndata) = target.call{value: value}(data) (node_modules/@openzeppelin/contracts/utils/Address.sol#119)
- token.safeTransfer(recipient,amount) (contracts/libraries/Token/LibTransfer.sol#82)
- LibTransfer.mintToken(C.bean(),incentiveAmount,account,mode) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#116)
External calls sending eth:
- incentivize(account,initialGasLeft,mode) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#56)
- (success,returndata) = target.call{value: value}(data) (node_modules/@openzeppelin/contracts/utils/Address.sol#119)
Event emitted after the call(s):
- LibIncentive.Incentivization(account,incentiveAmount) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#118)
- incentivize(account,initialGasLeft,mode) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#56)
- InternalBalanceChanged(account,token,delta) (contracts/libraries/Token/LibBalance.sol#102)
- incentivize(account,initialGasLeft,mode) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#56)
L#51, L#54, L#56, L#116, L#118,
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: }
116: LibTransfer.mintToken(C.bean(), incentiveAmount, account, mode);
118: emit LibIncentive.Incentivization(account, incentiveAmount);
GitHub : 44-57
File: contracts/beanstalk/sun/SeasonFacet/Sun.sol
Reentrancy (events) 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)
Event emitted after the call(s):
- Soil(s.season.current,amount.toUint128()) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#229)
- setSoilAbovePeg(newHarvestable,caseId) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#67)
L#66, L#86, L#229, L#67,
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);
229: emit Soil(s.season.current, amount.toUint128());
GitHub : 63-76
File: contracts/libraries/LibFertilizer.sol
Reentrancy (events) in LibFertilizer.addFertilizer(uint128,uint256,uint256) (contracts/libraries/LibFertilizer.sol#37-63):
External calls:
- addUnderlying(fertilizerAmount.mul(DECIMALS),minLP) (contracts/libraries/LibFertilizer.sol#57)
- C.bean().mint(address(this),newDepositedBeans) (contracts/libraries/LibFertilizer.sol#103-106)
- C.bean().mint(address(C.BEAN_ETH_WELL),newDepositedLPBeans) (contracts/libraries/LibFertilizer.sol#109-112)
- newLP = IWell(C.BEAN_ETH_WELL).sync(address(this),minAmountOut) (contracts/libraries/LibFertilizer.sol#114-117)
Event emitted after the call(s):
- SetFertilizer(id,bpf) (contracts/libraries/LibFertilizer.sol#62)
L#57, L#103-106, L#109-112, L#114-117, L#62,
57: addUnderlying(fertilizerAmount.mul(DECIMALS), minLP);
62: emit SetFertilizer(id, bpf);
103: C.bean().mint(
104: address(this),
105: newDepositedBeans
106: );
109: C.bean().mint(
110: address(C.BEAN_ETH_WELL),
111: newDepositedLPBeans
112: );
114: uint256 newLP = IWell(C.BEAN_ETH_WELL).sync(
115: address(this),
116: minAmountOut
117: );
GitHub : 37-63
File: contracts/beanstalk/sun/SeasonFacet/Weather.sol
Reentrancy (events) 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)
Event emitted after the call(s):
- SeasonOfPlenty(s.season.current,sopWell,address(sopToken),amountOut,newHarvestable) (contracts/beanstalk/sun/SeasonFacet/Weather.sol#212)
L#196, L#198, L#202, L#203-210, L#212,
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: );
212: emit SeasonOfPlenty(s.season.current, sopWell, address(sopToken), amountOut, newHarvestable);
GitHub : 181-213
File: contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol
Reentrancy (events) in SeasonFacet.incentivize(address,uint256,LibTransfer.To) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#88-120):
External calls:
- LibTransfer.mintToken(C.bean(),incentiveAmount,account,mode) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#116)
Event emitted after the call(s):
- LibIncentive.Incentivization(account,incentiveAmount) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#118)
L#116, L#118,
116: LibTransfer.mintToken(C.bean(), incentiveAmount, account, mode);
118: emit LibIncentive.Incentivization(account, incentiveAmount);
GitHub : 88-120
File: contracts/beanstalk/silo/SiloFacet/Silo.sol
Reentrancy (events) 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)
Event emitted after the call(s):
- ClaimPlenty(account,address(sopToken),plenty) (contracts/beanstalk/silo/SiloFacet/Silo.sol#163)
L#160, L#163,
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 (events) in Sun.rewardBeans(uint256) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#83-104):
External calls:
- C.bean().mint(address(this),newSupply) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#86)
Event emitted after the call(s):
- Reward(s.season.current,newHarvestable,newSupply,newFertilized) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#103)
L#86, L#103,
83: function rewardBeans(uint256 newSupply) internal returns (uint256 newHarvestable) {
84: uint256 newFertilized;
85:
86: C.bean().mint(address(this), newSupply);
87:
88:
89: if (s.season.fertilizing) {
90: newFertilized = rewardToFertilizer(newSupply);
91: newSupply = newSupply.sub(newFertilized);
92: }
93:
94:
95: if (s.f.harvestable < s.f.pods) {
96: newHarvestable = rewardToHarvestable(newSupply);
97: newSupply = newSupply.sub(newHarvestable);
98: }
99:
100:
101: rewardToSilo(newSupply);
102:
103: emit Reward(s.season.current, newHarvestable, newSupply, newFertilized);
104: }
GitHub : 83-104
File: contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol
Reentrancy (events) 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)
Event emitted after the call(s):
- Reward(s.season.current,newHarvestable,newSupply,newFertilized) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#103)
- stepSun(deltaB,caseId) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#54)
- Soil(s.season.current,amount.toUint128()) (contracts/beanstalk/sun/SeasonFacet/Sun.sol#229)
- stepSun(deltaB,caseId) (contracts/beanstalk/sun/SeasonFacet/SeasonFacet.sol#54)
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
Offline apps or tools will not able to check and report latest correct state of the protocol due to out of order events.
Tools Used
Manual Review
Recommendations
Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls