Swivel contest - itsmeSTYJ's results

The Decentralized Protocol For Fixed-Rate Lending & Tokenized Cash-Flows.

General Information

Platform: Code4rena

Start Date: 30/09/2021

Pot Size: $75,000 ETH

Total HM: 9

Participants: 15

Period: 7 days

Judge: 0xean

Total Solo HM: 2

Id: 39

League: ETH

Swivel

Findings Distribution

Researcher Performance

Rank: 3/15

Findings: 5

Award: $10,766.37

🌟 Selected for report: 8

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: itsmeSTYJ

Also found by: gpersoon

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

1.4875 ETH - $4,410.12

External Links

Handle

itsmeSTYJ

Vulnerability details

Impact

Taker is charged fees twice in exitVaultFillingVaultInitiate() . Maker is transferring less than premiumFilled to taker and then taker is expected to pay fees i.e. taker's net balance is premiumFilled - 2*fee

function exitVaultFillingVaultInitiate(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
    bytes32 hash = validOrderHash(o, c);

    require(a <= (o.principal - filled[hash]), 'taker amount > available volume');
    
    filled[hash] += a;
        
    uint256 premiumFilled = (((a * 1e18) / o.principal) * o.premium) / 1e18;
    uint256 fee = ((premiumFilled * 1e18) / fenominator[3]) / 1e18;

    Erc20 uToken = Erc20(o.underlying);
    // transfer premium from maker to sender
    uToken.transferFrom(o.maker, msg.sender, premiumFilled);

    // transfer fee in underlying to swivel from sender
    uToken.transferFrom(msg.sender, address(this), fee);

    // transfer <a> vault.notional (nTokens) from sender to maker
    require(MarketPlace(marketPlace).p2pVaultExchange(o.underlying, o.maturity, msg.sender, o.maker, a), 'vault exchange failed');

    emit Exit(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, premiumFilled);
}

#0 - 0xean

2021-10-16T22:47:20Z

based on

3 — High: Assets can be stolen/lost/compromised directly (or indirectly if there is a valid attack path that does not have hand-wavy hypotheticals).

This is being upgraded to a high risk. The duplicate of it was at that level by the submitting warden and considering that fees are being incorrectly taken from the taker and not the maker, the maker ends up with a higher balance than expected and the taker has no way to recoup these fees (assets are now lost).

#1 - JTraversa

2021-10-16T23:07:39Z

Is that how it is interpreted? I'd assume that high risk would imply a valid attack path that a user could use to drain deposited funds based on that description.

I wont fight this one obviously, just think theres a clear differentiation between this and the other high risk issue.

Findings Information

🌟 Selected for report: 0xRajeev

Also found by: 0xsanson, cmichel, gpersoon, itsmeSTYJ, pauliax

Labels

bug
duplicate
2 (Med Risk)
sponsor confirmed

Awards

0.0976 ETH - $289.35

External Links

Handle

itsmeSTYJ

Vulnerability details

Impact

createMarket() is a privileged function that can only be called by an admin but that doesn't necessarily mean that it is not susceptible to mistakes. Furthermore, it is a function that is called somewhat often so following murphy's law - anything can go wrong, will go wrong. Smart contracts are one of those things that cannot go wrong because you cannot take it down so it's always better to err on the side of caution.

If a market already exists but somehow the admin accidentally created the same market again, all positions in that market will be lost forever because functions in VaultTracker.sol can only be called by admin i.e. MarketPlace.sol but there is no way to call the old market from MarketPlace.sol

Require that markets[u][m] is address(0) before setting the market.

#0 - 0xean

2021-10-16T23:13:32Z

dupe #97

Findings Information

🌟 Selected for report: itsmeSTYJ

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

0.9917 ETH - $2,940.08

External Links

Handle

itsmeSTYJ

Vulnerability details

Impact

In initiateZcTokenFillingZcTokenExit() , this comment // transfer underlying tokens - the premium paid + fee in underlying to swivel (from sender) is incorrect because you are actually transferring the underlying tokens - premium paid to the maker (from sender) AND you have to pay fee separately to swivel.

initiateZCTokenFillingZcTokenExit means I want to sell my nTokens so that means a is the amount of principal I want to fill. Let's use a hypothetical example where I (taker) wants to fill 10 units of ZcTokenExit for maker.

  1. I transfer 10 units of underlying to Swivel. The net balances are: me (-a), swivel (+a)
  2. I transfer fee (in underlying) to Swivel. The net balances are: me (-a-fee), swivel (+a+fee)
  3. Swivel initiates my position, sends me the ZcToken and sends Maker the nTokens
  4. Maker pays me premiumFilled for the nTokens. The net balances are: me (-a-fee+premiumsFilled), swivel (+a+fee), maker (-premiumsFilled)
  5. Maker closes position. The net balances are: me (-a-fee+premiumsFilled), swivel (+fee), maker (-premiumsFilled+a)

So effectively, I (taker) should be paying a-premium to maker and fee to swivel.

function initiateZcTokenFillingZcTokenExit(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
    bytes32 hash = validOrderHash(o, c);

    require(a <= o.principal - filled[hash]), 'taker amount > available volume'); // Note: you don't need to wrap these in brackets because if you look at the https://docs.soliditylang.org/en/latest/cheatsheet.html#order-of-precedence-of-operators, subtraction will always go before comparison 

    filled[hash] += a;

    uint256 premiumFilled = (((a * 1e18) / o.principal) * o.premium) / 1e18;
    uint256 fee = ((premiumFilled * 1e18) / fenominator[0]) / 1e18;

    // transfer underlying tokens - the premium paid in underlying to maker (from sender)
    Erc20(o.underlying).transferFrom(msg.sender, o.maker, a - premiumFilled);
		Erc20(o.underlying).transferFrom(msg.sender, swivel, fee);
    // transfer <a> zcTokens between users in marketplace
    require(MarketPlace(marketPlace).p2pZcTokenExchange(o.underlying, o.maturity, o.maker, msg.sender, a), 'zcToken exchange failed');
            
    emit Initiate(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, premiumFilled);
}

#0 - JTraversa

2021-10-07T16:56:15Z

Really good eye.

We had o.maker send the fee after receiving it (same # of transfers as suggested mitigation) and actually accidentally lost that in a larger organizational commit 😅 .

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter