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: 45/141
Findings: 4
Award: $218.47
🌟 Selected for report: 0
🚀 Solo Findings: 0
how does buyout become realy low?
because of this percison error in the code if you supply
msg.value=1
depsitAmount
=1
totalSupply=5
buyoutPrice
= 1*100 / (100 -(1/5))=1
fractionPrice
1/5=0
because the smart contract dosnt know the fraction price is lowerd you can sell your fraction tokens or more then there worth and then buy it back the fractions for 0.   uint256 buyoutPrice = (msg.value * 100) /       (100 - ((depositAmount * 100) / totalSupply));  uint256 fractionPrice = buyoutPrice / totalSupply  Â
you can do the same but with the price infilited if you are taking a flash loan on the fractionprice you can inflate it and make the fraction worth more and cause a aribtrary trade /flashloan to cause loss of funds instead
ifts a very high amount then fractionPrice *amount =ethbalacne
would be more then the attacker supplied causing loss of funds
but if the token was low price then when
if (msg.value != fractionPrice * _amount) revert InvalidPayment(); // Transfers fractional tokens to caller IERC1155(token).safeTransferFrom( address(this), msg.sender, id, _amount, "" );
then you dont need to supply eth and just take as much tokens as possible with _amount=1232133333
large amount
also if fractionprice is large price then users will have to supplly a large amount of eth that will dos the functions
this in general percission errors causes the smart contract to brake
fractionPrice = buyoutPrice *1000 /totalsupply
just make sure decimals cover the percision lost.
require (fractionprice>0) ;
#0 - Ferret-san
2022-07-21T16:51:33Z
Duplicate of #629
#1 - HardlyDifficult
2022-08-01T23:42:14Z
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, Amithuddar, Avci, BowTiedWardens, Kthere, Limbooo, MEP, Ruhum, StyxRave, TomJ, Treasure-Seeker, TrungOre, Tutturu, Waze, bardamu, c3phas, cccz, codexploder, cryptphi, hake, horsefacts, hyh, oyc_109, pashov, peritoflores, scaraven, simon135, slywaters, sseefried, tofunmi, xiaoming90
1.3977 USDC - $1.40
https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/modules/Migration.sol#L172 https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/modules/Migration.sol#L325
To withdraw eth it uses transfer(), this trnansaction will fail inevitably when : -
The withdrwer smart contract does not implement a payable function.
Withdrawer smart contract does implement a payable fallback which uses more than 2300 gas unit
Thw withdrawer smart contract implements a payable fallback function whicn needs less than 2300 gas unit but is called through proxy that raise the call's gas usage above 2300
https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
payable(msg.sender).transfer(userEth);
use call instead of transfer
#0 - stevennevins
2022-07-19T21:37:49Z
Duplicate of #325
#1 - HardlyDifficult
2022-07-28T15:44:50Z
Duping to #504
🌟 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.178 USDC - $62.18
 // Reverts if payment amount does not equal price of fractional amount
it should say instead of payment make it eth payment Buyout:164
after this: Migration.sol:71: /// @param _targetPrice Target price of the buyout
   address _to,     uint256 _fractionSupply,     bytes32[] calldata _mintProof
function execute(address _target, bytes memory _data, bytes32[] memory _proof) external payable returns (bool success, bytes memory response)
function init() external { Â Â Â Â if (nonce != 0) revert Initialized(owner, msg.sender, nonce); Â Â Â Â nonce = 1;
totalsupply
wouldnt matter if the buyout is successful or notas it says in the comments
  // Checks totalSupply of auction pool to determine if buyout is successful or not
this above is not true
it is very likely to pass even though tokenBalance is 0 and didnt get anything because the vault should have engouhg funds for 500 wei
     (tokenBalance * 1000) /         IVaultRegistry(registry).totalSupply(_vault) >       500
as long as there is little dust in the vault then it will pass even though most of the funds where tooken out.
make sure tokenbalance is > 0 or have require statement that checks if the vault is in the middle of buyout or did it recently. https://github.com/code-423n4/2022-07-fractional/blob/f862c14f86adf7de232cd4e9cca6b611e6023b98/src/modules/Buyout.sol#L208
   uint256 totalSupply = IVaultRegistry(registry).totalSupply(_vault);     uint256 buyoutShare = (tokenBalance * ethBalance) /       (totalSupply + tokenBalance);
when doing the caluction you can have a token that is not eth or weht and more deicamls making it more eth getting transferd to you then you paid for
10*1 / 10+10=0 which in this case with small amount of money the user will loose 2. 10*100/ 10+10=50 50 shares but it can be worth more in weht/eth making it loss of funds
transfer those funds to him but convert to eth and and check it with a price orcal to make sure they are equal
because it can revert if transfering 0 or fail sliance if transfer dosnt go through protoforms/BaseVault.sol:65: IERC20(_tokens[i]).transferFrom(_from, _to, _amounts[i]);
if    vault is random but real vault that fits the if statement
      proposalId  is ,some time ago you have left over Fractions and now you want to get it out even though your main vault had a succesful migration and you still have  an amount in it then that vault is loosing funds  because you had some value in another vault and it sends you the funds even though its a diffrent vault that migrited
uint256 userFractions = userProposalFractions[_proposalId][msg.sender]; // Updates fractional balance of caller userProposalFractions[_proposalId][msg.sender] = 0; // Withdraws fractional tokens from contract back to caller IFERC1155(token).safeTransferFrom( address(this), msg.sender, id, userFractions, "" );
so as long as you its a real vault that has funds but proposalId has some amount into from it an attacker can steal funds from another vault. same thing with eth to. https://github.com/code-423n4/2022-07-fractional/blob/f862c14f86adf7de232cd4e9cca6b611e6023b98/src/modules/Migration.sol#L292
and the user dosnt choose so if a user dosnt have 1 ether and instead has a 10**5 wei then it will revert.
 uint256 totalInEth = _calculateTotal(       1 ether,       lastTotalSupply,       migrationInfo[_vault][_proposalId].totalEth,       migrationInfo[_vault][_proposalId].totalFractions     );
🌟 Selected for report: joestakey
Also found by: 0x1f8b, 0x29A, 0xA5DF, 0xKitsune, 0xNazgul, 0xNineDec, 0xalpharush, 0xkatana, 0xsanson, 0xsolstars, 8olidity, Avci, Bnke0x0, BowTiedWardens, Chom, Deivitto, ElKu, Fitraldys, Funen, IllIllI, JC, Kaiziron, Lambda, Limbooo, MEP, NoamYakov, PwnedNoMore, RedOneN, ReyAdmirado, Rohan16, Ruhum, Saintcode_, Sm4rty, TomJ, Tomio, TrungOre, Tutturu, Waze, _Adam, __141345__, ajtra, apostle0x01, asutorufos, benbaessler, brgltd, c3phas, codexploder, cryptphi, delfin454000, dharma09, djxploit, durianSausage, fatherOfBlocks, giovannidisiena, gogo, horsefacts, hrishibhat, hyh, ignacio, jocxyen, jonatascm, karanctf, kebabsec, kyteg, m_Rassska, mektigboy, oyc_109, pedr02b2, rbserver, robee, rokinot, sach1r0, sashik_eth, simon135, slywaters
37.7866 USDC - $37.79
FERC1155.sol:15: string public constant NAME = "FERC1155"; FERC1155.sol:17: string public constant VERSION = "1";
ERC1155.sol:15: string public constant NAME = "FERC1155"; FERC1155.sol:17: string public constant VERSION = "1"; Buyout.sol:35: uint256 public constant PROPOSAL_PERIOD = 2 days; Buyout.sol:37: uint256 public constant REJECTION_PERIOD = 4 days; Migration.sol:43: uint256 public constant PROPOSAL_PERIOD = 7 days;
protoforms/BaseVault.sol:64: for (uint256 i = 0; i < _tokens.length; ) { protoforms/BaseVault.sol:83: for (uint256 i = 0; i < _tokens.length; ) { protoforms/BaseVault.sol:107: for (uint256 i = 0; i < _tokens.length; ++i) { protoforms/BaseVault.sol:130: for (uint256 i; i < _modules.length; ++i) { protoforms/BaseVault.sol:132: for (uint256 j; j < leaves.length; ++j) { Buyout.sol:454: for (uint256 i; i < permissions.length; ) {
Buyout.sol:35: uint256 public constant PROPOSAL_PERIOD = 2 days; Buyout.sol:37: uint256 public constant REJECTION_PERIOD = 4 days; Migration.sol:43: uint256 public constant PROPOSAL_PERIOD = 7 days;
Buyout.sol:29: address public registry; Buyout.sol:31: address public supply; Buyout.sol:33: address public transfer; Migration.sol:37: address payable public buyout; Migration.sol:39: address public registry;
buyoutInfo[_vault].ethBalance += msg.value; @audit instead use ethbalance=buyoutInfo[_vault].ethBalance @audit ethbalacne+=msg.value saving ssload
Buyout.sol:456: nodes[i] = keccak256(abi.encode(permissions[i])); Minter.sol:26: nodes[0] = keccak256(abi.encode(getPermissions()[0]));
0x0000000000000 instead to save gas Migration.sol:228: if (proposal.newVault != address(0)) Migration.sol:267: if (proposal.newVault == address(0)) revert NoVaultToMigrateTo(); Migration.sol:304: migrationInfo[_vault][_proposalId].newVault != address(0)
++i costs less gas compared to i++ or i += 1 for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled. i++ increments i and returns the initial value of i. Which means: uint i = 1; i++; // == 1 but i == 2 But ++i returns the actual incremented value: uint i = 1; ++i; // == 2 and i == 2 too, so no need for a temporary variable In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2 https://github.com/code-423n4/2022-07-fractional/blob/f862c14f86adf7de232cd4e9cca6b611e6023b98/src/utils/MerkleBase.sol#L189
becauser payble dosnt need msg.value check of 0 https://github.com/code-423n4/2022-07-fractional/blob/f862c14f86adf7de232cd4e9cca6b611e6023b98/src/Vault.sol#L93 https://github.com/code-423n4/2022-07-fractional/blob/f862c14f86adf7de232cd4e9cca6b611e6023b98/src/Vault.sol#L73-L101