Platform: Code4rena
Start Date: 16/11/2021
Pot Size: $30,000 USDC
Total HM: 3
Participants: 18
Period: 3 days
Judge: leastwood
Total Solo HM: 2
Id: 56
League: ETH
Rank: 3/18
Findings: 1
Award: $3,344.26
π Selected for report: 4
π Solo Findings: 0
π Selected for report: cmichel
751.5154 USDC - $751.52
cmichel
The acceptGovernance
function in Alchemist
& Transmuter
does not reset pendingGovernance
to zero.
The pending governor can repeatedly accept the governance, emitting a GovernanceUpdated
event each time, bloating listeners for this event with unnecessary data.
Validate the parameters.
π Selected for report: cmichel
751.5154 USDC - $751.52
cmichel
The updateAccount
function should capture the latest distributed yield to the Transmuter (stored in buffer
) and therefore work with the latest totalDividendPoints
variable.
This variable is updated when running a phase distribution with runPhasedDistribution
.
Unlike all other function that call updateAccount
, the unstake
function does not first run a runPhasedDistribution
modifer to distribute the latest yield.
Users that unstake lose out on some yield by not having their alTokens transmuted.
Call runPhasedDistribution
in unstake
before the updateAccount
call, as in stake
or transmute
.
π Selected for report: cmichel
751.5154 USDC - $751.52
cmichel
The Alchemist.migrate
function pushes the adapter to the _vaults
list.
It does not check if the adapter is already registered.
Duplicate adapters can be registered and will then track the totalDeposited
independently.
Check if adapter already exists in _vaults
before adding it.
cmichel
Some tokens (like USDT) don't correctly implement the EIP20 standard and their approve
function returns void
instead of a success boolean. Calling these functions with the correct EIP20 function signatures will always revert.
For the tokens that return a success value, the contract does not check it.
Non-safe transfers are used in:
Alchemist._distributeToTransmuter
: token.approve(transmuter, amount);
Tokens that return false
on a failed approve
or that don't correctly implement the latest EIP20 spec, like USDT, will be unusable in the protocol as they revert the transaction because of the missing return value.
We recommend using OpenZeppelinβs SafeERC20
versions with the safeApprove
function that handle the return value check as well as non-standard-compliant tokens.
#0 - Xuefeng-Zhu
2021-12-01T17:48:39Z
will not use USDT
#1 - Xuefeng-Zhu
2021-12-09T07:00:04Z
π Selected for report: cmichel
751.5154 USDC - $751.52
cmichel
The difference between the Transmuter.transmute
function and calling forceTransmute(self)
on the own account is that transmute
reallocates any excess yield to all other stakers, while forceTransmute
pays out the excess yield to the user.
"One interesting side effect of this process is that a user's position can be over-filled. For example, a user deposits 1000 alUSD, and some time later, that staking position will have 1050 DAI filled. Once a user has gone past this limit, it is possible for other users to initialize a claim on their behalf. For any balance of DAI over their staked alUSD, the user who claimed on their behalf will have that surplus in amount immediately convert their staked alUSD." (
forceTransmute
) vs " If the person who staked the alUSD claims their DAI with a surplus, the surplus will be cycled back into the Transmuter and be spread globally." (transmute
) https://alchemix-finance.gitbook.io/alchemix-finance/transmuter
There doesn't seem to be an economic incentive for rational agents to call transmute
instead of just force-transmuting themselves.
Remove the transmute
function or remove forceTransmute
and make its behavior the default for transmute
.
#0 - Xuefeng-Zhu
2022-01-31T05:27:50Z
forceTransmute require wait for a longer time, so it is not equivalent totransmute