Platform: Code4rena
Start Date: 07/07/2022
Pot Size: $75,000 USDC
Total HM: 32
Participants: 141
Period: 7 days
Judge: HardlyDifficult
Total Solo HM: 4
Id: 144
League: ETH
Rank: 35/141
Findings: 3
Award: $344.41
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: xiaoming90
Also found by: 0x29A, 0xDjango, 0xalpharush, Critical, Treasure-Seeker, ayeslick, infosec_us_team
These three transfer functions allow an attacker to supply an arbitrary from
and to
to transfer ERC20s, ERC721s, and ERC1155s. The moment that a user sets approval for the contract to spend their tokens, an attacker can front-run the next call and steal the tokens for themselves.
This function allows anyone to transfer anyone's ERC20s given that the owner has already provided approval to the contract.
function batchDepositERC20( address _from, address _to, address[] calldata _tokens, uint256[] calldata _amounts ) external { for (uint256 i = 0; i < _tokens.length; ) { IERC20(_tokens[i]).transferFrom(_from, _to, _amounts[i]); unchecked { ++i; } } }
The same is true for the ERC721 and ERC1155 deposit functions.
Manual review.
Do not allow users to supply arbitrary to and from parameters to the token transfer functions.
#0 - 0x0aa0
2022-07-18T15:18:40Z
Duplicate of #468
🌟 Selected for report: 0xA5DF
Also found by: 0x52, 0xDjango, 0xsanson, Lambda, PwnedNoMore, Ruhum, Treasure-Seeker, async, berndartmueller, cccz, hubble, kenzo, scaraven, shenwilly, sseefried, xiaoming90
14.6423 USDC - $14.64
All migration actions such as propose, join, leave, and commit require that the pool's buyout state is INACTIVE. At any point, a user can call buyout.Start()
to start the buyout process which will change the buyout state to LIVE.
migration.join()
for example looks as follows:
function join( address _vault, uint256 _proposalId, uint256 _amount ) external payable nonReentrant { // Reverts if address is not a registered vault (address token, uint256 id) = IVaultRegistry(registry).vaultToToken( _vault ); if (id == 0) revert NotVault(_vault); // Reverts if buyout state is not inactive (, , State current, , , ) = IBuyout(buyout).buyoutInfo(_vault); State required = State.INACTIVE; if (current != required) revert IBuyout.InvalidState(required, current); // Gets the migration proposal for the given ID Proposal storage proposal = migrationInfo[_vault][_proposalId]; // Updates ether balances of the proposal and caller proposal.totalEth += msg.value; userProposalEth[_proposalId][msg.sender] += msg.value; // Deposits fractional tokens into contract IFERC1155(token).safeTransferFrom( msg.sender, address(this), id, _amount, "" ); // Updates fraction balances of the proposal and caller proposal.totalFractions += _amount; userProposalFractions[_proposalId][msg.sender] += _amount; }
After the proposal has been created, this function allows users to transfer funds in support of the proposal. At any time, another user can start the buyout directly via Buyout.start()
to change the buyout state to LIVE, therefore calls to propose, join, leave, and commit will revert.
Manual review.
Restrict Buyout.start()
to be called from either the migrations
contract or internally if used within a proxy structure.
#0 - 0x0aa0
2022-07-18T16:51:38Z
Duplicate of #87
#1 - HardlyDifficult
2022-08-02T21:54:21Z
🌟 Selected for report: xiaoming90
Also found by: 0x1f8b, 0x29A, 0x52, 0xA5DF, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 0xsanson, 0xsolstars, 242, 8olidity, Amithuddar, Aymen0909, Bnke0x0, BowTiedWardens, David_, Deivitto, ElKu, Funen, Hawkeye, IllIllI, JC, Kaiziron, Keen_Sheen, Kthere, Kulk0, Kumpa, Lambda, MEP, ReyAdmirado, Rohan16, Ruhum, Sm4rty, TomJ, Tomio, Treasure-Seeker, TrungOre, Tutturu, Viksaa39, Waze, _Adam, __141345__, ak1, apostle0x01, asutorufos, async, ayeslick, aysha, bbrho, benbaessler, berndartmueller, c3phas, cccz, chatch, cloudjunky, codexploder, cryptphi, delfin454000, dipp, durianSausage, dy, exd0tpy, fatherOfBlocks, hake, hansfriese, horsefacts, hubble, joestakey, jonatascm, kebabsec, kenzo, kyteg, mektigboy, neumo, oyc_109, pashov, pedr02b2, peritoflores, rajatbeladiya, rbserver, robee, rokinot, s3cunda, sach1r0, sahar, sashik_eth, scaraven, shenwilly, simon135, sorrynotsorry, sseefried, svskaushik, unforgiven, z3s, zzzitron
62.0607 USDC - $62.06
It is recommended that ownership transfer be completed in two steps, firstly by setting a pending admin and finally by accepting the change from the pending admin account.
I was not able to determine a valid attack vector arising from the ERC1155 transfer prior to updating ethBalance
, though it would be wise to change the order.
transfer
over call
It is recommended to use call
in order to avoid potential future op code gas inflations.
https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L172 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L325