Platform: Code4rena
Start Date: 05/05/2022
Pot Size: $125,000 DAI
Total HM: 17
Participants: 62
Period: 14 days
Judge: leastwood
Total Solo HM: 15
Id: 120
League: ETH
Rank: 30/62
Findings: 2
Award: $272.37
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xkatana, 0xsomeone, AuditsAreUS, BouSalman, BowTiedWardens, Cityscape, Funen, GimelSec, Hawkeye, JC, MaratCerby, MiloTruck, Picodes, Ruhum, TerrierLover, WatchPug, Waze, bobirichman, catchup, cccz, cryptphi, csanuragjain, delfin454000, ellahi, fatherOfBlocks, hake, horsefacts, hyh, jayjonah8, joestakey, kebabsec, kenta, mics, oyc_109, robee, samruna, shenwilly, sikorico, simon135, throttle, tintin
183.1627 DAI - $183.16
WethGateway#withdrawUnderlying
unwraps and transfers the contract's full WETH balance to recipient
:
WethGateway#withdrawUnderlying
function withdrawUnderlying( address alchemist, address yieldToken, uint256 shares, address recipient, uint256 minimumAmountOut ) external { _onlyWhitelisted(); // Ensure that the underlying of the target yield token is in fact WETH IAlchemistV2.YieldTokenParams memory params = IAlchemistV2(alchemist).getYieldTokenParameters(yieldToken); if (params.underlyingToken != address(WETH)) { revert IllegalArgument(); } IAlchemistV2(alchemist).withdrawUnderlyingFrom(msg.sender, yieldToken, shares, address(this), minimumAmountOut); uint256 amount = WETH.balanceOf(address(this)); WETH.withdraw(amount); (bool success, ) = recipient.call{value: amount}(new bytes(0)); if (!success) { revert IllegalState(); } }
If the gateway contract has an existing WETH balance, (for example, if a third party has accidentally or intentionally transferred WETH to the contract address), the caller may receive more ETH than expected.
Suggestion: use the return value of IAlchemistV2(alchemist).withdrawUnderlyingFrom
.
The internal accounting in StakingPools
credits users with an amount equal to the _depositAmount
argument:
[StakingPools#_deposit
[(https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/StakingPools.sol#L375-L385)
function _deposit(uint256 _poolId, uint256 _depositAmount) internal { Pool.Data storage _pool = _pools.get(_poolId); Stake.Data storage _stake = _stakes[msg.sender][_poolId]; _pool.totalDeposited = _pool.totalDeposited + _depositAmount; _stake.totalDeposited = _stake.totalDeposited + _depositAmount; _pool.token.safeTransferFrom(msg.sender, address(this), _depositAmount); emit TokensDeposited(msg.sender, _poolId, _depositAmount); }
However, if the pool token is a fee-on-transfer ERC20, the amount received from safeTransferFrom
on line 382 may be less than _depositAmount
. This could impact reward accounting and potentially cause attempted withdrawals to revert for unlucky users.
Since the supported tokens are permissioned, this is a noncritical issue, but be aware of this incompatibility.
_isLocked
mutability to view
Mutex#_isLocked
can be declared a view
:
function _isLocked() internal returns (bool) { return _lockState == 1; }
Address inputs are validated in most locations, but are not validated in a few places. Consider whether a zero or invalid address would impact these contracts.
It's a best practice to lock version pragmas for concrete contracts. Many contracts are using a fixed 0.8.13
pragma, but the following contracts have a floating ^0.8.11
pragma:
AlchemicTokenV1
AlchemicTokenV2
AlchemicTokenV2Base
AlchemistV2
AutoleverageBase
AutoleverageCurveFactoryethpool
AutoleverageCurveMetapool
CrossChainCanonicalAlchemicTokenV2
CrossChainCanonicalBase
CrossChainCanonicalGALCX
gALCX
StakingPools
TransmuterBuffer
TransmuterV2
WETHGateway
YearnTokenAdapter
Multicall
Math.sol
in TransmuterBuffer
LiquidityMath.sol
in TransmuterBuffer
TransmuterV2#notPaused
normalizedAmount
in TransmuterV2#exchange
for
in comment for TransmuterBuffer#lastFlowrateUpdate
#0 - 0xleastwood
2022-06-11T17:06:36Z
I think the first point is useful to keep as is. Helps to ensure there is never locked ETH in the contract. If Ether is mistakenly sent to this contract, that is fair game.
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xf15ers, 0xkatana, 0xsomeone, AlleyCat, BowTiedWardens, Cityscape, Fitraldys, Funen, GimelSec, Hawkeye, JC, MaratCerby, MiloTruck, Randyyy, TerrierLover, Tomio, UnusualTurtle, WatchPug, Waze, _Adam, augustg, bobirichman, catchup, csanuragjain, ellahi, fatherOfBlocks, hake, hansfriese, horsefacts, ignacio, joestakey, kenta, mics, oyc_109, robee, samruna, sashik_eth, sikorico, simon135, throttle
89.2136 DAI - $89.21
TransmuterConduit
state variables immutableThe token
, sourceTransmuter
, and sinkTransmuter
variables in TransmuterConduit
are set permanently at construction time. They can be declared immutable
to save runtime gas.
/// @notice The address of the underlying token that is being transmuted. address public token; /// @notice The address of the transmuter to pull funds from. address public sourceTransmuter; /// @notice The address of the transmuter to send funds to;. address public sinkTransmuter; constructor(address _token, address _source, address _sink) { token = _token; sourceTransmuter = _source; sinkTransmuter = _sink; }
StakingPool
reward token immutableSimilarly, the reward
token in StakingPool
can be declared immutable
:
/// @dev The token which will be minted as a reward for staking. IERC20Mintable public reward;
#0 - 0xfoobar
2022-05-30T06:56:08Z
Good recommendations