Platform: Code4rena
Start Date: 09/09/2022
Pot Size: $42,000 USDC
Total HM: 2
Participants: 101
Period: 3 days
Judge: hickuphh3
Total Solo HM: 2
Id: 161
League: ETH
Rank: 15/101
Findings: 1
Award: $38.41
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: GalloDaSballo
Also found by: 0x040, 0x1f8b, 0x4non, 0x52, 0x85102, 0xNazgul, 0xSky, 0xSmartContract, Aymen0909, Bnke0x0, CertoraInc, Chandr, Chom, CodingNameKiki, Deivitto, Diana, Funen, JC, Jeiwan, Junnon, KIntern_NA, Lambda, Mohandes, Noah3o6, Ocean_Sky, Picodes, R2, Randyyy, RaymondFam, ReyAdmirado, Rohan16, Rolezn, Samatak, Sm4rty, SnowMan, SooYa, StevenL, Tagir2003, Tointer, TomJ, Tomo, V_B, Waze, _Adam, __141345__, a12jmx, ajtra, ak1, asutorufos, bharg4v, bobirichman, brgltd, c3phas, cccz, cryptonue, cryptostellar5, cryptphi, csanuragjain, d3e4, datapunk, delfin454000, dipp, djxploit, durianSausage, erictee, fatherOfBlocks, gogo, got_targ, hansfriese, horsefacts, hyh, ignacio, innertia, izhuer, karanctf, ladboy233, leosathya, lucacez, lukris02, mics, oyc_109, pashov, pauliax, prasantgupta52, rbserver, ret2basic, rfa, robee, rokinot, rotcivegaf, rvierdiiev, sach1r0, scaraven, sikorico, simon135, smiling_heretic, sorrynotsorry, unforgiven, wagmi, yixxas
38.4075 USDC - $38.41
redeem()
The redeem
() function inside the TribeRedeemer.sol
contract will execute the external transfer call for the redeemedToken
before updating the state variable redeemBase
.
function redeem(address to, uint256 amountIn) external nonReentrant { IERC20(redeemedToken).safeTransferFrom(msg.sender, address(this), amountIn); (address[] memory tokens, uint256[] memory amountsOut) = previewRedeem(amountIn); uint256 base = redeemBase; redeemBase = base - amountIn; // decrement the base for future redemptions for (uint256 i = 0; i < tokens.length; i++) { IERC20(tokens[i]).safeTransfer(to, amountsOut[i]); } emit Redeemed(msg.sender, to, amountIn, base); }
The function is not at risk of reentrancy due to the nonReentrancy
modifier, but the state update should be executed before the external to act in conformance with the checks-effects-interactions
pattern, similarly to what's done in the _redeem()
function for the RariMerkleRedeemer.sol
contract.
redeemBase
is bigger than zeroFor the TribeRedeemer.sol
constructor, if redeemBase
accidentaly receives the value 0, the function previewRedeem()
will always generate a divide-by-zero error.
constructor( address _redeemedToken, address[] memory _tokensReceived, uint256 _redeemBase ) { redeemedToken = _redeemedToken; tokensReceived = _tokensReceived; redeemBase = _redeemBase; }
function previewRedeem(uint256 amountIn) public view returns (address[] memory tokens, uint256[] memory amountsOut) { tokens = tokensReceivedOnRedeem(); amountsOut = new uint256[](tokens.length); uint256 base = redeemBase; for (uint256 i = 0; i < tokensReceived.length; i++) { uint256 balance = IERC20(tokensReceived[i]).balanceOf(address(this)); require(balance != 0, "ZERO_BALANCE"); // @dev, this assumes all of `tokensReceived` and `redeemedToken` // have the same number of decimals uint256 redeemedAmount = (amountIn * balance) / base; amountsOut[i] = redeemedAmount; } }
Add a check to ensure redeemBase
is bigger than zero to avoid the need for redeployments in case of user-setting accidental mistakes.
Avoid using floating pragmas, e.g.TribeRedeemer.sol
is using ^0.8.4.
Furthermore, all the contracts in scope are using OpenZeppelin, which makes use of inline assembly. Solidity version prior to 0.8.15 has a bug related to inline assemby: Optimizer Bug Regarding Memory Side Effects of Inline Assembly.
Use the latest version of solidity where possible to ensure the project is in conformance with the latest bugfixes in solidity.
burnFeiHeld()
function is called to facilidate offchain monitoringfunction burnFeiHeld() external { uint256 feiBalance = FEI.balanceOf(address(this)); if (feiBalance != 0) { FEI.burn(feiBalance); } }
https://github.com/code-423n4/2022-09-tribe/blob/main/contracts/peg/SimpleFeiDaiPSM.sol#L105-L110
138: function _configureMerkleRoots(address[] memory _cTokens, bytes32[] memory _roots) internal {
148: function _configureBaseToken(address _baseToken) internal {
previewRedeem()
is declaring a return named variable and manually returning a valuefunction previewRedeem(address cToken, uint256 amount) public view override returns (uint256 baseTokenAmount) { // Each ctoken exchange rate is stored as how much you should get for 1e18 of the particular cToken // Thus, we divide by 1e18 when returning the amount that a person should get when they provide // the amount of cTokens they're turning into the contract return (cTokenExchangeRates[cToken] * amount) / 1e18; }
The function previewRedeem()
can be refactored to use the return named variable or maintain the current return and removed the named variable in the function header.
Option 1:
function previewRedeem(address cToken, uint256 amount) public view override returns (uint256 baseTokenAmount) { baseTokenAmount = (cTokenExchangeRates[cToken] * amount) / 1e18; }
Option 2:
function previewRedeem(address cToken, uint256 amount) public view override returns (uint256) { return (cTokenExchangeRates[cToken] * amount) / 1e18; }
function _configureExchangeRates(address[] memory _cTokens, uint256[] memory _exchangeRates) internal { require(_cTokens.length == 27, "Must provide exactly 27 exchange rates."); require(_cTokens.length == _exchangeRates.length, "Exchange rates must be provided for each cToken");
function _configureMerkleRoots(address[] memory _cTokens, bytes32[] memory _roots) internal { require(_cTokens.length == 27, "Must provide exactly 27 merkle roots"); require(_cTokens.length == _roots.length, "Merkle roots must be provided for each cToken");
tokenReceived
can be converted into a public state variable which will remove the need for the tokensReceivedOnRedeem()
functionaddress[] private tokensReceived;
function tokensReceivedOnRedeem() public view returns (address[] memory) { return tokensReceived; }
By refactoring tokensReceived
to public, solidity will create a getter function automatically.